Compare commits

...

40 Commits

Author SHA1 Message Date
advplyr
bc1b99efd6 Fix socket events check user permissions for library items #4199 2025-04-12 17:39:51 -05:00
advplyr
26309019e7 Merge pull request #4195 from advplyr/fix_podcast_episode_scanner_promise
Fix podcast re-scan promise
2025-04-10 17:54:26 -05:00
advplyr
b47d7b734d Update podcast scanner to remove media progress and episodes from playlist 2025-04-10 17:51:42 -05:00
advplyr
62194b8781 Fix podcast re-scan promise 2025-04-10 17:39:41 -05:00
advplyr
7c114a051a Set podcast episode audio file index to 1 for scanned in episodes 2025-04-09 08:33:00 -05:00
advplyr
26c0c89b94 Update podcast latest page to show latest 50 episodes #3343 2025-04-08 17:39:24 -05:00
advplyr
c81071a7b3 Fix item page text overlap on details #4187 2025-04-07 17:19:48 -05:00
advplyr
31f48edcc3 Update close feed button on rss feeds table to be consistent with other UI 2025-04-06 17:56:17 -05:00
advplyr
f7109a055c Fix rss feeds table overflow with long slugs 2025-04-06 17:50:39 -05:00
advplyr
9d0e7759e0 Merge pull request #4158 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-04-04 17:24:59 -05:00
peter cerny
d15ccbd2fc Translated using Weblate (Slovak)
Currently translated at 50.7% (556 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-04-05 00:17:31 +02:00
Coxe
cfeb1743df Translated using Weblate (Danish)
Currently translated at 99.8% (1093 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-04-05 00:17:31 +02:00
peter cerny
a7e0330b06 Translated using Weblate (Slovak)
Currently translated at 43.2% (474 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-04-05 00:17:30 +02:00
biuklija
5f69e83d46 Translated using Weblate (Croatian)
Currently translated at 100.0% (1095 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-04-05 00:17:30 +02:00
peter cerny
fdde62896f Translated using Weblate (Slovak)
Currently translated at 41.9% (459 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-04-05 00:17:29 +02:00
thehijacker
f1909d0fc7 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1095 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-04-05 00:17:28 +02:00
Mikkel Dupont Olesen
28225618fd Translated using Weblate (Danish)
Currently translated at 99.2% (1087 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-04-05 00:17:27 +02:00
Marc
1a562a5f23 Translated using Weblate (German)
Currently translated at 99.6% (1091 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-04-05 00:17:27 +02:00
peter cerny
bfbbcba160 Translated using Weblate (Slovak)
Currently translated at 29.5% (324 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-04-05 00:17:26 +02:00
Jan-Eric Myhrgren
21e4d17ef3 Translated using Weblate (Swedish)
Currently translated at 94.8% (1039 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-04-05 00:17:25 +02:00
Augusto Massini Pinto
87faebc7d9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 71.4% (782 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pt_BR/
2025-04-05 00:17:25 +02:00
Adolfo Jayme Barrientos
5d868d1355 Translated using Weblate (Spanish)
Currently translated at 99.4% (1089 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-04-05 00:17:24 +02:00
Adolfo Jayme Barrientos
ac0fd41740 Translated using Weblate (Catalan)
Currently translated at 94.2% (1032 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-04-05 00:17:24 +02:00
Adolfo Jayme Barrientos
1202e95b66 Translated using Weblate (Spanish)
Currently translated at 99.4% (1089 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-04-05 00:17:23 +02:00
Simple16
c05cf9718b Translated using Weblate (Russian)
Currently translated at 100.0% (1095 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-04-05 00:17:22 +02:00
Adolfo Jayme Barrientos
d8f1e43e85 Translated using Weblate (Spanish)
Currently translated at 99.2% (1087 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-04-05 00:17:22 +02:00
Adolfo Jayme Barrientos
ed7e87b168 Translated using Weblate (Catalan)
Currently translated at 94.1% (1031 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-04-05 00:17:21 +02:00
Martin Balko
4ca2c9e97f Translated using Weblate (Slovak)
Currently translated at 28.5% (313 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-04-05 00:17:20 +02:00
Kabika82
01dd1c0615 Translated using Weblate (Hungarian)
Currently translated at 95.9% (1051 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-04-05 00:17:20 +02:00
Martin Balko
cd387b8fed Translated using Weblate (Slovak)
Currently translated at 27.7% (304 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-04-05 00:17:19 +02:00
SunSpring
c85cd69152 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1095 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-04-05 00:17:18 +02:00
Максим Горпиніч
9e9b52a252 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1095 of 1095 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-04-05 00:17:18 +02:00
advplyr
292d5783a9 Update player queue button on item page to be to the right of the read button to keep consistent spacing 2025-04-04 17:17:06 -05:00
advplyr
c7faafd0f3 Fix author order when scanning in multi-author books by setting bookAuthor.createdAt when bulk creating #4177 2025-04-03 17:50:10 -05:00
advplyr
ca7388b14e Fix download podcast update library item size #4180 2025-04-02 17:35:57 -05:00
advplyr
ddf2ca3670 Update item image in audio player when updated on item #4025 2025-04-01 17:32:21 -05:00
advplyr
96825c3c2b Update feedepisode psc customElement 2025-03-31 17:59:16 -05:00
advplyr
6ed66fea16 Update podcast rss feed parser to use psc chapters on episodes 2025-03-31 17:57:39 -05:00
advplyr
ddcda197b4 Fix manage, chapters edit tracks and library stats page not setting the current library properly #4170 2025-03-30 17:27:36 -05:00
advplyr
8bea5d83f5 Merge pull request #4168 from advplyr/new_stats_controller
Create new StatsController and move year in review stats endpoint
2025-03-29 17:44:25 -05:00
40 changed files with 737 additions and 158 deletions

View File

@@ -156,7 +156,7 @@ export default {
return this.mediaMetadata.authors || []
},
libraryId() {
return this.streamLibraryItem ? this.streamLibraryItem.libraryId : null
return this.streamLibraryItem?.libraryId || null
},
totalDurationPretty() {
// Adjusted by playback rate

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div v-if="narrators?.length" class="flex py-0.5 mt-4">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelNarrators }}</span>
</div>
<div class="max-w-[calc(100vw-10rem)] overflow-hidden text-ellipsis">
@@ -12,7 +12,7 @@
</div>
</div>
<div v-if="publishedYear" role="paragraph" class="flex py-0.5">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelPublishYear }}</span>
</div>
<div>
@@ -20,7 +20,7 @@
</div>
</div>
<div v-if="publisher" role="paragraph" class="flex py-0.5">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelPublisher }}</span>
</div>
<div>
@@ -28,7 +28,7 @@
</div>
</div>
<div v-if="podcastType" role="paragraph" class="flex py-0.5">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelPodcastType }}</span>
</div>
<div class="capitalize">
@@ -36,7 +36,7 @@
</div>
</div>
<div class="flex py-0.5" v-if="genres.length">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelGenres }}</span>
</div>
<div class="max-w-[calc(100vw-10rem)] overflow-hidden text-ellipsis">
@@ -47,7 +47,7 @@
</div>
</div>
<div class="flex py-0.5" v-if="tags.length">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelTags }}</span>
</div>
<div class="max-w-[calc(100vw-10rem)] overflow-hidden text-ellipsis">
@@ -58,7 +58,7 @@
</div>
</div>
<div v-if="language" class="flex py-0.5">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelLanguage }}</span>
</div>
<div>
@@ -66,7 +66,7 @@
</div>
</div>
<div v-if="tracks.length || (isPodcast && totalPodcastDuration)" role="paragraph" class="flex py-0.5">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelDuration }}</span>
</div>
<div>
@@ -74,7 +74,7 @@
</div>
</div>
<div role="paragraph" class="flex py-0.5">
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
<div class="w-34 min-w-34 sm:w-34 sm:min-w-34 break-words">
<span class="text-white/60 uppercase text-sm">{{ $strings.LabelSize }}</span>
</div>
<div>

View File

@@ -26,9 +26,9 @@
<span class="material-symbols text-2xl text-error">error_outline</span>
</ui-tooltip>
<button aria-label="Download Backup" class="inline-flex material-symbols text-xl mx-1 mt-1 text-white/70 hover:text-white/100" @click.stop="downloadBackup(backup)">download</button>
<button aria-label="Download backup" class="inline-flex material-symbols text-xl mx-1 mt-1 text-white/70 hover:text-white/100" @click.stop="downloadBackup(backup)">download</button>
<button aria-label="Delete Backup" class="inline-flex material-symbols text-xl mx-1 text-white/70 hover:text-error" @click.stop="deleteBackupClick(backup)">delete</button>
<button aria-label="Delete backup" class="inline-flex material-symbols text-xl mx-1 text-white/70 hover:text-error" @click.stop="deleteBackupClick(backup)">delete</button>
</div>
</td>
</tr>

View File

@@ -183,7 +183,7 @@ export default {
this.$store.commit('libraries/updateFilterDataWithItem', libraryItem)
},
libraryItemUpdated(libraryItem) {
if (this.$store.state.selectedLibraryItem && this.$store.state.selectedLibraryItem.id === libraryItem.id) {
if (this.$store.state.selectedLibraryItem?.id === libraryItem.id) {
this.$store.commit('setSelectedLibraryItem', libraryItem)
if (this.$store.state.globals.selectedEpisode && libraryItem.mediaType === 'podcast') {
const episode = libraryItem.media.episodes.find((ep) => ep.id === this.$store.state.globals.selectedEpisode.id)
@@ -192,6 +192,9 @@ export default {
}
}
}
if (this.$store.state.streamLibraryItem?.id === libraryItem.id) {
this.$store.commit('updateStreamLibraryItem', libraryItem)
}
this.$eventBus.$emit(`${libraryItem.id}_updated`, libraryItem)
this.$store.commit('libraries/updateFilterDataWithItem', libraryItem)
},

View File

@@ -221,6 +221,11 @@ export default {
return redirect('/')
}
// Fetch and set library if this items library does not match the current
if (store.state.libraries.currentLibraryId !== libraryItem.libraryId || !store.state.libraries.filterData) {
await store.dispatch('libraries/fetch', libraryItem.libraryId)
}
var previousRoute = from ? from.fullPath : null
if (from && from.path === '/login') previousRoute = null
return {

View File

@@ -103,6 +103,12 @@ export default {
console.error('No need to edit library item that is 1 file...')
return redirect('/')
}
// Fetch and set library if this items library does not match the current
if (store.state.libraries.currentLibraryId !== libraryItem.libraryId || !store.state.libraries.filterData) {
await store.dispatch('libraries/fetch', libraryItem.libraryId)
}
return {
libraryItem,
files: libraryItem.media.audioFiles ? libraryItem.media.audioFiles.map((af) => ({ ...af, include: !af.exclude })) : []

View File

@@ -195,10 +195,15 @@ export default {
return redirect('/?error=invalid media type')
}
if (!libraryItem.media.audioFiles.length) {
cnosole.error('No audio files')
console.error('No audio files')
return redirect('/?error=no audio files')
}
// Fetch and set library if this items library does not match the current
if (store.state.libraries.currentLibraryId !== libraryItem.libraryId || !store.state.libraries.filterData) {
await store.dispatch('libraries/fetch', libraryItem.libraryId)
}
return {
libraryItem
}

View File

@@ -32,7 +32,7 @@
<p class="truncate">{{ feed.meta.title }}</p>
</td>
<!-- -->
<td class="hidden xl:table-cell">
<td class="hidden xl:table-cell max-w-48">
<p class="truncate">{{ feed.slug }}</p>
</td>
<!-- -->
@@ -57,7 +57,7 @@
</td>
<!-- -->
<td class="text-center">
<ui-icon-btn icon="delete" class="mx-0.5" :size="7" bg-color="bg-error" outlined @click.stop="deleteFeedClick(feed)" />
<ui-icon-btn icon="delete" class="mx-0.5 text-white/70" borderless :size="7" iconFontSize="1.25rem" outlined @click.stop="deleteFeedClick(feed)" />
</td>
</tr>
</table>

View File

@@ -91,15 +91,15 @@
{{ isMissing ? $strings.LabelMissing : $strings.LabelIncomplete }}
</ui-btn>
<ui-tooltip v-if="showQueueBtn" :text="isQueued ? $strings.ButtonQueueRemoveItem : $strings.ButtonQueueAddItem" direction="top">
<ui-icon-btn :icon="isQueued ? 'playlist_add_check' : 'playlist_play'" :bg-color="isQueued ? 'bg-primary' : 'bg-success/60'" class="mx-0.5" :class="isQueued ? 'text-success' : ''" @click="queueBtnClick" />
</ui-tooltip>
<ui-btn v-if="showReadButton" color="bg-info" :padding-x="4" small class="flex items-center h-9 mr-2" @click="openEbook">
<span class="material-symbols text-2xl -ml-2 pr-2 text-white" aria-hidden="true">auto_stories</span>
{{ $strings.ButtonRead }}
</ui-btn>
<ui-tooltip v-if="showQueueBtn" :text="isQueued ? $strings.ButtonQueueRemoveItem : $strings.ButtonQueueAddItem" direction="top">
<ui-icon-btn :icon="isQueued ? 'playlist_add_check' : 'playlist_play'" :bg-color="isQueued ? 'bg-primary' : 'bg-success/60'" class="mx-0.5" :class="isQueued ? 'text-success' : ''" @click="queueBtnClick" />
</ui-tooltip>
<ui-tooltip v-if="userCanUpdate" :text="$strings.LabelEdit" direction="top">
<ui-icon-btn icon="&#xe3c9;" outlined class="mx-0.5" :aria-label="$strings.LabelEdit" @click="editClick" />
</ui-tooltip>

View File

@@ -249,7 +249,7 @@ export default {
},
async loadRecentEpisodes(page = 0) {
this.processing = true
const episodePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/recent-episodes?limit=25&page=${page}`).catch((error) => {
const episodePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/recent-episodes?limit=50&page=${page}`).catch((error) => {
console.error('Failed to get recent episodes', error)
this.$toast.error(this.$strings.ToastFailedToLoadData)
return null

View File

@@ -89,14 +89,16 @@
<script>
export default {
asyncData({ redirect, store }) {
async asyncData({ redirect, store, params }) {
if (!store.getters['user/getIsAdminOrUp']) {
redirect('/')
return
}
if (!store.state.libraries.currentLibraryId) {
return redirect('/config')
const libraryId = params.library
const library = await store.dispatch('libraries/fetch', libraryId)
if (!library) {
return redirect(`/oops?message=Library "${libraryId}" not found`)
}
return {}
},

View File

@@ -171,6 +171,10 @@ export const mutations = {
state.playerQueueItems = payload.queueItems || []
}
},
updateStreamLibraryItem(state, libraryItem) {
if (!libraryItem) return
state.streamLibraryItem = libraryItem
},
setIsPlaying(state, isPlaying) {
state.streamIsPlaying = isPlaying
},

View File

@@ -117,7 +117,7 @@
"HeaderAccount": "Compte",
"HeaderAddCustomMetadataProvider": "Afegeix un proveïdor de metadades personalitzat",
"HeaderAdvanced": "Avançat",
"HeaderAppriseNotificationSettings": "Configuració de Notificacions Apprise",
"HeaderAppriseNotificationSettings": "Paràmetres de notificacions Apprise",
"HeaderAudioTracks": "Pistes d'àudio",
"HeaderAudiobookTools": "Eines de gestió de fitxers de l'audiollibre",
"HeaderAuthentication": "Autenticació",
@@ -133,9 +133,9 @@
"HeaderCustomMetadataProviders": "Proveïdors de metadades personalitzats",
"HeaderDetails": "Detalls",
"HeaderDownloadQueue": "Cua de baixades",
"HeaderEbookFiles": "Fitxers de Llibres Digitals",
"HeaderEbookFiles": "Fitxers de llibres digitals",
"HeaderEmail": "Correu electrònic",
"HeaderEmailSettings": "Configuració de Correu Electrònic",
"HeaderEmailSettings": "Paràmetres de correu electrònic",
"HeaderEpisodes": "Episodis",
"HeaderEreaderDevices": "Dispositius Ereader",
"HeaderEreaderSettings": "Paràmetres del lector",
@@ -171,7 +171,7 @@
"HeaderPasswordAuthentication": "Autenticació per Contrasenya",
"HeaderPermissions": "Permisos",
"HeaderPlayerQueue": "Cua del Reproductor",
"HeaderPlayerSettings": "Configuració del Reproductor",
"HeaderPlayerSettings": "Paràmetres del reproductor",
"HeaderPlaylist": "Llista de Reproducció",
"HeaderPlaylistItems": "Elements de la Llista de Reproducció",
"HeaderPodcastsToAdd": "Pòdcasts a afegir",
@@ -725,7 +725,8 @@
"MessageConfirmResetProgress": "Estàs segur que vols reiniciar el teu progrés?",
"MessageConfirmSendEbookToDevice": "Estàs segur que vols enviar {0} ebook(s) \"{1}\" al dispositiu \"{2}\"?",
"MessageConfirmUnlinkOpenId": "Estàs segur que vols desvincular aquest usuari d'OpenID?",
"MessageDownloadingEpisode": "Descarregant capítol",
"MessageDaysListenedInTheLastYear": "{0} dies escoltats l'any passat",
"MessageDownloadingEpisode": "S'està baixant l'episodi",
"MessageDragFilesIntoTrackOrder": "Arrossega els fitxers en l'ordre correcte de les pistes",
"MessageEmbedFailed": "Error en incrustar!",
"MessageEmbedFinished": "Incrustació acabada!",
@@ -1026,6 +1027,8 @@
"ToastUnknownError": "Error desconegut",
"ToastUnlinkOpenIdFailed": "Error en desvincular l'usuari d'OpenID",
"ToastUnlinkOpenIdSuccess": "Usuari desvinculat d'OpenID",
"ToastUploaderFilepathExistsError": "El camí del fitxer «{0}» ja existeix al servidor",
"ToastUploaderItemExistsInSubdirectoryError": "L'element «{0}» usa un subdirectori del camí de pujada.",
"ToastUserDeleteFailed": "Error en eliminar l'usuari",
"ToastUserDeleteSuccess": "Usuari eliminat",
"ToastUserPasswordChangeSuccess": "Contrasenya canviada correctament",

View File

@@ -229,6 +229,7 @@
"LabelAddedDate": "Tilføjet {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",
@@ -252,7 +253,7 @@
"LabelBackToUser": "Tilbage til Bruger",
"LabelBackupAudioFiles": "Sikkerhedskopier lydfiler",
"LabelBackupLocation": "Backup Placering",
"LabelBackupsEnableAutomaticBackups": "Aktivér automatisk sikkerhedskopiering",
"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.",
@@ -558,6 +559,8 @@
"LabelSettingsBookshelfViewHelp": "Skeumorfisk design med træhylder",
"LabelSettingsChromecastSupport": "Chromecast-understøttelse",
"LabelSettingsDateFormat": "Datoformat",
"LabelSettingsEnableWatcher": "Scan automatisk bibliotek for ændringer",
"LabelSettingsEnableWatcherForLibrary": "Scan automatisk bibliotek for ændringer",
"LabelSettingsEnableWatcherHelp": "Aktiverer automatisk tilføjelse/opdatering af elementer, når filændringer registreres. *Kræver servergenstart",
"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.",
@@ -1063,6 +1066,7 @@
"ToastSelectAtLeastOneUser": "Vælg mindst en bruger",
"ToastSendEbookToDeviceFailed": "Mislykkedes afsendelse af e-bog til enhed",
"ToastSendEbookToDeviceSuccess": "E-bog afsendt til enhed \"{0}\"",
"ToastSeriesSubmitFailedSameName": "Kan ikke tilføje to serier med samme navn",
"ToastSeriesUpdateFailed": "Mislykkedes opdatering af serie",
"ToastSeriesUpdateSuccess": "Serieopdatering lykkedes",
"ToastServerSettingsUpdateSuccess": "Server indstillinger opdateret",
@@ -1081,6 +1085,8 @@
"ToastUnknownError": "Ukendt fejl",
"ToastUnlinkOpenIdFailed": "Fejlede i af afkoble bruger fra OpenID",
"ToastUnlinkOpenIdSuccess": "Bruger afkoblet fra OpenID",
"ToastUploaderFilepathExistsError": "Filsti \"{0}\" findes allerede på serveren",
"ToastUploaderItemExistsInSubdirectoryError": "Genstand \"{0}\" benytter en undermappe af upload stien",
"ToastUserDeleteFailed": "Mislykkedes sletning af bruger",
"ToastUserDeleteSuccess": "Bruger slettet",
"ToastUserPasswordChangeSuccess": "Password ændret",

View File

@@ -229,6 +229,7 @@
"LabelAddedDate": "{0} Hinzugefügt",
"LabelAdminUsersOnly": "Nur Admin Benutzer",
"LabelAll": "Alle",
"LabelAllEpisodesDownloaded": "Alle Episoden heruntergeladen",
"LabelAllUsers": "Alle Benutzer",
"LabelAllUsersExcludingGuests": "Alle Benutzer außer Gästen",
"LabelAllUsersIncludingGuests": "Alle Benutzer und Gäste",
@@ -953,6 +954,7 @@
"ToastBackupRestoreFailed": "Sicherung konnte nicht wiederhergestellt werden",
"ToastBackupUploadFailed": "Sicherung konnte nicht hochgeladen werden",
"ToastBackupUploadSuccess": "Sicherung hochgeladen",
"ToastBatchApplyDetailsToItemsSuccess": "Details auf Medien anwenden",
"ToastBatchDeleteFailed": "Batch-Löschen fehlgeschlagen",
"ToastBatchDeleteSuccess": "Batch-Löschung erfolgreich",
"ToastBatchQuickMatchFailed": "Batch-Schnellabgleich fehlgeschlagen!",
@@ -1065,6 +1067,7 @@
"ToastSelectAtLeastOneUser": "Wähle mindestens einen Benutzer aus",
"ToastSendEbookToDeviceFailed": "E-Buch konnte nicht auf Gerät übertragen werden",
"ToastSendEbookToDeviceSuccess": "E-Buch an Gerät „{0}“ gesendet",
"ToastSeriesSubmitFailedSameName": "Serien mit dem selben Namen können nicht hinzugefügt werden",
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
"ToastSeriesUpdateSuccess": "Serien aktualisiert",
"ToastServerSettingsUpdateSuccess": "Die Server-Einstellungen wurden geupdated",
@@ -1083,6 +1086,7 @@
"ToastUnknownError": "Unbekannter Fehler",
"ToastUnlinkOpenIdFailed": "Fehler beim entkoppeln des Benutzers von OpenID",
"ToastUnlinkOpenIdSuccess": "Benutzer entkoppelt von OpenID",
"ToastUploaderFilepathExistsError": "Dateipfad \"{0}\" ist bereits auf dem Server horhanden",
"ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
"ToastUserDeleteSuccess": "Benutzer gelöscht",
"ToastUserPasswordChangeSuccess": "Passwort erfolgreich verändert",

View File

@@ -77,9 +77,9 @@
"ButtonRemove": "Quitar",
"ButtonRemoveAll": "Quitar todo",
"ButtonRemoveAllLibraryItems": "Quitar todos los elementos de la biblioteca",
"ButtonRemoveFromContinueListening": "Remover de Continuar Escuchando",
"ButtonRemoveFromContinueReading": "Remover de Continuar Leyendo",
"ButtonRemoveSeriesFromContinueSeries": "Remover Serie de Continuar Series",
"ButtonRemoveFromContinueListening": "Quitar de Continuar escuchando",
"ButtonRemoveFromContinueReading": "Quitar de Continuar leyendo",
"ButtonRemoveSeriesFromContinueSeries": "Quitar serie de Continuar serie",
"ButtonReset": "Restablecer",
"ButtonResetToDefault": "Restaurar valores predeterminados",
"ButtonRestore": "Restaurar",
@@ -227,6 +227,7 @@
"LabelAddedDate": "{0} Añadido",
"LabelAdminUsersOnly": "Solamente usuarios administradores",
"LabelAll": "Todos",
"LabelAllEpisodesDownloaded": "Todos los episodios descargados",
"LabelAllUsers": "Todos los usuarios",
"LabelAllUsersExcludingGuests": "Todos los usuarios excepto invitados",
"LabelAllUsersIncludingGuests": "Todos los usuarios e invitados",
@@ -436,8 +437,8 @@
"LabelMinute": "Minuto",
"LabelMinutes": "Minutos",
"LabelMissing": "Falta",
"LabelMissingEbook": "No tiene ebook",
"LabelMissingSupplementaryEbook": "No tiene ebook suplementario",
"LabelMissingEbook": "No tiene libro electrónico",
"LabelMissingSupplementaryEbook": "No tiene libro electrónico suplementario",
"LabelMobileRedirectURIs": "URIs de redirección a móviles permitidos",
"LabelMobileRedirectURIsDescription": "Esta es una lista blanca de URI de redireccionamiento válidos para aplicaciones móviles. El predeterminado es <code> audiobookshelf</code> , que puede eliminar o complementar con URI adicionales para la integración de aplicaciones de terceros. Usando un asterisco (<code> *</code> ) como única entrada que permite cualquier URI.",
"LabelMore": "Más",
@@ -497,7 +498,7 @@
"LabelPort": "Puerto",
"LabelPrefixesToIgnore": "Prefijos para Ignorar (no distingue entre mayúsculas y minúsculas.)",
"LabelPreventIndexing": "Evite que los directorios de pódcast de iTunes y Google indicen su suministro",
"LabelPrimaryEbook": "Ebook principal",
"LabelPrimaryEbook": "Libro electrónico principal",
"LabelProgress": "Progreso",
"LabelProvider": "Proveedor",
"LabelProviderAuthorizationValue": "Valor del encabezado de autorización",
@@ -713,20 +714,20 @@
"MessageBookshelfNoResultsForQuery": "No hay resultados para la consulta",
"MessageBookshelfNoSeries": "No tiene ninguna serie",
"MessageChapterEndIsAfter": "El final del capítulo es después del final de tu audiolibro",
"MessageChapterErrorFirstNotZero": "El primer capitulo debe iniciar en 0",
"MessageChapterErrorFirstNotZero": "El primer capítulo debe iniciar en 0",
"MessageChapterErrorStartGteDuration": "El tiempo de inicio no es válido: debe ser inferior a la duración del audiolibro",
"MessageChapterErrorStartLtPrev": "El tiempo de inicio no es válido: debe ser mayor o igual que el tiempo de inicio del capítulo anterior",
"MessageChapterStartIsAfter": "El comienzo del capítulo es después del final de su audiolibro",
"MessageCheckingCron": "Revisando cron...",
"MessageConfirmCloseFeed": "¿Confirma que quiere cerrar este suministro?",
"MessageConfirmDeleteBackup": "¿Está seguro de que desea eliminar el respaldo {0}?",
"MessageConfirmDeleteDevice": "¿Estás seguro de que deseas eliminar el lector electrónico \"{0}\"?",
"MessageConfirmDeleteDevice": "¿Confirma que quiere eliminar el lector electrónico «{0}»?",
"MessageConfirmDeleteFile": "Esto eliminará el archivo de su sistema de archivos. ¿Está seguro?",
"MessageConfirmDeleteLibrary": "¿Está seguro de que desea eliminar permanentemente la biblioteca \"{0}\"?",
"MessageConfirmDeleteLibrary": "¿Confirma que quiere eliminar permanentemente la biblioteca «{0}»?",
"MessageConfirmDeleteLibraryItem": "Esto eliminará el elemento de la biblioteca de la base de datos y del sistema de archivos. ¿Confirma que quiere hacerlo?",
"MessageConfirmDeleteLibraryItems": "Esto eliminará {0} elementos de la biblioteca de la base de datos y del sistema de archivos. ¿Confirma que quiere hacerlo?",
"MessageConfirmDeleteMetadataProvider": "¿Estás seguro de que deseas eliminar el proveedor de metadatos personalizado \"{0}\"?",
"MessageConfirmDeleteNotification": "¿Estás seguro de que deseas eliminar esta notificación?",
"MessageConfirmDeleteMetadataProvider": "¿Confirma que quiere eliminar el proveedor de metadatos personalizado «{0}»?",
"MessageConfirmDeleteNotification": "¿Confirma que quiere eliminar esta notificación?",
"MessageConfirmDeleteSession": "¿Está seguro de que desea eliminar esta sesión?",
"MessageConfirmEmbedMetadataInAudioFiles": "¿Está seguro de que desea incrustar metadatos en {0} archivos de audio?",
"MessageConfirmForceReScan": "¿Está seguro de que desea forzar un re-escaneo?",
@@ -741,27 +742,27 @@
"MessageConfirmPurgeItemsCache": "Purgar la caché de los elementos eliminará todo el directorio <code>/metadata/cache/items</code>.<br />¿Estás seguro?",
"MessageConfirmQuickEmbed": "¡Advertencia! La integración rápida no realiza copias de seguridad a ninguno de tus archivos de audio. Asegúrate de haber realizado una copia de los mismos previamente. <br><br>¿Deseas continuar?",
"MessageConfirmQuickMatchEpisodes": "El reconocimiento rápido de extensiones sobrescribirá los detalles si se encuentra una coincidencia. Se actualizarán las extensiones no reconocidas. ¿Está seguro?",
"MessageConfirmReScanLibraryItems": "¿Estás seguro de querer re escanear {0} elemento(s)?",
"MessageConfirmRemoveAllChapters": "¿Está seguro de que desea remover todos los capitulos?",
"MessageConfirmRemoveAuthor": "¿Está seguro de que desea remover el autor \"{0}\"?",
"MessageConfirmRemoveCollection": "¿Está seguro de que desea remover la colección \"{0}\"?",
"MessageConfirmRemoveEpisode": "¿Está seguro de que desea remover el episodio \"{0}\"?",
"MessageConfirmRemoveEpisodes": "¿Está seguro de que desea remover {0} episodios?",
"MessageConfirmRemoveListeningSessions": "¿Está seguro que desea remover {0} sesiones de escuchar?",
"MessageConfirmReScanLibraryItems": "¿Confirma que quiere volver a analizar {0} elementos?",
"MessageConfirmRemoveAllChapters": "¿Confirma que quiere quitar todos los capítulos?",
"MessageConfirmRemoveAuthor": "¿Confirma que quiere quitar el autor «{0}»?",
"MessageConfirmRemoveCollection": "¿Confirma que quiere quitar la colección «{0}»?",
"MessageConfirmRemoveEpisode": "¿Confirma que quiere quitar el episodio «{0}»?",
"MessageConfirmRemoveEpisodes": "¿Confirma que quiere quitar {0} episodios?",
"MessageConfirmRemoveListeningSessions": "¿Confirma que quiere quitar {0} sesiones de escucha?",
"MessageConfirmRemoveMetadataFiles": "¿Está seguro de que desea eliminar todos los archivos de metadatos.{0} en las carpetas de elementos de su biblioteca?",
"MessageConfirmRemoveNarrator": "¿Está seguro de que desea remover el narrador \"{0}\"?",
"MessageConfirmRemovePlaylist": "¿Está seguro de que desea remover la lista de reproducción \"{0}\"?",
"MessageConfirmRemoveNarrator": "¿Confirma que quiere quitar el narrador «{0}»?",
"MessageConfirmRemovePlaylist": "¿Confirma que quiere quitar la lista de reproducción «{0}»?",
"MessageConfirmRenameGenre": "¿Está seguro de que desea renombrar el genero \"{0}\" a \"{1}\" de todos los elementos?",
"MessageConfirmRenameGenreMergeNote": "Nota: Este género ya existe, por lo que se fusionarán.",
"MessageConfirmRenameGenreWarning": "Advertencia! Un genero similar ya existe \"{0}\".",
"MessageConfirmRenameTag": "¿Está seguro de que desea renombrar la etiqueta \"{0}\" a \"{1}\" de todos los elementos?",
"MessageConfirmRenameTagMergeNote": "Nota: Esta etiqueta ya existe, por lo que se fusionarán.",
"MessageConfirmRenameTagWarning": "Advertencia! Una etiqueta similar ya existe \"{0}\".",
"MessageConfirmResetProgress": "¿Estás seguro de que quieres reiniciar tu progreso?",
"MessageConfirmSendEbookToDevice": "¿Está seguro de que enviar {0} ebook(s) \"{1}\" al dispositivo \"{2}\"?",
"MessageConfirmResetProgress": "¿Confirma que quiere restablecer su progreso?",
"MessageConfirmSendEbookToDevice": "¿Confirma que quiere enviar el libro electrónico {0} «{1}» al dispositivo «{2}»?",
"MessageConfirmUnlinkOpenId": "¿Estás seguro de que deseas desvincular este usuario de OpenID?",
"MessageDaysListenedInTheLastYear": "{0} dies escoltats en l'últim any",
"MessageDownloadingEpisode": "Descargando Capitulo",
"MessageDaysListenedInTheLastYear": "{0} días escuchados el año pasado",
"MessageDownloadingEpisode": "Descargando episodio",
"MessageDragFilesIntoTrackOrder": "Arrastra los archivos al orden correcto de las pistas",
"MessageEmbedFailed": "¡Error al insertar!",
"MessageEmbedFinished": "Incrustación Terminada!",
@@ -810,7 +811,7 @@
"MessageNoMediaProgress": "Multimedia sin Progreso",
"MessageNoNotifications": "Ninguna notificación",
"MessageNoPodcastFeed": "Podcast no válido: Sin feed",
"MessageNoPodcastsFound": "Ningún podcast encontrado",
"MessageNoPodcastsFound": "No se encontró ningún pódcast",
"MessageNoResults": "Sin Resultados",
"MessageNoSearchResultsFor": "La búsqueda «{0}» no produjo ningún resultado",
"MessageNoSeries": "Ninguna serie",
@@ -832,10 +833,10 @@
"MessageQuickEmbedQueue": "En cola para inserción rápida ({0} en cola)",
"MessageQuickMatchAllEpisodes": "Combina rápidamente todos los episodios",
"MessageQuickMatchDescription": "Rellenar detalles de elementos vacíos y portada con los primeros resultados de '{0}'. No sobrescribe los detalles a menos que la opción \"Preferir Metadatos Encontrados\" del servidor esté habilitada.",
"MessageRemoveChapter": "Remover capítulos",
"MessageRemoveEpisodes": "Remover {0} episodio(s)",
"MessageRemoveFromPlayerQueue": "Remover la cola de reproducción",
"MessageRemoveUserWarning": "¿Está seguro de que desea eliminar el usuario \"{0}\"?",
"MessageRemoveChapter": "Quitar capítulo",
"MessageRemoveEpisodes": "Quitar {0} episodio(s)",
"MessageRemoveFromPlayerQueue": "Quitar de la cola de reproducción",
"MessageRemoveUserWarning": "¿Confirma que quiere eliminar permanentemente el usuario «{0}»?",
"MessageReportBugsAndContribute": "Reporte erres, solicite funciones y contribuya en",
"MessageResetChaptersConfirm": "¿Está seguro de que desea deshacer los cambios y revertir los capítulos a su estado original?",
"MessageRestoreBackupConfirm": "¿Está seguro de que desea para restaurar del respaldo creado en",
@@ -851,7 +852,7 @@
"MessageStartPlaybackAtTime": "Iniciar reproducción para \"{0}\" en {1}?",
"MessageTaskAudioFileNotWritable": "El archivo de audio \"{0}\" no se puede grabar",
"MessageTaskCanceledByUser": "Tarea cancelada por el usuario",
"MessageTaskDownloadingEpisodeDescription": "Descargando el episodio \"{0}\"",
"MessageTaskDownloadingEpisodeDescription": "Descargando el episodio «{0}»",
"MessageTaskEmbeddingMetadata": "Inserción de metadatos",
"MessageTaskEmbeddingMetadataDescription": "Inserción de metadatos en el audiolibro \"{0}\"",
"MessageTaskEncodingM4b": "Codificación M4B",
@@ -863,16 +864,16 @@
"MessageTaskFailedToMergeAudioFiles": "Error al fusionar archivos de audio",
"MessageTaskFailedToMoveM4bFile": "Error al mover el archivo m4b",
"MessageTaskFailedToWriteMetadataFile": "Error al escribir el archivo de metadatos",
"MessageTaskMatchingBooksInLibrary": "Libros coincidentes en la biblioteca \"{0}\"",
"MessageTaskMatchingBooksInLibrary": "Libros coincidentes en la biblioteca «{0}»",
"MessageTaskNoFilesToScan": "Sin archivos para escanear",
"MessageTaskOpmlImport": "Importar OPML",
"MessageTaskOpmlImportDescription": "Creando pódcast a partir de {0} suministros RSS",
"MessageTaskOpmlImportFeed": "Feed de importación OPML",
"MessageTaskOpmlImportFeedDescription": "Importando el feed RSS \"{0}\"",
"MessageTaskOpmlImportFeedDescription": "Importando el suministro RSS «{0}»",
"MessageTaskOpmlImportFeedFailed": "No se puede obtener el podcast",
"MessageTaskOpmlImportFeedPodcastDescription": "Creando podcast \"{0}\"",
"MessageTaskOpmlImportFeedPodcastDescription": "Creando pódcast «{0}»",
"MessageTaskOpmlImportFeedPodcastExists": "Podcast ya existe en la ruta",
"MessageTaskOpmlImportFeedPodcastFailed": "Error al crear podcast",
"MessageTaskOpmlImportFeedPodcastFailed": "No se pudo crear el pódcast",
"MessageTaskOpmlImportFinished": "Añadido {0} podcasts",
"MessageTaskOpmlParseFailed": "No se pudo analizar el archivo OPML",
"MessageTaskOpmlParseFastFail": "No se encontró la etiqueta <opml> del archivo OPML no válido O no se encontró la etiqueta <outline>",
@@ -948,6 +949,7 @@
"ToastBackupRestoreFailed": "Error al restaurar el respaldo",
"ToastBackupUploadFailed": "Error al subir el respaldo",
"ToastBackupUploadSuccess": "Respaldo cargado",
"ToastBatchApplyDetailsToItemsSuccess": "Detalles aplicados a los elementos",
"ToastBatchDeleteFailed": "Error al eliminar por lotes",
"ToastBatchDeleteSuccess": "Borrado por lotes correcto",
"ToastBatchQuickMatchFailed": "¡Error en la sincronización rápida por lotes!",
@@ -964,7 +966,7 @@
"ToastChaptersRemoved": "Capítulos eliminados",
"ToastChaptersUpdated": "Capítulos actualizados",
"ToastCollectionItemsAddFailed": "Artículo(s) añadido(s) a la colección fallido(s)",
"ToastCollectionRemoveSuccess": "Colección removida",
"ToastCollectionRemoveSuccess": "Colección quitada",
"ToastCollectionUpdateSuccess": "Colección actualizada",
"ToastCoverUpdateFailed": "Error al actualizar la cubierta",
"ToastDateTimeInvalidOrIncomplete": "Fecha y hora inválidas o incompletas",
@@ -998,12 +1000,12 @@
"ToastItemMarkedAsNotFinishedSuccess": "Elemento marcado como No Terminado",
"ToastItemUpdateSuccess": "Elemento actualizado",
"ToastLibraryCreateFailed": "Error al crear biblioteca",
"ToastLibraryCreateSuccess": "Biblioteca \"{0}\" creada",
"ToastLibraryCreateSuccess": "Se creó la biblioteca «{0}»",
"ToastLibraryDeleteFailed": "Error al eliminar biblioteca",
"ToastLibraryDeleteSuccess": "Biblioteca eliminada",
"ToastLibraryScanFailedToStart": "Error al iniciar el escaneo",
"ToastLibraryScanStarted": "Se inició el escaneo de la biblioteca",
"ToastLibraryUpdateSuccess": "Biblioteca \"{0}\" actualizada",
"ToastLibraryUpdateSuccess": "Se actualizó la biblioteca «{0}»",
"ToastMatchAllAuthorsFailed": "No se pudo encontrar a todos los autores",
"ToastMetadataFilesRemovedError": "Error al eliminar metadatos de {0} archivo(s)",
"ToastMetadataFilesRemovedNoneFound": "No hay metadatos.{0} archivo(s) encontrado(s) en la biblioteca",
@@ -1013,7 +1015,7 @@
"ToastNameEmailRequired": "Son obligatorios el nombre y el correo electrónico",
"ToastNameRequired": "Nombre obligatorio",
"ToastNewEpisodesFound": "{0} nuevo(s) episodio(s) encontrado(s)",
"ToastNewUserCreatedFailed": "Error al crear la cuenta: \"{0}\"",
"ToastNewUserCreatedFailed": "No se pudo crear la cuenta: «{0}»",
"ToastNewUserCreatedSuccess": "Nueva cuenta creada",
"ToastNewUserLibraryError": "Debes seleccionar al menos una biblioteca",
"ToastNewUserPasswordError": "Debes tener una contraseña, solo el usuario root puede estar sin contraseña",
@@ -1034,8 +1036,8 @@
"ToastPlaylistCreateSuccess": "Lista de reproducción creada",
"ToastPlaylistRemoveSuccess": "Lista de reproducción eliminada",
"ToastPlaylistUpdateSuccess": "Lista de reproducción actualizada",
"ToastPodcastCreateFailed": "Error al crear podcast",
"ToastPodcastCreateSuccess": "Podcast creado",
"ToastPodcastCreateFailed": "No se pudo crear el pódcast",
"ToastPodcastCreateSuccess": "Se creó el pódcast correctamente",
"ToastPodcastGetFeedFailed": "No se puede obtener el podcast",
"ToastPodcastNoEpisodesInFeed": "No se han encontrado episodios en el feed del RSS",
"ToastPodcastNoRssFeed": "El podcast no tiene feed RSS",
@@ -1057,9 +1059,9 @@
"ToastRescanUpToDate": "Reescaneado del artículo completo, estaba actualizado",
"ToastRescanUpdated": "Reescaneado completado, el artículo ha sido actualizado",
"ToastScanFailed": "No se pudo escanear el elemento de la biblioteca",
"ToastSelectAtLeastOneUser": "Selecciona al menos un usuario",
"ToastSendEbookToDeviceFailed": "Error al enviar el ebook al dispositivo",
"ToastSendEbookToDeviceSuccess": "Ebook enviado al dispositivo \"{0}\"",
"ToastSelectAtLeastOneUser": "Seleccione al menos un usuario",
"ToastSendEbookToDeviceFailed": "No se pudo enviar el libro electrónico al dispositivo",
"ToastSendEbookToDeviceSuccess": "Libro electrónico enviado al dispositivo «{0}»",
"ToastSeriesSubmitFailedSameName": "No se puede añadir dos series con el mismo nombre",
"ToastSeriesUpdateFailed": "Error al actualizar la serie",
"ToastSeriesUpdateSuccess": "Serie actualizada",
@@ -1079,10 +1081,12 @@
"ToastUnknownError": "Error desconocido",
"ToastUnlinkOpenIdFailed": "Error al desvincular el usuario de OpenID",
"ToastUnlinkOpenIdSuccess": "Usuario desvinculado de OpenID",
"ToastUploaderFilepathExistsError": "La ruta de archivo «{0}» ya existe en el servidor",
"ToastUploaderItemExistsInSubdirectoryError": "El elemento «{0}» usa un subdirectorio de la ruta de carga.",
"ToastUserDeleteFailed": "Error al eliminar el usuario",
"ToastUserDeleteSuccess": "Usuario eliminado",
"ToastUserPasswordChangeSuccess": "Contraseña modificada correctamente",
"ToastUserPasswordMismatch": "No coinciden las contraseñas",
"ToastUserPasswordMustChange": "La nueva contraseña no puede ser igual que la anterior",
"ToastUserRootRequireName": "Debes introducir un nombre de usuario root"
"ToastUserRootRequireName": "Debe introducir un nombre de usuario administrativo"
}

View File

@@ -1086,6 +1086,8 @@
"ToastUnknownError": "Nepoznata pogreška",
"ToastUnlinkOpenIdFailed": "Uklanjanje OpenID veze korisnika nije uspjelo",
"ToastUnlinkOpenIdSuccess": "Korisnik odspojen od OpenID-ja",
"ToastUploaderFilepathExistsError": "Putanja \"{0}\" već postoji na poslužitelju",
"ToastUploaderItemExistsInSubdirectoryError": "Stavka \"{0}\" koristi se podmapom u putanje za učitavanje.",
"ToastUserDeleteFailed": "Brisanje korisnika nije uspjelo",
"ToastUserDeleteSuccess": "Korisnik izbrisan",
"ToastUserPasswordChangeSuccess": "Zaporka je uspješno promijenjena",

View File

@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Fejezetek alkalmazása",
"ButtonAuthors": "Szerzők",
"ButtonBack": "Vissza",
"ButtonBatchEditPopulateFromExisting": "Létezőből feltöltés",
"ButtonBatchEditPopulateMapDetails": "",
"ButtonBrowseForFolder": "Mappa keresése",
"ButtonCancel": "Mégse",
"ButtonCancelEncode": "Kódolás megszakítása",

View File

@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Aplicar Capítulos",
"ButtonAuthors": "Autores",
"ButtonBack": "Voltar",
"ButtonBatchEditPopulateFromExisting": "Popular de um existente",
"ButtonBatchEditPopulateMapDetails": "Popular mapeamento de detalhes",
"ButtonBrowseForFolder": "Procurar por Pasta",
"ButtonCancel": "Cancelar",
"ButtonCancelEncode": "Cancelar Codificação",
@@ -19,6 +21,7 @@
"ButtonChooseFiles": "Escolha arquivos",
"ButtonClearFilter": "Limpar Filtro",
"ButtonCloseFeed": "Fechar Feed",
"ButtonCloseSession": "Fechar Sessão Aberta",
"ButtonCollections": "Coleções",
"ButtonConfigureScanner": "Configurar Verificador",
"ButtonCreate": "Criar",
@@ -28,6 +31,9 @@
"ButtonEdit": "Editar",
"ButtonEditChapters": "Editar Capítulos",
"ButtonEditPodcast": "Editar Podcast",
"ButtonEnable": "Ativar",
"ButtonFireAndFail": "Disparar e Falhar",
"ButtonFireOnTest": "Disparar evento onTest",
"ButtonForceReScan": "Forcar Nova Verificação",
"ButtonFullPath": "Caminho Completo",
"ButtonHide": "Ocultar",
@@ -37,6 +43,7 @@
"ButtonJumpForward": "Adiantar",
"ButtonLatest": "Mais Recentes",
"ButtonLibrary": "Biblioteca",
"ButtonLogout": "Logout",
"ButtonLookup": "Procurar",
"ButtonManageTracks": "Gerenciar Faixas",
"ButtonMapChapterTitles": "Designar Títulos de Capítulos",
@@ -45,18 +52,24 @@
"ButtonNevermind": "Cancelar",
"ButtonNext": "Próximo",
"ButtonNextChapter": "Próximo Capítulo",
"ButtonNextItemInQueue": "Próximo Item da Fila",
"ButtonOk": "Ok",
"ButtonOpenFeed": "Abrir Feed",
"ButtonOpenManager": "Abrir Gerenciador",
"ButtonPause": "Pausar",
"ButtonPlay": "Reproduzir",
"ButtonPlayAll": "Reproduzir Tudo",
"ButtonPlaying": "Reproduzindo",
"ButtonPlaylists": "Lista de Reprodução",
"ButtonPrevious": "Anterior",
"ButtonPreviousChapter": "Capítulo Anterior",
"ButtonProbeAudioFile": "Sondar Arquivo de Áudio",
"ButtonPurgeAllCache": "Apagar Todo o Cache",
"ButtonPurgeItemsCache": "Apagar o Cache de Itens",
"ButtonQueueAddItem": "Adicionar à Lista",
"ButtonQueueRemoveItem": "Remover da Lista",
"ButtonQuickEmbed": "Incorporação Rápida",
"ButtonQuickEmbedMetadata": "Incorporação Rápida de Metadata",
"ButtonQuickMatch": "Consulta rápida",
"ButtonReScan": "Nova Verificação",
"ButtonRead": "Ler",
@@ -77,6 +90,8 @@
"ButtonSaveTracklist": "Salvar Lista de Faixas",
"ButtonScan": "Verificar",
"ButtonScanLibrary": "Verificar Biblioteca",
"ButtonScrollLeft": "Arrastar para Esquerda",
"ButtonScrollRight": "Arrastar para Direita",
"ButtonSearch": "Pesquisar",
"ButtonSelectFolderPath": "Selecionar Caminho da Pasta",
"ButtonSeries": "Séries",
@@ -196,6 +211,7 @@
"LabelAddToPlaylist": "Adicionar à Lista de Reprodução",
"LabelAddToPlaylistBatch": "Adicionar {0} itens à Lista de Reprodução",
"LabelAddedAt": "Acrescentado em",
"LabelAddedDate": "Adicionado {0}",
"LabelAdminUsersOnly": "Apenas usuários administradores",
"LabelAll": "Todos",
"LabelAllUsers": "Todos Usuários",

View File

@@ -229,6 +229,7 @@
"LabelAddedDate": "Добавлено {0}",
"LabelAdminUsersOnly": "Только для пользователей с правами администратора",
"LabelAll": "Все",
"LabelAllEpisodesDownloaded": "Все эпизоды загружены",
"LabelAllUsers": "Все пользователи",
"LabelAllUsersExcludingGuests": "Все пользователи, кроме гостей",
"LabelAllUsersIncludingGuests": "Все пользователи, включая гостей",
@@ -777,8 +778,8 @@
"MessageForceReScanDescription": "будет сканировать все файлы снова, как свежее сканирование. Теги ID3 аудиофайлов, OPF-файлы и текстовые файлы будут сканироваться как новые.",
"MessageImportantNotice": "Важное замечание!",
"MessageInsertChapterBelow": "Вставить главу ниже",
"MessageItemsSelected": "{0} Элементов выделено",
"MessageItemsUpdated": "{0} Элементов обновлено",
"MessageItemsSelected": "{0} элементов выделено",
"MessageItemsUpdated": "{0} элементов обновлено",
"MessageJoinUsOn": "Присоединяйтесь к нам в",
"MessageLoading": "Загрузка...",
"MessageLoadingFolders": "Загрузка каталогов...",
@@ -953,6 +954,7 @@
"ToastBackupRestoreFailed": "Не удалось восстановить из бэкапа",
"ToastBackupUploadFailed": "Не удалось загрузить бэкап",
"ToastBackupUploadSuccess": "Бэкап загружен",
"ToastBatchApplyDetailsToItemsSuccess": "Подробности, применяемые к элементам",
"ToastBatchDeleteFailed": "Не удалось выполнить пакетное удаление",
"ToastBatchDeleteSuccess": "Успешное пакетное удаление",
"ToastBatchQuickMatchFailed": "Не удалось выполнить пакетное быстрое сопоставление!",
@@ -1065,6 +1067,7 @@
"ToastSelectAtLeastOneUser": "Выберите хотя бы одного пользователя",
"ToastSendEbookToDeviceFailed": "Не удалось отправить e-книгу на устройство",
"ToastSendEbookToDeviceSuccess": "E-книга отправлена на устройство \"{0}\"",
"ToastSeriesSubmitFailedSameName": "Невозможно добавить две серии с одинаковым названием",
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
"ToastServerSettingsUpdateSuccess": "Обновлены настройки сервера",
@@ -1083,6 +1086,8 @@
"ToastUnknownError": "Неизвестная ошибка",
"ToastUnlinkOpenIdFailed": "Не удалось отвязать пользователя от OpenID",
"ToastUnlinkOpenIdSuccess": "Пользователь отвязан от OpenID",
"ToastUploaderFilepathExistsError": "Путь к файлу \"{0}\" уже существует на сервере",
"ToastUploaderItemExistsInSubdirectoryError": "Элемент «{0}» использует подкаталог пути загрузки.",
"ToastUserDeleteFailed": "Не удалось удалить пользователя",
"ToastUserDeleteSuccess": "Пользователь удален",
"ToastUserPasswordChangeSuccess": "Пароль успешно изменен",

View File

@@ -32,7 +32,7 @@
"ButtonEditChapters": "Upraviť kapitoly",
"ButtonEditPodcast": "Upraviť podcast",
"ButtonEnable": "Povoliť",
"ButtonFireAndFail": "Fire and Fail",
"ButtonFireAndFail": "Spustiť a zlyhať",
"ButtonFireOnTest": "Fire onTest udalosť",
"ButtonForceReScan": "Vynútiť preskenovanie",
"ButtonFullPath": "Zobraziť cestu",
@@ -87,7 +87,7 @@
"ButtonRestore": "Obnoviť zo zálohy",
"ButtonSave": "Uložiť",
"ButtonSaveAndClose": "Uložiť a zavrieť",
"ButtonSaveTracklist": "Uložiť zoznam",
"ButtonSaveTracklist": "Uložiť tracklist",
"ButtonScan": "Skenovať",
"ButtonScanLibrary": "Skenovať knižnicu",
"ButtonScrollLeft": "Doľava",
@@ -107,7 +107,7 @@
"ButtonUnlinkOpenId": "Odhlásiť OpenID",
"ButtonUpload": "Nahrať",
"ButtonUploadBackup": "Nahrať zálohu",
"ButtonUploadCover": "Nahrať obal",
"ButtonUploadCover": "Nahrať prebal",
"ButtonUploadOPMLFile": "Nahrať súbor OPML",
"ButtonUserDelete": "Odstrániť užívateľa {0}",
"ButtonUserEdit": "Upraviť užívateľa {0}",
@@ -129,7 +129,7 @@
"HeaderChooseAFolder": "Vybrať priečinok",
"HeaderCollection": "Zbierka",
"HeaderCollectionItems": "Položky zbierky",
"HeaderCover": "Obal",
"HeaderCover": "Prebal",
"HeaderCurrentDownloads": "Aktuálne sťahovanie",
"HeaderCustomMessageOnLogin": "Vlastné privítanie pri prihlásení",
"HeaderCustomMetadataProviders": "Vlastné zdroje metadát",
@@ -147,7 +147,7 @@
"HeaderItemFiles": "Položka Súbory",
"HeaderItemMetadataUtils": "Položka Nástroje metadát",
"HeaderLastListeningSession": "Posledné pripojenie",
"HeaderLatestEpisodes": "Najnovšie epizódy",
"HeaderLatestEpisodes": "Posledné epizódy",
"HeaderLibraries": "Knižnice",
"HeaderLibraryFiles": "Súbory knižnice",
"HeaderLibraryStats": "Štatistiky knižnice",
@@ -157,6 +157,7 @@
"HeaderLogs": "Záznamy udalostí",
"HeaderManageGenres": "Spravovať žánre",
"HeaderManageTags": "Spravovať štítky",
"HeaderMapDetails": "Podrobnosti mapovania",
"HeaderMatch": "Spárovať",
"HeaderMetadataOrderOfPrecedence": "Metadáta pravidiel poradia",
"HeaderMetadataToEmbed": "Metadáta na vloženie",
@@ -176,11 +177,382 @@
"HeaderPlaylist": "Playlist",
"HeaderPlaylistItems": "Položky playlistu",
"HeaderPodcastsToAdd": "Podcasty na pridanie",
"HeaderPreviewCover": "Ukážka obalu",
"HeaderPreviewCover": "Ukážka prebalu",
"HeaderRSSFeedGeneral": "Detaily RSS",
"HeaderRSSFeedIsOpen": "RSS zdroj je otvorený",
"HeaderRSSFeeds": "RSS zdroje",
"HeaderRemoveEpisode": "Odstrániť epizódu",
"HeaderRemoveEpisodes": "Odstrániť {0} epizód",
"LabelBackupsNumberToKeepHelp": "Týmto spôsobom odstránite vždy iba jednu zálohu. V prípade, ak chcete odtrániť viacero záloh, mali by ste ich odstrániť manuálne."
"HeaderSavedMediaProgress": "Priebeh uložených médií",
"HeaderSchedule": "Plán",
"HeaderScheduleEpisodeDownloads": "Naplánovať automatické sťahovanie epizód",
"HeaderScheduleLibraryScans": "Naplánovanovať automatické skenovanie knižnice",
"HeaderSession": "Relácia",
"HeaderSetBackupSchedule": "Naplánovať zálohovanie",
"HeaderSettings": "Nastavenia",
"HeaderSettingsDisplay": "Zobraziť",
"HeaderSettingsExperimental": "Experimentálne funkcie",
"HeaderSettingsGeneral": "Hlavné",
"HeaderSettingsScanner": "Skener",
"HeaderSettingsWebClient": "Webový klient",
"HeaderSleepTimer": "Časovač spánku",
"HeaderStatsLargestItems": "Najväčšie položky",
"HeaderStatsLongestItems": "Najdlhšie položky (v hodinách)",
"HeaderStatsMinutesListeningChart": "Vypočutých minút (za posledných 7 dní)",
"HeaderStatsRecentSessions": "Nedávne relácie",
"HeaderStatsTop10Authors": "Top 10 autorov",
"HeaderStatsTop5Genres": "Top 5 žánrov",
"HeaderTableOfContents": "Obsah",
"HeaderTools": "Nástroje",
"HeaderUpdateAccount": "Aktualizovať účet",
"HeaderUpdateAuthor": "Aktualizovať autora",
"HeaderUpdateDetails": "Aktualizovať detaily",
"HeaderUpdateLibrary": "Aktualizovať knižnicu",
"HeaderUsers": "Užívatelia",
"HeaderYearReview": "Prehľad roka {0}",
"HeaderYourStats": "Tvoje štatistiky",
"LabelAbridged": "Skrátená verzia",
"LabelAbridgedChecked": "Skrátená verzia (zaškrtnuté)",
"LabelAbridgedUnchecked": "Neskrátená verzia (nezaškrtnuté)",
"LabelAccessibleBy": "Prístupné pre",
"LabelAccountType": "Typ účtu",
"LabelAccountTypeAdmin": "Administrátor",
"LabelAccountTypeGuest": "Hosť",
"LabelAccountTypeUser": "Užívateľ",
"LabelActivities": "Aktivity",
"LabelActivity": "Aktivita",
"LabelAddToCollection": "Pridať do kolekcie",
"LabelAddToCollectionBatch": "Pridať {0} kníh do kolekcie",
"LabelAddToPlaylist": "Pridať do playlistu",
"LabelAddToPlaylistBatch": "Pridať {0} položie do playlistu",
"LabelAddedAt": "Pridané",
"LabelAddedDate": "Pridané {0}",
"LabelAdminUsersOnly": "Iba administrátory",
"LabelAll": "Všetko",
"LabelAllEpisodesDownloaded": "Všetky epizódy stiahnuté",
"LabelAllUsers": "Všetci užívatelia",
"LabelAllUsersExcludingGuests": "Všetci užívatelia okrem hostí",
"LabelAllUsersIncludingGuests": "Všetci užívatelia vrátane hostí",
"LabelAlreadyInYourLibrary": "Už v tvojej knižnici",
"LabelApiToken": "API Token",
"LabelAppend": "Pridať",
"LabelAudioBitrate": "Bitrate audio stopy (napr. 128k)",
"LabelAudioChannels": "Počet kanálov audio stopy (1 alebo 2)",
"LabelAudioCodec": "Kodek audio stopy",
"LabelAuthor": "Autor",
"LabelAuthorFirstLast": "Autor (Meno, Priezvisko)",
"LabelAuthorLastFirst": "Autor (Priezvisko, Meno)",
"LabelAuthors": "Autori",
"LabelAutoDownloadEpisodes": "Automaticky sťahovať epizódy",
"LabelAutoFetchMetadata": "Automaticky načítať metadáta",
"LabelAutoFetchMetadataHelp": "Načíta metadáta pre názov, autra a sériu pre optimalizované nahranie. Dodatočné metadáta môžu byť priradené po nahraní.",
"LabelAutoLaunch": "Automaticky spustiť",
"LabelAutoLaunchDescription": "Presmerovať na poskytovateľa authentifikácie pri otvorení prihlasovacej stránky (manuálny prepis cesty <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Automatická registrácia",
"LabelAutoRegisterDescription": "Automaticky vytvoriť nových užívateľov po prihlásení",
"LabelBackToUser": "Späť na užívateľa",
"LabelBackupAudioFiles": "Zálohovať audio súbory",
"LabelBackupLocation": "Zálohovať lokáciu",
"LabelBackupsEnableAutomaticBackups": "Automatické zálohy",
"LabelBackupsEnableAutomaticBackupsHelp": "Zálohy uložené v /metadata/backups",
"LabelBackupsMaxBackupSize": "Maximálna veľkosť zálohy (v GB) (0 pre neobmedzenú)",
"LabelBackupsMaxBackupSizeHelp": "Ako poistka proti miskonfigurácii, zálohy zlyhajú ak prekročia konfigurovanú veľkosť.",
"LabelBackupsNumberToKeep": "Počet uložených záloh",
"LabelBackupsNumberToKeepHelp": "Týmto spôsobom odstránite vždy iba jednu zálohu. V prípade, ak chcete odtrániť viacero záloh, mali by ste ich odstrániť manuálne.",
"LabelBitrate": "Bitrate",
"LabelBonus": "Bonus",
"LabelBooks": "Knihy",
"LabelButtonText": "Text tlačidla",
"LabelByAuthor": "od {0}",
"LabelChangePassword": "Zmeniť heslo",
"LabelChannels": "Kanály",
"LabelChapterCount": "{0} kapitol",
"LabelChapterTitle": "Názov kapitoly",
"LabelChapters": "Kapitoly",
"LabelChaptersFound": "nájdených kapitol",
"LabelClickForMoreInfo": "Klikni pre viac informácií",
"LabelClickToUseCurrentValue": "Klikni pre použitie aktuálnej hodnoty",
"LabelClosePlayer": "Zavrieť prehrávač",
"LabelCodec": "Kodek",
"LabelCollapseSeries": "Zbaliť série",
"LabelCollapseSubSeries": "Zbaliť podsérie",
"LabelCollection": "Kolekcia",
"LabelCollections": "Kolekcie",
"LabelComplete": "Hotovo",
"LabelConfirmPassword": "Potvrdiť heslo",
"LabelContinueListening": "Pokračovať v počúvaní",
"LabelContinueReading": "Pokračovať v čítaní",
"LabelContinueSeries": "Pokračovať v sérii",
"LabelCover": "Prebal",
"LabelCoverImageURL": "URL obrázku prebalu",
"LabelCoverProvider": "Poskytovateľ prebalu",
"LabelCreatedAt": "Vytvorené",
"LabelCronExpression": "Cron príkaz",
"LabelCurrent": "Aktuálny",
"LabelCurrently": "Aktuálne:",
"LabelCustomCronExpression": "Vlastný Cron príkaz:",
"LabelDatetime": "Dátum a čas",
"LabelDays": "Dni",
"LabelDeleteFromFileSystemCheckbox": "Zmazať zo systému (odškrtni len pre odstránenie z databázy)",
"LabelDescription": "Popis",
"LabelDeselectAll": "Odznačiť všetko",
"LabelDevice": "Zariadenie",
"LabelDeviceInfo": "Informácie o zariadení",
"LabelDeviceIsAvailableTo": "Zariadenie je k dispozícii...",
"LabelDirectory": "Priečinok",
"LabelDiscFromFilename": "Disk z názvu súboru",
"LabelDiscFromMetadata": "Disk z metadát",
"LabelDiscover": "Objaviť",
"LabelDownload": "Stiahnuť",
"LabelDownloadNEpisodes": "Stiahnuť {0} epizód",
"LabelDownloadable": "Dostupné na stiahnutie",
"LabelDuration": "Dĺžka",
"LabelDurationComparisonExactMatch": "(presná zhoda)",
"LabelDurationComparisonLonger": "({0} dlhšie)",
"LabelDurationComparisonShorter": "({0} kratšie)",
"LabelDurationFound": "Nájdená dlžka:",
"LabelEbook": "E-kniha",
"LabelEbooks": "E-knihy",
"LabelEdit": "Upraviť",
"LabelEmail": "E-mail",
"LabelEmailSettingsFromAddress": "Z e-mailu",
"LabelEmailSettingsRejectUnauthorized": "Odmietnuť neautorizované certifikáty",
"LabelEmailSettingsRejectUnauthorizedHelp": "Vypnutie validácie SSL certifikátu môže tvoje pripojenie vystaviť bezpečnostným rizikám, ako napríklad MitM útokom. Vypni túto možnosť len v prípade, že rozumieš dôsledkom a dôveruješ e-mailovému serveru, ku ktorému sa pripájaš.",
"LabelEmailSettingsSecure": "Bezpečné",
"LabelEmailSettingsSecureHelp": "Pri povolení bude na pripojenie k serveru použité TLS. V opačnom prípade je TLS použité iba v prípade, ak server podporuje rozšírenie STARTTLS. Vo väčšine prípadov povoľte túto možnosť, ak sa pripájate cez port 465. V prípadoch, ak používate port 587 alebo 25, túto voľbu nepovoľujte. (prevzaté z nodemailer.com/smtp/#authentification)",
"LabelEmailSettingsTestAddress": "Testovacia adresa",
"LabelEmbeddedCover": "Vložený prebal",
"LabelEnable": "Povoliť",
"LabelEncodingBackupLocation": "Záloha vašich pôvodných zvukových súborov bude uložená v:",
"LabelEncodingChaptersNotEmbedded": "Kapitoly nie sú vkladané do viacstopových audiokníh.",
"LabelEncodingClearItemCache": "Nezabudnite pravidelne vyčistiť vyrovnávaciu pamäť jednotlivých položiek.",
"LabelEncodingFinishedM4B": "Dokončený súbor M4B bude uložený do priečinka audioknihy v:",
"LabelEncodingInfoEmbedded": "Metadáta budú vložené do zvukových stôp v priečinku audioknihy.",
"LabelEncodingStartedNavigation": "Po spustení úlohy môžete túto stránku opustiť.",
"LabelEncodingTimeWarning": "Prekódovanie môže trvať aj 30 minút.",
"LabelEncodingWarningAdvancedSettings": "Pozor: Nemeňte uvedené nastavenia, pokiaľ nie ste dostatočne oboznámený s nastaveniami ffmpeg prekódovania.",
"LabelEncodingWatcherDisabled": "V prípade, ak nemáte povolené automatické sledovanie zmien, bude na konci potrebné audioknihu opätovne preskenovať.",
"LabelEnd": "Ukončiť",
"LabelEndOfChapter": "Koniec kapitoly",
"LabelEpisode": "Epizóda",
"LabelEpisodeNotLinkedToRssFeed": "Epizóda bez RSS zdroja",
"LabelEpisodeNumber": "Epizóda #{0}",
"LabelEpisodeTitle": "Názov epizódy",
"LabelEpisodeType": "Typ epizódy",
"LabelEpisodeUrlFromRssFeed": "URL epizódy z RSS",
"LabelEpisodes": "Epizódy",
"LabelEpisodic": "Epizódny",
"LabelExample": "Príklad",
"LabelExpandSeries": "Rozbaliť série",
"LabelExpandSubSeries": "Rozbaliť podsérie",
"LabelExplicit": "Explicitné",
"LabelExplicitChecked": "Explicitné (zaškrtnuté)",
"LabelExplicitUnchecked": "Ne-explicitné (nezaškrtnuté)",
"LabelExportOPML": "Exportovať OPML",
"LabelFeedURL": "URL zdroja",
"LabelFetchingMetadata": "Sťahovanie metadát",
"LabelFile": "Súbor",
"LabelFileBirthtime": "Čas vytvorenia súboru",
"LabelFileBornDate": "Vytvorené {0}",
"LabelFileModified": "Súbor zmenený",
"LabelFileModifiedDate": "Zmenený {0}",
"LabelFilename": "Názov súboru",
"LabelFilterByUser": "Užívateľský filter",
"LabelFindEpisodes": "Nájsť epizódy",
"LabelFinished": "Ukončené",
"LabelFolder": "Priečinok",
"LabelFolders": "Priečinky",
"LabelFontBold": "Tučné",
"LabelFontBoldness": "Hrúbka písma",
"LabelFontFamily": "Rodina písiem",
"LabelFontItalic": "Kurzíva",
"LabelFontScale": "Veľkosť písma",
"LabelFontStrikethrough": "Preškrtnuté",
"LabelFormat": "Formát",
"LabelFull": "Plné",
"LabelGenre": "Žáner",
"LabelGenres": "Žánre",
"LabelHardDeleteFile": "Nezvratné zmazanie súborov",
"LabelHasEbook": "Má e-knihu",
"LabelHasSupplementaryEbook": "Má doplnkovú e-knihu",
"LabelHideSubtitles": "Skryť titulky",
"LabelHighestPriority": "Najvyššia priorita",
"LabelHost": "Host",
"LabelHour": "Hodina",
"LabelHours": "Hodiny",
"LabelIcon": "Ikona",
"LabelImageURLFromTheWeb": "URL obrázku",
"LabelInProgress": "Prebieha",
"LabelIncludeInTracklist": "Vložiť do tracklistu",
"LabelIncomplete": "Nekompletné",
"LabelInterval": "Interval",
"LabelIntervalCustomDailyWeekly": "Vlastný",
"LabelIntervalEvery12Hours": "Každých 12 hodín",
"LabelIntervalEvery15Minutes": "Každých 15 minút",
"LabelIntervalEvery2Hours": "Každé 2 hodiny",
"LabelIntervalEvery30Minutes": "Každých 30 minút",
"LabelIntervalEvery6Hours": "Každých 6 hodín",
"LabelIntervalEveryDay": "Denne",
"LabelIntervalEveryHour": "Každú hodinu",
"LabelIntervalEveryMinute": "Každú minútu",
"LabelInvert": "Invertne",
"LabelItem": "Položka",
"LabelJumpBackwardAmount": "Posunúť vpred o",
"LabelJumpForwardAmount": "Posunúť dozadu o",
"LabelLanguage": "Jazyk",
"LabelLanguageDefaultServer": "Prednastavený jazyk servera",
"LabelLanguages": "Jazyky",
"LabelLastBookAdded": "Posledná pridaná kniha",
"LabelLastBookUpdated": "Posledná aktualizovaná kniha",
"LabelLastSeen": "Posledne videné",
"LabelLastTime": "Posledný čas",
"LabelLastUpdate": "Posledná aktualizácia",
"LabelLayout": "Rozloženie",
"LabelLayoutSinglePage": "Jedna stránka",
"LabelLayoutSplitPage": "Rozdelená stránka",
"LabelLess": "Menej",
"LabelLibrariesAccessibleToUser": "Knižnice dostupné užívateľovi",
"LabelLibrary": "Knižnica",
"LabelLibraryFilterSublistEmpty": "Žiadne {0}",
"LabelLibraryItem": "Položka knižnice",
"LabelLibraryName": "Názov knižnice",
"LabelLimit": "Limit",
"LabelLineSpacing": "Riadkovanie",
"LabelListenAgain": "Počúvať znova",
"LabelLogLevelDebug": "Ladenie",
"LabelLogLevelInfo": "Informácia",
"LabelLogLevelWarn": "Varovanie",
"LabelLookForNewEpisodesAfterDate": "Hľadať nové epizódy od uvedeného dátumu",
"LabelLowestPriority": "Najnižšia priorita",
"LabelMatchExistingUsersBy": "Vyhľadaj vytvorených užívateľov podľa",
"LabelMatchExistingUsersByDescription": "Používané na pripájanie vytvorených užívateľov. Po pripojení budú užívatelia vyhľadaní na základe jedinečného id poskytnutého Vaším poskytovateľom SSO",
"LabelMaxEpisodesToDownload": "Stiahnuť maximálne # epizód. Pre neobmedzené sťahovanie zadajte 0.",
"LabelMaxEpisodesToDownloadPerCheck": "Pri kontrole stiahnuť maximálne # epizód",
"LabelMaxEpisodesToKeep": "Uchovávať maximálne # epizód",
"LabelMaxEpisodesToKeepHelp": "Hodnota 0 znamená bez limitu. Po automatickom stiahnutí novej epizódy bude najstaršia epizóda zmazaná a ponechaných zostane X epizód. Pri každom stiahnutí 1 epizódy bude vždy zmazaná iba 1 najstaršia epizóda.",
"LabelMediaPlayer": "Prehrávač",
"LabelMediaType": "Typ média",
"LabelMetaTag": "Meta štítok",
"LabelMetaTags": "Meta štítky",
"LabelMetadataOrderOfPrecedenceDescription": "Zdroje metadát s vyššou prioritou prepíšu zdroje metadát s nižšou prioritou",
"LabelMetadataProvider": "Poskytovateľ metadát",
"LabelMinute": "Minúta",
"LabelMinutes": "Minúty",
"LabelMissing": "Chýbajúce",
"LabelMissingEbook": "Nemá e-knihu",
"LabelMissingSupplementaryEbook": "Nemá doplnkovú e-knihu",
"LabelMobileRedirectURIs": "Povolené Mobile Redirect URI",
"LabelMobileRedirectURIsDescription": "Toto je zoznam povolených URI pre mobilné aplikácie. Prednastavená je <code>audiobookshelf://oauth</code>, ktorú však môžete odstrániť alebo nahradiť inou URI pre integráciu aplikácií tretích strán. Použitím hviezdičky (<code>*</code>) povolíte všetky URI.",
"LabelMore": "Viac",
"LabelMoreInfo": "Viac informácií",
"LabelName": "Názov",
"LabelNarrator": "Interpret",
"LabelNarrators": "Interpreti",
"LabelNew": "Nový",
"LabelNewPassword": "Nové heslo",
"LabelNewestAuthors": "Najnovší autori",
"LabelNewestEpisodes": "Najnovšie epizódy",
"LabelNextBackupDate": "Ďalší dátum zálohovania",
"LabelNextScheduledRun": "Ďalší plánovaný beh",
"LabelNoCustomMetadataProviders": "Žiadne vlastné zdroje metadát",
"LabelNoEpisodesSelected": "Neboli vybrané žiadne epizódy",
"LabelNotFinished": "Nedokončené",
"LabelNotStarted": "Nezačaté",
"LabelNotes": "Poznámky",
"LabelNotificationAppriseURL": "URL odkaz(-y) Apprise",
"LabelNotificationAvailableVariables": "Dostupné premenné",
"LabelNotificationBodyTemplate": "Šablóna obsahu",
"LabelNotificationEvent": "Udalosť oznámení",
"LabelNotificationTitleTemplate": "Šablóna názvu",
"LabelNotificationsMaxFailedAttempts": "Maximálny počet neúspešných pokusov",
"LabelNotificationsMaxFailedAttemptsHelp": "Notifikácie sa automaticky vypnú, ak ich odoslanie zlyhá nasledovný počet krát",
"LabelNotificationsMaxQueueSize": "Maximálna dĺžka fronty oznámení",
"LabelNotificationsMaxQueueSizeHelp": "Odosielanie udalostí je ohraničené na jedno oznámenie za sekundu. Novovzniknuté udalosti budú ignorované, ak bude fronta oznámení naplnená. Toto nastavenie zabraňuje nevyžiadanému zahlteniu oznámeniami.",
"LabelNumberOfBooks": "Počet kníh",
"LabelNumberOfEpisodes": "# epizód",
"LabelOpenIDAdvancedPermsClaimDescription": "Názov OpenID predpokladá prítomnosť pokročilých povolení pre užívateľské akcie v rámci aplikácie, ktoré sú aplikovateľné na ne-administrátorské role (<b>ak sú nakonfigurované</b>). Ak potvrdenie takýchto pokročilých povolení nie je v odozve prítomné, prístup do ABS bude automaticky zamietnutý. Ak v odozve chýba len niektoré z očakávaných nastavení, tak bude jeho hodnota automaticky nastavená na <code>false</code>. Uistite sa prosím, že forma odozvy poskytovateľa identity má nasledovnú štruktúru:",
"LabelOpenIDClaims": "Ak ponecháte nasledujúce nastavenia prázdne, pokročilé nastavenia skupín a povolení nebudú aktivované a automaticky bude nastavená skupina 'Užívateľ'.",
"LabelOpenIDGroupClaimDescription": "Pri názve požiadavky OpenID sa predpokladá, že obsahuje zoznam užívateľských skupín. Bežne označovaný ako <code>groups</code>. <b>Ak je správne nakonfigurovaný</b>, aplikácia automaticky pridelí role podľa príslušnosti k užívateľským skupinám pod podmienkou, že sú tieto skupiny v požiadavke nazvané (bez ohľadu na veľkosť písmen) ako 'admin', 'user' alebo 'guest'. Požiadavka musí obsahovať zoznam skupín a ak užívateľ patrí do viacerých skupín, aplikácia mu priradí rolu, ktorá zodpovedá skupine s najvyššími prístupovými právami. Ak sa žiadna z poskytnutých skupín nezhoduje, prístup bude zamietnutý.",
"LabelOpenRSSFeed": "Otvor RSS zdroj",
"LabelOverwrite": "Prepísať",
"LabelPaginationPageXOfY": "Stránka {0} z {1}",
"LabelPassword": "Heslo",
"LabelPath": "Cesta",
"LabelPermanent": "Trvalé",
"LabelPermissionsAccessAllLibraries": "Má prístup do všetkých knižníc",
"LabelPermissionsAccessAllTags": "Má prístup ku všetkým štítkom",
"LabelPermissionsAccessExplicitContent": "Má prístup k explicitnému obsahu",
"LabelPermissionsCreateEreader": "Môže vytvoriť čítačku e-kníh",
"LabelPermissionsDelete": "Môže mazať",
"LabelPermissionsDownload": "Môže sťahovať",
"LabelPermissionsUpdate": "Môže aktualizovať",
"LabelPermissionsUpload": "Môže nahrávať",
"LabelPersonalYearReview": "Váš rok v prehľade ({0})",
"LabelPhotoPathURL": "Cesta/URL fotky",
"LabelPlayMethod": "Metóda prehrávania",
"LabelPlaybackRateIncrementDecrement": "Veľkosť kroku zrýchlenia/spomalenia rýchlosti prehávania",
"LabelPlayerChapterNumberMarker": "{0} z {1}",
"LabelPlaylists": "Playlisty",
"LabelPodcast": "Podcast",
"LabelPodcastSearchRegion": "Región vyhľadávania podcastu",
"LabelPodcastType": "Typ podcastu",
"LabelPodcasts": "Podcasty",
"LabelPort": "Prístav",
"LabelPrefixesToIgnore": "Ignorované predpony (bez ohľadu na veľkosť písmen)",
"LabelPreventIndexing": "Zabráňte indexovaniu Vášho zdroja službami iTunes a Google podcasts directories",
"LabelPrimaryEbook": "Primárny e-book",
"LabelProgress": "Pokrok",
"LabelProvider": "Poskytovateľ",
"LabelProviderAuthorizationValue": "Obsah hlavičky autorizácie",
"LabelPubDate": "Dátum vydania",
"LabelPublishYear": "Rok vydania",
"LabelPublishedDate": "Vydané {0}",
"LabelPublishedDecade": "Dekáda vydania",
"LabelPublishedDecades": "Dekády vydania",
"LabelPublisher": "Vydavateľ",
"LabelPublishers": "Vydavatelia",
"LabelRSSFeedCustomOwnerEmail": "Vlastný e-mail vlastníka",
"LabelRSSFeedCustomOwnerName": "Vlastné meno vlastníka",
"LabelRSSFeedOpen": "RSS zdroj otvorený",
"LabelRSSFeedPreventIndexing": "Zakázať indexovanie",
"LabelRSSFeedSlug": "Slug RSS zdroja",
"LabelRSSFeedURL": "URL RSS zdroja",
"LabelRandomly": "Náhodne",
"LabelReAddSeriesToContinueListening": "Znova pridať série do pokračujúceho počúvania",
"LabelRead": "Čítať",
"LabelReadAgain": "Čítať znova",
"LabelReadEbookWithoutProgress": "Čítať e-knihu bez sledovania pokroku",
"LabelRecentSeries": "Posledné série",
"LabelRecentlyAdded": "Posledné pridané",
"LabelRecommended": "Odporúčané",
"LabelRedo": "Zopakovať",
"LabelRegion": "Región",
"LabelReleaseDate": "Dátum vydania",
"LabelRemoveAllMetadataAbs": "Odstrániť všetky súbory metadata.abs",
"LabelRemoveAllMetadataJson": "Odstrániť všetky súbory metadata.json",
"LabelRemoveCover": "Odstrániť prebal",
"LabelRemoveMetadataFile": "Odstrániť súbory metadát z priečinkov položiek v knižnici",
"LabelRemoveMetadataFileHelp": "Odstrániť všetky súbory metadata.json a metadata.abs vo Vašich {0} priečinkoch.",
"LabelRowsPerPage": "Počet riadkov na stránku",
"LabelSearchTerm": "Hľadaj výraz",
"LabelSearchTitle": "Hľadaj názov",
"LabelSearchTitleOrASIN": "Hľadaj názov alebo ASIN",
"LabelSeason": "Sezóna",
"LabelSeasonNumber": "Sezóna #{0}",
"LabelSelectAll": "Vybrať všetko",
"LabelSelectAllEpisodes": "Vybrať všetky epizódy",
"LabelSelectEpisodesShowing": "Vybrať {0} zobrazených epizód",
"LabelSelectUsers": "Vybrať užívateľov",
"LabelSendEbookToDevice": "Poslať e-knihu do...",
"LabelSequence": "Postupnosť",
"LabelSerial": "Na pokračovanie",
"LabelSeries": "Séria",
"LabelSeriesName": "Názov série",
"LabelSeriesProgress": "Pokrok série",
"LabelServerLogLevel": "Úroveň logovania servera",
"LabelServerYearReview": "Rok servera v prehľade ({0})",
"LabelSetEbookAsPrimary": "Nastaviť ako primárny",
"LabelSetEbookAsSupplementary": "Nastaviť ako doplnkový",
"LabelSettingsAllowIframe": "Povoliť vkladanie do iframe",
"LabelSettingsAudiobooksOnly": "Len audioknihy"
}

View File

@@ -1086,6 +1086,8 @@
"ToastUnknownError": "Neznana napaka",
"ToastUnlinkOpenIdFailed": "Prekinitev povezave uporabnika z OpenID ni uspela",
"ToastUnlinkOpenIdSuccess": "Uporabnik je prekinil povezavo z OpenID",
"ToastUploaderFilepathExistsError": "Datoteka s potjo \"{0}\" že obstaja na strežniku",
"ToastUploaderItemExistsInSubdirectoryError": "Element \"{0}\" uporablja podmapo v poti za nalaganje.",
"ToastUserDeleteFailed": "Brisanje uporabnika ni uspelo",
"ToastUserDeleteSuccess": "Uporabnik je bil izbrisan",
"ToastUserPasswordChangeSuccess": "Geslo je bilo uspešno spremenjeno",

View File

@@ -544,7 +544,8 @@
"LabelSettingsBookshelfViewHelp": "Bakgrund med ett utseende liknande en bokhylla i trä",
"LabelSettingsChromecastSupport": "Stöd för Chromecast",
"LabelSettingsDateFormat": "Datumformat",
"LabelSettingsEnableWatcher": "Automatiskt upptäcka förändringar i biblioteket",
"LabelSettingsEnableWatcher": "Upptäck automatiskt förändringar i biblioteket",
"LabelSettingsEnableWatcherForLibrary": "Upptäck automatiskt förändringar i biblioteket",
"LabelSettingsEnableWatcherHelp": "Aktiverar automatik att upptäcka när objekt<br>adderas, uppdateras eller raderas.<br>OBS: Kräver en omstart av servern",
"LabelSettingsEpubsAllowScriptedContent": "Tillåt e-böcker i epubs-format som innehåller script",
"LabelSettingsEpubsAllowScriptedContentHelp": "Tillåt att epub-filer får innehålla script.<br>Det rekommenderas att denna inställning är<br>avstängd när du inte litar på källan för epub-filerna.",
@@ -819,7 +820,7 @@
"MessageResetChaptersConfirm": "Är du säker på att du vill återställa alla kapitel och ångra de ändringarna du gjort?",
"MessageRestoreBackupConfirm": "Är du säker på att du vill läsa in säkerhetskopian som skapades den",
"MessageRestoreBackupWarning": "Att återställa en säkerhetskopia kommer att skriva över hela databasen som finns i /config och omslagsbilder i /metadata/items & /metadata/authors.<br /><br />Säkerhetskopior ändrar inte några filer i dina biblioteksmappar. Om du har aktiverat serverinställningar för att lagra omslagskonst och metadata i dina biblioteksmappar säkerhetskopieras eller skrivs de inte över.<br /><br />Alla klienter som använder din server kommer att uppdateras automatiskt.",
"MessageScheduleLibraryScanNote": "För de flesta användare rekommenderas att denna funktion ej aktiveras. Istället bör funktionen som automatisk upptäcker ändringar av filerna vara aktiverad. För vissa filsystem (som t.ex. NFS) fungerar inte denna funktion. Då kan schemalagda skanningar av biblioteken användas istället.",
"MessageScheduleLibraryScanNote": "För de flesta användare rekommenderas att denna funktion ej aktiveras. Istället bör funktionen som automatisk upptäcker ändringar i biblioteket vara aktiverad. För vissa filsystem (som t.ex. NFS) fungerar inte denna funktion. Då kan schemalagda skanningar av biblioteken användas istället.",
"MessageScheduleRunEveryWeekdayAtTime": "Startar varje {0} klockan {1}",
"MessageSearchResultsFor": "Sökresultat för",
"MessageSelected": "{0} valda",
@@ -909,6 +910,7 @@
"ToastAuthorUpdateMerged": "Författaren sammanslagen",
"ToastAuthorUpdateSuccess": "Författaren uppdaterad",
"ToastAuthorUpdateSuccessNoImageFound": "Författaren uppdaterad (ingen bild hittad)",
"ToastBackupAppliedSuccess": "Säkerhetskopian är importerad",
"ToastBackupCreateFailed": "Det gick inte att skapa en säkerhetskopia",
"ToastBackupCreateSuccess": "Säkerhetskopian har skapats",
"ToastBackupDeleteFailed": "Det gick inte att radera säkerhetskopian",
@@ -918,6 +920,7 @@
"ToastBackupRestoreFailed": "Det gick inte att återställa säkerhetskopian",
"ToastBackupUploadFailed": "Det gick inte att ladda upp säkerhetskopian",
"ToastBackupUploadSuccess": "Säkerhetskopian uppladdad",
"ToastBatchApplyDetailsToItemsSuccess": "Informationen har adderats till alla objekt",
"ToastBatchQuickMatchStarted": "Snabbmatchning av {0} böcker har påbörjats!",
"ToastBatchUpdateFailed": "Batchuppdateringen misslyckades",
"ToastBatchUpdateSuccess": "Batchuppdateringen lyckades",
@@ -948,6 +951,8 @@
"ToastEpisodeDownloadQueueClearSuccess": "Kö för nedladdning av avsnitt har tömts",
"ToastEpisodeUpdateSuccess": "{0} avsnitt uppdaterades",
"ToastFailedToLoadData": "Misslyckades med att ladda data",
"ToastFailedToMatch": "Misslyckades med att matcha",
"ToastFailedToShare": "Misslyckades med att dela",
"ToastFailedToUpdate": "Misslyckades med att uppdatera",
"ToastInvalidImageUrl": "Felaktig URL-adress till omslagsbilden",
"ToastInvalidMaxEpisodesToDownload": "Ogiltigt maximalt antal avsnitt att ladda ner",
@@ -1030,6 +1035,7 @@
"ToastSortingPrefixesUpdateSuccess": "{0} begrepp för sortering har uppdateras",
"ToastTitleRequired": "En titel måste anges",
"ToastUnknownError": "Ett okänt fel inträffade",
"ToastUploaderFilepathExistsError": "En fil med namnet \"{0}\" finns redan på servern",
"ToastUserDeleteFailed": "Misslyckades med att ta bort användaren",
"ToastUserDeleteSuccess": "Användaren borttagen",
"ToastUserPasswordChangeSuccess": "Lösenordet har ändrats",

View File

@@ -1086,6 +1086,8 @@
"ToastUnknownError": "Невідома помилка",
"ToastUnlinkOpenIdFailed": "Не вдалося відв'язати користувача від OpenID",
"ToastUnlinkOpenIdSuccess": "Користувача відв'язано від OpenID",
"ToastUploaderFilepathExistsError": "Шлях до файлу \"{0}\" уже існує на сервері",
"ToastUploaderItemExistsInSubdirectoryError": "Елемент \"{0}\" використовує підкаталог шляху завантаження.",
"ToastUserDeleteFailed": "Не вдалося видалити користувача",
"ToastUserDeleteSuccess": "Користувача видалено",
"ToastUserPasswordChangeSuccess": "Пароль успішно змінено",

View File

@@ -229,6 +229,7 @@
"LabelAddedDate": "已添加 {0}",
"LabelAdminUsersOnly": "仅限管理员用户",
"LabelAll": "全部",
"LabelAllEpisodesDownloaded": "所有剧集已下载",
"LabelAllUsers": "所有用户",
"LabelAllUsersExcludingGuests": "除访客外的所有用户",
"LabelAllUsersIncludingGuests": "包括访客的所有用户",
@@ -953,6 +954,7 @@
"ToastBackupRestoreFailed": "备份还原失败",
"ToastBackupUploadFailed": "上传备份失败",
"ToastBackupUploadSuccess": "备份已上传",
"ToastBatchApplyDetailsToItemsSuccess": "应用于项目的详细信息",
"ToastBatchDeleteFailed": "批量删除失败",
"ToastBatchDeleteSuccess": "批量删除成功",
"ToastBatchQuickMatchFailed": "批量快速匹配失败!",
@@ -1065,6 +1067,7 @@
"ToastSelectAtLeastOneUser": "至少选择一位用户",
"ToastSendEbookToDeviceFailed": "发送电子书到设备失败",
"ToastSendEbookToDeviceSuccess": "电子书已经发送到设备 \"{0}\"",
"ToastSeriesSubmitFailedSameName": "无法添加两个同名系列",
"ToastSeriesUpdateFailed": "更新系列失败",
"ToastSeriesUpdateSuccess": "系列已更新",
"ToastServerSettingsUpdateSuccess": "服务器设置已更新",
@@ -1083,6 +1086,8 @@
"ToastUnknownError": "未知错误",
"ToastUnlinkOpenIdFailed": "无法取消用户与 OpenID 的关联",
"ToastUnlinkOpenIdSuccess": "用户已取消与 OpenID 的关联",
"ToastUploaderFilepathExistsError": "文件路径 \"{0}\" 在服务器上已存在",
"ToastUploaderItemExistsInSubdirectoryError": "项目 \"{0}\" 正在使用上传路径的子目录。",
"ToastUserDeleteFailed": "删除用户失败",
"ToastUserDeleteSuccess": "用户已删除",
"ToastUserPasswordChangeSuccess": "密码修改成功",

View File

@@ -84,6 +84,42 @@ class SocketAuthority {
}
}
/**
* Emits event with library item to all clients that can access the library item
* Note: Emits toOldJSONExpanded()
*
* @param {string} evt
* @param {import('./models/LibraryItem')} libraryItem
*/
libraryItemEmitter(evt, libraryItem) {
for (const socketId in this.clients) {
if (this.clients[socketId].user?.checkCanAccessLibraryItem(libraryItem)) {
this.clients[socketId].socket.emit(evt, libraryItem.toOldJSONExpanded())
}
}
}
/**
* Emits event with library items to all clients that can access the library items
* Note: Emits toOldJSONExpanded()
*
* @param {string} evt
* @param {import('./models/LibraryItem')[]} libraryItems
*/
libraryItemsEmitter(evt, libraryItems) {
for (const socketId in this.clients) {
if (this.clients[socketId].user) {
const libraryItemsAccessibleToUser = libraryItems.filter((li) => this.clients[socketId].user.checkCanAccessLibraryItem(li))
if (libraryItemsAccessibleToUser.length) {
this.clients[socketId].socket.emit(
evt,
libraryItemsAccessibleToUser.map((li) => li.toOldJSONExpanded())
)
}
}
}
}
/**
* Closes the Socket.IO server and disconnect all clients
*

View File

@@ -152,10 +152,7 @@ class AuthorController {
for (const libraryItem of libraryItems) {
await libraryItem.saveMetadataFile()
}
SocketAuthority.emitter(
'items_updated',
libraryItems.map((li) => li.toOldJSONExpanded())
)
SocketAuthority.libraryItemsEmitter('items_updated', libraryItems)
}
// Remove old author
@@ -210,10 +207,7 @@ class AuthorController {
}
if (libraryItems.length) {
SocketAuthority.emitter(
'items_updated',
libraryItems.map((li) => li.toOldJSONExpanded())
)
SocketAuthority.libraryItemsEmitter('items_updated', libraryItems)
}
} else {
numBooksForAuthor = await Database.bookAuthorModel.getCountForAuthor(req.author.id)

View File

@@ -1188,10 +1188,7 @@ class LibraryController {
}
if (itemsUpdated.length) {
SocketAuthority.emitter(
'items_updated',
itemsUpdated.map((li) => li.toOldJSONExpanded())
)
SocketAuthority.libraryItemsEmitter('items_updated', itemsUpdated)
}
res.json({
@@ -1232,10 +1229,7 @@ class LibraryController {
}
if (itemsUpdated.length) {
SocketAuthority.emitter(
'items_updated',
itemsUpdated.map((li) => li.toOldJSONExpanded())
)
SocketAuthority.libraryItemsEmitter('items_updated', itemsUpdated)
}
res.json({

View File

@@ -253,7 +253,7 @@ class LibraryItemController {
}
Logger.debug(`[LibraryItemController] Updated library item media ${req.libraryItem.media.title}`)
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
}
res.json({
updated: hasUpdates,
@@ -300,7 +300,7 @@ class LibraryItemController {
req.libraryItem.changed('updatedAt', true)
await req.libraryItem.save()
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
res.json({
success: true,
cover: result.cover
@@ -332,7 +332,7 @@ class LibraryItemController {
req.libraryItem.changed('updatedAt', true)
await req.libraryItem.save()
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
}
res.json({
success: true,
@@ -358,7 +358,7 @@ class LibraryItemController {
await CacheManager.purgeCoverCache(req.libraryItem.id)
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
}
res.sendStatus(200)
@@ -485,7 +485,7 @@ class LibraryItemController {
req.libraryItem.media.changed('audioFiles', true)
await req.libraryItem.media.save()
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
res.json(req.libraryItem.toOldJSON())
}
@@ -663,7 +663,7 @@ class LibraryItemController {
await libraryItem.saveMetadataFile()
Logger.debug(`[LibraryItemController] Updated library item media "${libraryItem.media.title}"`)
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
itemsUpdated++
}
}
@@ -894,7 +894,7 @@ class LibraryItemController {
await req.libraryItem.saveMetadataFile()
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
}
res.json({
@@ -1005,7 +1005,7 @@ class LibraryItemController {
await req.libraryItem.save()
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
res.sendStatus(200)
}
@@ -1153,7 +1153,7 @@ class LibraryItemController {
await req.libraryItem.save()
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
res.sendStatus(200)
}

View File

@@ -343,7 +343,7 @@ class MiscController {
})
await libraryItem.saveMetadataFile()
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
numItemsUpdated++
}
}
@@ -386,7 +386,7 @@ class MiscController {
})
await libraryItem.saveMetadataFile()
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
numItemsUpdated++
}
@@ -481,7 +481,7 @@ class MiscController {
})
await libraryItem.saveMetadataFile()
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
numItemsUpdated++
}
}
@@ -524,7 +524,7 @@ class MiscController {
})
await libraryItem.saveMetadataFile()
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
numItemsUpdated++
}

View File

@@ -161,7 +161,7 @@ class PodcastController {
}
}
SocketAuthority.emitter('item_added', newLibraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_added', newLibraryItem)
res.json(newLibraryItem.toOldJSONExpanded())
@@ -379,7 +379,7 @@ class PodcastController {
const overrideDetails = req.query.override === '1'
const episodesUpdated = await Scanner.quickMatchPodcastEpisodes(req.libraryItem, { overrideDetails })
if (episodesUpdated) {
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
}
res.json({
@@ -418,7 +418,7 @@ class PodcastController {
Logger.info(`[PodcastController] Updated episode "${episode.title}" keys`, episode.changed())
await episode.save()
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
} else {
Logger.info(`[PodcastController] No changes to episode "${episode.title}"`)
}
@@ -504,7 +504,7 @@ class PodcastController {
req.libraryItem.media.numEpisodes = req.libraryItem.media.podcastEpisodes.length
await req.libraryItem.media.save()
SocketAuthority.emitter('item_updated', req.libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', req.libraryItem)
res.json(req.libraryItem.toOldJSON())
}

View File

@@ -211,6 +211,14 @@ class PodcastManager {
const podcastEpisode = await Database.podcastEpisodeModel.createFromRssPodcastEpisode(this.currentDownload.rssPodcastEpisode, libraryItem.media.id, audioFile)
libraryItem.libraryFiles.push(libraryFile.toJSON())
// Re-calculating library item size because this wasnt being updated properly for podcasts in v2.20.0 and below
let libraryItemSize = 0
libraryItem.libraryFiles.forEach((lf) => {
if (lf.metadata.size && !isNaN(lf.metadata.size)) {
libraryItemSize += Number(lf.metadata.size)
}
})
libraryItem.size = libraryItemSize
libraryItem.changed('libraryFiles', true)
libraryItem.media.podcastEpisodes.push(podcastEpisode)
@@ -246,7 +254,7 @@ class PodcastManager {
await libraryItem.media.save()
}
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
const podcastEpisodeExpanded = podcastEpisode.toOldJSONExpanded(libraryItem.id)
podcastEpisodeExpanded.libraryItem = libraryItem.toOldJSONExpanded()
SocketAuthority.emitter('episode_added', podcastEpisodeExpanded)
@@ -359,7 +367,7 @@ class PodcastManager {
libraryItem.changed('updatedAt', true)
await libraryItem.save()
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
return libraryItem.media.autoDownloadEpisodes
}
@@ -417,7 +425,7 @@ class PodcastManager {
libraryItem.changed('updatedAt', true)
await libraryItem.save()
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
return newEpisodes || []
}
@@ -704,7 +712,7 @@ class PodcastManager {
}
}
SocketAuthority.emitter('item_added', newLibraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_added', newLibraryItem)
// Turn on podcast auto download cron if not already on
if (newLibraryItem.media.autoDownloadEpisodes) {

View File

@@ -80,9 +80,13 @@ class PodcastEpisode extends Model {
if (rssPodcastEpisode.guid) {
podcastEpisode.extraData.guid = rssPodcastEpisode.guid
}
if (audioFile.chapters?.length) {
podcastEpisode.chapters = audioFile.chapters.map((ch) => ({ ...ch }))
} else if (rssPodcastEpisode.chapters?.length) {
podcastEpisode.chapters = rssPodcastEpisode.chapters.map((ch) => ({ ...ch }))
}
return this.create(podcastEpisode)
}

View File

@@ -475,6 +475,8 @@ class BookScanner {
bookAuthors: [],
bookSeries: []
}
const createdAtTimestamp = new Date().getTime()
if (bookMetadata.authors.length) {
for (const authorName of bookMetadata.authors) {
const matchingAuthorId = await Database.getAuthorIdByName(libraryItemData.libraryId, authorName)
@@ -485,6 +487,8 @@ class BookScanner {
} else {
// New author
bookObject.bookAuthors.push({
// Ensures authors are in a set order
createdAt: createdAtTimestamp + bookObject.bookAuthors.length,
author: {
libraryId: libraryItemData.libraryId,
name: authorName,

View File

@@ -64,7 +64,7 @@ class LibraryItemScanner {
const { libraryItem: expandedLibraryItem, wasUpdated } = await this.rescanLibraryItemMedia(libraryItem, libraryItemScanData, library.settings, scanLogger)
if (libraryItemDataUpdated || wasUpdated) {
SocketAuthority.emitter('item_updated', expandedLibraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', expandedLibraryItem)
await this.checkAuthorsAndSeriesRemovedFromBooks(library.id, scanLogger)

View File

@@ -223,11 +223,7 @@ class LibraryScanner {
// Emit item updates in chunks of 10 to client
if (libraryItemsUpdated.length === 10) {
// TODO: Should only emit to clients where library item is accessible
SocketAuthority.emitter(
'items_updated',
libraryItemsUpdated.map((li) => li.toOldJSONExpanded())
)
SocketAuthority.libraryItemsEmitter('items_updated', libraryItemsUpdated)
libraryItemsUpdated = []
}
@@ -235,11 +231,7 @@ class LibraryScanner {
}
// Emit item updates to client
if (libraryItemsUpdated.length) {
// TODO: Should only emit to clients where library item is accessible
SocketAuthority.emitter(
'items_updated',
libraryItemsUpdated.map((li) => li.toOldJSONExpanded())
)
SocketAuthority.libraryItemsEmitter('items_updated', libraryItemsUpdated)
}
// Authors and series that were removed from books should be removed if they are now empty
@@ -277,11 +269,7 @@ class LibraryScanner {
// Emit new items in chunks of 10 to client
if (newLibraryItems.length === 10) {
// TODO: Should only emit to clients where library item is accessible
SocketAuthority.emitter(
'items_added',
newLibraryItems.map((li) => li.toOldJSONExpanded())
)
SocketAuthority.libraryItemsEmitter('items_added', newLibraryItems)
newLibraryItems = []
}
@@ -289,11 +277,7 @@ class LibraryScanner {
}
// Emit new items to client
if (newLibraryItems.length) {
// TODO: Should only emit to clients where library item is accessible
SocketAuthority.emitter(
'items_added',
newLibraryItems.map((li) => li.toOldJSONExpanded())
)
SocketAuthority.libraryItemsEmitter('items_added', newLibraryItems)
}
}
@@ -609,7 +593,7 @@ class LibraryScanner {
Logger.info(`[LibraryScanner] Scanning file update group and library item was deleted "${existingLibraryItem.media.title}" - marking as missing`)
existingLibraryItem.isMissing = true
await existingLibraryItem.save()
SocketAuthority.emitter('item_updated', existingLibraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', existingLibraryItem)
itemGroupingResults[itemDir] = ScanResult.REMOVED
continue
@@ -643,7 +627,7 @@ class LibraryScanner {
const isSingleMediaItem = isSingleMediaFile(fileUpdateGroup, itemDir)
const newLibraryItem = await LibraryItemScanner.scanPotentialNewLibraryItem(fullPath, library, folder, isSingleMediaItem)
if (newLibraryItem) {
SocketAuthority.emitter('item_added', newLibraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_added', newLibraryItem)
}
itemGroupingResults[itemDir] = newLibraryItem ? ScanResult.ADDED : ScanResult.NOTHING
}

View File

@@ -59,17 +59,36 @@ class PodcastScanner {
if (libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== existingPodcastEpisodes.length) {
// Filter out and destroy episodes that were removed
existingPodcastEpisodes = await Promise.all(
existingPodcastEpisodes.filter(async (ep) => {
if (libraryItemData.checkAudioFileRemoved(ep.audioFile)) {
libraryScan.addLog(LogLevel.INFO, `Podcast episode "${ep.title}" audio file was removed`)
// TODO: Should clean up other data linked to this episode
await ep.destroy()
return false
const episodesToRemove = []
existingPodcastEpisodes = existingPodcastEpisodes.filter((ep) => {
if (libraryItemData.checkAudioFileRemoved(ep.audioFile)) {
episodesToRemove.push(ep)
return false
}
return true
})
if (episodesToRemove.length) {
// Remove episodes from playlists and media progress
const episodeIds = episodesToRemove.map((ep) => ep.id)
await Database.playlistModel.removeMediaItemsFromPlaylists(episodeIds)
const mediaProgressRemoved = await Database.mediaProgressModel.destroy({
where: {
mediaItemId: episodeIds
}
return true
})
)
if (mediaProgressRemoved) {
libraryScan.addLog(LogLevel.INFO, `Removed ${mediaProgressRemoved} media progress for episodes`)
}
// Remove episodes
await Promise.all(
episodesToRemove.map(async (ep) => {
await ep.destroy()
libraryScan.addLog(LogLevel.INFO, `Podcast episode "${ep.title}" audio file was removed`)
})
)
}
// Update audio files that were modified
if (libraryItemData.audioLibraryFilesModified.length) {
@@ -113,6 +132,9 @@ class PodcastScanner {
// Create new podcast episodes from new found audio files
for (const newAudioFile of newAudioFiles) {
// Podcast episode audio files always have index 1
newAudioFile.index = 1
const newEpisode = {
title: newAudioFile.metaTags.tagTitle || newAudioFile.metadata.filenameNoExt,
subtitle: null,
@@ -136,7 +158,6 @@ class PodcastScanner {
}
let hasMediaChanges = false
if (existingPodcastEpisodes.length !== media.numEpisodes) {
media.numEpisodes = existingPodcastEpisodes.length
hasMediaChanges = true
@@ -253,6 +274,9 @@ class PodcastScanner {
// Create podcast episodes from audio files
for (const audioFile of scannedAudioFiles) {
// Podcast episode audio files always have index 1
audioFile.index = 1
const newEpisode = {
title: audioFile.metaTags.tagTitle || audioFile.metadata.filenameNoExt,
subtitle: null,

View File

@@ -126,7 +126,7 @@ class Scanner {
await libraryItem.saveMetadataFile()
SocketAuthority.emitter('item_updated', libraryItem.toOldJSONExpanded())
SocketAuthority.libraryItemEmitter('item_updated', libraryItem)
}
return {

View File

@@ -243,3 +243,29 @@ module.exports.isValidASIN = (str) => {
if (!str || typeof str !== 'string') return false
return /^[A-Z0-9]{10}$/.test(str)
}
/**
* Convert timestamp to seconds
* @example "01:00:00" => 3600
* @example "01:00" => 60
* @example "01" => 1
*
* @param {string} timestamp
* @returns {number}
*/
module.exports.timestampToSeconds = (timestamp) => {
if (typeof timestamp !== 'string') {
return null
}
const parts = timestamp.split(':').map(Number)
if (parts.some(isNaN)) {
return null
} else if (parts.length === 1) {
return parts[0]
} else if (parts.length === 2) {
return parts[0] * 60 + parts[1]
} else if (parts.length === 3) {
return parts[0] * 3600 + parts[1] * 60 + parts[2]
}
return null
}

View File

@@ -1,9 +1,17 @@
const axios = require('axios')
const ssrfFilter = require('ssrf-req-filter')
const Logger = require('../Logger')
const { xmlToJSON, levenshteinDistance } = require('./index')
const { xmlToJSON, levenshteinDistance, timestampToSeconds } = require('./index')
const htmlSanitizer = require('../utils/htmlSanitizer')
/**
* @typedef RssPodcastChapter
* @property {number} id
* @property {string} title
* @property {number} start
* @property {number} end
*/
/**
* @typedef RssPodcastEpisode
* @property {string} title
@@ -22,6 +30,7 @@ const htmlSanitizer = require('../utils/htmlSanitizer')
* @property {string} guid
* @property {string} chaptersUrl
* @property {string} chaptersType
* @property {RssPodcastChapter[]} chapters
*/
/**
@@ -205,12 +214,53 @@ function extractEpisodeData(item) {
const cleanKey = key.split(':').pop()
episode[cleanKey] = extractFirstArrayItemString(item, key)
})
// Extract psc:chapters if duration is set
let episodeDuration = !isNaN(episode.duration) ? timestampToSeconds(episode.duration) : null
if (item['psc:chapters']?.[0]?.['psc:chapter']?.length && episodeDuration) {
// Example chapter:
// {"id":0,"start":0,"end":43.004286,"title":"chapter 1"}
const cleanedChapters = item['psc:chapters'][0]['psc:chapter'].map((chapter, index) => {
if (!chapter['$']?.title || !chapter['$']?.start || typeof chapter['$']?.start !== 'string' || typeof chapter['$']?.title !== 'string') {
return null
}
const start = timestampToSeconds(chapter['$'].start)
if (start === null) {
return null
}
return {
id: index,
title: chapter['$'].title,
start
}
})
if (cleanedChapters.some((chapter) => !chapter)) {
Logger.warn(`[podcastUtils] Invalid chapter data for ${episode.enclosure.url}`)
} else {
episode.chapters = cleanedChapters.map((chapter, index) => {
const nextChapter = cleanedChapters[index + 1]
const end = nextChapter ? nextChapter.start : episodeDuration
return {
id: chapter.id,
title: chapter.title,
start: chapter.start,
end
}
})
}
}
return episode
}
function cleanEpisodeData(data) {
const pubJsDate = data.pubDate ? new Date(data.pubDate) : null
const publishedAt = pubJsDate && !isNaN(pubJsDate) ? pubJsDate.valueOf() : null
return {
title: data.title,
subtitle: data.subtitle || '',
@@ -227,7 +277,8 @@ function cleanEpisodeData(data) {
enclosure: data.enclosure,
guid: data.guid || null,
chaptersUrl: data.chaptersUrl || null,
chaptersType: data.chaptersType || null
chaptersType: data.chaptersType || null,
chapters: data.chapters || []
}
}