Merge branch 'refs/heads/master' into mf/rssInboundManager

# Conflicts:
#	client/strings/en-us.json
This commit is contained in:
mfcar 2024-05-19 00:09:39 +01:00
commit f2c9fd5a80
No known key found for this signature in database
71 changed files with 2147 additions and 461 deletions

View file

@ -4,10 +4,10 @@
<widgets-cover-size-widget class="fixed right-4 z-50" :style="{ bottom: streamLibraryItem ? '181px' : '16px' }" />
<div v-if="loaded && !shelves.length && !search" class="w-full flex flex-col items-center justify-center py-12">
<p class="text-center text-2xl mb-4 py-4">{{ libraryName }} Library is empty!</p>
<p class="text-center text-2xl mb-4 py-4">{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}</p>
<div v-if="userIsAdminOrUp" class="flex">
<ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn>
<ui-btn color="success" class="w-52" @click="scan">Scan Library</ui-btn>
<ui-btn to="/config" color="primary" class="w-52 mr-2">{{ $strings.ButtonConfigureScanner }}</ui-btn>
<ui-btn color="success" class="w-52" :loading="isScanningLibrary || tempIsScanning" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
</div>
</div>
<div v-else-if="loaded && !shelves.length && search" class="w-full h-40 flex items-center justify-center">
@ -58,7 +58,8 @@ export default {
scannerParseSubtitle: false,
wrapperClientWidth: 0,
shelves: [],
lastItemIndexSelected: -1
lastItemIndexSelected: -1,
tempIsScanning: false
}
},
computed: {
@ -97,6 +98,9 @@ export default {
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
isScanningLibrary() {
return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId)
}
},
methods: {
@ -273,14 +277,15 @@ export default {
this.shelves = shelves
},
scan() {
this.tempIsScanning = true
this.$store
.dispatch('libraries/requestLibraryScan', { libraryId: this.$store.state.libraries.currentLibraryId })
.then(() => {
this.$toast.success('Library scan started')
})
.catch((error) => {
console.error('Failed to start scan', error)
this.$toast.error('Failed to start scan')
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
})
.finally(() => {
this.tempIsScanning = false
})
},
userUpdated(user) {

View file

@ -10,7 +10,7 @@
<p class="text-center text-2xl mb-4 py-4">{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}</p>
<div v-if="userIsAdminOrUp" class="flex">
<ui-btn to="/config" color="primary" class="w-52 mr-2">{{ $strings.ButtonConfigureScanner }}</ui-btn>
<ui-btn color="success" class="w-52" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
<ui-btn color="success" class="w-52" :loading="isScanningLibrary || tempIsScanning" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
</div>
</div>
<div v-else-if="!totalShelves && initialized" class="w-full py-16">
@ -62,7 +62,8 @@ export default {
currScrollTop: 0,
resizeTimeout: null,
mountWindowWidth: 0,
lastItemIndexSelected: -1
lastItemIndexSelected: -1,
tempIsScanning: false
}
},
watch: {
@ -208,6 +209,9 @@ export default {
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
isScanningLibrary() {
return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId)
}
},
methods: {
@ -727,14 +731,15 @@ export default {
}
},
scan() {
this.tempIsScanning = true
this.$store
.dispatch('libraries/requestLibraryScan', { libraryId: this.currentLibraryId })
.then(() => {
this.$toast.success('Library scan started')
})
.catch((error) => {
console.error('Failed to start scan', error)
this.$toast.error('Failed to start scan')
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
})
.finally(() => {
this.tempIsScanning = false
})
}
},

View file

@ -6,9 +6,12 @@
</div>
<div class="flex items-start mb-6 lg:mb-0" :class="playerHandler.isVideo ? 'ml-4 pl-96' : isSquareCover ? 'pl-18 sm:pl-24' : 'pl-12 sm:pl-16'">
<div class="min-w-0">
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
{{ title }}
</nuxt-link>
<div class="flex items-center">
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
{{ title }}
</nuxt-link>
<widgets-explicit-indicator v-if="isExplicit" />
</div>
<div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center">
<span class="material-icons text-sm">person</span>
<div class="flex items-center">
@ -18,7 +21,6 @@
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">,&nbsp;</span></nuxt-link>
</div>
<div v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">{{ $strings.LabelUnknown }}</div>
<widgets-explicit-indicator :explicit="isExplicit"></widgets-explicit-indicator>
</div>
</div>
@ -136,7 +138,7 @@ export default {
return this.streamLibraryItem?.mediaType === 'music'
},
isExplicit() {
return this.mediaMetadata.explicit || false
return !!this.mediaMetadata.explicit
},
mediaMetadata() {
return this.media.metadata || {}

View file

@ -13,9 +13,9 @@
<div class="flex-grow" />
<p class="text-sm md:text-base">{{ book.publishedYear }}</p>
</div>
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">by {{ book.author }}</p>
<p v-if="book.narrator" class="text-gray-400 text-xs">Narrated by {{ book.narrator }}</p>
<p v-if="book.duration" class="text-gray-400 text-xs">Runtime: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">{{ $getString('LabelByAuthor', [book.author]) }}</p>
<p v-if="book.narrator" class="text-gray-400 text-xs">{{ $strings.LabelNarrators }}: {{ book.narrator }}</p>
<p v-if="book.duration" class="text-gray-400 text-xs">{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
<div v-if="book.series?.length" class="flex py-1 -mx-1">
<div v-for="(series, index) in book.series" :key="index" class="bg-white bg-opacity-10 rounded-full px-1 py-0.5 mx-1">
<p class="leading-3 text-xs text-gray-400">
@ -29,9 +29,9 @@
</div>
<div v-else class="px-4 flex-grow">
<h1>
<div class="flex items-center">{{ book.title }}<widgets-explicit-indicator :explicit="book.explicit" /></div>
<div class="flex items-center">{{ book.title }}<widgets-explicit-indicator v-if="book.explicit" /></div>
</h1>
<p class="text-base text-gray-300 whitespace-nowrap truncate">by {{ book.author }}</p>
<p class="text-base text-gray-300 whitespace-nowrap truncate">{{ $getString('LabelByAuthor', [book.author]) }}</p>
<p v-if="book.genres" class="text-xs text-gray-400 leading-5">{{ book.genres.join(', ') }}</p>
<p class="text-xs text-gray-400 leading-5">{{ book.trackCount }} Episodes</p>
</div>
@ -75,11 +75,11 @@ export default {
let differenceInMinutes = currentBookDurationMinutes - this.book.duration
if (differenceInMinutes < 0) {
differenceInMinutes = Math.abs(differenceInMinutes)
return `(${this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)} shorter)`
return this.$getString('LabelDurationComparisonLonger', [this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)])
} else if (differenceInMinutes > 0) {
return `(${this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)} longer)`
return this.$getString('LabelDurationComparisonShorter', [this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)])
}
return '(exact match)'
return this.$strings.LabelDurationComparisonExactMatch
}
},
methods: {

View file

@ -7,7 +7,7 @@
<p v-if="matchKey === 'subtitle'" class="truncate text-xs text-gray-300" v-html="matchHtml" />
<p v-if="matchKey !== 'authors'" class="text-xs text-gray-200 truncate">by {{ authorName }}</p>
<p v-if="matchKey !== 'authors'" class="text-xs text-gray-200 truncate">{{ $getString('LabelByAuthor', [authorName]) }}</p>
<p v-else class="truncate text-xs text-gray-200" v-html="matchHtml" />
<div v-if="matchKey === 'series' || matchKey === 'tags' || matchKey === 'isbn' || matchKey === 'asin' || matchKey === 'episode' || matchKey === 'narrators'" class="m-0 p-0 truncate text-xs" v-html="matchHtml" />
@ -69,7 +69,7 @@ export default {
if (this.matchKey === 'episode') return `<p class="truncate">${this.$strings.LabelEpisode}: ${html}</p>`
if (this.matchKey === 'tags') return `<p class="truncate">${this.$strings.LabelTags}: ${html}</p>`
if (this.matchKey === 'subtitle') return `<p class="truncate">${html}</p>`
if (this.matchKey === 'authors') return `by ${html}`
if (this.matchKey === 'authors') this.$getString('LabelByAuthor', [html])
if (this.matchKey === 'isbn') return `<p class="truncate">ISBN: ${html}</p>`
if (this.matchKey === 'asin') return `<p class="truncate">ASIN: ${html}</p>`
if (this.matchKey === 'series') return `<p class="truncate">${this.$strings.LabelSeries}: ${html}</p>`

View file

@ -1,128 +1,124 @@
<template>
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }" class="rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }" class="absolute rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<!-- When cover image does not fill -->
<div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
<div cy-id="coverBg" v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
<div class="absolute cover-bg" ref="coverBg" />
</div>
<!-- Alternative bookshelf title/author/sort -->
<div v-if="isAlternativeBookshelfView || isAuthorBookshelfView" dir="auto" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
<div cy-id="detailBottom" v-if="isAlternativeBookshelfView || isAuthorBookshelfView" dir="auto" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
<div :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
<ui-tooltip v-if="displayTitle" :text="displayTitle" :disabled="!displayTitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
<p ref="displayTitle" class="truncate">{{ displayTitle }}</p>
<widgets-explicit-indicator :explicit="isExplicit" />
<p cy-id="title" ref="displayTitle" class="truncate">{{ displayTitle }}</p>
<widgets-explicit-indicator cy-id="explicitIndicator" v-if="isExplicit" />
</ui-tooltip>
</div>
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || '&nbsp;' }}</p>
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
<p cy-id="line2" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || '&nbsp;' }}</p>
<p cy-id="line3" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
</div>
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #78350f">
<div cy-id="seriesSequenceList" v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #78350f">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequenceList }}</p>
</div>
<div v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #cd9d49dd">
<div cy-id="booksInSeries" v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #cd9d49dd">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ booksInSeries }}</p>
</div>
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
<div v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
<div cy-id="titleImageNotReady" v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="text-gray-300 text-center">{{ title }}</p>
</div>
<!-- Cover Image -->
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
<img cy-id="coverImage" v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
<!-- Placeholder Cover Title & Author -->
<div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
<div cy-id="placeholderTitle" v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
<div>
<p class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">
{{ titleCleaned }}
</p>
<p cy-id="placeholderTitleText" class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
</div>
</div>
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
<p class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
<div cy-id="placeholderAuthor" v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
<p cy-id="placeholderAuthorText" class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
</div>
</div>
<!-- No progress shown for collapsed series in library and podcasts (unless showing podcast episode) -->
<div v-if="!booksInSeries && (!isPodcast || episodeProgress)" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
<!-- Finished progress bar for collapsed series -->
<div v-else-if="booksInSeries && seriesIsFinished" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b bg-success" :style="{ width: width * userProgressPercent + 'px' }"></div>
<!-- No progress shown for podcasts (unless showing podcast episode) -->
<div cy-id="progressBar" v-if="!isPodcast || episodeProgress" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
<!-- Overlay is not shown if collapsing series in library -->
<div v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block" :class="overlayWrapperClasslist">
<div v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
<div cy-id="overlay" v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded md:block" :class="overlayWrapperClasslist">
<div cy-id="playButton" v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play">
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
</div>
</div>
<div v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
<div cy-id="readButton" v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="clickReadEBook">
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">auto_stories</span>
</div>
</div>
<div v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
<div cy-id="editButton" v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
<span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
</div>
<!-- Radio button -->
<div v-if="!isAuthorBookshelfView" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
<div cy-id="selectedRadioButton" v-if="!isAuthorBookshelfView" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
</div>
<!-- More Menu Icon -->
<div ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="hidden md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore">
<div cy-id="moreButton" ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore">
<span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
</div>
<div v-if="ebookFormat" class="absolute" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }">
<div cy-id="ebookFormat" v-if="ebookFormat" class="absolute" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }">
<span class="text-white/80" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ ebookFormat }}</span>
</div>
</div>
<!-- Processing/loading spinner overlay -->
<div v-if="processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
<div cy-id="loadingSpinner" v-if="processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
<widgets-loading-spinner size="la-lg" />
</div>
<!-- Series name overlay -->
<div v-if="booksInSeries && libraryItem && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: sizeMultiplier + 'rem' }">
<div cy-id="seriesNameOverlay" v-if="booksInSeries && libraryItem && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: sizeMultiplier + 'rem' }">
<p v-if="seriesName" class="text-gray-200 text-center" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">{{ seriesName }}</p>
</div>
<!-- Error widget -->
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
<ui-tooltip cy-id="ErrorTooltip" v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
<div :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
</div>
</ui-tooltip>
<div v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }">
<div cy-id="rssFeed" v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }">
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.5 + 'rem' }">rss_feed</span>
</div>
<!-- Series sequence -->
<div v-if="seriesSequence && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
<div cy-id="seriesSequence" v-if="seriesSequence && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
</div>
<!-- Podcast Episode # -->
<div v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
<div cy-id="podcastEpisodeNumber" v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">
Episode<span v-if="recentEpisodeNumber"> #{{ recentEpisodeNumber }}</span>
</p>
</div>
<!-- Podcast Num Episodes -->
<div v-else-if="!numEpisodesIncomplete && numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
<div cy-id="numEpisodes" v-else-if="!numEpisodesIncomplete && numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
</div>
<!-- Podcast Num Episodes -->
<div v-else-if="numEpisodesIncomplete && !isHovering && !isSelectionMode" class="absolute rounded-full bg-yellow-400 text-black font-semibold box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
<div cy-id="numEpisodesIncomplete" v-else-if="numEpisodesIncomplete && !isHovering && !isSelectionMode" class="absolute rounded-full bg-yellow-400 text-black font-semibold box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodesIncomplete }}</p>
</div>
</div>
@ -343,11 +339,22 @@ export default {
if (!this.userProgress || this.userProgress.progress) return false
return this.userProgress.ebookProgress > 0
},
seriesProgressPercent() {
if (!this.libraryItemIdsInSeries.length) return 0
let progressPercent = 0
const useEBookProgress = this.useEBookProgress
this.libraryItemIdsInSeries.forEach((lid) => {
const progress = this.store.getters['user/getUserMediaProgress'](lid)
if (progress) progressPercent += progress.isFinished ? 1 : useEBookProgress ? progress.ebookProgress || 0 : progress.progress || 0
})
return progressPercent / this.libraryItemIdsInSeries.length
},
userProgressPercent() {
if (this.useEBookProgress) return Math.max(Math.min(1, this.userProgress.ebookProgress), 0)
return this.userProgress ? Math.max(Math.min(1, this.userProgress.progress), 0) || 0 : 0
let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0
return Math.max(Math.min(1, progressPercent), 0)
},
itemIsFinished() {
if (this.booksInSeries) return this.seriesIsFinished
return this.userProgress ? !!this.userProgress.isFinished : false
},
seriesIsFinished() {

View file

@ -119,9 +119,13 @@ export default {
return this.seriesBookProgress.some((p) => !p.isFinished && p.progress > 0)
},
seriesPercentInProgress() {
let totalFinishedAndInProgress = this.seriesBooksFinished.length
if (this.hasSeriesBookInProgress) totalFinishedAndInProgress += 1
return Math.min(1, Math.max(0, totalFinishedAndInProgress / this.books.length))
if (!this.books.length) return 0
let progressPercent = 0
this.seriesBookProgress.forEach((progress) => {
progressPercent += progress.isFinished ? 1 : progress.progress || 0
})
progressPercent /= this.books.length
return Math.min(1, Math.max(0, progressPercent))
},
isSeriesFinished() {
return this.books.length === this.seriesBooksFinished.length

View file

@ -8,7 +8,7 @@
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
<div class="flex items-center">
<p class="text-base text-gray-200">{{ _session.displayTitle }}</p>
<p v-if="_session.displayAuthor" class="text-xs text-gray-400 px-4">by {{ _session.displayAuthor }}</p>
<p v-if="_session.displayAuthor" class="text-xs text-gray-400 px-4">{{ $getString('LabelByAuthor', [_session.displayAuthor]) }}</p>
</div>
<div class="w-full h-px bg-white bg-opacity-10 my-4" />

View file

@ -122,7 +122,7 @@ export default {
})
.catch((error) => {
console.error('Failed to get collections', error)
this.$toast.error('Failed to load collections')
this.$toast.error(this.$strings.ToastFailedToLoadData)
})
.finally(() => {
this.processing = false

View file

@ -42,13 +42,13 @@
<div class="flex py-2">
<div>
<p class="text-center text-gray-200">New</p>
<p class="text-center text-gray-200">{{ $strings.LabelNew }}</p>
<a :href="selectedMatch.cover" target="_blank" class="bg-primary">
<covers-preview-cover :src="selectedMatch.cover" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</a>
</div>
<div v-if="media.coverPath" class="ml-0.5">
<p class="text-center text-gray-200">Current</p>
<p class="text-center text-gray-200">{{ $strings.LabelCurrent }}</p>
<a :href="$store.getters['globals/getLibraryItemCoverSrc'](libraryItem, null, true)" target="_blank" class="bg-primary">
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrc'](libraryItem, null, true)" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</a>
@ -180,14 +180,14 @@
<ui-checkbox v-model="selectedMatchUsage.explicit" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4" :class="{ 'pt-4': mediaMetadata.explicit != null }">
<ui-checkbox v-model="selectedMatch.explicit" :label="$strings.LabelExplicit" :disabled="!selectedMatchUsage.explicit" :checkbox-bg="!selectedMatchUsage.explicit ? 'bg' : 'primary'" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
<p v-if="mediaMetadata.explicit != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.explicit ? 'Explicit (checked)' : 'Not Explicit (unchecked)' }}</p>
<p v-if="mediaMetadata.explicit != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.explicit ? $strings.LabelExplicitChecked : $strings.LabelExplicitUnchecked }}</p>
</div>
</div>
<div v-if="selectedMatchOrig.abridged != null" class="flex items-center pb-2" :class="{ 'pt-2': mediaMetadata.abridged == null }">
<ui-checkbox v-model="selectedMatchUsage.abridged" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4" :class="{ 'pt-4': mediaMetadata.abridged != null }">
<ui-checkbox v-model="selectedMatch.abridged" :label="$strings.LabelAbridged" :disabled="!selectedMatchUsage.abridged" :checkbox-bg="!selectedMatchUsage.abridged ? 'bg' : 'primary'" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
<p v-if="mediaMetadata.abridged != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.abridged ? 'Abridged (checked)' : 'Unabridged (unchecked)' }}</p>
<p v-if="mediaMetadata.abridged != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.abridged ? $strings.LabelAbridgedChecked : $strings.LabelAbridgedUnchecked }}</p>
</div>
</div>

View file

@ -115,7 +115,7 @@ export default {
})
.catch((error) => {
console.error('Failed to get playlists', error)
this.$toast.error('Failed to load user playlists')
this.$toast.error(this.$strings.ToastFailedToLoadData)
})
.finally(() => {
this.processing = false

View file

@ -271,7 +271,7 @@ export default {
this.$emit('update:processing', true)
this.yearStats = await this.$axios.$get(`/api/me/stats/year/${this.year}`).catch((err) => {
console.error('Failed to load stats for year', err)
this.$toast.error('Failed to load year stats')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return null
})
await this.initCanvas()

View file

@ -250,7 +250,7 @@ export default {
this.$emit('update:processing', true)
this.yearStats = await this.$axios.$get(`/api/stats/year/${this.year}`).catch((err) => {
console.error('Failed to load stats for year', err)
this.$toast.error('Failed to load year stats')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return null
})
await this.initCanvas()

View file

@ -180,7 +180,7 @@ export default {
this.$emit('update:processing', true)
this.yearStats = await this.$axios.$get(`/api/me/stats/year/${this.year}`).catch((err) => {
console.error('Failed to load stats for year', err)
this.$toast.error('Failed to load year stats')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return null
})
await this.initCanvas()

View file

@ -94,11 +94,11 @@ export default {
this.$axios
.$delete(`/api/items/${this.libraryItemId}/file/${this.track.audioFile.ino}`)
.then(() => {
this.$toast.success('File deleted')
this.$toast.success(this.$strings.ToastDeleteFileSuccess)
})
.catch((error) => {
console.error('Failed to delete file', error)
this.$toast.error('Failed to delete file')
this.$toast.error(this.$strings.ToastDeleteFileFailed)
})
}
},

View file

@ -176,7 +176,7 @@ export default {
})
.catch((error) => {
console.error('Failed to load backups', error)
this.$toast.error('Failed to load backups')
this.$toast.error(this.$strings.ToastFailedToLoadData)
})
.finally(() => {
this.processing = false

View file

@ -115,11 +115,11 @@ export default {
this.$axios
.$delete(`/api/items/${this.libraryItemId}/file/${this.file.ino}`)
.then(() => {
this.$toast.success('File deleted')
this.$toast.success(this.$strings.ToastDeleteFileSuccess)
})
.catch((error) => {
console.error('Failed to delete file', error)
this.$toast.error('Failed to delete file')
this.$toast.error(this.$strings.ToastDeleteFileFailed)
})
.finally(() => {
this.processing = false

View file

@ -89,11 +89,11 @@ export default {
this.$axios
.$delete(`/api/items/${this.libraryItemId}/file/${this.file.ino}`)
.then(() => {
this.$toast.success('File deleted')
this.$toast.success(this.$strings.ToastDeleteFileSuccess)
})
.catch((error) => {
console.error('Failed to delete file', error)
this.$toast.error('Failed to delete file')
this.$toast.error(this.$strings.ToastDeleteFileFailed)
})
}
},

View file

@ -20,7 +20,7 @@
<td class="px-4">
<div class="flex items-center">
<nuxt-link :to="`/item/${downloadQueued.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ downloadQueued.podcastTitle }}</nuxt-link>
<widgets-explicit-indicator :explicit="downloadQueued.podcastExplicit" />
<widgets-explicit-indicator v-if="downloadQueued.podcastExplicit" />
</div>
</td>
<td>

View file

@ -1,5 +1,5 @@
<template>
<ui-tooltip v-if="explicit" :text="$strings.LabelExplicit" direction="top">
<ui-tooltip :text="$strings.LabelExplicit" direction="top">
<svg xmlns="http://www.w3.org/2000/svg" width="12px" height="12px" viewBox="0 0 512 512" class="ml-1">
<path
fill="white"
@ -40,9 +40,7 @@
<script>
export default {
props: {
explicit: Boolean
},
props: {},
data() {
return {}
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

View file

@ -4,7 +4,6 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="stylesheet" href="tailwind.compiled.css">
<title>Components App</title>
</head>
<body class="text-white bg-bg">

View file

@ -0,0 +1,342 @@
import LazyBookCard from '@/components/cards/LazyBookCard'
import Tooltip from '@/components/ui/Tooltip.vue'
import ExplicitIndicator from '@/components/widgets/ExplicitIndicator.vue'
import LoadingSpinner from '@/components/widgets/LoadingSpinner.vue'
import { Constants } from '@/plugins/constants'
function createMountOptions() {
const book = {
id: '1',
ino: '281474976785140',
libraryId: 'library-123',
folderId: 'folder-123',
path: '/path/to/book',
relPath: 'book',
isFile: false,
mtimeMs: 1689017292016,
ctimeMs: 1689017292016,
birthtimeMs: 1689017281555,
addedAt: 1700154928492,
updatedAt: 1713300533345,
isMissing: false,
isInvalid: false,
mediaType: 'book',
media: {
id: 'book1',
metadata: {
title: 'The Fellowship of the Ring',
titleIgnorePrefix: 'Fellowship of the Ring',
subtitle: 'LOTR, Book 1',
authorName: 'J. R. R. Tolkien',
authorNameLF: 'Tolkien, J. R. R.',
narratorName: 'Andy Sirkis',
genres: ['Science Fiction & Fantasy'],
publishedYear: '2017',
publishedDate: null,
publisher: 'Book Publisher',
description: 'Book Description',
isbn: null,
asin: 'B075LXMLNV',
language: 'English',
explicit: false,
abridged: false
},
coverPath: null,
tags: ['Fantasy', 'Adventure'],
numTracks: 1,
numAudioFiles: 1,
numChapters: 31,
duration: 64410,
size: 511206878
},
numFiles: 4,
size: 511279587
}
const propsData = {
index: 0,
bookMount: book,
bookCoverAspectRatio: 1,
bookshelfView: Constants.BookshelfView.DETAIL,
continueListeningShelf: false,
filterBy: null,
width: 192,
height: 192,
sortingIgnorePrefix: false,
orderBy: null
}
const stubs = {
'ui-tooltip': Tooltip,
'widgets-explicit-indicator': ExplicitIndicator,
'widgets-loading-spinner': LoadingSpinner
}
const mocks = {
$config: {
routerBasePath: 'https://my.server.com'
},
$store: {
commit: () => {},
getters: {
'user/getUserCanUpdate': true,
'user/getUserCanDelete': true,
'user/getUserCanDownload': true,
'user/getIsAdminOrUp': true,
'user/getUserMediaProgress': (id) => null,
'libraries/getLibraryProvider': () => 'audible.us',
'globals/getLibraryItemCoverSrc': () => 'https://my.server.com/book_placeholder.jpg',
getLibraryItemsStreaming: () => null,
getIsMediaQueued: () => false,
getIsStreamingFromDifferentLibrary: () => false
},
state: {
libraries: {
currentLibraryId: 'library-123'
},
processingBatch: false,
serverSettings: {
dateFormat: 'MM/dd/yyyy'
}
}
}
}
return { propsData, stubs, mocks }
}
describe('LazyBookCard', () => {
let mountOptions = null
beforeEach(() => {
mountOptions = createMountOptions()
// cy.intercept(
// 'https://my.server.com/**/*',
// { middleware: true },
// (req) => {
// req.on('before:response', (res) => {
// // force all API responses to not be cached
// res.headers['cache-control'] = 'no-store'
// })
// }
// )
})
before(() => {
// Put placeholder image is in the browser cache
mountOptions = createMountOptions()
cy.intercept('https://my.server.com/book_placeholder.jpg', { fixture: 'images/book_placeholder.jpg' }).as('bookCover')
cy.mount(LazyBookCard, mountOptions)
cy.wait('@bookCover')
// Put cover1 (aspect ratio 1.6) image in the browser cache
mountOptions = createMountOptions()
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover1.jpg'
cy.intercept('https://my.server.com/cover1.jpg', { fixture: 'images/cover1.jpg' }).as('bookCover1')
cy.mount(LazyBookCard, mountOptions)
cy.wait('@bookCover1')
// Put cover2 (aspect ratio 1) image in the browser cache
mountOptions = createMountOptions()
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover2.jpg'
cy.intercept('https://my.server.com/cover2.jpg', { fixture: 'images/cover2.jpg' }).as('bookCover2')
cy.mount(LazyBookCard, mountOptions)
cy.wait('@bookCover2')
})
it('renders the component correctly', () => {
cy.mount(LazyBookCard, mountOptions)
cy.get('&titleImageNotReady').should('be.hidden')
cy.get('&coverImage').should('have.css', 'opacity', '1')
cy.get('&coverBg').should('be.hidden')
cy.get('&overlay').should('be.hidden')
cy.get('&detailBottom').should('be.visible')
cy.get('&title').should('have.text', 'The Fellowship of the Ring')
cy.get('&explicitIndicator').should('not.exist')
cy.get('&line2').should('have.text', 'J. R. R. Tolkien')
cy.get('&line3').should('not.exist')
cy.get('seriesSequenceList').should('not.exist')
cy.get('&booksInSeries').should('not.exist')
cy.get('&placeholderTitle').should('be.visible')
cy.get('&placeholderTitleText').should('have.text', 'The Fellowship of the Ring')
cy.get('&placeholderAuthor').should('be.visible')
cy.get('&placeholderAuthorText').should('have.text', 'J. R. R. Tolkien')
cy.get('&progressBar').should('be.hidden')
cy.get('&finishedProgressBar').should('not.exist')
cy.get('&loadingSpinner').should('not.exist')
cy.get('&seriesNameOverlay').should('not.exist')
cy.get('&errorTooltip').should('not.exist')
cy.get('&rssFeed').should('not.exist')
cy.get('&seriesSequence').should('not.exist')
cy.get('&podcastEpisdeNumber').should('not.exist')
// this should actually fail, since the height does not cover
// the detailBottom element, currently rendered outside the card's area,
// and requires complex layout calculations outside of the component.
// todo: fix the component to render the detailBottom element inside the card's area
cy.get('#book-card-0').should(($el) => {
const width = $el.width()
const height = $el.height()
expect(width).to.be.closeTo(mountOptions.propsData.width, 0.01)
expect(height).to.be.closeTo(mountOptions.propsData.height, 0.01)
})
})
it('shows overlay on mouseover', () => {
cy.mount(LazyBookCard, mountOptions)
cy.get('#book-card-0').trigger('mouseover')
cy.get('&titleImageNotReady').should('be.hidden')
cy.get('&overlay').should('be.visible')
cy.get('&playButton').should('be.visible')
cy.get('&readButton').should('be.hidden')
cy.get('&editButton').should('be.visible')
cy.get('&selectedRadioButton').should('be.visible').and('have.text', 'radio_button_unchecked')
cy.get('&moreButton').should('be.visible')
cy.get('&ebookFormat').should('not.exist')
})
it('routes to item page when clicked', () => {
mountOptions.mocks.$router = { push: cy.stub().as('routerPush') }
cy.mount(LazyBookCard, mountOptions)
cy.get('#book-card-0').click()
cy.get('@routerPush').should('have.been.calledOnceWithExactly', '/item/1')
})
it('shows titleImageNotReady and sets opacity 0 on coverImage when image not ready', () => {
cy.mount(LazyBookCard, mountOptions)
cy.get('&titleImageNotReady').should('be.visible')
cy.get('&coverImage').should('have.css', 'opacity', '0')
})
it('shows coverBg when coverImage has different aspect ratio', () => {
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover1.jpg'
cy.mount(LazyBookCard, mountOptions)
cy.get('&coverBg').should('be.visible')
cy.get('&coverImage').should('have.class', 'object-contain')
})
it('hides coverBg when coverImage has same aspect ratio', () => {
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover2.jpg'
cy.mount(LazyBookCard, mountOptions)
cy.get('&coverBg').should('be.hidden')
cy.get('&coverImage').should('have.class', 'object-fill')
})
// The logic for displaying placeholder title and author seems incorrect.
// It is currently based on existence of coverPath, but should be based weater the actual cover image is placeholder or not.
// todo: fix the logic to display placeholder title and author based on the actual cover image.
it('hides placeholderTitle and placeholderAuthor when book has cover', () => {
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover1.jpg'
mountOptions.propsData.bookMount.media.coverPath = 'cover1.jpg'
cy.mount(LazyBookCard, mountOptions)
cy.get('&placeholderTitle').should('not.exist')
cy.get('&placeholderAuthor').should('not.exist')
})
it('hides detailBottom when bookShelfView is STANDARD', () => {
mountOptions.propsData.bookshelfView = Constants.BookshelfView.STANDARD
cy.mount(LazyBookCard, mountOptions)
cy.get('&detailBottom').should('not.exist')
})
it('shows explicit indicator when book is explicit', () => {
mountOptions.propsData.bookMount.media.metadata.explicit = true
cy.mount(LazyBookCard, mountOptions)
cy.get('&explicitIndicator').should('be.visible')
})
describe('when collapsedSeries is present', () => {
beforeEach(() => {
mountOptions.propsData.bookMount.collapsedSeries = {
id: 'series-123',
name: 'The Lord of the Rings',
nameIgnorePrefix: 'Lord of the Rings',
numBooks: 3,
libraryItemIds: ['1', '2', '3']
}
})
it('shows the collpased series', () => {
cy.mount(LazyBookCard, mountOptions)
cy.get('&seriesSequenceList').should('not.exist')
cy.get('&booksInSeries').should('be.visible').and('have.text', '3')
cy.get('&title').should('be.visible').and('have.text', 'The Lord of the Rings')
cy.get('&line2').should('be.visible').and('have.text', '\u00a0')
cy.get('&progressBar').should('be.hidden')
})
it('shows the seriesNameOverlay on mouseover', () => {
mountOptions.propsData.bookMount.media.metadata.series = {
id: 'series-456',
name: 'Middle Earth Chronicles',
sequence: 1
}
cy.mount(LazyBookCard, mountOptions)
cy.get('#book-card-0').trigger('mouseover')
cy.get('&seriesNameOverlay').should('be.visible').and('have.text', 'Middle Earth Chronicles')
})
it('shows the seriesSequenceList when collapsed series has a sequence list', () => {
mountOptions.propsData.bookMount.collapsedSeries.seriesSequenceList = '1-3'
cy.mount(LazyBookCard, mountOptions)
cy.get('&seriesSequenceList').should('be.visible').and('have.text', '#1-3')
cy.get('&booksInSeries').should('not.exist')
})
it('routes to the series page when clicked', () => {
mountOptions.mocks.$router = { push: cy.stub().as('routerPush') }
cy.mount(LazyBookCard, mountOptions)
cy.get('#book-card-0').click()
cy.get('@routerPush').should('have.been.calledOnceWithExactly', '/library/library-123/series/series-123')
})
it('shows the series progress bar when series has progress', () => {
mountOptions.mocks.$store.getters['user/getUserMediaProgress'] = (id) => {
switch (id) {
case '1':
return { isFinished: true }
case '2':
return { progress: 0.5 }
default:
return null
}
}
cy.mount(LazyBookCard, mountOptions)
cy.get('&progressBar')
.should('be.visible')
.and('have.class', 'bg-yellow-400')
.and(($el) => {
const width = $el.width()
expect(width).to.be.closeTo(((1 + 0.5) / 3) * mountOptions.propsData.width, 0.01)
})
})
it('shows full green progress bar when all books are finished', () => {
mountOptions.mocks.$store.getters['user/getUserMediaProgress'] = (id) => {
return { isFinished: true }
}
cy.mount(LazyBookCard, mountOptions)
cy.get('&progressBar')
.should('be.visible')
.and('have.class', 'bg-success')
.and(($el) => {
const width = $el.width()
expect(width).to.be.equal(mountOptions.propsData.width)
})
})
})
})

View file

@ -26,7 +26,7 @@ describe('LazySeriesCard', () => {
isCategorized: false,
seriesMount: series,
sortingIgnorePrefix: false,
orderBy: 'addedAt',
orderBy: 'addedAt'
}
const stubs = {
@ -126,7 +126,7 @@ describe('LazySeriesCard', () => {
.and('have.class', 'bg-yellow-400')
.and(($el) => {
const width = $el.width()
expect(width).to.be.closeTo((2 / 3) * propsData.width, 0.01)
expect(width).to.be.closeTo(((1 + 0.5) / 3) * propsData.width, 0.01)
})
})
@ -137,7 +137,9 @@ describe('LazySeriesCard', () => {
...mocks.$store,
getters: {
...mocks.$store.getters,
'user/getUserMediaProgress': (id) => { return { isFinished: true } }
'user/getUserMediaProgress': (id) => {
return { isFinished: true }
}
}
}
}
@ -212,5 +214,4 @@ describe('LazySeriesCard', () => {
cy.get('&detailBottomDisplayTitle').should('have.text', 'Lord of the Rings')
})
})

View file

@ -18,7 +18,7 @@
<div class="w-12 hidden lg:block" />
<p class="text-lg mb-4 font-semibold">{{ $strings.HeaderChapters }}</p>
<div class="flex-grow" />
<ui-checkbox v-model="showSecondInputs" checkbox-bg="primary" small label-class="text-sm text-gray-200 pl-1" label="Show seconds" class="mx-2" />
<ui-checkbox v-model="showSecondInputs" checkbox-bg="primary" small label-class="text-sm text-gray-200 pl-1" :label="$strings.LabelShowSeconds" class="mx-2" />
<div class="w-32 hidden lg:block" />
</div>
<div class="flex items-center mb-3 py-1 -mx-1">

View file

@ -310,14 +310,14 @@ export default {
.then((data) => {
this.$store.commit('setServerSettings', data.serverSettings)
if (data.updated) {
this.$toast.success('Server settings updated')
this.$toast.success(this.$strings.ToastServerSettingsUpdateSuccess)
} else {
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
}
})
.catch((error) => {
console.error('Failed to update server settings', error)
this.$toast.error('Failed to update server settings')
this.$toast.error(this.$strings.ToastServerSettingsUpdateFailed)
})
.finally(() => {
this.savingSettings = false

View file

@ -259,7 +259,7 @@ export default {
})
.catch((error) => {
console.error('Failed to get email settings', error)
this.$toast.error('Failed to load email settings')
this.$toast.error(this.$strings.ToastFailedToLoadData)
})
.finally(() => {
this.loading = false

View file

@ -199,16 +199,15 @@
<div class="h-0.5 bg-primary bg-opacity-30 w-full" />
<!-- confirm cache purge dialog -->
<prompt-dialog v-model="showConfirmPurgeCache" :width="675">
<div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300">
<p class="text-error font-semibold">Important Notice!</p>
<p class="my-2 text-center">Purge cache will delete the entire directory at <span class="font-mono">/metadata/cache</span>.</p>
<p class="text-center mb-8">Are you sure you want to remove the cache directory?</p>
<p class="text-error font-semibold">{{ $strings.MessageImportantNotice }}</p>
<p class="my-8 text-center" v-html="$strings.MessageConfirmPurgeCache" />
<div class="flex px-1 items-center">
<ui-btn color="primary" @click="showConfirmPurgeCache = false">Nevermind</ui-btn>
<ui-btn color="primary" @click="showConfirmPurgeCache = false">{{ $strings.ButtonNevermind }}</ui-btn>
<div class="flex-grow" />
<ui-btn color="success" @click="confirmPurge">Yes, Purge!</ui-btn>
<ui-btn color="success" @click="confirmPurge">{{ $strings.ButtonYes }}</ui-btn>
</div>
</div>
</prompt-dialog>
@ -275,7 +274,7 @@ export default {
updateSortingPrefixes() {
const prefixes = [...new Set(this.newServerSettings.sortingPrefixes.map((prefix) => prefix.trim().toLowerCase()) || [])]
if (!prefixes.length) {
this.$toast.error('Must have at least 1 prefix')
this.$toast.error(this.$strings.ToastSortingPrefixesEmptyError)
return
}
@ -283,7 +282,7 @@ export default {
this.$axios
.$patch(`/api/sorting-prefixes`, { sortingPrefixes: prefixes })
.then((data) => {
this.$toast.success(`Sorting prefixes updated. ${data.rowsUpdated} rows`)
this.$toast.success(this.$getString('ToastSortingPrefixesUpdateSuccess', [data.rowsUpdated]))
if (data.serverSettings) {
this.$store.commit('setServerSettings', data.serverSettings)
}
@ -291,7 +290,7 @@ export default {
})
.catch((error) => {
console.error('Failed to update prefixes', error)
this.$toast.error('Failed to update sorting prefixes')
this.$toast.error(this.$strings.ToastSortingPrefixesUpdateFailed)
})
.finally(() => {
this.savingPrefixes = false
@ -329,7 +328,7 @@ export default {
.dispatch('updateServerSettings', payload)
.then(() => {
this.updatingServerSettings = false
this.$toast.success('Server settings updated')
this.$toast.success(this.$strings.ToastServerSettingsUpdateSuccess)
if (payload.language) {
// Updating language after save allows for re-rendering
@ -339,7 +338,7 @@ export default {
.catch((error) => {
console.error('Failed to update server settings', error)
this.updatingServerSettings = false
this.$toast.error('Failed to update server settings')
this.$toast.error(this.$strings.ToastServerSettingsUpdateFailed)
})
},
initServerSettings() {
@ -359,11 +358,11 @@ export default {
await this.$axios
.$post('/api/cache/purge')
.then(() => {
this.$toast.success('Cache Purged!')
this.$toast.success(this.$strings.ToastCachePurgeSuccess)
})
.catch((error) => {
console.error('Failed to purge cache', error)
this.$toast.error('Failed to purge cache')
this.$toast.error(this.$strings.ToastCachePurgeFailed)
})
this.isPurgingCache = false
},
@ -384,11 +383,11 @@ export default {
await this.$axios
.$post('/api/cache/items/purge')
.then(() => {
this.$toast.success('Items Cache Purged!')
this.$toast.success(this.$strings.ToastCachePurgeSuccess)
})
.catch((error) => {
console.error('Failed to purge items cache', error)
this.$toast.error('Failed to purge items cache')
this.$toast.error(this.$strings.ToastCachePurgeFailed)
})
this.isPurgingCache = false
}

View file

@ -58,7 +58,7 @@ export default {
})
.catch((error) => {
console.error('Failed', error)
this.$toast.error('Failed to load custom metadata providers')
this.$toast.error(this.$strings.ToastFailedToLoadData)
})
.finally(() => {
this.processing = false

View file

@ -147,7 +147,7 @@ export default {
async loadLoggerData() {
const loggerData = await this.$axios.$get('/api/logger-data').catch((error) => {
console.error('Failed to load logger data', error)
this.$toast.error('Failed to load logger data')
this.$toast.error(this.$strings.ToastFailedToLoadData)
})
this.loadedLogs = loggerData?.currentDailyLogs || []

View file

@ -142,7 +142,7 @@ export default {
this.loading = true
const notificationResponse = await this.$axios.$get('/api/notifications').catch((error) => {
console.error('Failed to get notification settings', error)
this.$toast.error('Failed to load notification settings')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return null
})
this.loading = false

View file

@ -350,7 +350,7 @@ export default {
return null
})
if (!data) {
this.$toast.error('Failed to load RSS feeds')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return
}
this.feeds = data.feeds

View file

@ -426,7 +426,7 @@ export default {
})
this.loading = false
if (!data) {
this.$toast.error('Failed to load listening sessions')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return
}
@ -447,7 +447,7 @@ export default {
return null
})
if (!data) {
this.$toast.error('Failed to load open sessions')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return
}

View file

@ -214,7 +214,7 @@ export default {
return null
})
if (!data) {
this.$toast.error('Failed to load listening sessions')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return
}

View file

@ -27,7 +27,7 @@
<h1 class="text-2xl md:text-3xl font-semibold">
<div class="flex items-center">
{{ title }}
<widgets-explicit-indicator :explicit="isExplicit" />
<widgets-explicit-indicator v-if="isExplicit" />
<widgets-abridged-indicator v-if="isAbridged" />
</div>
</h1>
@ -40,7 +40,7 @@
</template>
<template v-if="!isVideo">
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">by {{ podcastAuthor || 'Unknown' }}</p>
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">{{ $getString('LabelByAuthor', [podcastAuthor]) }}</p>
<p v-else-if="musicArtists.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
<nuxt-link v-for="(artist, index) in musicArtists" :key="index" :to="`/artist/${$encode(artist)}`" class="hover:underline">{{ artist }}<span v-if="index < musicArtists.length - 1">,&nbsp;</span></nuxt-link>
</p>
@ -285,7 +285,7 @@ export default {
return this.mediaMetadata.subtitle
},
podcastAuthor() {
return this.mediaMetadata.author || ''
return this.mediaMetadata.author || 'Unknown'
},
authors() {
return this.mediaMetadata.authors || []

View file

@ -16,7 +16,7 @@
<div class="flex-grow px-2">
<div class="flex items-center">
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcastTitle }}</nuxt-link>
<widgets-explicit-indicator :explicit="episode.podcastExplicit" />
<widgets-explicit-indicator v-if="episode.podcastExplicit" />
</div>
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
</div>
@ -25,7 +25,7 @@
<div class="hidden md:block">
<div class="flex items-center">
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcastTitle }}</nuxt-link>
<widgets-explicit-indicator :explicit="episode.podcastExplicit" />
<widgets-explicit-indicator v-if="episode.podcastExplicit" />
</div>
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
</div>

View file

@ -18,7 +18,7 @@
<div class="flex" @click.stop>
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcast.metadata.title }}</nuxt-link>
</div>
<widgets-explicit-indicator :explicit="episode.podcast.metadata.explicit" />
<widgets-explicit-indicator v-if="episode.podcast.metadata.explicit" />
</div>
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
</div>
@ -29,7 +29,7 @@
<div class="flex" @click.stop>
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcast.metadata.title }}</nuxt-link>
</div>
<widgets-explicit-indicator :explicit="episode.podcast.metadata.explicit" />
<widgets-explicit-indicator v-if="episode.podcast.metadata.explicit" />
</div>
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
</div>

View file

@ -21,10 +21,10 @@
<div class="flex-grow pl-4 max-w-2xl">
<div class="flex items-center">
<a :href="podcast.pageUrl" class="text-base md:text-lg text-gray-200 hover:underline" target="_blank" @click.stop>{{ podcast.title }}</a>
<widgets-explicit-indicator :explicit="podcast.explicit" />
<widgets-explicit-indicator v-if="podcast.explicit" />
<widgets-already-in-library-indicator :already-in-library="podcast.alreadyInLibrary" />
</div>
<p class="text-sm md:text-base text-gray-300 whitespace-nowrap truncate">by {{ podcast.artistName }}</p>
<p class="text-sm md:text-base text-gray-300 whitespace-nowrap truncate">{{ $getString('LabelByAuthor', [podcast.artistName]) }}</p>
<p class="text-xs text-gray-400 leading-5">{{ podcast.genres.join(', ') }}</p>
<p class="text-xs text-gray-400 leading-5">{{ podcast.trackCount }} {{ $strings.HeaderEpisodes }}</p>
</div>

View file

@ -5,6 +5,7 @@ import { supplant } from './utils'
const defaultCode = 'en-us'
const languageCodeMap = {
'bg': { label: 'Български', dateFnsLocale: 'bg' },
'bn': { label: 'বাংলা', dateFnsLocale: 'bn' },
'cs': { label: 'Čeština', dateFnsLocale: 'cs' },
'da': { label: 'Dansk', dateFnsLocale: 'da' },

806
client/strings/bg.json Normal file
View file

@ -0,0 +1,806 @@
{
"ButtonAdd": "Добави",
"ButtonAddChapters": "Добави Глави",
"ButtonAddDevice": "Добави Устройство",
"ButtonAddLibrary": "Добави Библиотека",
"ButtonAddPodcasts": "Добави Подкаст",
"ButtonAddUser": "Добави Потребител",
"ButtonAddYourFirstLibrary": "Добави първата ти библиотека",
"ButtonApply": "Приложи",
"ButtonApplyChapters": "Приложи Глави",
"ButtonAuthors": "Автори",
"ButtonBrowseForFolder": "Прегледай за папка",
"ButtonCancel": "Откажи",
"ButtonCancelEncode": "Откажи закодирането",
"ButtonChangeRootPassword": "Промени паролата за Root",
"ButtonCheckAndDownloadNewEpisodes": "Провери и Свали Нови Епизоди",
"ButtonChooseAFolder": "Избери Папка",
"ButtonChooseFiles": "Избери Файлове",
"ButtonClearFilter": "Изчисти Филтър",
"ButtonCloseFeed": "Затвори Feed",
"ButtonCollections": "Колекции",
"ButtonConfigureScanner": "Конфигурирай Скенера",
"ButtonCreate": "Създай",
"ButtonCreateBackup": "Създай Backup",
"ButtonDelete": "Изтрий",
"ButtonDownloadQueue": "Опашка за Сваляне",
"ButtonEdit": "Редактирай",
"ButtonEditChapters": "Редактирай Глави",
"ButtonEditPodcast": "Редактирай Подкаст",
"ButtonForceReScan": "Принудително Пресканиране",
"ButtonFullPath": "Пълен Път",
"ButtonHide": "Скрий",
"ButtonHome": "Начало",
"ButtonIssues": "Проблеми",
"ButtonJumpBackward": "Прескочи назад",
"ButtonJumpForward": "Прескоци напред",
"ButtonLatest": "Последни",
"ButtonLibrary": "Библиотека",
"ButtonLogout": "Изход",
"ButtonLookup": "Търси",
"ButtonManageTracks": "Управление на Канали",
"ButtonMapChapterTitles": "Асоцийрай Заглавия на Глави",
"ButtonMatchAllAuthors": "Съвпадение на Всички Автори",
"ButtonMatchBooks": "Съвпадение на Книги",
"ButtonNevermind": "Няма значение",
"ButtonNext": "Next",
"ButtonNextChapter": "Следваща Глава",
"ButtonOk": "Добре",
"ButtonOpenFeed": "Отвори Feed",
"ButtonOpenManager": "Отвори Мениджър",
"ButtonPause": "Пауза",
"ButtonPlay": "Пусни",
"ButtonPlaying": "Пуска се",
"ButtonPlaylists": "Плейлисти",
"ButtonPrevious": "Previous",
"ButtonPreviousChapter": "Предишна Глава",
"ButtonPurgeAllCache": "Изчисти Всички Кешове",
"ButtonPurgeItemsCache": "Изчисти Кеша на Елементи",
"ButtonPurgeMediaProgress": "Изчисти Прогреса на Медията",
"ButtonQueueAddItem": "Добави към опашката",
"ButtonQueueRemoveItem": "Премахни от опашката",
"ButtonQuickMatch": "Бързо Съпоставяне",
"ButtonRead": "Прочети",
"ButtonReadLess": "Read less",
"ButtonReadMore": "Read more",
"ButtonRefresh": "Refresh",
"ButtonRemove": "Премахни",
"ButtonRemoveAll": "Премахни Всички",
"ButtonRemoveAllLibraryItems": "Премахни Всички Елементи от Библиотеката",
"ButtonRemoveFromContinueListening": "Премахни от Продължаване на Слушане",
"ButtonRemoveFromContinueReading": "Премахни от Продължаване на Четене",
"ButtonRemoveSeriesFromContinueSeries": "Премахни Серия от Продължаване на Серии",
"ButtonReScan": "Пресканирай",
"ButtonReset": "Нулиране",
"ButtonResetToDefault": "Нулиране до стойност по подразбиране",
"ButtonRestore": "Възстанови",
"ButtonSave": "Запази",
"ButtonSaveAndClose": "Запази и Затвори",
"ButtonSaveTracklist": "Запази Списък с Канали",
"ButtonScan": "Сканирай",
"ButtonScanLibrary": "Сканирай Библиотека",
"ButtonSearch": "Търси",
"ButtonSelectFolderPath": "Избери Път на Папка",
"ButtonSeries": "Серии",
"ButtonSetChaptersFromTracks": "Задай Глави от Песни",
"ButtonShare": "Share",
"ButtonShiftTimes": "Измести Времената",
"ButtonShow": "Покажи",
"ButtonStartM4BEncode": "Започни M4B Кодиране",
"ButtonStartMetadataEmbed": "Започни Вграждане на Метаданни",
"ButtonSubmit": "Изпрати",
"ButtonTest": "Тест",
"ButtonUpload": "Качи",
"ButtonUploadBackup": "Качи Backup",
"ButtonUploadCover": "Качи Корица",
"ButtonUploadOPMLFile": "Качи OPML Файл",
"ButtonUserDelete": "Изтрий Потребител {0}",
"ButtonUserEdit": "Редактирай Потребител {0}",
"ButtonViewAll": "Виж Всички",
"ButtonYes": "Да",
"ErrorUploadFetchMetadataAPI": "Грешка при Взимане на Метаданни ",
"ErrorUploadFetchMetadataNoResults": "Метаданните не могат да бъдат взети - опитайте да обновите заглавието и/или автора",
"ErrorUploadLacksTitle": "Трябва да има Заглавие",
"HeaderAccount": "Профил",
"HeaderAdvanced": "Разширени",
"HeaderAppriseNotificationSettings": "Apprise Notification Опции",
"HeaderAudiobookTools": "Инструмент за Менижиране на Аудиокниги ",
"HeaderAudioTracks": "Звуков Канал",
"HeaderAuthentication": "Аутентикация",
"HeaderBackups": "Backups",
"HeaderChangePassword": "Промяна на Парола",
"HeaderChapters": "Глави",
"HeaderChooseAFolder": "Избети Папка",
"HeaderCollection": "Колекция",
"HeaderCollectionItems": "Елементи на Колекция",
"HeaderCover": "Корица",
"HeaderCurrentDownloads": "Текущи Сваляния",
"HeaderCustomMetadataProviders": "Потребителски Доставчици на Метаданни",
"HeaderDetails": "Детайли",
"HeaderDownloadQueue": "Опашка за Сваляне",
"HeaderEbookFiles": "Файлове на Електронни книги",
"HeaderEmail": "Емейл",
"HeaderEmailSettings": "Настройки Емайл",
"HeaderEpisodes": "Епизоди",
"HeaderEreaderDevices": "Елктронни Четци",
"HeaderEreaderSettings": "Настройки на Електронни Четци",
"HeaderFiles": "Файлове",
"HeaderFindChapters": "Намери Глави",
"HeaderIgnoredFiles": "Игнорирани Файлове",
"HeaderItemFiles": "Файлове на Елемент",
"HeaderItemMetadataUtils": "Инструменти за Метаданни на Елемент",
"HeaderLastListeningSession": "Последна Сесия на Слушане",
"HeaderLatestEpisodes": "Последни Епизоди",
"HeaderLibraries": "Библиотеки",
"HeaderLibraryFiles": "Файлове на Библиотека",
"HeaderLibraryStats": "Статистика на Библиотека",
"HeaderListeningSessions": "Сесии на Слушане",
"HeaderListeningStats": "Статистика на Слушане",
"HeaderLogin": "Вход",
"HeaderLogs": "Логове",
"HeaderManageGenres": "Управление на Жанрове",
"HeaderManageTags": "Управление на Тагове",
"HeaderMapDetails": "Асоцирай Детайли",
"HeaderMatch": "Съпостави",
"HeaderMetadataOrderOfPrecedence": "Предимство на Метаданни",
"HeaderMetadataToEmbed": "Метаданни за Вграждане",
"HeaderNewAccount": "Нов Профил",
"HeaderNewLibrary": "Нова Библиотека",
"HeaderNotifications": "Известия",
"HeaderOpenIDConnectAuthentication": "OpenID Connect Аутентикация",
"HeaderOpenRSSFeed": "Отвори RSS Feed",
"HeaderOtherFiles": "Други Файлове",
"HeaderPasswordAuthentication": "Паролна Аутентикация",
"HeaderPermissions": "Права",
"HeaderPlayerQueue": "Опашка на Плейъра",
"HeaderPlaylist": "Плейлист",
"HeaderPlaylistItems": "Елементи на Плейлист",
"HeaderPodcastsToAdd": "Подкасти за Добавяне",
"HeaderPreviewCover": "Преглед на Корица",
"HeaderRemoveEpisode": "Премахни Епизод",
"HeaderRemoveEpisodes": "Премахни {0} Епизоди",
"HeaderRSSFeedGeneral": "RSS Детайли",
"HeaderRSSFeedIsOpen": "RSS Feed е Отворен",
"HeaderRSSFeeds": "RSS Feed-ове",
"HeaderSavedMediaProgress": "Запазен Прогрес на Медията",
"HeaderSchedule": "График",
"HeaderScheduleLibraryScans": "График за Автоматично Сканиране на Библиотека",
"HeaderSession": "Сесия",
"HeaderSetBackupSchedule": "Задай График за Backup",
"HeaderSettings": "Настройки",
"HeaderSettingsDisplay": "Визуализация",
"HeaderSettingsExperimental": "Експериментални Функции",
"HeaderSettingsGeneral": "Общи",
"HeaderSettingsScanner": "Скенер",
"HeaderSleepTimer": "Таймер за Сън",
"HeaderStatsLargestItems": "Най-Големите Елементи",
"HeaderStatsLongestItems": "Най-Дългите Елементи (часове)",
"HeaderStatsMinutesListeningChart": "Минути на Слушане (последни 7 дни)",
"HeaderStatsRecentSessions": "Скорошни Сесии",
"HeaderStatsTop10Authors": "Топ 10 Автори",
"HeaderStatsTop5Genres": "Топ 5 Жанрове",
"HeaderTableOfContents": "Съдържание",
"HeaderTools": "Инструменти",
"HeaderUpdateAccount": "Обнови Профил",
"HeaderUpdateAuthor": "Обнови Автор",
"HeaderUpdateDetails": "Обнови Детайли",
"HeaderUpdateLibrary": "Обнови Библиотека",
"HeaderUsers": "Потребители",
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Твоята Статистика",
"LabelAbridged": "Съкратен",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Тип на Профила",
"LabelAccountTypeAdmin": "Администратор",
"LabelAccountTypeGuest": "Гост",
"LabelAccountTypeUser": "Потребител",
"LabelActivity": "Дейност",
"LabelAdded": "Добавени",
"LabelAddedAt": "Добавени На",
"LabelAddToCollection": "Добави в Колекция",
"LabelAddToCollectionBatch": "Добави {0} Книги в Колекция",
"LabelAddToPlaylist": "Добави в Плейлист",
"LabelAddToPlaylistBatch": "Добави {0} Елемент в Плейлист",
"LabelAdminUsersOnly": "Само за Администратори",
"LabelAll": "Всички",
"LabelAllUsers": "Всички Потребители",
"LabelAllUsersExcludingGuests": "Всички потребители без гости",
"LabelAllUsersIncludingGuests": "Всички потребители включително гости",
"LabelAlreadyInYourLibrary": "Вече е в твоята библиотека",
"LabelAppend": "Добави",
"LabelAuthor": "Автор",
"LabelAuthorFirstLast": "Автор (Първо Име, Фамилия)",
"LabelAuthorLastFirst": "Автор (Фамилия, Първо Име)",
"LabelAuthors": "Автори",
"LabelAutoDownloadEpisodes": "Автоматично Сваляне на Епизоди",
"LabelAutoFetchMetadata": "Автоматично Взимане на Метаданни",
"LabelAutoFetchMetadataHelp": "Взима метаданни за заглвие, автор и серии за да опрости качването. Допълнителни метаданни може да трябва да бъде взера след качване.",
"LabelAutoLaunch": "Автоматично Стартиране",
"LabelAutoLaunchDescription": "Пренасочване към доставчик за аутентикация автоматично когато се навигира до страницата за вход (ръчно заменяне на пътя <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Автоматична Регистрация",
"LabelAutoRegisterDescription": "Автоматично създаване на нови потребители след вход",
"LabelBackToUser": "Обратно към Потребител",
"LabelBackupLocation": "Местоположение на Архив",
"LabelBackupsEnableAutomaticBackups": "Включи автоматично архивиране",
"LabelBackupsEnableAutomaticBackupsHelp": "Архиви запазени в /metadata/backups",
"LabelBackupsMaxBackupSize": "Максимален размер на архива (в GB)",
"LabelBackupsMaxBackupSizeHelp": "За защита срещу грешки в конфигурацията, архивите ще се провалят ако надхвърлят конфигурирания размер.",
"LabelBackupsNumberToKeep": "Брой архиви за запазване",
"LabelBackupsNumberToKeepHelp": "Само 1 архив ще бъде премахнат на веднъж, така че ако вече имате повече архиви от това трябва да ги премахнете ръчно.",
"LabelBitrate": "Битрейт",
"LabelBooks": "Книги",
"LabelButtonText": "Текст на Бутон",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Промени Парола",
"LabelChannels": "Канали",
"LabelChapters": "Глави",
"LabelChaptersFound": "намерени глави",
"LabelChapterTitle": "Заглавие на Глава",
"LabelClickForMoreInfo": "Кликни за повече информация",
"LabelClosePlayer": "Затвори Плейъра",
"LabelCodec": "Кодек",
"LabelCollapseSeries": "Свий Серия",
"LabelCollection": "Колекция",
"LabelCollections": "Колекции",
"LabelComplete": "Завършено",
"LabelConfirmPassword": "Потвърди Парола",
"LabelContinueListening": "Продължи Слушане",
"LabelContinueReading": "Продължи Четене",
"LabelContinueSeries": "Продължи Серия",
"LabelCover": "Корица",
"LabelCoverImageURL": "URL на Корица",
"LabelCreatedAt": "Създадено на",
"LabelCronExpression": "Cron Expression",
"LabelCurrent": "Текущо",
"LabelCurrently": "Текущо:",
"LabelCustomCronExpression": "Потребителски Cron Expression:",
"LabelDatetime": "Дата и Време",
"LabelDeleteFromFileSystemCheckbox": "Изтрий от файловата система (отмени за да бъдат премахни само от базата данни)",
"LabelDescription": "Описание",
"LabelDeselectAll": "Премахни всички",
"LabelDevice": "Устройство",
"LabelDeviceInfo": "Информация за Устройство",
"LabelDeviceIsAvailableTo": "Устройството е достъпно за ...",
"LabelDirectory": "Директория",
"LabelDiscFromFilename": "Диск от Име на Файл",
"LabelDiscFromMetadata": "Диск от Метаданни",
"LabelDiscover": "Открий",
"LabelDownload": "Сваляне",
"LabelDownloadNEpisodes": "Свали {0} епизоди",
"LabelDuration": "Продължителност",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Намерена продължителност:",
"LabelEbook": "Електронна книга",
"LabelEbooks": "Електронни книги",
"LabelEdit": "Редакция",
"LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "От Адрес",
"LabelEmailSettingsSecure": "Сигурна",
"LabelEmailSettingsSecureHelp": "Ако е вярно възката ще изполва TLS когате се свързва със сървъра. Ако не е то TLS ще се използва ако сървъра поддържа разширението STARTTLS. В повечето случаи задайте тази стойност на истина ако се свързвате към порт 465. За порт 587 или 25 оставете я на лъжа. (от nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Тестов Адрес",
"LabelEmbeddedCover": "Вградена Корица",
"LabelEnable": "Включи",
"LabelEnd": "Край",
"LabelEpisode": "Епизод",
"LabelEpisodeTitle": "Заглавие на Епизод",
"LabelEpisodeType": "Тип на Епизод",
"LabelExample": "Пример",
"LabelExplicit": "Експлицитно",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Взимане на Метаданни",
"LabelFile": "Файл",
"LabelFileBirthtime": "Дата на създаване на файла",
"LabelFileModified": "Файлът променен",
"LabelFilename": "Име на Файл",
"LabelFilterByUser": "Филтриране по Потребител",
"LabelFindEpisodes": "Намери Епизоди",
"LabelFinished": "Завършено",
"LabelFolder": "Папка",
"LabelFolders": "Папки",
"LabelFontBold": "Получерно",
"LabelFontFamily": "Шрифт",
"LabelFontItalic": "Курсив",
"LabelFontScale": "Мащаб на Шрифта",
"LabelFontStrikethrough": "Зачертан",
"LabelFormat": "Формат",
"LabelGenre": "Жанр",
"LabelGenres": "Жанрове",
"LabelHardDeleteFile": "Пълно Изтриване на Файл",
"LabelHasEbook": "Има електронна книга",
"LabelHasSupplementaryEbook": "Има допълнителна електронна книга",
"LabelHighestPriority": "Най-висок Приоритет",
"LabelHost": "Хост",
"LabelHour": "Час",
"LabelIcon": "Икона",
"LabelImageURLFromTheWeb": "URL на Изображение от Интернет",
"LabelIncludeInTracklist": "Включи в Списъка с Канали",
"LabelIncomplete": "Незавършено",
"LabelInProgress": "В Прогрес",
"LabelInterval": "Интервал",
"LabelIntervalCustomDailyWeekly": "Потребително дневно/седмично",
"LabelIntervalEvery12Hours": "Всеки 12 часа",
"LabelIntervalEvery15Minutes": "Всеки 15 минути",
"LabelIntervalEvery2Hours": "Всеки 2 часа",
"LabelIntervalEvery30Minutes": "Всеки 30 минути",
"LabelIntervalEvery6Hours": "Всеки 6 часа",
"LabelIntervalEveryDay": "Всеки ден",
"LabelIntervalEveryHour": "Всеки час",
"LabelInvert": "Обърни",
"LabelItem": "Елемент",
"LabelLanguage": "Език",
"LabelLanguageDefaultServer": "Език по подразбиране на сървъра",
"LabelLastBookAdded": "Последно Добавена Книга",
"LabelLastBookUpdated": "Последно Обновена Книга",
"LabelLastSeen": "Последно Видян",
"LabelLastTime": "Последно Време",
"LabelLastUpdate": "Последно Обновяване",
"LabelLayout": "Оформление",
"LabelLayoutSinglePage": "Една Страница",
"LabelLayoutSplitPage": "Разделена Страница",
"LabelLess": "По-малко",
"LabelLibrariesAccessibleToUser": "Библиотеки Достъпни за Потребителя",
"LabelLibrary": "Библиотека",
"LabelLibraryItem": "Елемент на Библиотека",
"LabelLibraryName": "Име на Библиотека",
"LabelLimit": "Лимит",
"LabelLineSpacing": "Линейно Разстояние",
"LabelListenAgain": "Слушай Отново",
"LabelLogLevelDebug": "Дебъг",
"LabelLogLevelInfo": "Информация",
"LabelLogLevelWarn": "Предупреждение",
"LabelLookForNewEpisodesAfterDate": "Търси нови епизоди след дата",
"LabelLowestPriority": "Най-нисък Приоритет",
"LabelMatchExistingUsersBy": "Съпостави съществуващи потребители по",
"LabelMatchExistingUsersByDescription": "Използва се за свързване на съществуващи потребители. След свързване потребителите ще бъдат съпоставени по уникален идентификатор от вашия доставчик на SSO",
"LabelMediaPlayer": "Медия Плейър",
"LabelMediaType": "Тип на Медията",
"LabelMetadataOrderOfPrecedenceDescription": "По-високите източници на метаданни ще заменят по-ниските",
"LabelMetadataProvider": "Доставчик на Метаданни",
"LabelMetaTag": "Мета Таг",
"LabelMetaTags": "Мета Тагове",
"LabelMinute": "Минута",
"LabelMissing": "Липсващо",
"LabelMissingEbook": "Has no ebook",
"LabelMissingSupplementaryEbook": "Has no supplementary ebook",
"LabelMobileRedirectURIs": "Позволени URI за Мобилно Пренасочване",
"LabelMobileRedirectURIsDescription": "Това е whitelist на валидни URI за пренасочване за мобилни приложения. По подразбиране е <code>audiobookshelf://oauth</code>, който може да премахнете или допълните с допълнителни URI за интеграция на приложения от трети страни. Използването на звезда (<code>*</code>) като единствен запис позволява всеки URI.",
"LabelMore": "Повече",
"LabelMoreInfo": "Повече Информация",
"LabelName": "Име",
"LabelNarrator": "Разказвач",
"LabelNarrators": "Разказвачи",
"LabelNew": "Нови",
"LabelNewestAuthors": "Най-нови Автори",
"LabelNewestEpisodes": "Най-нови Епизоди",
"LabelNewPassword": "Нова Парола",
"LabelNextBackupDate": "Следваща Дата на Архивиране",
"LabelNextScheduledRun": "Следващо Планирано Изпълнение",
"LabelNoEpisodesSelected": "Няма избрани епизоди",
"LabelNotes": "Бележки",
"LabelNotFinished": "Не е завършено",
"LabelNotificationAppriseURL": "Apprise URL-и",
"LabelNotificationAvailableVariables": "Налични променливи",
"LabelNotificationBodyTemplate": "Тяло на Шаблона",
"LabelNotificationEvent": "Събитие за Известие",
"LabelNotificationsMaxFailedAttempts": "Максимален брой неуспешни опити за известия",
"LabelNotificationsMaxFailedAttemptsHelp": "Известията се деактивират след като не успеят да бъдат изпратени толкова пъти",
"LabelNotificationsMaxQueueSize": "Максимален размер на опашката за известия",
"LabelNotificationsMaxQueueSizeHelp": "Събитията са ограничени до изстрелване на 1 на секунда. Събитията ще бъдат игнорирани ако опашката е на максимален размер. Това предотвратява спамирането на известия.",
"LabelNotificationTitleTemplate": "Заглавие на Шаблона",
"LabelNotStarted": "Не е започнато",
"LabelNumberOfBooks": "Брой на Книги",
"LabelNumberOfEpisodes": "# Епизоди",
"LabelOpenIDAdvancedPermsClaimDescription": "Name of the OpenID claim that contains advanced permissions for user actions within the application which will apply to non-admin roles (<b>if configured</b>). If the claim is missing from the response, access to ABS will be denied. If a single option is missing, it will be treated as <code>false</code>. Ensure the identity provider's claim matches the expected structure:",
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
"LabelOpenIDGroupClaimDescription": "Name of the OpenID claim that contains a list of the user's groups. Commonly referred to as <code>groups</code>. <b>If configured</b>, the application will automatically assign roles based on the user's group memberships, provided that these groups are named case-insensitively 'admin', 'user', or 'guest' in the claim. The claim should contain a list, and if a user belongs to multiple groups, the application will assign the role corresponding to the highest level of access. If no group matches, access will be denied.",
"LabelOpenRSSFeed": "Отвори RSS Feed",
"LabelOverwrite": "Презапиши",
"LabelPassword": "Парола",
"LabelPath": "Път",
"LabelPermissionsAccessAllLibraries": "Може да достъпи до всички библиотеки",
"LabelPermissionsAccessAllTags": "Може да достъпи всички тагове",
"LabelPermissionsAccessExplicitContent": "Може да достъпи експлицитно съдържание",
"LabelPermissionsDelete": "Може да трие",
"LabelPermissionsDownload": "Може да сваля",
"LabelPermissionsUpdate": "Може да обновява",
"LabelPermissionsUpload": "Може да качва",
"LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Път/URL на Снимка",
"LabelPlaylists": "Плейлисти",
"LabelPlayMethod": "Метод на Пускане",
"LabelPodcast": "Подкаст",
"LabelPodcasts": "Подкасти",
"LabelPodcastSearchRegion": "Регион за Търсене на Подкасти",
"LabelPodcastType": "Тип на Подкаст",
"LabelPort": "Порт",
"LabelPrefixesToIgnore": "Префикси за Игнориране (без значение за главни/малки букви)",
"LabelPreventIndexing": "Предотврати индексирането на вашия feed от iTunes и Google podcast директории",
"LabelPrimaryEbook": "Основна Електронна Книга",
"LabelProgress": "Прогрес",
"LabelProvider": "Доставчик",
"LabelPubDate": "Дата на Издаване",
"LabelPublisher": "Издател",
"LabelPublishYear": "Година на Издаване",
"LabelRead": "Прочети",
"LabelReadAgain": "Прочети Отново",
"LabelReadEbookWithoutProgress": "Прочети електронната книга без записване прогрес",
"LabelRecentlyAdded": "Наскоро Добавени",
"LabelRecentSeries": "Скорошни Серии",
"LabelRecommended": "Препоръчано",
"LabelRedo": "Повтори",
"LabelRegion": "Регион",
"LabelReleaseDate": "Дата на Издаване",
"LabelRemoveCover": "Премахни Корица",
"LabelRowsPerPage": "Редове на Страница",
"LabelRSSFeedCustomOwnerEmail": "Потребителски собственик Email",
"LabelRSSFeedCustomOwnerName": "Потребителски собственик Име",
"LabelRSSFeedOpen": "RSS Feed Оптворен",
"LabelRSSFeedPreventIndexing": "Предотврати индексиране",
"LabelRSSFeedSlug": "RSS Feed слъг",
"LabelRSSFeedURL": "RSS Feed URL",
"LabelSearchTerm": "Търси Термин",
"LabelSearchTitle": "Търси Заглавие",
"LabelSearchTitleOrASIN": "Търси Заглавие или ASIN",
"LabelSeason": "Сезон",
"LabelSelectAll": "Select all",
"LabelSelectAllEpisodes": "Избери всички епизоди",
"LabelSelectEpisodesShowing": "Избери {0} епизоди показани",
"LabelSelectUsers": "Избери Потребители",
"LabelSendEbookToDevice": "Изпрати електронна книга до ...",
"LabelSequence": "Последователност",
"LabelSeries": "Серия",
"LabelSeriesName": "Име на Серия",
"LabelSeriesProgress": "Прогрес на Серия",
"LabelServerYearReview": "Server Year in Review ({0})",
"LabelSetEbookAsPrimary": "Задай като основна",
"LabelSetEbookAsSupplementary": "Задай като допълнителна",
"LabelSettingsAudiobooksOnly": "Само аудиокниги",
"LabelSettingsAudiobooksOnlyHelp": "Активирането на тази настройка ще игнорира файловете на електронни книги, освен ако не са в папка с аудиокниги, в което случай ще бъдат зададени като допълнителни електронни книги",
"LabelSettingsBookshelfViewHelp": "Скеуморфен дизайн с дървени рафтове",
"LabelSettingsChromecastSupport": "Chromecast поддръжка",
"LabelSettingsDateFormat": "Формат на Дата",
"LabelSettingsDisableWatcher": "Изключи наблюдателя",
"LabelSettingsDisableWatcherForLibrary": "Изключи наблюдателя за библиотека",
"LabelSettingsDisableWatcherHelp": "Изключва автоматичното добавяне/обновяване на елементи, когато се открият промени във файловете. *Изисква рестарт на сървъра",
"LabelSettingsEnableWatcher": "Включи наблюдателя",
"LabelSettingsEnableWatcherForLibrary": "Включи наблюдателя за библиотека",
"LabelSettingsEnableWatcherHelp": "Включва автоматичното добавяне/обновяване на елементи, когато се открият промени във файловете. *Изисква рестарт на сървъра",
"LabelSettingsExperimentalFeatures": "Експериментални Функции",
"LabelSettingsExperimentalFeaturesHelp": "Функции в разработка, които могат да изискват вашето мнение и помощ за тестване. Кликнете за да отворите дискусия в github.",
"LabelSettingsFindCovers": "Намери Корици",
"LabelSettingsFindCoversHelp": "Ако аудиокнигата ви няма вградена корица или изображение на корицата в папката, скенерът ще опита да намери корица.<br>Забележка: Това ще удължи времето за сканиране",
"LabelSettingsHideSingleBookSeries": "Скрий серии с една книга",
"LabelSettingsHideSingleBookSeriesHelp": "Сериите с една книга ще бъдат скрити от страницата на серията и рафтовете на началната страница.",
"LabelSettingsHomePageBookshelfView": "Начална страница изглед на рафт",
"LabelSettingsLibraryBookshelfView": "Библиотека изглед на рафт",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Skip earlier books in Continue Series",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "The Continue Series home page shelf shows the first book not started in series that have at least one book finished and no books in progress. Enabling this setting will continue series from the furthest completed book instead of the first book not started.",
"LabelSettingsParseSubtitles": "Извлечи подзаглавия",
"LabelSettingsParseSubtitlesHelp": "Извлича подзаглавия от имената на папките на аудиокнигите.<br>Подзаглавията трябва да бъдат разделени с \" - \"<br>например \"Заглавие на Книга - Тук е Подзаглавито\" има подзаглавие \"Тук е Подзаглавито\"",
"LabelSettingsPreferMatchedMetadata": "Предпочети съвпадащи метаданни",
"LabelSettingsPreferMatchedMetadataHelp": "Съвпадащите данни ще заменят детайлите на елемента при използване на Бързо Съпоставяне. По подразбиране Бързото Съпоставяне ще попълни само липсващите детайли.",
"LabelSettingsSkipMatchingBooksWithASIN": "Пропусни съвпадащи книги, които вече имат ASIN",
"LabelSettingsSkipMatchingBooksWithISBN": "Пропусни съвпадащи книги, които вече имат ISBN",
"LabelSettingsSortingIgnorePrefixes": "Игнорирай Префикси при Сортиране",
"LabelSettingsSortingIgnorePrefixesHelp": "например за префикс \"the\" заглавието на книгата \"The Book Title\" ще се сортира като \"Book Title, The\"",
"LabelSettingsSquareBookCovers": "Квадратни Корици на Книги",
"LabelSettingsSquareBookCoversHelp": "Предпочитайте квадратни корици пред стандартни 1.6:1 корици на книги",
"LabelSettingsStoreCoversWithItem": "Запази кориците с елемента",
"LabelSettingsStoreCoversWithItemHelp": "По подразбиране кориците се съхраняват в /metadata/items, като активирате тази настройка кориците ще се съхраняват в папката на елемента на вашата библиотека. Само един файл с име \"cover\" ще бъде запазен",
"LabelSettingsStoreMetadataWithItem": "Запази метаданните с елемента",
"LabelSettingsStoreMetadataWithItemHelp": "По подразбиране метаданните се съхраняват в /metadata/items, като активирате тази настройка метаданните ще се съхраняват в папката на елемента на вашата библиотека",
"LabelSettingsTimeFormat": "Формат на Време",
"LabelShowAll": "Покажи Всички",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Размер",
"LabelSleepTimer": "Таймер за Сън",
"LabelSlug": "Слъг",
"LabelStart": "Старт",
"LabelStarted": "Стартирано",
"LabelStartedAt": "Стартирано на",
"LabelStartTime": "Начално Време",
"LabelStatsAudioTracks": "Аудио Канали",
"LabelStatsAuthors": "Автори",
"LabelStatsBestDay": "Най-добър Ден",
"LabelStatsDailyAverage": "Дневна Средна Стойност",
"LabelStatsDays": "Дни",
"LabelStatsDaysListened": "Дни Слушани",
"LabelStatsHours": "Часове",
"LabelStatsInARow": "подред",
"LabelStatsItemsFinished": "Завършени Елементи",
"LabelStatsItemsInLibrary": "Елементи в Библиотеката",
"LabelStatsMinutes": "минути",
"LabelStatsMinutesListening": "Минути Слушани",
"LabelStatsOverallDays": "Общо Дни",
"LabelStatsOverallHours": "Общо Часове",
"LabelStatsWeekListening": "Седмица Слушане",
"LabelSubtitle": "Подзаглавие",
"LabelSupportedFileTypes": "Поддържани Типове Файлове",
"LabelTag": "Таг",
"LabelTags": "Тагове",
"LabelTagsAccessibleToUser": "Тагове Достъпни за Потребителя",
"LabelTagsNotAccessibleToUser": "Тагове Недостъпни за Потребителя",
"LabelTasks": "Вървящи Задачи",
"LabelTextEditorBulletedList": "Лист с точки",
"LabelTextEditorLink": "Свържи",
"LabelTextEditorNumberedList": "Лист с номера",
"LabelTextEditorUnlink": "Развържи",
"LabelTheme": "Тема",
"LabelThemeDark": "Тъмна",
"LabelThemeLight": "Светла",
"LabelTimeBase": "Времева Основа",
"LabelTimeListened": "Време Слушано",
"LabelTimeListenedToday": "Време Слушано Днес",
"LabelTimeRemaining": "{0} оставащо време",
"LabelTimeToShift": "Време за изместване в секунди",
"LabelTitle": "Заглавие",
"LabelToolsEmbedMetadata": "Вграждане на Метаданни",
"LabelToolsEmbedMetadataDescription": "Вграждане на метаданни в аудио файлове, включително корица и глави.",
"LabelToolsMakeM4b": "Направи M4B Аудиокнига Файл",
"LabelToolsMakeM4bDescription": "Генериране на .M4B аудиокнига файл с вградени метаданни, корица и глави.",
"LabelToolsSplitM4b": "Раздели M4B на MP3-ки",
"LabelToolsSplitM4bDescription": "Създай MP3-ки от M4B разделени по глави с вградени метаданни, корица и глави.",
"LabelTotalDuration": "Обща Продължителност",
"LabelTotalTimeListened": "Общо Време Слушано",
"LabelTrackFromFilename": "Канал от Име на Файл",
"LabelTrackFromMetadata": "Канал от Метаданни",
"LabelTracks": "Канали",
"LabelTracksMultiTrack": "Многоканален",
"LabelTracksNone": "Няма канали",
"LabelTracksSingleTrack": "Единичен канал",
"LabelType": "Тип",
"LabelUnabridged": "Несъкратен",
"LabelUndo": "Отмени",
"LabelUnknown": "Неизвестно",
"LabelUpdateCover": "Обнови Корица",
"LabelUpdateCoverHelp": "Позволи презаписване на съществуващите корици за избраните книги, когато се намери съвпадение",
"LabelUpdatedAt": "Обновено на",
"LabelUpdateDetails": "Обнови Детайли",
"LabelUpdateDetailsHelp": "Позволи презаписване на съществуващите детайли за избраните книги, когато се намери съвпадение",
"LabelUploaderDragAndDrop": "Плъзни и Пусни Файлове или Папки",
"LabelUploaderDropFiles": "Пусни Файлове",
"LabelUploaderItemFetchMetadataHelp": "Автоматично вземи заглавие, автор и серия",
"LabelUseChapterTrack": "Използвай канал за глава",
"LabelUseFullTrack": "Използвай пълен канал",
"LabelUser": "Потребител",
"LabelUsername": "Потребителско Име",
"LabelValue": "Стойност",
"LabelVersion": "Версия",
"LabelViewBookmarks": "Виж Отметки",
"LabelViewChapters": "Виж Глави",
"LabelViewQueue": "Виж Опашка",
"LabelVolume": "Сила на Звука",
"LabelWeekdaysToRun": "Делници за изпълнение",
"LabelYearReviewHide": "Hide Year in Review",
"LabelYearReviewShow": "See Year in Review",
"LabelYourAudiobookDuration": "Продължителност на вашата аудиокнига",
"LabelYourBookmarks": "Вашите Отметки",
"LabelYourPlaylists": "Вашите Плейлисти",
"LabelYourProgress": "Вашият Прогрес",
"MessageAddToPlayerQueue": "Добави към опашката на плейъра",
"MessageAppriseDescription": "За да ползвате тази функция трябва да имате активна инстанция на <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или на друго АПИ което да обработва тези заявки. <br />The Apprise API Url-а трябва дае пълния URL път за изпращане на известията, например, ако вашето АПИ ве подава от <code>http://192.168.1.1:8337</code> трябва да сложитев <code>http://192.168.1.1:8337/notify</code>.",
"MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in <code>/metadata/items</code> & <code>/metadata/authors</code>. Backups <strong>do not</strong> include any files stored in your library folders.",
"MessageBatchQuickMatchDescription": "Бързото Съпоставяне ще опита да добави липсващи корици и метаданни за избраните елементи. Активирайте опциите по-долу, за да позволите на Бързото съпоставяне да презапише съществуващите корици и/или метаданни.",
"MessageBookshelfNoCollections": "Все още нямате създадени колекции",
"MessageBookshelfNoResultsForFilter": "Няма резултат за филтер \"{0}: {1}\"",
"MessageBookshelfNoRSSFeeds": "Няма отворени RSS feed-ове",
"MessageBookshelfNoSeries": "Нямаш сеЗЙ",
"MessageChapterEndIsAfter": "Краят на главата е след края на вашата аудиокнига",
"MessageChapterErrorFirstNotZero": "Първата глава трябва да започва от 0",
"MessageChapterErrorStartGteDuration": "Началото на главата трябва да бъде по-малко от продължителността на аудиокнигата",
"MessageChapterErrorStartLtPrev": "Началото на главата трябва да бъде по-голямо или равно на края на предишната глава",
"MessageChapterStartIsAfter": "Началото на главата е след края на вашата аудиокнига",
"MessageCheckingCron": "Проверяване на cron...",
"MessageConfirmCloseFeed": "Сигурни ли сте, че искате да затворите този feed?",
"MessageConfirmDeleteBackup": "Сигурни ли сте, че искате да изтриете този архив {0}?",
"MessageConfirmDeleteFile": "Това ще изтрие файла от файловата Ви система. Сигурни ли сте?",
"MessageConfirmDeleteLibrary": "Сигурни ли сте, че искате да изтриете за винаги библиотека \"{0}\"?",
"MessageConfirmDeleteLibraryItem": "Това ще изтрие елемента от базата данни и файловата Ви система. Сигурни ли сте?",
"MessageConfirmDeleteLibraryItems": "Това ще изтрие {0} елемента от базата данни и файловата Ви система. Сигурни ли сте?",
"MessageConfirmDeleteSession": "Сигурни ли сте, че искате да изтриете тази сесия?",
"MessageConfirmForceReScan": "Сигурни ли сте, че искате да принудите повторно сканиране?",
"MessageConfirmMarkAllEpisodesFinished": "Сигурни ли сте, че искате да маркирате всички епизоди като завършени?",
"MessageConfirmMarkAllEpisodesNotFinished": "Сигурни ли сте, че искате да маркирате всички епизоди като незавършени?",
"MessageConfirmMarkSeriesFinished": "Сигурни ли сте, че искате да маркирате всички книги в тази серия като завършени?",
"MessageConfirmMarkSeriesNotFinished": "Сигурни ли сте, че искате да маркирате всички книги в тази серия като незавършени?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Внимание! Бързото вграждане няма да архивира вашите аудио файлове. Уверете се, че имате резервно копие на вашите аудио файлове. <br><br>Искате ли да продължите?",
"MessageConfirmRemoveAllChapters": "Сигурни ли сте, че искате да премахнете всички глави?",
"MessageConfirmRemoveAuthor": "Сигурни ли сте, че искате да премахнете автор \"{0}\"?",
"MessageConfirmRemoveCollection": "Сигурни ли сте, че искате да премахнете колекция \"{0}\"?",
"MessageConfirmRemoveEpisode": "Сигурни ли сте, че искате да премахнете епизод \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Сигурни ли сте, че искате да премахнете {0} епизода?",
"MessageConfirmRemoveListeningSessions": "Сигурни ли сте, че искате да премахнете {0} слушателски сесии?",
"MessageConfirmRemoveNarrator": "Сигурни ли сте, че искате да премахнете разказвач \"{0}\"?",
"MessageConfirmRemovePlaylist": "Сигурни ли сте, че искате да премахнете плейлиста \"{0}\"?",
"MessageConfirmRenameGenre": "Сигурни ли сте, че искате да преименувате жанра \"{0}\" на \"{1}\" за всички елементи?",
"MessageConfirmRenameGenreMergeNote": "Забележка: Този жанр вече съществува и ще бъде слято.",
"MessageConfirmRenameGenreWarning": "Внимание! Вече съществува подобен жанр с различно писане \"{0}\".",
"MessageConfirmRenameTag": "Сигурни ли сте, че искате да преименувате таг \"{0}\" на \"{1}\" за всички елементи?",
"MessageConfirmRenameTagMergeNote": "Забележка: Този таг вече съществува и ще бъде слято.",
"MessageConfirmRenameTagWarning": "Внимание! Вече съществува подобен таг с различно писане \"{0}\".",
"MessageConfirmReScanLibraryItems": "Сигурни ли сте, че искате да сканирате отново {0} елемента?",
"MessageConfirmSendEbookToDevice": "Сигурни ли сте, че искате да изпратите {0} електронна книга \"{1}\" до устройство \"{2}\"?",
"MessageDownloadingEpisode": "Изтегляне на епизод",
"MessageDragFilesIntoTrackOrder": "Плъзнете файлове в правилния ред на каналите",
"MessageEmbedFinished": "Вграждането завърши!",
"MessageEpisodesQueuedForDownload": "{0} епизод(и) в опашка за изтегляне",
"MessageFeedURLWillBe": "Feed URL-a ще бъде {0}",
"MessageFetching": "Взимане...",
"MessageForceReScanDescription": "ще сканира всички файлове отново като прясно сканиране. Аудио файлове ID3 тагове, OPF файлове и текстови файлове ще бъдат сканирани като нови.",
"MessageImportantNotice": "Важно Съобщение!",
"MessageInsertChapterBelow": "Вмъкни глава под",
"MessageItemsSelected": "{0} избрани",
"MessageItemsUpdated": "{0} елемента обновени",
"MessageJoinUsOn": "Присъединете се към нас",
"MessageListeningSessionsInTheLastYear": "{0} слушателски сесии през последната година",
"MessageLoading": "Зареждане...",
"MessageLoadingFolders": "Зареждане на Папки...",
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
"MessageM4BFailed": "M4B Провалено!",
"MessageM4BFinished": "M4B Завършено!",
"MessageMapChapterTitles": "Съпостави заглавията на главите със съществуващите глави на аудиокнигата без да променяш времената",
"MessageMarkAllEpisodesFinished": "Маркирай всички епизоди като завършени",
"MessageMarkAllEpisodesNotFinished": "Маркирай всички епизоди като незавършени",
"MessageMarkAsFinished": "Маркирай като Завършено",
"MessageMarkAsNotFinished": "Маркирай като Незавършено",
"MessageMatchBooksDescription": "ще се опита да съпостави книги в библиотеката с книга от избрания доставчик за търсене и ще попълни празни детайли и корици. Не презаписва детайлите.",
"MessageNoAudioTracks": "Няма аудио канали",
"MessageNoAuthors": "Няма Автори",
"MessageNoBackups": "Няма архиви",
"MessageNoBookmarks": "Няма Отметки",
"MessageNoChapters": "Няма Глави",
"MessageNoCollections": "Няма Колекции",
"MessageNoCoversFound": "Не са намерени корици",
"MessageNoDescription": "Няма описание",
"MessageNoDownloadsInProgress": "Няма изтегляния в прогрес",
"MessageNoDownloadsQueued": "Няма изтегляния в опашка",
"MessageNoEpisodeMatchesFound": "Няма намерени съвпадения за епизоди",
"MessageNoEpisodes": "Няма Епизоди",
"MessageNoFoldersAvailable": "Няма налични папки",
"MessageNoGenres": "Няма Жанрове",
"MessageNoIssues": "Няма проблеми",
"MessageNoItems": "Няма Елементи",
"MessageNoItemsFound": "Няма намерени елементи",
"MessageNoListeningSessions": "Няма слушателски сесии",
"MessageNoLogs": "Няма логове",
"MessageNoMediaProgress": "Няма прогрес на медията",
"MessageNoNotifications": "Няма известия",
"MessageNoPodcastsFound": "Няма намерени подкасти",
"MessageNoResults": "Няма резултати",
"MessageNoSearchResultsFor": "Няма резултати за \"{0}\"",
"MessageNoSeries": "Няма Серии",
"MessageNoTags": "Няма Тагове",
"MessageNoTasksRunning": "Няма вършещи се задачи",
"MessageNotYetImplemented": "Още не е изпълнено",
"MessageNoUpdateNecessary": "Не е необходимо обновяване",
"MessageNoUpdatesWereNecessary": "Не бяха необходими обновления",
"MessageNoUserPlaylists": "Няма плейлисти на потребителя",
"MessageOr": "или",
"MessagePauseChapter": "Пауза на глава",
"MessagePlayChapter": "Пусни налчалото на глава",
"MessagePlaylistCreateFromCollection": "Създай плейлист от колекция",
"MessagePodcastHasNoRSSFeedForMatching": "Подкастът няма URL адрес на RSS feed за използване за съпоставяне",
"MessageQuickMatchDescription": "Попълни празните детайли и корици с първия резултат от '{0}'. Не презаписва детайлите, освен ако не е активирана настройката 'Предпочети съвпадащи метаданни' на сървъра.",
"MessageRemoveChapter": "Премахни глава",
"MessageRemoveEpisodes": "Премахни {0} епизод(и)",
"MessageRemoveFromPlayerQueue": "Премахни от опашката на плейъра",
"MessageRemoveUserWarning": "Сигурни ли сте, че искате да изтриете потребител \"{0}\" завинаги?",
"MessageReportBugsAndContribute": "Съобщавайте за грешки, заявявайте функции и допринасяйте на",
"MessageResetChaptersConfirm": "Сигурни ли сте, че искате да нулирате главите и да отмените промените, които сте направили?",
"MessageRestoreBackupConfirm": "Сигурни ли сте, че искате да възстановите архива създаден на",
"MessageRestoreBackupWarning": "Възстановяването на архив ще презапише цялата база данни, намираща се в /config и кориците в /metadata/items & /metadata/authors.<br /><br />Архивите не променят файловете в папките на вашата библиотека. Ако сте активирали настройките на сървъра за съхранение на корици и метаданни в папките на вашата библиотека, те няма да бъдат архивирани или презаписани.<br /><br />Всички клиенти, използващи вашия сървър, ще бъдат автоматично обновени.",
"MessageSearchResultsFor": "Резултати от търсенето за",
"MessageSelected": "{0} избрани",
"MessageServerCouldNotBeReached": "Сървърът не може да бъде достигнат",
"MessageSetChaptersFromTracksDescription": "Задайте глави, като използвате всеки аудио файл като глава и заглавие на главата като име на аудио файла",
"MessageStartPlaybackAtTime": "Започни възпроизвеждане на \"{0}\" в {1}?",
"MessageThinking": "Мисля...",
"MessageUploaderItemFailed": "Неуспешно качване",
"MessageUploaderItemSuccess": "Успешно качване!",
"MessageUploading": "Качва се...",
"MessageValidCronExpression": "Валиден cron expression",
"MessageWatcherIsDisabledGlobally": "Наблюдателят е деактивиран глобално в настройките на сървъра",
"MessageXLibraryIsEmpty": "{0} библиотеката е празна!",
"MessageYourAudiobookDurationIsLonger": "Продължителността на вашата аудиокнига е по-дълга от намерената",
"MessageYourAudiobookDurationIsShorter": "Продължителността на вашата аудиокнига е по-кратка от намерената",
"NoteChangeRootPassword": "Root потребителят е единственият потребител, който може да има празна парола",
"NoteChapterEditorTimes": "Забележка: Първото време на начало на главата трябва да остане на 0:00, а последното време на начало на главата не може да надвишава продължителността на тази аудиокнига.",
"NoteFolderPicker": "Забележка: папките, които вече са картографирани, няма да бъдат показани",
"NoteRSSFeedPodcastAppsHttps": "Внимание: Повечето приложения за подкасти изискват URL адреса на RSS feed да използва HTTPS",
"NoteRSSFeedPodcastAppsPubDate": "Внимание: 1 или повече от вашите епизоди нямат дата на публикуване. Някои приложения за подкасти изискват това",
"NoteUploaderFoldersWithMediaFiles": "Папките с медийни файлове ще бъдат обработени като отделни елементи на библиотеката.",
"NoteUploaderOnlyAudioFiles": "Ако качвате само аудио файлове, то всеки аудио файл ще бъде обработен като отделна аудиокнига.",
"NoteUploaderUnsupportedFiles": "Неподдържаните файлове се игнорират. При избор или пускане на папка, други файлове, които не са в папка на елемент, се игнорират.",
"PlaceholderNewCollection": "Ново име на колекцията",
"PlaceholderNewFolderPath": "Нов път на папката",
"PlaceholderNewPlaylist": "Ново име на плейлиста",
"PlaceholderSearch": "Търсене...",
"PlaceholderSearchEpisode": "Търсене на Епизоди...",
"ToastAccountUpdateFailed": "Неуспешно обновяване на акаунта",
"ToastAccountUpdateSuccess": "Успешно обновяване на акаунта",
"ToastAuthorImageRemoveFailed": "Неуспешно премахване на авторска снимка",
"ToastAuthorImageRemoveSuccess": "Авторската снимка е премахната",
"ToastAuthorUpdateFailed": "Неуспешно обновяване на автора",
"ToastAuthorUpdateMerged": "Обновяване на автора сливано",
"ToastAuthorUpdateSuccess": "Автора обновен",
"ToastAuthorUpdateSuccessNoImageFound": "Автор обновен (не е намерена снимка)",
"ToastBackupCreateFailed": "Неуспешно създаване на архив",
"ToastBackupCreateSuccess": "Архивът е създаден",
"ToastBackupDeleteFailed": "Неуспешно изтриване на архив",
"ToastBackupDeleteSuccess": "Архивът е изтрит",
"ToastBackupRestoreFailed": "Неуспешно възстановяване на архив",
"ToastBackupUploadFailed": "Неуспешно качване на архив",
"ToastBackupUploadSuccess": "Архивът е качен",
"ToastBatchUpdateFailed": "Batch update failed",
"ToastBatchUpdateSuccess": "Batch update success",
"ToastBookmarkCreateFailed": "Неуспешно създаване на отметка",
"ToastBookmarkCreateSuccess": "Отметката е създадена",
"ToastBookmarkRemoveFailed": "Неуспешно премахване на отметка",
"ToastBookmarkRemoveSuccess": "Отметката е премахната",
"ToastBookmarkUpdateFailed": "Неуспешно обновяване на отметка",
"ToastBookmarkUpdateSuccess": "Отметката е обновена",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Главите имат грешки",
"ToastChaptersMustHaveTitles": "Главите трябва да имат заглавия",
"ToastCollectionItemsRemoveFailed": "Неуспешно премахване на елемент(и) от колекция",
"ToastCollectionItemsRemoveSuccess": "Елемент(и) премахнати от колекция",
"ToastCollectionRemoveFailed": "Неуспешно премахване на колекция",
"ToastCollectionRemoveSuccess": "Колекцията е премахната",
"ToastCollectionUpdateFailed": "Неуспешно обновяване на колекция",
"ToastCollectionUpdateSuccess": "Колекцията е обновена",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Неуспешно обновяване на корица на елемент",
"ToastItemCoverUpdateSuccess": "Корицата на елемента е обновена",
"ToastItemDetailsUpdateFailed": "Неуспешно обновяване на детайли на елемент",
"ToastItemDetailsUpdateSuccess": "Детайлите на елемента са обновени",
"ToastItemDetailsUpdateUnneeded": "Не са необходими обновления на детайлите на елемента",
"ToastItemMarkedAsFinishedFailed": "Неуспешно маркиране като завършено",
"ToastItemMarkedAsFinishedSuccess": "Елементът е маркиран като завършен",
"ToastItemMarkedAsNotFinishedFailed": "Неуспешно маркиране като незавършено",
"ToastItemMarkedAsNotFinishedSuccess": "Елементът е маркиран като незавършен",
"ToastLibraryCreateFailed": "Неуспешно създаване на библиотека",
"ToastLibraryCreateSuccess": "Библиотеката \"{0}\" е създадена",
"ToastLibraryDeleteFailed": "Неуспешно изтриване на библиотека",
"ToastLibraryDeleteSuccess": "Библиотеката е изтрита",
"ToastLibraryScanFailedToStart": "Неуспешно стартиране на сканиране",
"ToastLibraryScanStarted": "Сканирането на библиотеката е стартирано",
"ToastLibraryUpdateFailed": "Неуспешно обновяване на библиотека",
"ToastLibraryUpdateSuccess": "Библиотеката \"{0}\" е обновена",
"ToastPlaylistCreateFailed": "Неуспешно създаване на плейлист",
"ToastPlaylistCreateSuccess": "Плейлистът е създаден",
"ToastPlaylistRemoveFailed": "Неуспешно премахване на плейлист",
"ToastPlaylistRemoveSuccess": "Плейлистът е премахнат",
"ToastPlaylistUpdateFailed": "Неуспешно обновяване на плейлист",
"ToastPlaylistUpdateSuccess": "Плейлистът е обновен",
"ToastPodcastCreateFailed": "Неуспешно създаване на подкаст",
"ToastPodcastCreateSuccess": "Подкастът е създаден",
"ToastRemoveItemFromCollectionFailed": "Неуспешно премахване на елемент от колекция",
"ToastRemoveItemFromCollectionSuccess": "Елементът е премахнат от колекция",
"ToastRSSFeedCloseFailed": "Неуспешно затваряне на RSS feed",
"ToastRSSFeedCloseSuccess": "RSS feed затворен",
"ToastSendEbookToDeviceFailed": "Неуспешно изпращане на електронна книга до устройство",
"ToastSendEbookToDeviceSuccess": "Електронната книга е изпратена до устройство \"{0}\"",
"ToastSeriesUpdateFailed": "Неуспешно обновяване на серия",
"ToastSeriesUpdateSuccess": "Серията е обновена",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Неуспешно изтриване на сесия",
"ToastSessionDeleteSuccess": "Сесията е изтрита",
"ToastSocketConnected": "Свързан сокет",
"ToastSocketDisconnected": "Сокетът е прекъснат",
"ToastSocketFailedToConnect": "Неуспешно свързване на сокет",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Неуспешно изтриване на потребител",
"ToastUserDeleteSuccess": "Потребителят е изтрит"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "বাৎসরিক পর্যালোচনা {0}",
"HeaderYourStats": "আপনার পরিসংখ্যান",
"LabelAbridged": "সংক্ষিপ্ত",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "অ্যাকাউন্টের প্রকার",
"LabelAccountTypeAdmin": "প্রশাসন",
"LabelAccountTypeGuest": "অতিথি",
@ -229,6 +231,7 @@
"LabelBitrate": "বিটরেট",
"LabelBooks": "বইগুলো",
"LabelButtonText": "ঘর পাঠ্য",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "পাসওয়ার্ড পরিবর্তন করুন",
"LabelChannels": "চ্যানেল",
"LabelChapters": "অধ্যায়",
@ -266,6 +269,9 @@
"LabelDownload": "ডাউনলোড করুন",
"LabelDownloadNEpisodes": "{0}টি পর্ব ডাউনলোড করুন",
"LabelDuration": "সময়কাল",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "সময়কাল পাওয়া গেছে:",
"LabelEbook": "ই-বই",
"LabelEbooks": "ই-বইগুলো",
@ -283,6 +289,8 @@
"LabelEpisodeType": "পর্বের ধরন",
"LabelExample": "উদাহরণ",
"LabelExplicit": "বিশদ",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "ফিড ইউআরএল",
"LabelFetchingMetadata": "মেটাডেটা আনা হচ্ছে",
"LabelFile": "ফাইল",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "ডিফল্টরূপে মেটাডেটা ফাইলগুলি /মেটাডাটা/আইটেমগুলি -এ সংরক্ষণ করা হয়, এই সেটিংটি সক্ষম করলে মেটাডেটা ফাইলগুলি আপনার লাইব্রেরি আইটেম ফোল্ডারে সংরক্ষণ করা হবে",
"LabelSettingsTimeFormat": "সময় বিন্যাস",
"LabelShowAll": "সব দেখান",
"LabelShowSeconds": "Show seconds",
"LabelSize": "আকার",
"LabelSleepTimer": "স্লিপ টাইমার",
"LabelSlug": "স্লাগ",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "আপনি কি নিশ্চিত যে আপনি সমস্ত পর্বকে শেষ হয়নি বলে চিহ্নিত করতে চান?",
"MessageConfirmMarkSeriesFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে সমাপ্ত হিসাবে চিহ্নিত করতে চান?",
"MessageConfirmMarkSeriesNotFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে শেষ হয়নি বলে চিহ্নিত করতে চান?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "সতর্কতা! দ্রুত এম্বেড আপনার অডিও ফাইলের ব্যাকআপ করবে না। নিশ্চিত করুন যে আপনার অডিও ফাইলগুলির একটি ব্যাকআপ আছে। <br><br>আপনি কি চালিয়ে যেতে চান?",
"MessageConfirmRemoveAllChapters": "আপনি কি নিশ্চিত যে আপনি সমস্ত অধ্যায় সরাতে চান?",
"MessageConfirmRemoveAuthor": "আপনি কি নিশ্চিত যে আপনি লেখক \"{0}\" অপসারণ করতে চান?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "বুকমার্ক সরানো হয়েছে",
"ToastBookmarkUpdateFailed": "বুকমার্ক আপডেট করতে ব্যর্থ",
"ToastBookmarkUpdateSuccess": "বুকমার্ক আপডেট করা হয়েছে",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "অধ্যায়ে ত্রুটি আছে",
"ToastChaptersMustHaveTitles": "অধ্যায়ের শিরোনাম থাকতে হবে",
"ToastCollectionItemsRemoveFailed": "সংগ্রহ থেকে আইটেম(গুলি) সরাতে ব্যর্থ",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "সংগ্রহ সরানো হয়েছে",
"ToastCollectionUpdateFailed": "সংগ্রহ আপডেট করতে ব্যর্থ",
"ToastCollectionUpdateSuccess": "সংগ্রহ আপডেট করা হয়েছে",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "আইটেম কভার আপডেট করতে ব্যর্থ হয়েছে",
"ToastItemCoverUpdateSuccess": "আইটেম কভার আপডেট করা হয়েছে",
"ToastItemDetailsUpdateFailed": "আইটেমের বিবরণ আপডেট করতে ব্যর্থ",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "ইবুক \"{0}\" ডিভাইসে পাঠানো হয়েছে",
"ToastSeriesUpdateFailed": "সিরিজ আপডেট ব্যর্থ হয়েছে",
"ToastSeriesUpdateSuccess": "সিরিজ আপডেট সাফল্য",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "সেশন মুছে ফেলতে ব্যর্থ",
"ToastSessionDeleteSuccess": "সেশন মুছে ফেলা হয়েছে",
"ToastSocketConnected": "সকেট সংযুক্ত",
"ToastSocketDisconnected": "সকেট সংযোগ বিচ্ছিন্ন",
"ToastSocketFailedToConnect": "সকেট সংযোগ করতে ব্যর্থ হয়েছে",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "ব্যবহারকারী মুছতে ব্যর্থ",
"ToastUserDeleteSuccess": "ব্যবহারকারী মুছে ফেলা হয়েছে"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Vaše statistiky",
"LabelAbridged": "Zkráceno",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Typ účtu",
"LabelAccountTypeAdmin": "Správce",
"LabelAccountTypeGuest": "Host",
@ -229,6 +231,7 @@
"LabelBitrate": "Datový tok",
"LabelBooks": "Knihy",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Změnit heslo",
"LabelChannels": "Kanály",
"LabelChapters": "Kapitoly",
@ -266,6 +269,9 @@
"LabelDownload": "Stáhnout",
"LabelDownloadNEpisodes": "Stáhnout {0} epizody",
"LabelDuration": "Doba trvání",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Doba trvání nalezena:",
"LabelEbook": "Elektronická kniha",
"LabelEbooks": "Elektronické knihy",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Typ epizody",
"LabelExample": "Příklad",
"LabelExplicit": "Explicitní",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "URL zdroje",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Soubor",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Ve výchozím nastavení jsou soubory metadat uloženy v adresáři /metadata/items, povolením tohoto nastavení budou soubory metadat uloženy ve složkách položek knihovny",
"LabelSettingsTimeFormat": "Formát času",
"LabelShowAll": "Zobrazit vše",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Velikost",
"LabelSleepTimer": "Časovač vypnutí",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Opravdu chcete označit všechny epizody jako nedokončené?",
"MessageConfirmMarkSeriesFinished": "Opravdu chcete označit všechny knihy z této série jako dokončené?",
"MessageConfirmMarkSeriesNotFinished": "Opravdu chcete označit všechny knihy z této série jako nedokončené?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Varování! Rychlé vložení nezálohuje vaše zvukové soubory. Ujistěte se, že máte zálohu zvukových souborů. <br><br>Chcete pokračovat?",
"MessageConfirmRemoveAllChapters": "Opravdu chcete odstranit všechny kapitoly?",
"MessageConfirmRemoveAuthor": "Opravdu chcete odstranit autora \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Záložka odstraněna",
"ToastBookmarkUpdateFailed": "Aktualizace záložky se nezdařila",
"ToastBookmarkUpdateSuccess": "Záložka aktualizována",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Kapitoly obsahují chyby",
"ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy",
"ToastCollectionItemsRemoveFailed": "Nepodařilo se odstranit položky z kolekce",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Kolekce odstraněna",
"ToastCollectionUpdateFailed": "Aktualizace kolekce se nezdařila",
"ToastCollectionUpdateSuccess": "Kolekce aktualizována",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Aktualizace obálky se nezdařila",
"ToastItemCoverUpdateSuccess": "Obálka předmětu byl aktualizována",
"ToastItemDetailsUpdateFailed": "Nepodařilo se aktualizovat podrobnosti o položce",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-kniha odeslána do zařízení \"{0}\"",
"ToastSeriesUpdateFailed": "Aktualizace série se nezdařila",
"ToastSeriesUpdateSuccess": "Aktualizace série byla úspěšná",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Nepodařilo se smazat relaci",
"ToastSessionDeleteSuccess": "Relace smazána",
"ToastSocketConnected": "Socket připojen",
"ToastSocketDisconnected": "Socket odpojen",
"ToastSocketFailedToConnect": "Socket se nepodařilo připojit",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Nepodařilo se smazat uživatele",
"ToastUserDeleteSuccess": "Uživatel smazán"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Dine Statistikker",
"LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Kontotype",
"LabelAccountTypeAdmin": "Administrator",
"LabelAccountTypeGuest": "Gæst",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Bøger",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Ændre Adgangskode",
"LabelChannels": "Kanaler",
"LabelChapters": "Kapitler",
@ -266,6 +269,9 @@
"LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episoder",
"LabelDuration": "Varighed",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Fundet varighed:",
"LabelEbook": "E-bog",
"LabelEbooks": "E-bøger",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episodetype",
"LabelExample": "Eksempel",
"LabelExplicit": "Eksplisit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Fil",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Som standard gemmes metadatafiler i /metadata/items, aktivering af denne indstilling vil gemme metadatafiler i dine bibliotekselementmapper",
"LabelSettingsTimeFormat": "Tidsformat",
"LabelShowAll": "Vis alle",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Størrelse",
"LabelSleepTimer": "Søvntimer",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Er du sikker på, at du vil markere alle episoder som ikke afsluttet?",
"MessageConfirmMarkSeriesFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som afsluttet?",
"MessageConfirmMarkSeriesNotFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som ikke afsluttet?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Er du sikker på, at du vil fjerne alle kapitler?",
"MessageConfirmRemoveAuthor": "Er du sikker på, at du vil fjerne forfatteren \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Bogmærke fjernet",
"ToastBookmarkUpdateFailed": "Mislykkedes opdatering af bogmærke",
"ToastBookmarkUpdateSuccess": "Bogmærke opdateret",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Kapitler har fejl",
"ToastChaptersMustHaveTitles": "Kapitler skal have titler",
"ToastCollectionItemsRemoveFailed": "Mislykkedes fjernelse af element(er) fra samlingen",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Samling fjernet",
"ToastCollectionUpdateFailed": "Mislykkedes opdatering af samling",
"ToastCollectionUpdateSuccess": "Samling opdateret",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Mislykkedes opdatering af varens omslag",
"ToastItemCoverUpdateSuccess": "Varens omslag opdateret",
"ToastItemDetailsUpdateFailed": "Mislykkedes opdatering af varedetaljer",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-bog afsendt til enhed \"{0}\"",
"ToastSeriesUpdateFailed": "Mislykkedes opdatering af serie",
"ToastSeriesUpdateSuccess": "Serieopdatering lykkedes",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Mislykkedes sletning af session",
"ToastSessionDeleteSuccess": "Session slettet",
"ToastSocketConnected": "Socket forbundet",
"ToastSocketDisconnected": "Socket afbrudt",
"ToastSocketFailedToConnect": "Socket kunne ikke oprettes",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Mislykkedes sletning af bruger",
"ToastUserDeleteSuccess": "Bruger slettet"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Jahr {0} in Übersicht",
"HeaderYourStats": "Eigene Statistiken",
"LabelAbridged": "Gekürzt",
"LabelAbridgedChecked": "Gekürzt (angehakt)",
"LabelAbridgedUnchecked": "Ungekürzt (nicht angehakt)",
"LabelAccountType": "Kontoart",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Gast",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Bücher",
"LabelButtonText": "Button Text",
"LabelByAuthor": "von {0}",
"LabelChangePassword": "Passwort ändern",
"LabelChannels": "Kanäle",
"LabelChapters": "Kapitel",
@ -266,6 +269,9 @@
"LabelDownload": "Herunterladen",
"LabelDownloadNEpisodes": "Download {0} Episoden",
"LabelDuration": "Laufzeit",
"LabelDurationComparisonExactMatch": "(genauer Treffer)",
"LabelDurationComparisonLonger": "({0} änger)",
"LabelDurationComparisonShorter": "({0} kürzer)",
"LabelDurationFound": "Gefundene Laufzeit:",
"LabelEbook": "E-Book",
"LabelEbooks": "E-Books",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episodentyp",
"LabelExample": "Beispiel",
"LabelExplicit": "Explizit (Altersbeschränkung)",
"LabelExplicitChecked": "Explicit (Altersbeschränkung) (angehakt)",
"LabelExplicitUnchecked": "Not Explicit (Altersbeschränkung) (nicht angehakt)",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Abholen der Metadaten",
"LabelFile": "Datei",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Standardmäßig werden die Metadaten in /metadata/items gespeichert. Wenn diese Option aktiviert ist, werden die Metadaten als OPF-Datei (Textdatei) in dem gleichen Ordner gespeichert in welchem sich auch das Medium befindet",
"LabelSettingsTimeFormat": "Zeitformat",
"LabelShowAll": "Alles anzeigen",
"LabelShowSeconds": "Zeige Sekunden",
"LabelSize": "Größe",
"LabelSleepTimer": "Sleep-Timer",
"LabelSlug": "URL Teil",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Alle Episoden werden als nicht abgeschlossen markiert! Bist du dir sicher?",
"MessageConfirmMarkSeriesFinished": "Alle Medien dieser Reihe werden als abgeschlossen markiert! Bist du dir sicher?",
"MessageConfirmMarkSeriesNotFinished": "Alle Medien dieser Reihe werden als nicht abgeschlossen markiert! Bist du dir sicher?",
"MessageConfirmPurgeCache": "Cache leeren wird das ganze Verzeichnis <code>/metadata/cache</code> löschen. <br /><br />Bist du dir sicher, dass das Cache Verzeichnis gelöscht werden soll?",
"MessageConfirmQuickEmbed": "Warnung! Audiodateien werden bei der Schnelleinbettung nicht gesichert! Achte darauf, dass du eine Sicherungskopie der Audiodateien besitzt. <br><br>Möchtest du fortfahren?",
"MessageConfirmRemoveAllChapters": "Alle Kapitel werden entfernt! Bist du dir sicher?",
"MessageConfirmRemoveAuthor": "Autor \"{0}\" wird enfernt! Bist du dir sicher?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Lesezeichen gelöscht",
"ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
"ToastBookmarkUpdateSuccess": "Lesezeichen aktualisiert",
"ToastCachePurgeFailed": "Cache leeren fehlgeschlagen",
"ToastCachePurgeSuccess": "Cache geleert",
"ToastChaptersHaveErrors": "Kapitel sind fehlerhaft",
"ToastChaptersMustHaveTitles": "Kapitel benötigen eindeutige Namen",
"ToastCollectionItemsRemoveFailed": "Fehler beim Entfernen der Medien aus der Sammlung",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Sammlung gelöscht",
"ToastCollectionUpdateFailed": "Sammlung konnte nicht aktualisiert werden",
"ToastCollectionUpdateSuccess": "Sammlung aktualisiert",
"ToastDeleteFileFailed": "Die Datei konnte nicht gelöscht werden",
"ToastDeleteFileSuccess": "Datei gelöscht",
"ToastFailedToLoadData": "Daten laden fehlgeschlagen",
"ToastItemCoverUpdateFailed": "Fehler bei der Aktualisierung des Titelbildes",
"ToastItemCoverUpdateSuccess": "Titelbild aktualisiert",
"ToastItemDetailsUpdateFailed": "Fehler bei der Aktualisierung der Artikeldetails",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-Book an Gerät \"{0}\" gesendet",
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
"ToastSeriesUpdateSuccess": "Serien aktualisiert",
"ToastServerSettingsUpdateFailed": "Die Server-Einstellungen wurden nicht gespeichert.",
"ToastServerSettingsUpdateSuccess": "Die Server-Einstellungen wurden geupdated",
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
"ToastSessionDeleteSuccess": "Sitzung gelöscht",
"ToastSocketConnected": "Verbindung zum WebSocket hergestellt",
"ToastSocketDisconnected": "Verbindung zum WebSocket verloren",
"ToastSocketFailedToConnect": "Verbindung zum WebSocket fehlgeschlagen",
"ToastSortingPrefixesEmptyError": "Es muss mindestens ein Sortier-Prefix vorhanden sein",
"ToastSortingPrefixesUpdateFailed": "Update der Sortier-Prefixe ist fehlgeschlagen",
"ToastSortingPrefixesUpdateSuccess": "Die Sortier-Prefixe wirden geupdated ({0} Einträge)",
"ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
"ToastUserDeleteSuccess": "Benutzer gelöscht"
}

View file

@ -193,6 +193,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Your Stats",
"LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Account Type",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Guest",
@ -233,6 +235,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Books",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Change Password",
"LabelChannels": "Channels",
"LabelChapters": "Chapters",
@ -271,6 +274,9 @@
"LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Duration",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Duration found:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
@ -289,6 +295,8 @@
"LabelEpisodeType": "Episode Type",
"LabelExample": "Example",
"LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedHealthy": "Feed Healthy",
"LabelFeedLastChecked": "Last Checked",
"LabelFeedLastSuccessfulCheck": "Last Successful Check",
@ -501,6 +509,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
"LabelSettingsTimeFormat": "Time Format",
"LabelShowAll": "Show All",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Size",
"LabelSleepTimer": "Sleep timer",
"LabelSlug": "Slug",
@ -612,6 +621,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -750,6 +760,8 @@
"ToastBookmarkRemoveSuccess": "Bookmark removed",
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
"ToastBookmarkUpdateSuccess": "Bookmark updated",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
@ -758,6 +770,9 @@
"ToastCollectionRemoveSuccess": "Collection removed",
"ToastCollectionUpdateFailed": "Failed to update collection",
"ToastCollectionUpdateSuccess": "Collection updated",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Failed to update item cover",
"ToastItemCoverUpdateSuccess": "Item cover updated",
"ToastItemDetailsUpdateFailed": "Failed to update item details",
@ -791,11 +806,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Failed to delete session",
"ToastSessionDeleteSuccess": "Session deleted",
"ToastSocketConnected": "Socket connected",
"ToastSocketDisconnected": "Socket disconnected",
"ToastSocketFailedToConnect": "Socket failed to connect",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Tus Estadísticas",
"LabelAbridged": "Abreviado",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Tipo de Cuenta",
"LabelAccountTypeAdmin": "Administrador",
"LabelAccountTypeGuest": "Invitado",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Libros",
"LabelButtonText": "Texto del botón",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Cambiar Contraseña",
"LabelChannels": "Canales",
"LabelChapters": "Capítulos",
@ -266,6 +269,9 @@
"LabelDownload": "Descargar",
"LabelDownloadNEpisodes": "Descargar {0} episodios",
"LabelDuration": "Duración",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Duración Comprobada:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Tipo de Episodio",
"LabelExample": "Ejemplo",
"LabelExplicit": "Explicito",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Fuente de URL",
"LabelFetchingMetadata": "Obteniendo metadatos",
"LabelFile": "Archivo",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Por defecto, los archivos de metadatos se almacenan en /metadata/items. Si habilita esta opción, los archivos de metadatos se guardarán en la carpeta de elementos de su biblioteca",
"LabelSettingsTimeFormat": "Formato de Tiempo",
"LabelShowAll": "Mostrar Todos",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Tamaño",
"LabelSleepTimer": "Temporizador para Dormir",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "¿Está seguro de que desea marcar todos los episodios como no terminados?",
"MessageConfirmMarkSeriesFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como terminados?",
"MessageConfirmMarkSeriesNotFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como no terminados?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"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?",
"MessageConfirmRemoveAllChapters": "¿Está seguro de que desea remover todos los capitulos?",
"MessageConfirmRemoveAuthor": "¿Está seguro de que desea remover el autor \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Marcador eliminado",
"ToastBookmarkUpdateFailed": "Error al actualizar el marcador",
"ToastBookmarkUpdateSuccess": "Marcador actualizado",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Los capítulos tienen errores",
"ToastChaptersMustHaveTitles": "Los capítulos tienen que tener un título",
"ToastCollectionItemsRemoveFailed": "Error al remover elemento(s) de la colección",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Colección removida",
"ToastCollectionUpdateFailed": "Error al actualizar la colección",
"ToastCollectionUpdateSuccess": "Colección actualizada",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Error al actualizar la portada del elemento",
"ToastItemCoverUpdateSuccess": "Portada del elemento actualizada",
"ToastItemDetailsUpdateFailed": "Error al actualizar los detalles del elemento",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook enviado al dispositivo \"{0}\"",
"ToastSeriesUpdateFailed": "Error al actualizar la serie",
"ToastSeriesUpdateSuccess": "Serie actualizada",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Error al eliminar sesión",
"ToastSessionDeleteSuccess": "Sesión eliminada",
"ToastSocketConnected": "Socket conectado",
"ToastSocketDisconnected": "Socket desconectado",
"ToastSocketFailedToConnect": "Error al conectar al Socket",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Error al eliminar el usuario",
"ToastUserDeleteSuccess": "Usuario eliminado"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Sinu statistika",
"LabelAbridged": "Kärbitud",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Konto tüüp",
"LabelAccountTypeAdmin": "Administraator",
"LabelAccountTypeGuest": "Külaline",
@ -229,6 +231,7 @@
"LabelBitrate": "Bittkiirus",
"LabelBooks": "Raamatud",
"LabelButtonText": "Nupu tekst",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Muuda parooli",
"LabelChannels": "Kanalid",
"LabelChapters": "Peatükid",
@ -266,6 +269,9 @@
"LabelDownload": "Lae alla",
"LabelDownloadNEpisodes": "Lae alla {0} episoodi",
"LabelDuration": "Kestus",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Leitud kestus:",
"LabelEbook": "E-raamat",
"LabelEbooks": "E-raamatud",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episoodi tüüp",
"LabelExample": "Näide",
"LabelExplicit": "Vulgaarne",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Voogu URL",
"LabelFetchingMetadata": "Metaandmete hankimine",
"LabelFile": "Fail",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Vaikimisi salvestatakse metaandmed /metadata/items kausta. Selle seadistuse lubamine salvestab metaandmed teie raamatukogu üksuse kaustadesse",
"LabelSettingsTimeFormat": "Kellaaja vorming",
"LabelShowAll": "Näita kõiki",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Suurus",
"LabelSleepTimer": "Uinaku taimer",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Olete kindel, et soovite kõik episoodid mitte lõpetatuks märkida?",
"MessageConfirmMarkSeriesFinished": "Olete kindel, et soovite selle seeria kõik raamatud lõpetatuks märkida?",
"MessageConfirmMarkSeriesNotFinished": "Olete kindel, et soovite selle seeria kõik raamatud mitte lõpetatuks märkida?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Hoiatus! Quick Embed ei tee varukoopiaid teie helifailidest. Veenduge, et teil oleks varukoopia oma helifailidest. <br><br>Kas soovite jätkata?",
"MessageConfirmRemoveAllChapters": "Olete kindel, et soovite eemaldada kõik peatükid?",
"MessageConfirmRemoveAuthor": "Olete kindel, et soovite autori \"{0}\" eemaldada?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Järjehoidja eemaldatud",
"ToastBookmarkUpdateFailed": "Järjehoidja värskendamine ebaõnnestus",
"ToastBookmarkUpdateSuccess": "Järjehoidja värskendatud",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Peatükkidel on vigu",
"ToastChaptersMustHaveTitles": "Peatükkidel peab olema pealkiri",
"ToastCollectionItemsRemoveFailed": "Üksuse(te) eemaldamine kogumist ebaõnnestus",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Kogum eemaldatud",
"ToastCollectionUpdateFailed": "Kogumi värskendamine ebaõnnestus",
"ToastCollectionUpdateSuccess": "Kogum värskendatud",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Üksuse kaane värskendamine ebaõnnestus",
"ToastItemCoverUpdateSuccess": "Üksuse kaas värskendatud",
"ToastItemDetailsUpdateFailed": "Üksuse üksikasjade värskendamine ebaõnnestus",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-raamat saadetud seadmesse \"{0}\"",
"ToastSeriesUpdateFailed": "Sarja värskendamine ebaõnnestus",
"ToastSeriesUpdateSuccess": "Sarja värskendamine õnnestus",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Seansi kustutamine ebaõnnestus",
"ToastSessionDeleteSuccess": "Sessioon kustutatud",
"ToastSocketConnected": "Pesa ühendatud",
"ToastSocketDisconnected": "Pesa ühendus katkenud",
"ToastSocketFailedToConnect": "Pesa ühendamine ebaõnnestus",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Kasutaja kustutamine ebaõnnestus",
"ToastUserDeleteSuccess": "Kasutaja kustutatud"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Vos statistiques",
"LabelAbridged": "Version courte",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Type de compte",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Invité",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Livres",
"LabelButtonText": "Texte du bouton",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Modifier le mot de passe",
"LabelChannels": "Canaux",
"LabelChapters": "Chapitres",
@ -266,6 +269,9 @@
"LabelDownload": "Téléchargement",
"LabelDownloadNEpisodes": "Télécharger {0} épisode(s)",
"LabelDuration": "Durée",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Durée trouvée :",
"LabelEbook": "Livre numérique",
"LabelEbooks": "Livres numériques",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Type de lépisode",
"LabelExample": "Exemple",
"LabelExplicit": "Restriction",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "URL du flux",
"LabelFetchingMetadata": "Récupération des métadonnées",
"LabelFile": "Fichier",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Par défaut, les métadonnées sont enregistrées dans /metadata/items",
"LabelSettingsTimeFormat": "Format dheure",
"LabelShowAll": "Tout afficher",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Taille",
"LabelSleepTimer": "Minuterie",
"LabelSlug": "Balise",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Êtes-vous sûr de vouloir marquer tous les épisodes comme non terminés ?",
"MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme terminées ?",
"MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme comme non terminés ?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Attention ! Lintégration rapide ne sauvegardera pas vos fichiers audio. Assurez-vous davoir effectuer une sauvegarde de vos fichiers audio.<br><br>Souhaitez-vous continuer ?",
"MessageConfirmRemoveAllChapters": "Êtes-vous sûr de vouloir supprimer tous les chapitres ?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Signet supprimé",
"ToastBookmarkUpdateFailed": "Échec de la mise à jour de signet",
"ToastBookmarkUpdateSuccess": "Signet mis à jour",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs",
"ToastChaptersMustHaveTitles": "Les chapitre doivent avoir un titre",
"ToastCollectionItemsRemoveFailed": "Échec de la suppression de(s) article(s) de la collection",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Collection supprimée",
"ToastCollectionUpdateFailed": "Échec de la mise à jour de la collection",
"ToastCollectionUpdateSuccess": "Collection mise à jour",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Échec de la mise à jour de la couverture de larticle",
"ToastItemCoverUpdateSuccess": "Couverture de larticle mise à jour",
"ToastItemDetailsUpdateFailed": "Échec de la mise à jour des détails de larticle",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Livre numérique envoyé à lappareil : {0}",
"ToastSeriesUpdateFailed": "Échec de la mise à jour de la série",
"ToastSeriesUpdateSuccess": "Mise à jour de la série réussie",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Échec de la suppression de session",
"ToastSessionDeleteSuccess": "Session supprimée",
"ToastSocketConnected": "WebSocket connecté",
"ToastSocketDisconnected": "WebSocket déconnecté",
"ToastSocketFailedToConnect": "Échec de la connexion WebSocket",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Échec de la suppression de lutilisateur",
"ToastUserDeleteSuccess": "Utilisateur supprimé"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Your Stats",
"LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Account Type",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Guest",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Books",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Change Password",
"LabelChannels": "Channels",
"LabelChapters": "Chapters",
@ -266,6 +269,9 @@
"LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Duration",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Duration found:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episode Type",
"LabelExample": "Example",
"LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "File",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
"LabelSettingsTimeFormat": "Time Format",
"LabelShowAll": "Show All",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Size",
"LabelSleepTimer": "Sleep timer",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Bookmark removed",
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
"ToastBookmarkUpdateSuccess": "Bookmark updated",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Collection removed",
"ToastCollectionUpdateFailed": "Failed to update collection",
"ToastCollectionUpdateSuccess": "Collection updated",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Failed to update item cover",
"ToastItemCoverUpdateSuccess": "Item cover updated",
"ToastItemDetailsUpdateFailed": "Failed to update item details",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Failed to delete session",
"ToastSessionDeleteSuccess": "Session deleted",
"ToastSocketConnected": "Socket connected",
"ToastSocketDisconnected": "Socket disconnected",
"ToastSocketFailedToConnect": "Socket failed to connect",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "שנת {0} בסקירה",
"HeaderYourStats": "הסטטיסטיקות שלך",
"LabelAbridged": "מקוצר",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "סוג חשבון",
"LabelAccountTypeAdmin": "מנהל",
"LabelAccountTypeGuest": "אורח",
@ -229,6 +231,7 @@
"LabelBitrate": "קצב סיביות",
"LabelBooks": "ספרים",
"LabelButtonText": "טקסט לחצן",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "שינוי סיסמה",
"LabelChannels": "ערוצים",
"LabelChapters": "פרקים",
@ -266,6 +269,9 @@
"LabelDownload": "הורד",
"LabelDownloadNEpisodes": "הורד {0} פרקים",
"LabelDuration": "משך",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "משך נמצא:",
"LabelEbook": "ספר אלקטרוני",
"LabelEbooks": "ספרים אלקטרוניים",
@ -283,6 +289,8 @@
"LabelEpisodeType": "סוג הפרק",
"LabelExample": "דוגמה",
"LabelExplicit": "בוטה",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "כתובת ערוץ",
"LabelFetchingMetadata": "מושך מטא-נתונים",
"LabelFile": "קובץ",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "כברירת מחדל, קבצי מטה-נתונים מאוחסנים ב- /metadata/items, הפעלת ההגדרה תאחסן קבצי מטה-נתונים בתיקיית פריט שלך בספרייה",
"LabelSettingsTimeFormat": "פורמט זמן",
"LabelShowAll": "הצג הכל",
"LabelShowSeconds": "Show seconds",
"LabelSize": "גודל",
"LabelSleepTimer": "טיימר שינה",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הפרקים כלא הסתיימו?",
"MessageConfirmMarkSeriesFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כהסתיימו?",
"MessageConfirmMarkSeriesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כלא הסתיימו?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "אזהרה! הטמעה מהירה לא תגבה גיבוי של קבצי האודיו שלך. וודא שיש לך גיבוי של קבצי האודיו שלך. <br><br>האם ברצונך להמשיך?",
"MessageConfirmRemoveAllChapters": "האם אתה בטוח שברצונך להסיר את כל הפרקים?",
"MessageConfirmRemoveAuthor": "האם אתה בטוח שברצונך להסיר את המחבר \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "הסימניה הוסרה בהצלחה",
"ToastBookmarkUpdateFailed": "עדכון הסימניה נכשל",
"ToastBookmarkUpdateSuccess": "הסימניה עודכנה בהצלחה",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "פרקים מכילים שגיאות",
"ToastChaptersMustHaveTitles": "פרקים חייבים לכלול כותרות",
"ToastCollectionItemsRemoveFailed": "הסרת הפריט(ים) מהאוסף נכשלה",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "האוסף הוסר בהצלחה",
"ToastCollectionUpdateFailed": "עדכון האוסף נכשל",
"ToastCollectionUpdateSuccess": "האוסף עודכן בהצלחה",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "עדכון כריכת הפריט נכשל",
"ToastItemCoverUpdateSuccess": "כריכת הפריט עודכנה בהצלחה",
"ToastItemDetailsUpdateFailed": "עדכון פרטי הפריט נכשל",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "הספר נשלח אל המכשיר \"{0}\"",
"ToastSeriesUpdateFailed": "עדכון הסדרה נכשל",
"ToastSeriesUpdateSuccess": "הסדרה עודכנה בהצלחה",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "מחיקת הפעולה נכשלה",
"ToastSessionDeleteSuccess": "הפעולה נמחקה בהצלחה",
"ToastSocketConnected": "קצה תקשורת חובר",
"ToastSocketDisconnected": "קצה תקשורת נותק",
"ToastSocketFailedToConnect": "התחברות קצה התקשורת נכשלה",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "מחיקת המשתמש נכשלה",
"ToastUserDeleteSuccess": "המשתמש נמחק בהצלחה"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Your Stats",
"LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Account Type",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Guest",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Books",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Change Password",
"LabelChannels": "Channels",
"LabelChapters": "Chapters",
@ -266,6 +269,9 @@
"LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Duration",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Duration found:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episode Type",
"LabelExample": "Example",
"LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "File",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
"LabelSettingsTimeFormat": "Time Format",
"LabelShowAll": "Show All",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Size",
"LabelSleepTimer": "Sleep timer",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Bookmark removed",
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
"ToastBookmarkUpdateSuccess": "Bookmark updated",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Collection removed",
"ToastCollectionUpdateFailed": "Failed to update collection",
"ToastCollectionUpdateSuccess": "Collection updated",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Failed to update item cover",
"ToastItemCoverUpdateSuccess": "Item cover updated",
"ToastItemDetailsUpdateFailed": "Failed to update item details",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Failed to delete session",
"ToastSessionDeleteSuccess": "Session deleted",
"ToastSocketConnected": "Socket connected",
"ToastSocketDisconnected": "Socket disconnected",
"ToastSocketFailedToConnect": "Socket failed to connect",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Tvoja statistika",
"LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Vrsta korisničkog računa",
"LabelAccountTypeAdmin": "Administrator",
"LabelAccountTypeGuest": "Gost",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Knjige",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Promijeni lozinku",
"LabelChannels": "Channels",
"LabelChapters": "Chapters",
@ -266,6 +269,9 @@
"LabelDownload": "Preuzmi",
"LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Trajanje",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Pronađeno trajanje:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Vrsta epizode",
"LabelExample": "Example",
"LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Datoteka",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Po defaultu metapodatci su spremljeni u /metadata/items, uključujućite li ovu postavku, metapodatci će biti spremljeni u folderima od biblioteke",
"LabelSettingsTimeFormat": "Time Format",
"LabelShowAll": "Prikaži sve",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Veličina",
"LabelSleepTimer": "Sleep timer",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Knjižnja bilješka uklonjena",
"ToastBookmarkUpdateFailed": "Aktualizacija knjižne bilješke neuspješna",
"ToastBookmarkUpdateSuccess": "Knjižna bilješka aktualizirana",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Neuspješno brisanje stavke/-i iz kolekcije",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Kolekcija obrisana",
"ToastCollectionUpdateFailed": "Aktualiziranje kolekcije neuspješno",
"ToastCollectionUpdateSuccess": "Kolekcija aktualizirana",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Aktualiziranje covera stavke neuspješna",
"ToastItemCoverUpdateSuccess": "Cover stavke aktualiziran",
"ToastItemDetailsUpdateFailed": "Aktualiziranje detalja stavke neuspješno",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Neuspješno brisanje serije",
"ToastSessionDeleteSuccess": "Sesija obrisana",
"ToastSocketConnected": "Socket connected",
"ToastSocketDisconnected": "Socket disconnected",
"ToastSocketFailedToConnect": "Socket failed to connect",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Neuspješno brisanje korisnika",
"ToastUserDeleteSuccess": "Korisnik obrisan"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Saját statisztikák",
"LabelAbridged": "Tömörített",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Fióktípus",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Vendég",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitráta",
"LabelBooks": "Könyvek",
"LabelButtonText": "Gomb szövege",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Jelszó megváltoztatása",
"LabelChannels": "Csatornák",
"LabelChapters": "Fejezetek",
@ -266,6 +269,9 @@
"LabelDownload": "Letöltés",
"LabelDownloadNEpisodes": "{0} epizód letöltése",
"LabelDuration": "Időtartam",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Megtalált időtartam:",
"LabelEbook": "E-könyv",
"LabelEbooks": "E-könyvek",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Epizód típusa",
"LabelExample": "Példa",
"LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Hírcsatorna URL",
"LabelFetchingMetadata": "Metaadatok lekérése",
"LabelFile": "Fájl",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Alapértelmezés szerint a metaadatfájlok a /metadata/items mappában vannak tárolva, ennek a beállításnak az engedélyezése a metaadatfájlokat a könyvtári elem mappáiban tárolja",
"LabelSettingsTimeFormat": "Időformátum",
"LabelShowAll": "Mindent mutat",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Méret",
"LabelSleepTimer": "Alvásidőzítő",
"LabelSlug": "Rövid cím",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Biztosan meg szeretné jelölni az összes epizódot nem befejezettnek?",
"MessageConfirmMarkSeriesFinished": "Biztosan meg szeretné jelölni a sorozat összes könyvét befejezettnek?",
"MessageConfirmMarkSeriesNotFinished": "Biztosan meg szeretné jelölni a sorozat összes könyvét nem befejezettnek?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Figyelem! A Gyors beágyazás nem készít biztonsági másolatot az audiofájlokról. Győződjön meg arról, hogy van biztonsági másolata az audiofájlokról. <br><br>Szeretné folytatni?",
"MessageConfirmRemoveAllChapters": "Biztosan eltávolítja az összes fejezetet?",
"MessageConfirmRemoveAuthor": "Biztosan eltávolítja a(z) \"{0}\" szerzőt?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Könyvjelző eltávolítva",
"ToastBookmarkUpdateFailed": "Könyvjelző frissítése sikertelen",
"ToastBookmarkUpdateSuccess": "Könyvjelző frissítve",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "A fejezetek hibákat tartalmaznak",
"ToastChaptersMustHaveTitles": "A fejezeteknek címekkel kell rendelkezniük",
"ToastCollectionItemsRemoveFailed": "Elem(ek) eltávolítása a gyűjteményből sikertelen",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Gyűjtemény eltávolítva",
"ToastCollectionUpdateFailed": "Gyűjtemény frissítése sikertelen",
"ToastCollectionUpdateSuccess": "Gyűjtemény frissítve",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Elem borítójának frissítése sikertelen",
"ToastItemCoverUpdateSuccess": "Elem borítója frissítve",
"ToastItemDetailsUpdateFailed": "Elem részleteinek frissítése sikertelen",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-könyv elküldve az eszközre \"{0}\"",
"ToastSeriesUpdateFailed": "Sorozat frissítése sikertelen",
"ToastSeriesUpdateSuccess": "Sorozat frissítése sikeres",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Munkamenet törlése sikertelen",
"ToastSessionDeleteSuccess": "Munkamenet törölve",
"ToastSocketConnected": "Socket csatlakoztatva",
"ToastSocketDisconnected": "Socket lecsatlakoztatva",
"ToastSocketFailedToConnect": "A Socket csatlakoztatása sikertelen",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Felhasználó törlése sikertelen",
"ToastUserDeleteSuccess": "Felhasználó törölve"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Anno {0} in Sintesi",
"HeaderYourStats": "Statistiche Personali",
"LabelAbridged": "Abbreviato",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Tipo di Account",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Ospite",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Libri",
"LabelButtonText": "Buttone Testo",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Cambia Password",
"LabelChannels": "Canali",
"LabelChapters": "Capitoli",
@ -266,6 +269,9 @@
"LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Durata",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Durata Trovata:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Tipo Episodio",
"LabelExample": "Esempio",
"LabelExplicit": "Esplicito",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Recupero dei metadati",
"LabelFile": "File",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Di default, i metadati sono salvati dentro /metadata/items, abilitando questa opzione si memorizzeranno i metadata nella cartella della libreria",
"LabelSettingsTimeFormat": "Formato Ora",
"LabelShowAll": "Mostra Tutto",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Dimensione",
"LabelSleepTimer": "Sleep timer",
"LabelSlug": "Lento",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Sei sicuro di voler contrassegnare tutti gli episodi come non completati?",
"MessageConfirmMarkSeriesFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come completati?",
"MessageConfirmMarkSeriesNotFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come non completati?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Attenzione! L'incorporamento rapido non eseguirà il backup dei file audio. Assicurati di avere un backup dei tuoi file audio. <br><br>Vuoi Continuare?",
"MessageConfirmRemoveAllChapters": "Sei sicuro di voler rimuovere tutti i capitoli?",
"MessageConfirmRemoveAuthor": "Sei sicuro di voler rimuovere l'autore? \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Segnalibro Rimosso",
"ToastBookmarkUpdateFailed": "Aggiornamento Segnalibro fallito",
"ToastBookmarkUpdateSuccess": "Segnalibro aggiornato",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "I capitoli contengono errori",
"ToastChaptersMustHaveTitles": "I capitoli devono avere titoli",
"ToastCollectionItemsRemoveFailed": "Rimozione oggetti dalla Raccolta fallita",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Collezione rimossa",
"ToastCollectionUpdateFailed": "Errore aggiornamento Raccolta",
"ToastCollectionUpdateSuccess": "Raccolta aggiornata",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Errore Aggiornamento cover",
"ToastItemCoverUpdateSuccess": "Cover aggiornata",
"ToastItemDetailsUpdateFailed": "Errore Aggiornamento dettagli file",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook inviato al dispositivo \"{0}\"",
"ToastSeriesUpdateFailed": "Aggiornamento Serie Fallito",
"ToastSeriesUpdateSuccess": "Serie Aggiornate",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Errore eliminazione sessione",
"ToastSessionDeleteSuccess": "Sessione cancellata",
"ToastSocketConnected": "Socket connesso",
"ToastSocketDisconnected": "Socket disconnesso",
"ToastSocketFailedToConnect": "Socket non riesce a connettersi",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Errore eliminazione utente",
"ToastUserDeleteSuccess": "Utente eliminato"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Jūsų statistika",
"LabelAbridged": "Santrauka",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Paskyros tipas",
"LabelAccountTypeAdmin": "Administratorius",
"LabelAccountTypeGuest": "Svečias",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitų sparta",
"LabelBooks": "Knygos",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Pakeisti slaptažodį",
"LabelChannels": "Kanalai",
"LabelChapters": "Skyriai",
@ -266,6 +269,9 @@
"LabelDownload": "Atsisiųsti",
"LabelDownloadNEpisodes": "Atsisiųsti {0} epizodų",
"LabelDuration": "Trukmė",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Rasta trukmė:",
"LabelEbook": "Elektroninė knyga",
"LabelEbooks": "Elektroninės knygos",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Epizodo tipas",
"LabelExample": "Pavyzdys",
"LabelExplicit": "Suaugusiems",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Srauto URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Failas",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Pagal nutylėjimą metaduomenų failai saugomi /metadata/items aplanke, įjungus šią parinktį metaduomenų failai bus saugomi jūsų bibliotekos elemento aplanke",
"LabelSettingsTimeFormat": "Laiko formatas",
"LabelShowAll": "Rodyti viską",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Dydis",
"LabelSleepTimer": "Miego laikmatis",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Ar tikrai norite pažymėti visus epizodus kaip nebaigtus?",
"MessageConfirmMarkSeriesFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip užbaigtas?",
"MessageConfirmMarkSeriesNotFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip nebaigtas?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Ar tikrai norite pašalinti visus skyrius?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Žyma pašalinta",
"ToastBookmarkUpdateFailed": "Žymos atnaujinti nepavyko",
"ToastBookmarkUpdateSuccess": "Žyma atnaujinta",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Skyriai turi klaidų",
"ToastChaptersMustHaveTitles": "Skyriai turi turėti pavadinimus",
"ToastCollectionItemsRemoveFailed": "Elementų pašalinti iš kolekcijos nepavyko",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Kolekcija pašalinta",
"ToastCollectionUpdateFailed": "Kolekcijos atnaujinti nepavyko",
"ToastCollectionUpdateSuccess": "Kolekcija atnaujinta",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Elemento viršelio atnaujinti nepavyko",
"ToastItemCoverUpdateSuccess": "Elemento viršelis atnaujintas",
"ToastItemDetailsUpdateFailed": "Elemento detalių atnaujinti nepavyko",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-knyga išsiųsta į įrenginį \"{0}\"",
"ToastSeriesUpdateFailed": "Serijos atnaujinti nepavyko",
"ToastSeriesUpdateSuccess": "Serijos atnaujintos",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Sesijos ištrinti nepavyko",
"ToastSessionDeleteSuccess": "Sesija ištrinta",
"ToastSocketConnected": "Serveris prijungtas",
"ToastSocketDisconnected": "Severis atjungtas",
"ToastSocketFailedToConnect": "Nepavyko prisijungti prie serverio",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Nepavyko ištrinti naudotojo",
"ToastUserDeleteSuccess": "Naudotojas ištrintas"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Je statistieken",
"LabelAbridged": "Verkort",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Accounttype",
"LabelAccountTypeAdmin": "Beheerder",
"LabelAccountTypeGuest": "Gast",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Boeken",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Wachtwoord wijzigen",
"LabelChannels": "Kanalen",
"LabelChapters": "Hoofdstukken",
@ -266,6 +269,9 @@
"LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Duur",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Gevonden duur:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Afleveringtype",
"LabelExample": "Voorbeeld",
"LabelExplicit": "Expliciet",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Bestand",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Standaard worden metadata-bestanden bewaard in /metadata/items, door deze instelling in te schakelen zullen metadata bestanden in de map van je bibliotheekonderdeel bewaard worden",
"LabelSettingsTimeFormat": "Tijdformat",
"LabelShowAll": "Toon alle",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Grootte",
"LabelSleepTimer": "Slaaptimer",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Weet je zeker dat je alle afleveringen als niet-voltooid wil markeren?",
"MessageConfirmMarkSeriesFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als voltooid?",
"MessageConfirmMarkSeriesNotFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als niet voltooid?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Weet je zeker dat je alle hoofdstukken wil verwijderen?",
"MessageConfirmRemoveAuthor": "Weet je zeker dat je auteur \"{0}\" wil verwijderen?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Boekwijzer verwijderd",
"ToastBookmarkUpdateFailed": "Bijwerken boekwijzer mislukt",
"ToastBookmarkUpdateSuccess": "Boekwijzer bijgewerkt",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Hoofdstukken bevatten fouten",
"ToastChaptersMustHaveTitles": "Hoofdstukken moeten titels hebben",
"ToastCollectionItemsRemoveFailed": "Verwijderen onderdeel (of onderdelen) uit collectie mislukt",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Collectie verwijderd",
"ToastCollectionUpdateFailed": "Bijwerken collectie mislukt",
"ToastCollectionUpdateSuccess": "Collectie bijgewerkt",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Bijwerken cover onderdeel mislukt",
"ToastItemCoverUpdateSuccess": "Cover onderdeel bijgewerkt",
"ToastItemDetailsUpdateFailed": "Bijwerken details onderdeel mislukt",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook verstuurd naar apparaat \"{0}\"",
"ToastSeriesUpdateFailed": "Bijwerken serie mislukt",
"ToastSeriesUpdateSuccess": "Bijwerken serie gelukt",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Verwijderen sessie mislukt",
"ToastSessionDeleteSuccess": "Sessie verwijderd",
"ToastSocketConnected": "Socket verbonden",
"ToastSocketDisconnected": "Socket niet verbonden",
"ToastSocketFailedToConnect": "Verbinding Socket mislukt",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Verwijderen gebruiker mislukt",
"ToastUserDeleteSuccess": "Gebruiker verwijderd"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Din statistikk",
"LabelAbridged": "Forkortet",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Kontotype",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Gjest",
@ -229,6 +231,7 @@
"LabelBitrate": "Bithastighet",
"LabelBooks": "Bøker",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Endre passord",
"LabelChannels": "Kanaler",
"LabelChapters": "Kapitler",
@ -266,6 +269,9 @@
"LabelDownload": "Last ned",
"LabelDownloadNEpisodes": "Last ned {0} episoder",
"LabelDuration": "Varighet",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Varighet funnet:",
"LabelEbook": "Ebok",
"LabelEbooks": "Ebøker",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episode type",
"LabelExample": "Eksempel",
"LabelExplicit": "Eksplisitt",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed Adresse",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Fil",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Som standard vil metadata bli lagret under /metadata/items, aktiveres dette valget vil metadata bli lagret i samme mappe som gjenstanden",
"LabelSettingsTimeFormat": "Tid format",
"LabelShowAll": "Vis alt",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Størrelse",
"LabelSleepTimer": "Sove-timer",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Er du sikker på at du vil markere alle episodene som ikke fullført?",
"MessageConfirmMarkSeriesFinished": "Er du sikker på at du vil markere alle bøkene i serien som fullført?",
"MessageConfirmMarkSeriesNotFinished": "Er du sikker på at du vil markere alle bøkene i serien som ikke fullført?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Er du sikker på at du vil fjerne alle kapitler?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Bokmerke fjernet",
"ToastBookmarkUpdateFailed": "Misslykkes å oppdatere bokmerke",
"ToastBookmarkUpdateSuccess": "Bokmerke oppdatert",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Kapittel har feil",
"ToastChaptersMustHaveTitles": "Kapittel må ha titler",
"ToastCollectionItemsRemoveFailed": "Misslykkes å fjerne gjenstand(er) fra samling",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Samling fjernet",
"ToastCollectionUpdateFailed": "Misslykkes å oppdatere samling",
"ToastCollectionUpdateSuccess": "samlingupdated",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Misslykkes å oppdatere omslag",
"ToastItemCoverUpdateSuccess": "Omslag oppdatert",
"ToastItemDetailsUpdateFailed": "Misslykkes å oppdatere detaljer",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebok sendt til \"{0}\"",
"ToastSeriesUpdateFailed": "Misslykkes å oppdatere serie",
"ToastSeriesUpdateSuccess": "Serie oppdatert",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Misslykkes å slette sesjon",
"ToastSessionDeleteSuccess": "Sesjon slettet",
"ToastSocketConnected": "Socket koblet til",
"ToastSocketDisconnected": "Socket koblet fra",
"ToastSocketFailedToConnect": "Misslykkes å koble til Socket",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Misslykkes å slette bruker",
"ToastUserDeleteSuccess": "Bruker slettet"
}

View file

@ -1,10 +1,10 @@
{
"ButtonAdd": "Dodaj",
"ButtonAddChapters": "Dodaj rozdziały",
"ButtonAddDevice": "Add Device",
"ButtonAddLibrary": "Add Library",
"ButtonAddDevice": "Dodaj urządzenie",
"ButtonAddLibrary": "Dodaj bibliotekę",
"ButtonAddPodcasts": "Dodaj podcasty",
"ButtonAddUser": "Add User",
"ButtonAddUser": "Dodaj użytkownika",
"ButtonAddYourFirstLibrary": "Dodaj swoją pierwszą bibliotekę",
"ButtonApply": "Zatwierdź",
"ButtonApplyChapters": "Zatwierdź rozdziały",
@ -19,11 +19,11 @@
"ButtonClearFilter": "Wyczyść filtr",
"ButtonCloseFeed": "Zamknij kanał",
"ButtonCollections": "Kolekcje",
"ButtonConfigureScanner": "Configure Scanner",
"ButtonConfigureScanner": "Skonfiguruj skaner",
"ButtonCreate": "Utwórz",
"ButtonCreateBackup": "Utwórz kopię zapasową",
"ButtonDelete": "Usuń",
"ButtonDownloadQueue": "Queue",
"ButtonDownloadQueue": "Kolejka",
"ButtonEdit": "Edit",
"ButtonEditChapters": "Edytuj rozdziały",
"ButtonEditPodcast": "Edytuj podcast",
@ -32,8 +32,8 @@
"ButtonHide": "Ukryj",
"ButtonHome": "Strona główna",
"ButtonIssues": "Błędy",
"ButtonJumpBackward": "Jump Backward",
"ButtonJumpForward": "Jump Forward",
"ButtonJumpBackward": "Skocz do tyłu",
"ButtonJumpForward": "Skocz do przodu",
"ButtonLatest": "Aktualna wersja:",
"ButtonLibrary": "Biblioteka",
"ButtonLogout": "Wyloguj",
@ -43,17 +43,17 @@
"ButtonMatchAllAuthors": "Dopasuj wszystkich autorów",
"ButtonMatchBooks": "Dopasuj książki",
"ButtonNevermind": "Anuluj",
"ButtonNext": "Next",
"ButtonNextChapter": "Next Chapter",
"ButtonNext": "Następny",
"ButtonNextChapter": "Następny rozdział",
"ButtonOk": "Ok",
"ButtonOpenFeed": "Otwórz feed",
"ButtonOpenManager": "Otwórz menadżera",
"ButtonPause": "Pause",
"ButtonPlay": "Odtwarzaj",
"ButtonPlaying": "Odtwarzane",
"ButtonPlaylists": "Playlists",
"ButtonPrevious": "Previous",
"ButtonPreviousChapter": "Previous Chapter",
"ButtonPlaylists": "Listy odtwarzania",
"ButtonPrevious": "Poprzedni",
"ButtonPreviousChapter": "Poprzedni rozdział",
"ButtonPurgeAllCache": "Wyczyść dane tymczasowe",
"ButtonPurgeItemsCache": "Wyczyść dane tymczasowe pozycji",
"ButtonPurgeMediaProgress": "Wyczyść postęp",
@ -63,26 +63,26 @@
"ButtonRead": "Czytaj",
"ButtonReadLess": "Read less",
"ButtonReadMore": "Read more",
"ButtonRefresh": "Refresh",
"ButtonRefresh": "Odśwież",
"ButtonRemove": "Usuń",
"ButtonRemoveAll": "Usuń wszystko",
"ButtonRemoveAllLibraryItems": "Usuń wszystkie elementy z biblioteki",
"ButtonRemoveFromContinueListening": "Usuń z listy odtwarzania",
"ButtonRemoveFromContinueReading": "Remove from Continue Reading",
"ButtonRemoveFromContinueReading": "Usuń z Kontynuuj czytanie",
"ButtonRemoveSeriesFromContinueSeries": "Usuń serię z listy odtwarzania",
"ButtonReScan": "Ponowne skanowanie",
"ButtonReset": "Resetowanie",
"ButtonResetToDefault": "Reset to default",
"ButtonResetToDefault": "Przywróć ustawienia domyślne",
"ButtonRestore": "Przywróć",
"ButtonSave": "Zapisz",
"ButtonSaveAndClose": "Zapisz i zamknij",
"ButtonSaveTracklist": "Zapisz listę odtwarzania",
"ButtonScan": "Zeskanuj",
"ButtonScanLibrary": "Scan Library",
"ButtonScanLibrary": "Skanuj bibliotekę",
"ButtonSearch": "Szukaj",
"ButtonSelectFolderPath": "Wybierz ścieżkę folderu",
"ButtonSeries": "Seria",
"ButtonSetChaptersFromTracks": "Set chapters from tracks",
"ButtonSetChaptersFromTracks": "Ustawiaj rozdziały na podstawie utworów",
"ButtonShare": "Share",
"ButtonShiftTimes": "Przesunięcie czasowe",
"ButtonShow": "Pokaż",
@ -94,19 +94,19 @@
"ButtonUploadBackup": "Wgraj kopię zapasową",
"ButtonUploadCover": "Wgraj okładkę",
"ButtonUploadOPMLFile": "Wgraj plik OPML",
"ButtonUserDelete": "Delete user {0}",
"ButtonUserEdit": "Edit user {0}",
"ButtonUserDelete": "Usuń użytkownika {0}",
"ButtonUserEdit": "Edytuj użytkownika {0}",
"ButtonViewAll": "Zobacz wszystko",
"ButtonYes": "Tak",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"ErrorUploadFetchMetadataAPI": "Błąd podczas pobierania metadanych",
"ErrorUploadFetchMetadataNoResults": "Nie można pobrać metadanych — spróbuj zaktualizować tytuł i/lub autora",
"ErrorUploadLacksTitle": "Musi mieć tytuł",
"HeaderAccount": "Konto",
"HeaderAdvanced": "Zaawansowane",
"HeaderAppriseNotificationSettings": "Ustawienia powiadomień Apprise",
"HeaderAudiobookTools": "Narzędzia do zarządzania audiobookami",
"HeaderAudioTracks": "Ścieżki audio",
"HeaderAuthentication": "Authentication",
"HeaderAuthentication": "Uwierzytelnianie",
"HeaderBackups": "Kopie zapasowe",
"HeaderChangePassword": "Zmień hasło",
"HeaderChapters": "Rozdziały",
@ -138,8 +138,8 @@
"HeaderListeningStats": "Statystyki odtwarzania",
"HeaderLogin": "Zaloguj się",
"HeaderLogs": "Logi",
"HeaderManageGenres": "Manage Genres",
"HeaderManageTags": "Manage Tags",
"HeaderManageGenres": "Zarządzaj gatunkami",
"HeaderManageTags": "Zarządzaj tagami",
"HeaderMapDetails": "Map details",
"HeaderMatch": "Dopasuj",
"HeaderMetadataOrderOfPrecedence": "Metadata order of precedence",
@ -150,7 +150,7 @@
"HeaderOpenIDConnectAuthentication": "OpenID Connect Authentication",
"HeaderOpenRSSFeed": "Utwórz kanał RSS",
"HeaderOtherFiles": "Inne pliki",
"HeaderPasswordAuthentication": "Password Authentication",
"HeaderPasswordAuthentication": "Uwierzytelnianie hasłem",
"HeaderPermissions": "Uprawnienia",
"HeaderPlayerQueue": "Player Queue",
"HeaderPlaylist": "Playlist",
@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Twoje statystyki",
"LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Typ konta",
"LabelAccountTypeAdmin": "Administrator",
"LabelAccountTypeGuest": "Gość",
@ -200,11 +202,11 @@
"LabelAddToCollectionBatch": "Dodaj {0} książki do kolekcji",
"LabelAddToPlaylist": "Add to Playlist",
"LabelAddToPlaylistBatch": "Add {0} Items to Playlist",
"LabelAdminUsersOnly": "Admin users only",
"LabelAdminUsersOnly": "Tylko użytkownicy administracyjni",
"LabelAll": "All",
"LabelAllUsers": "Wszyscy użytkownicy",
"LabelAllUsersExcludingGuests": "All users excluding guests",
"LabelAllUsersIncludingGuests": "All users including guests",
"LabelAllUsersExcludingGuests": "Wszyscy użytkownicy z wyłączeniem gości",
"LabelAllUsersIncludingGuests": "Wszyscy użytkownicy, łącznie z gośćmi",
"LabelAlreadyInYourLibrary": "Already in your library",
"LabelAppend": "Append",
"LabelAuthor": "Autor",
@ -213,7 +215,7 @@
"LabelAuthors": "Autorzy",
"LabelAutoDownloadEpisodes": "Automatyczne pobieranie odcinków",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoFetchMetadataHelp": "Pobiera metadane dotyczące tytułu, autora i serii, aby usprawnić przesyłanie. Po przesłaniu może być konieczne dopasowanie dodatkowych metadanych.",
"LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Książki",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Zmień hasło",
"LabelChannels": "Channels",
"LabelChapters": "Chapters",
@ -253,7 +256,7 @@
"LabelCurrently": "Obecnie:",
"LabelCustomCronExpression": "Custom Cron Expression:",
"LabelDatetime": "Data i godzina",
"LabelDeleteFromFileSystemCheckbox": "Delete from file system (uncheck to only remove from database)",
"LabelDeleteFromFileSystemCheckbox": "Usuń z systemu plików (odznacz, aby usunąć tylko z bazy danych)",
"LabelDescription": "Opis",
"LabelDeselectAll": "Odznacz wszystko",
"LabelDevice": "Urządzenie",
@ -262,10 +265,13 @@
"LabelDirectory": "Katalog",
"LabelDiscFromFilename": "Oznaczenie dysku z nazwy pliku",
"LabelDiscFromMetadata": "Oznaczenie dysku z metadanych",
"LabelDiscover": "Discover",
"LabelDiscover": "Odkrywaj",
"LabelDownload": "Pobierz",
"LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Czas trwania",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Znaleziona długość:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Typ odcinka",
"LabelExample": "Example",
"LabelExplicit": "Nieprzyzwoite",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "URL kanału",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Plik",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Domyślnie metadane są przechowywane w folderze /metadata/items, włączenie tej opcji spowoduje, że okładka będzie przechowywana w folderze ksiązki. Tylko jedna okładka o nazwie pliku \"cover\" będzie przechowywana",
"LabelSettingsTimeFormat": "Time Format",
"LabelShowAll": "Pokaż wszystko",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Rozmiar",
"LabelSleepTimer": "Wyłącznik czasowy",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Zakładka została usunięta",
"ToastBookmarkUpdateFailed": "Nie udało się zaktualizować zakładki",
"ToastBookmarkUpdateSuccess": "Zaktualizowano zakładkę",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Nie udało się usunąć pozycji z kolekcji",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Kolekcja usunięta",
"ToastCollectionUpdateFailed": "Nie udało się zaktualizować kolekcji",
"ToastCollectionUpdateSuccess": "Zaktualizowano kolekcję",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Nie udało się zaktualizować okładki",
"ToastItemCoverUpdateSuccess": "Zaktualizowano okładkę",
"ToastItemDetailsUpdateFailed": "Nie udało się zaktualizować szczegółów",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Nie udało się usunąć sesji",
"ToastSessionDeleteSuccess": "Sesja usunięta",
"ToastSocketConnected": "Nawiązano połączenie z serwerem",
"ToastSocketDisconnected": "Połączenie z serwerem zostało zamknięte",
"ToastSocketFailedToConnect": "Poączenie z serwerem nie powiodło się",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Nie udało się usunąć użytkownika",
"ToastUserDeleteSuccess": "Użytkownik usunięty"
}

View file

@ -61,8 +61,8 @@
"ButtonQueueRemoveItem": "Remover da Lista",
"ButtonQuickMatch": "Consulta rápida",
"ButtonRead": "Ler",
"ButtonReadLess": "Read less",
"ButtonReadMore": "Read more",
"ButtonReadLess": "Ler menos",
"ButtonReadMore": "Ler mais",
"ButtonRefresh": "Atualizar",
"ButtonRemove": "Remover",
"ButtonRemoveAll": "Remover Todos",
@ -189,6 +189,8 @@
"HeaderYearReview": "Retrospectiva de {0} ",
"HeaderYourStats": "Suas Estatísticas",
"LabelAbridged": "Versão Abreviada",
"LabelAbridgedChecked": "Abreviada (verificada)",
"LabelAbridgedUnchecked": "Não Abreviada (não verificada)",
"LabelAccountType": "Tipo de Conta",
"LabelAccountTypeAdmin": "Administrador",
"LabelAccountTypeGuest": "Convidado",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Livros",
"LabelButtonText": "Texto do botão",
"LabelByAuthor": "por {0}",
"LabelChangePassword": "Trocar Senha",
"LabelChannels": "Canais",
"LabelChapters": "Capítulos",
@ -266,6 +269,9 @@
"LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download de {0} Episódios",
"LabelDuration": "Duração",
"LabelDurationComparisonExactMatch": "(exato)",
"LabelDurationComparisonLonger": "({0} maior)",
"LabelDurationComparisonShorter": "({0} menor)",
"LabelDurationFound": "Duração comprovada:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Tipo do Episódio",
"LabelExample": "Exemplo",
"LabelExplicit": "Explícito",
"LabelExplicitChecked": "Explícito (verificado)",
"LabelExplicitUnchecked": "Não explícito (não verificado)",
"LabelFeedURL": "URL do Feed",
"LabelFetchingMetadata": "Buscando Metadados",
"LabelFile": "Arquivo",
@ -439,7 +447,7 @@
"LabelSearchTitle": "Busca por Título",
"LabelSearchTitleOrASIN": "Busca por Título ou ASIN",
"LabelSeason": "Temporada",
"LabelSelectAll": "Select all",
"LabelSelectAll": "Selecionar todos",
"LabelSelectAllEpisodes": "Selecionar todos os Episódios",
"LabelSelectEpisodesShowing": "Selecionar os {0} Episódios Visíveis",
"LabelSelectUsers": "Selecionar usuários",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Por padrão os arquivos de metadados são armazenados em /metadata/items. Ao ativar essa configuração os arquivos de metadados serão armazenadas nas pastas dos itens na sua biblioteca",
"LabelSettingsTimeFormat": "Formato da Tempo",
"LabelShowAll": "Exibir Todos",
"LabelShowSeconds": "Exibir segundos",
"LabelSize": "Tamanho",
"LabelSleepTimer": "Timer",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Tem certeza de que deseja marcar todos os episódios como não concluídos?",
"MessageConfirmMarkSeriesFinished": "Tem certeza de que deseja marcar todos os livros nesta série como concluídos?",
"MessageConfirmMarkSeriesNotFinished": "Tem certeza de que deseja marcar todos os livros nesta série como não concluídos?",
"MessageConfirmPurgeCache": "Apagar o cache irá apagar o diretório todo localizado em <code>/metadata/cache</code>. <br /><br />Tem certeza que deseja apagar o diretório de cache?",
"MessageConfirmQuickEmbed": "Aviso! Inclusão rápida não fará backup dos seus arquivos de áudio. Verifique se tem um backup dos seus arquivos de áudio. <br><br>Quer continuar?",
"MessageConfirmRemoveAllChapters": "Tem certeza de que deseja remover todos os capítulos?",
"MessageConfirmRemoveAuthor": "Tem certeza de que deseja remover o autor \"{0}\"?",
@ -631,7 +641,7 @@
"MessageListeningSessionsInTheLastYear": "{0} sessões de escuta no ano anterior",
"MessageLoading": "Carregando...",
"MessageLoadingFolders": "Carregando pastas...",
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
"MessageLogsDescription": "Os logs estão armazenados em <code>/metadata/logs</code> como arquivos JSON. Logs de crash estão armazenados em <code>/metadata/logs/crash_logs.txt</code>.",
"MessageM4BFailed": "Falha no M4B!",
"MessageM4BFinished": "M4B Concluído!",
"MessageMapChapterTitles": "Designar títulos de capítulos a partir dos capítulos existentes no audiobook sem ajustar seus tempos",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Marcador removido",
"ToastBookmarkUpdateFailed": "Falha ao atualizar o marcador",
"ToastBookmarkUpdateSuccess": "Marcador atualizado",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Capítulos com erro",
"ToastChaptersMustHaveTitles": "Capítulos precisam ter títulos",
"ToastCollectionItemsRemoveFailed": "Falha ao remover item(ns) da coleção",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Coleção removida",
"ToastCollectionUpdateFailed": "Falha ao atualizar coleção",
"ToastCollectionUpdateSuccess": "Coleção atualizada",
"ToastDeleteFileFailed": "Falha ao apagar arquivo",
"ToastDeleteFileSuccess": "Arquivo apagado",
"ToastFailedToLoadData": "Falha ao carregar dados",
"ToastItemCoverUpdateFailed": "Falha ao atualizar capa do item",
"ToastItemCoverUpdateSuccess": "Capa do item atualizada",
"ToastItemDetailsUpdateFailed": "Falha ao atualizar detalhes do item",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook enviado para o dispositivo \"{0}\"",
"ToastSeriesUpdateFailed": "Falha ao atualizar série",
"ToastSeriesUpdateSuccess": "Série atualizada",
"ToastServerSettingsUpdateFailed": "Falha ao atualizar configurações do servidor",
"ToastServerSettingsUpdateSuccess": "Configurações do servidor atualizadas",
"ToastSessionDeleteFailed": "Falha ao apagar sessão",
"ToastSessionDeleteSuccess": "Sessão apagada",
"ToastSocketConnected": "Socket conectado",
"ToastSocketDisconnected": "Socket desconectado",
"ToastSocketFailedToConnect": "Falha na conexão do socket",
"ToastSortingPrefixesEmptyError": "É preciso ter pelo menos um prefixo de ordenação",
"ToastSortingPrefixesUpdateFailed": "Falha ao atualizar prefixos de ordenação",
"ToastSortingPrefixesUpdateSuccess": "Prefixos de ordenação atualizados ({0} item(ns))",
"ToastUserDeleteFailed": "Falha ao apagar usuário",
"ToastUserDeleteSuccess": "Usuário apagado"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Ваша статистика",
"LabelAbridged": "Сокращенное издание",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Тип учетной записи",
"LabelAccountTypeAdmin": "Администратор",
"LabelAccountTypeGuest": "Гость",
@ -229,6 +231,7 @@
"LabelBitrate": "Битрейт",
"LabelBooks": "Книги",
"LabelButtonText": "Текст кнопки",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Изменить пароль",
"LabelChannels": "Каналы",
"LabelChapters": "Главы",
@ -266,6 +269,9 @@
"LabelDownload": "Скачать",
"LabelDownloadNEpisodes": "Скачать {0} эпизодов",
"LabelDuration": "Длина",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Найденная длина:",
"LabelEbook": "E-книга",
"LabelEbooks": "E-книги",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Тип эпизода",
"LabelExample": "Пример",
"LabelExplicit": "Явный",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "URL канала",
"LabelFetchingMetadata": "Извлечение метаданных",
"LabelFile": "Файл",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "По умолчанию метаинформация сохраняется в папке /metadata/items, при включении этой настройки метаинформация будет храниться в папке элемента",
"LabelSettingsTimeFormat": "Формат времени",
"LabelShowAll": "Показать все",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Размер",
"LabelSleepTimer": "Таймер сна",
"LabelSlug": "Слизень",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Вы уверены, что хотите отметить все эпизоды как не завершенные?",
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как завершенные?",
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как не завершенные?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Предупреждение! Быстрое встраивание не позволяет создавать резервные копии аудиофайлов. Убедитесь, что у вас есть резервная копия аудиофайлов. <br><br>Хотите продолжить?",
"MessageConfirmRemoveAllChapters": "Вы уверены, что хотите удалить все главы?",
"MessageConfirmRemoveAuthor": "Вы уверены, что хотите удалить автора \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Закладка удалена",
"ToastBookmarkUpdateFailed": "Не удалось обновить закладку",
"ToastBookmarkUpdateSuccess": "Закладка обновлена",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Главы имеют ошибки",
"ToastChaptersMustHaveTitles": "Главы должны содержать названия",
"ToastCollectionItemsRemoveFailed": "Не удалось удалить элемент(ы) из коллекции",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Коллекция удалена",
"ToastCollectionUpdateFailed": "Не удалось обновить коллекцию",
"ToastCollectionUpdateSuccess": "Коллекция обновлена",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Не удалось обновить обложку элемента",
"ToastItemCoverUpdateSuccess": "Обложка элемента обновлена",
"ToastItemDetailsUpdateFailed": "Не удалось обновить сведения об элементе",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-книга отправлена на устройство \"{0}\"",
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Не удалось удалить сеанс",
"ToastSessionDeleteSuccess": "Сеанс удален",
"ToastSocketConnected": "Сокет подключен",
"ToastSocketDisconnected": "Сокет отключен",
"ToastSocketFailedToConnect": "Не удалось подключить сокет",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Не удалось удалить пользователя",
"ToastUserDeleteSuccess": "Пользователь удален"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Dina statistik",
"LabelAbridged": "Förkortad",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Kontotyp",
"LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Gäst",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitfrekvens",
"LabelBooks": "Böcker",
"LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Ändra lösenord",
"LabelChannels": "Kanaler",
"LabelChapters": "Kapitel",
@ -266,6 +269,9 @@
"LabelDownload": "Ladda ner",
"LabelDownloadNEpisodes": "Ladda ner {0} avsnitt",
"LabelDuration": "Varaktighet",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Varaktighet hittad:",
"LabelEbook": "E-bok",
"LabelEbooks": "E-böcker",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Avsnittstyp",
"LabelExample": "Exempel",
"LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Flödes-URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Fil",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Som standard lagras metadatafiler i /metadata/items, att aktivera detta alternativ kommer att lagra metadatafiler i dina biblioteksmappar",
"LabelSettingsTimeFormat": "Tidsformat",
"LabelShowAll": "Visa alla",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Storlek",
"LabelSleepTimer": "Sleeptimer",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Är du säker på att du vill markera alla avsnitt som inte avslutade?",
"MessageConfirmMarkSeriesFinished": "Är du säker på att du vill markera alla böcker i denna serie som avslutade?",
"MessageConfirmMarkSeriesNotFinished": "Är du säker på att du vill markera alla böcker i denna serie som inte avslutade?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Varning! Quick embed kommer inte att säkerhetskopiera dina ljudfiler. Se till att du har en säkerhetskopia av dina ljudfiler. <br><br>Vill du fortsätta?",
"MessageConfirmRemoveAllChapters": "Är du säker på att du vill ta bort alla kapitel?",
"MessageConfirmRemoveAuthor": "Är du säker på att du vill ta bort författaren \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Bokmärket borttaget",
"ToastBookmarkUpdateFailed": "Det gick inte att uppdatera bokmärket",
"ToastBookmarkUpdateSuccess": "Bokmärket uppdaterat",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Kapitlen har fel",
"ToastChaptersMustHaveTitles": "Kapitel måste ha titlar",
"ToastCollectionItemsRemoveFailed": "Det gick inte att ta bort objekt från samlingen",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Samlingen borttagen",
"ToastCollectionUpdateFailed": "Det gick inte att uppdatera samlingen",
"ToastCollectionUpdateSuccess": "Samlingen uppdaterad",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Det gick inte att uppdatera objektets omslag",
"ToastItemCoverUpdateSuccess": "Objektets omslag uppdaterat",
"ToastItemDetailsUpdateFailed": "Det gick inte att uppdatera objektdetaljerna",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-boken skickad till enheten \"{0}\"",
"ToastSeriesUpdateFailed": "Serieuppdateringen misslyckades",
"ToastSeriesUpdateSuccess": "Serieuppdateringen lyckades",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Misslyckades med att ta bort sessionen",
"ToastSessionDeleteSuccess": "Sessionen borttagen",
"ToastSocketConnected": "Socket ansluten",
"ToastSocketDisconnected": "Socket frånkopplad",
"ToastSocketFailedToConnect": "Socket misslyckades med att ansluta",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Misslyckades med att ta bort användaren",
"ToastUserDeleteSuccess": "Användaren borttagen"
}

View file

@ -18,7 +18,7 @@
"ButtonChooseFiles": "Обрати файли",
"ButtonClearFilter": "Очистити фільтр",
"ButtonCloseFeed": "Закрити стрічку",
"ButtonCollections": "Колекції",
"ButtonCollections": "Добірки",
"ButtonConfigureScanner": "Налаштувати сканер",
"ButtonCreate": "Створити",
"ButtonCreateBackup": "Створити резервну копію",
@ -61,8 +61,8 @@
"ButtonQueueRemoveItem": "Вилучити з черги",
"ButtonQuickMatch": "Швидкий пошук",
"ButtonRead": "Читати",
"ButtonReadLess": "Read less",
"ButtonReadMore": "Read more",
"ButtonReadLess": "Згорнути",
"ButtonReadMore": "Читати далі",
"ButtonRefresh": "Оновити",
"ButtonRemove": "Видалити",
"ButtonRemoveAll": "Видалити все",
@ -111,8 +111,8 @@
"HeaderChangePassword": "Змінити пароль",
"HeaderChapters": "Глави",
"HeaderChooseAFolder": "Обрати теку",
"HeaderCollection": "Колекція",
"HeaderCollectionItems": "Елементи колекції",
"HeaderCollection": "Добірка",
"HeaderCollectionItems": "Елементи добірки",
"HeaderCover": "Обкладинка",
"HeaderCurrentDownloads": "Поточні завантаження",
"HeaderCustomMetadataProviders": "Постачальники метаданих",
@ -189,6 +189,8 @@
"HeaderYearReview": "Підсумки {0} року",
"HeaderYourStats": "Ваша статистика",
"LabelAbridged": "Скорочена",
"LabelAbridgedChecked": "Скорочена (з прапорцем)",
"LabelAbridgedUnchecked": "Нескорочена (без прапорця)",
"LabelAccountType": "Тип профілю",
"LabelAccountTypeAdmin": "Адміністратор",
"LabelAccountTypeGuest": "Гість",
@ -229,6 +231,7 @@
"LabelBitrate": "Бітрейт",
"LabelBooks": "Книги",
"LabelButtonText": "Текст кнопки",
"LabelByAuthor": "від {0}",
"LabelChangePassword": "Змінити пароль",
"LabelChannels": "Канали",
"LabelChapters": "Глави",
@ -251,7 +254,7 @@
"LabelCronExpression": "Команда cron",
"LabelCurrent": "Поточне",
"LabelCurrently": "Поточний:",
"LabelCustomCronExpression": "Спецільна команда cron:",
"LabelCustomCronExpression": "Спеціальна команда cron:",
"LabelDatetime": "Дата й час",
"LabelDeleteFromFileSystemCheckbox": "Видалити з файлової системи (зніміть прапорець, щоб видалити лише з бази даних)",
"LabelDescription": "Опис",
@ -266,6 +269,9 @@
"LabelDownload": "Завантажити",
"LabelDownloadNEpisodes": "Завантажити епізодів: {0}",
"LabelDuration": "Тривалість",
"LabelDurationComparisonExactMatch": "(повний збіг)",
"LabelDurationComparisonLonger": "(на {0} довше)",
"LabelDurationComparisonShorter": "(на {0} коротше)",
"LabelDurationFound": "Виявлена тривалість:",
"LabelEbook": "Електронна книга",
"LabelEbooks": "Електронні книги",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Тип епізоду",
"LabelExample": "Приклад",
"LabelExplicit": "Відверта",
"LabelExplicitChecked": "Відверта (з прапорцем)",
"LabelExplicitUnchecked": "Не відверта (без прапорця)",
"LabelFeedURL": "Адреса стрічки",
"LabelFetchingMetadata": "Отримання метаданих",
"LabelFile": "Файл",
@ -387,9 +395,9 @@
"LabelNotStarted": "Не розпочато",
"LabelNumberOfBooks": "Кількість книг",
"LabelNumberOfEpisodes": "Кількість епізодів",
"LabelOpenIDAdvancedPermsClaimDescription": "Name of the OpenID claim that contains advanced permissions for user actions within the application which will apply to non-admin roles (<b>if configured</b>). If the claim is missing from the response, access to ABS will be denied. If a single option is missing, it will be treated as <code>false</code>. Ensure the identity provider's claim matches the expected structure:",
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
"LabelOpenIDGroupClaimDescription": "Name of the OpenID claim that contains a list of the user's groups. Commonly referred to as <code>groups</code>. <b>If configured</b>, the application will automatically assign roles based on the user's group memberships, provided that these groups are named case-insensitively 'admin', 'user', or 'guest' in the claim. The claim should contain a list, and if a user belongs to multiple groups, the application will assign the role corresponding to the highest level of access. If no group matches, access will be denied.",
"LabelOpenIDAdvancedPermsClaimDescription": "Назва OpenID claim, що містить розширені дозволи на дії користувачів у додатку, які будуть застосовуватися до ролей, що не є адміністраторами (<b>якщо налаштовано</b>). Якщо у відповіді нема claim, у доступі до Audiobookshelf буде відмовлено. Якщо відсутня хоча б одна опція, відповідь буде вважатися <code>хибною</code>. Переконайтеся, що запит постачальника ідентифікаційних даних відповідає очікуваній структурі:",
"LabelOpenIDClaims": "Не змінюйте наступні параметри, аби вимкнути розширене призначення груп і дозволів, автоматично призначаючи групу 'Користувач'.",
"LabelOpenIDGroupClaimDescription": "Ім'я OpenID claim, що містить список груп користувачів. Зазвичай їх називають <code>групами</code>. <b>Якщо налаштовано</b>, застосунок автоматично призначатиме ролі на основі членства користувача в групах, за умови, що ці групи названі в claim'і без урахування реєстру 'admin', 'user' або 'guest'. Claim мусить містити список, і якщо користувач належить до кількох груп, програма призначить йому роль, що відповідає найвищому рівню доступу. Якщо жодна група не збігається, у доступі буде відмовлено.",
"LabelOpenRSSFeed": "Відкрити RSS-канал",
"LabelOverwrite": "Перезаписати",
"LabelPassword": "Пароль",
@ -439,7 +447,7 @@
"LabelSearchTitle": "Пошук за назвою",
"LabelSearchTitleOrASIN": "Пошук назви або ASIN",
"LabelSeason": "Сезон",
"LabelSelectAll": "Select all",
"LabelSelectAll": "Вибрати все",
"LabelSelectAllEpisodes": "Вибрати всі серії",
"LabelSelectEpisodesShowing": "Обрати показані епізоди: {0}",
"LabelSelectUsers": "Вибрати користувачів",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "За замовчуванням файли метаданих зберігаються у /metadata/items. Цей параметр увімкне збереження метаданих у теці елемента бібліотеки",
"LabelSettingsTimeFormat": "Формат часу",
"LabelShowAll": "Показати все",
"LabelShowSeconds": "Показувати секунди",
"LabelSize": "Розмір",
"LabelSleepTimer": "Таймер вимкнення",
"LabelSlug": "Назва",
@ -527,7 +536,7 @@
"LabelTimeBase": "Шкала часу",
"LabelTimeListened": "Часу прослухано",
"LabelTimeListenedToday": "Сьогодні прослухано",
"LabelTimeRemaining": "Залишилося: {0}",
"LabelTimeRemaining": "Лишилося: {0}",
"LabelTimeToShift": "На скільки секунд зсунути",
"LabelTitle": "Назва",
"LabelToolsEmbedMetadata": "Вбудувати метадані",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Ви дійсно бажаєте позначити усі епізоди незавершеними?",
"MessageConfirmMarkSeriesFinished": "Ви дійсно бажаєте позначити усі книги серії завершеними?",
"MessageConfirmMarkSeriesNotFinished": "Ви дійсно бажаєте позначити всі книги серії незавершеними?",
"MessageConfirmPurgeCache": "Очищення кешу видалить усю теку <code>/metadata/cache</code>. <br /><br />Ви дійсно бажаєте видалити теку кешу?",
"MessageConfirmQuickEmbed": "Увага! Швидке вбудування не створює резервних копій ваших аудіо. Переконайтеся, що маєте копію ваших файлів.<br><br>Продовжити?",
"MessageConfirmRemoveAllChapters": "Ви дійсно бажаєте видалити усі глави?",
"MessageConfirmRemoveAuthor": "Ви дійсно бажаєте видалити автора \"{0}\"?",
@ -631,7 +641,7 @@
"MessageListeningSessionsInTheLastYear": "Сесій прослуховування минулого року: {0}",
"MessageLoading": "Завантаження...",
"MessageLoadingFolders": "Завантаження тек...",
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
"MessageLogsDescription": "Журнали зберігаються у <code>/metadata/logs</code> як JSON-файли. Журнали збоїв зберігаються у <code>/metadata/logs/crash_logs.txt</code>.",
"MessageM4BFailed": "Помилка M4B!",
"MessageM4BFinished": "M4B створено!",
"MessageMapChapterTitles": "Встановіть назви глав вашої аудіокниги без визначення налаштувань тривалості",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Закладку видалено",
"ToastBookmarkUpdateFailed": "Не вдалося оновити закладку",
"ToastBookmarkUpdateSuccess": "Закладку оновлено",
"ToastCachePurgeFailed": "Не вдалося очистити кеш",
"ToastCachePurgeSuccess": "Кеш очищено",
"ToastChaptersHaveErrors": "Глави містять помилки",
"ToastChaptersMustHaveTitles": "Глави повинні мати назви",
"ToastCollectionItemsRemoveFailed": "Не вдалося видалити елемент(и) з добірки",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Добірку видалено",
"ToastCollectionUpdateFailed": "Не вдалося оновити добірку",
"ToastCollectionUpdateSuccess": "Добірку оновлено",
"ToastDeleteFileFailed": "Не вдалося видалити файл",
"ToastDeleteFileSuccess": "Файл видалено",
"ToastFailedToLoadData": "Не вдалося завантажити дані",
"ToastItemCoverUpdateFailed": "Не вдалося оновити обкладинку",
"ToastItemCoverUpdateSuccess": "Обкладинку елемента оновлено",
"ToastItemDetailsUpdateFailed": "Не вдалося оновити подробиці елемента",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Електронну книгу надіслано на пристрій \"{0}\"",
"ToastSeriesUpdateFailed": "Не вдалося оновити серію",
"ToastSeriesUpdateSuccess": "Серію успішно оновлено",
"ToastServerSettingsUpdateFailed": "Не вдалося оновити налаштування сервера",
"ToastServerSettingsUpdateSuccess": "Налаштування сервера оновлено",
"ToastSessionDeleteFailed": "Не вдалося видалити сесію",
"ToastSessionDeleteSuccess": "Сесію видалено",
"ToastSocketConnected": "Сокет під'єднано",
"ToastSocketDisconnected": "Сокет від'єднано",
"ToastSocketFailedToConnect": "Не вдалося під'єднатися до сокета",
"ToastSortingPrefixesEmptyError": "Мусить мати хоча б 1 префікс сортування",
"ToastSortingPrefixesUpdateFailed": "Не вдалося оновити префікси сортування",
"ToastSortingPrefixesUpdateSuccess": "Префікси сортування оновлено ({0})",
"ToastUserDeleteFailed": "Не вдалося видалити користувача",
"ToastUserDeleteSuccess": "Користувача видалено"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Năm {0} trong Xem Xét",
"HeaderYourStats": "Thống Kê Của Bạn",
"LabelAbridged": "Rút Gọn",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Loại Tài Khoản",
"LabelAccountTypeAdmin": "Quản Trị Viên",
"LabelAccountTypeGuest": "Khách",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate",
"LabelBooks": "Sách",
"LabelButtonText": "Nút Văn Bản",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Đổi Mật Khẩu",
"LabelChannels": "Kênh",
"LabelChapters": "Chương",
@ -266,6 +269,9 @@
"LabelDownload": "Tải Xuống",
"LabelDownloadNEpisodes": "Tải Xuống {0} Tập",
"LabelDuration": "Thời Lượng",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Thời lượng được tìm thấy:",
"LabelEbook": "Ebook",
"LabelEbooks": "Các Ebook",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Loại Tập",
"LabelExample": "Ví Dụ",
"LabelExplicit": "Rõ Ràng",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "URL Feed",
"LabelFetchingMetadata": "Đang Lấy Metadata",
"LabelFile": "Tệp",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Theo mặc định, các tệp siêu dữ liệu được lưu trữ trong /metadata/items, bật cài đặt này sẽ lưu trữ các tệp siêu dữ liệu trong các thư mục mục của thư viện bạn",
"LabelSettingsTimeFormat": "Định dạng Thời gian",
"LabelShowAll": "Hiển thị Tất cả",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Kích thước",
"LabelSleepTimer": "Hẹn giờ tắt",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Bạn có chắc chắn muốn đánh dấu tất cả các tập phim chưa kết thúc không?",
"MessageConfirmMarkSeriesFinished": "Bạn có chắc chắn muốn đánh dấu tất cả các sách trong loạt sách này đã kết thúc không?",
"MessageConfirmMarkSeriesNotFinished": "Bạn có chắc chắn muốn đánh dấu tất cả các sách trong loạt sách này chưa kết thúc không?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "Cảnh báo! Quick embed sẽ không sao lưu các tệp âm thanh của bạn. Đảm bảo bạn có một bản sao lưu của các tệp âm thanh của bạn. <br><br>Bạn có muốn tiếp tục không?",
"MessageConfirmRemoveAllChapters": "Bạn có chắc chắn muốn xóa tất cả các chương không?",
"MessageConfirmRemoveAuthor": "Bạn có chắc chắn muốn xóa tác giả \"{0}\" không?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Đánh dấu đã được xóa",
"ToastBookmarkUpdateFailed": "Cập nhật đánh dấu thất bại",
"ToastBookmarkUpdateSuccess": "Đánh dấu đã được cập nhật",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Các chương có lỗi",
"ToastChaptersMustHaveTitles": "Các chương phải có tiêu đề",
"ToastCollectionItemsRemoveFailed": "Xóa mục từ bộ sưu tập thất bại",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Bộ sưu tập đã được xóa",
"ToastCollectionUpdateFailed": "Cập nhật bộ sưu tập thất bại",
"ToastCollectionUpdateSuccess": "Bộ sưu tập đã được cập nhật",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Cập nhật ảnh bìa mục thất bại",
"ToastItemCoverUpdateSuccess": "Ảnh bìa mục đã được cập nhật",
"ToastItemDetailsUpdateFailed": "Cập nhật chi tiết mục thất bại",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook đã được gửi đến thiết bị \"{0}\"",
"ToastSeriesUpdateFailed": "Cập nhật loạt truyện thất bại",
"ToastSeriesUpdateSuccess": "Cập nhật loạt truyện thành công",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Xóa phiên thất bại",
"ToastSessionDeleteSuccess": "Phiên đã được xóa",
"ToastSocketConnected": "Kết nối socket",
"ToastSocketDisconnected": "Ngắt kết nối socket",
"ToastSocketFailedToConnect": "Không thể kết nối socket",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "Xóa người dùng thất bại",
"ToastUserDeleteSuccess": "Người dùng đã được xóa"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "{0} 年回顾",
"HeaderYourStats": "你的统计数据",
"LabelAbridged": "概要",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "帐户类型",
"LabelAccountTypeAdmin": "管理员",
"LabelAccountTypeGuest": "来宾",
@ -229,6 +231,7 @@
"LabelBitrate": "比特率",
"LabelBooks": "图书",
"LabelButtonText": "按钮文本",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "修改密码",
"LabelChannels": "声道",
"LabelChapters": "章节",
@ -266,6 +269,9 @@
"LabelDownload": "下载",
"LabelDownloadNEpisodes": "下载 {0} 集",
"LabelDuration": "持续时间",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "找到持续时间:",
"LabelEbook": "电子书",
"LabelEbooks": "电子书",
@ -283,6 +289,8 @@
"LabelEpisodeType": "剧集类型",
"LabelExample": "示例",
"LabelExplicit": "信息准确",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "源 URL",
"LabelFetchingMetadata": "正在获取元数据",
"LabelFile": "文件",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "默认情况下元数据文件存储在/metadata/items文件夹中, 启用此设置将存储元数据在你媒体项目文件夹中",
"LabelSettingsTimeFormat": "时间格式",
"LabelShowAll": "全部显示",
"LabelShowSeconds": "Show seconds",
"LabelSize": "文件大小",
"LabelSleepTimer": "睡眠定时",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "你确定要将所有剧集都标记为未完成吗?",
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "警告! 快速嵌入不会备份你的音频文件. 确保你有音频文件的备份. <br><br>你是否想继续吗?",
"MessageConfirmRemoveAllChapters": "你确定要移除所有章节吗?",
"MessageConfirmRemoveAuthor": "你确定要删除作者 \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "书签已删除",
"ToastBookmarkUpdateFailed": "书签更新失败",
"ToastBookmarkUpdateSuccess": "书签已更新",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "章节有错误",
"ToastChaptersMustHaveTitles": "章节必须有标题",
"ToastCollectionItemsRemoveFailed": "从收藏夹移除项目失败",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "收藏夹已删除",
"ToastCollectionUpdateFailed": "更新收藏夹失败",
"ToastCollectionUpdateSuccess": "收藏夹已更新",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "更新项目封面失败",
"ToastItemCoverUpdateSuccess": "项目封面已更新",
"ToastItemDetailsUpdateFailed": "更新项目详细信息失败",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "电子书已经发送到设备 \"{0}\"",
"ToastSeriesUpdateFailed": "更新系列失败",
"ToastSeriesUpdateSuccess": "系列已更新",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "删除会话失败",
"ToastSessionDeleteSuccess": "会话已删除",
"ToastSocketConnected": "网络已连接",
"ToastSocketDisconnected": "网络已断开",
"ToastSocketFailedToConnect": "网络连接失败",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "删除用户失败",
"ToastUserDeleteSuccess": "用户已删除"
}

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "你的統計數據",
"LabelAbridged": "概要",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "帳號類型",
"LabelAccountTypeAdmin": "管理員",
"LabelAccountTypeGuest": "來賓",
@ -229,6 +231,7 @@
"LabelBitrate": "位元率",
"LabelBooks": "圖書",
"LabelButtonText": "按鈕文本",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "修改密碼",
"LabelChannels": "聲道",
"LabelChapters": "章節",
@ -266,6 +269,9 @@
"LabelDownload": "下載",
"LabelDownloadNEpisodes": "下載 {0} 集",
"LabelDuration": "持續時間",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "找到持續時間:",
"LabelEbook": "電子書",
"LabelEbooks": "電子書",
@ -283,6 +289,8 @@
"LabelEpisodeType": "劇集類型",
"LabelExample": "示例",
"LabelExplicit": "信息準確",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "源 URL",
"LabelFetchingMetadata": "正在獲取元數據",
"LabelFile": "檔案",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "預設情況下元數據檔案存儲在/metadata/items資料夾中, 啟用此設定將存儲元數據在你媒體項目資料夾中",
"LabelSettingsTimeFormat": "時間格式",
"LabelShowAll": "全部顯示",
"LabelShowSeconds": "Show seconds",
"LabelSize": "檔案大小",
"LabelSleepTimer": "睡眠定時",
"LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "你確定要將所有劇集都標記為未完成嗎?",
"MessageConfirmMarkSeriesFinished": "你確定要將此系列中的所有書籍都標記為已聽完嗎?",
"MessageConfirmMarkSeriesNotFinished": "你確定要將此系列中的所有書籍都標記為未聽完嗎?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmQuickEmbed": "警告! 快速嵌入不會備份你的音頻檔案. 確保你有音頻檔案的備份. <br><br>你是否想繼續嗎?",
"MessageConfirmRemoveAllChapters": "你確定要移除所有章節嗎?",
"MessageConfirmRemoveAuthor": "你確定要刪除作者 \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "書籤已刪除",
"ToastBookmarkUpdateFailed": "書籤更新失敗",
"ToastBookmarkUpdateSuccess": "書籤已更新",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "章節有錯誤",
"ToastChaptersMustHaveTitles": "章節必須有標題",
"ToastCollectionItemsRemoveFailed": "從收藏夾移除項目失敗",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "收藏夾已刪除",
"ToastCollectionUpdateFailed": "更新收藏夾失敗",
"ToastCollectionUpdateSuccess": "收藏夾已更新",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "更新項目封面失敗",
"ToastItemCoverUpdateSuccess": "項目封面已更新",
"ToastItemDetailsUpdateFailed": "更新項目詳細信息失敗",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "電子書已經發送到設備 \"{0}\"",
"ToastSeriesUpdateFailed": "更新系列失敗",
"ToastSeriesUpdateSuccess": "系列已更新",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "刪除會話失敗",
"ToastSessionDeleteSuccess": "會話已刪除",
"ToastSocketConnected": "網路已連接",
"ToastSocketDisconnected": "網路已斷開",
"ToastSocketFailedToConnect": "網路連接失敗",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastUserDeleteFailed": "刪除使用者失敗",
"ToastUserDeleteSuccess": "使用者已刪除"
}

View file

@ -23,7 +23,7 @@ const libraryItemsPodcastFilters = require('../utils/queries/libraryItemsPodcast
const authorFilters = require('../utils/queries/authorFilters')
class LibraryController {
constructor() { }
constructor() {}
async create(req, res) {
const newLibraryPayload = {
@ -35,7 +35,7 @@ class LibraryController {
// Validate that the custom provider exists if given any
if (newLibraryPayload.provider?.startsWith('custom-')) {
if (!await Database.customMetadataProviderModel.checkExistsBySlug(newLibraryPayload.provider)) {
if (!(await Database.customMetadataProviderModel.checkExistsBySlug(newLibraryPayload.provider))) {
Logger.error(`[LibraryController] Custom metadata provider "${newLibraryPayload.provider}" does not exist`)
return res.status(400).send('Custom metadata provider does not exist')
}
@ -43,14 +43,15 @@ class LibraryController {
// Validate folder paths exist or can be created & resolve rel paths
// returns 400 if a folder fails to access
newLibraryPayload.folders = newLibraryPayload.folders.map(f => {
newLibraryPayload.folders = newLibraryPayload.folders.map((f) => {
f.fullPath = fileUtils.filePathToPOSIX(Path.resolve(f.fullPath))
return f
})
for (const folder of newLibraryPayload.folders) {
try {
const direxists = await fs.pathExists(folder.fullPath)
if (!direxists) { // If folder does not exist try to make it and set file permissions/owner
if (!direxists) {
// If folder does not exist try to make it and set file permissions/owner
await fs.mkdir(folder.fullPath)
}
} catch (error) {
@ -85,12 +86,12 @@ class LibraryController {
const librariesAccessible = req.user.librariesAccessible || []
if (librariesAccessible.length) {
return res.json({
libraries: libraries.filter(lib => librariesAccessible.includes(lib.id)).map(lib => lib.toJSON())
libraries: libraries.filter((lib) => librariesAccessible.includes(lib.id)).map((lib) => lib.toJSON())
})
}
res.json({
libraries: libraries.map(lib => lib.toJSON())
libraries: libraries.map((lib) => lib.toJSON())
})
}
@ -140,7 +141,7 @@ class LibraryController {
// Validate that the custom provider exists if given any
if (req.body.provider?.startsWith('custom-')) {
if (!await Database.customMetadataProviderModel.checkExistsBySlug(req.body.provider)) {
if (!(await Database.customMetadataProviderModel.checkExistsBySlug(req.body.provider))) {
Logger.error(`[LibraryController] Custom metadata provider "${req.body.provider}" does not exist`)
return res.status(400).send('Custom metadata provider does not exist')
}
@ -150,7 +151,7 @@ class LibraryController {
// returns 400 if a new folder fails to access
if (req.body.folders) {
const newFolderPaths = []
req.body.folders = req.body.folders.map(f => {
req.body.folders = req.body.folders.map((f) => {
if (!f.id) {
f.fullPath = fileUtils.filePathToPOSIX(Path.resolve(f.fullPath))
newFolderPaths.push(f.fullPath)
@ -161,10 +162,13 @@ class LibraryController {
const pathExists = await fs.pathExists(path)
if (!pathExists) {
// Ensure dir will recursively create directories which might be preferred over mkdir
const success = await fs.ensureDir(path).then(() => true).catch((error) => {
Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error)
return false
})
const success = await fs
.ensureDir(path)
.then(() => true)
.catch((error) => {
Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error)
return false
})
if (!success) {
return res.status(400).send(`Invalid folder directory "${path}"`)
}
@ -173,7 +177,7 @@ class LibraryController {
// Handle removing folders
for (const folder of library.folders) {
if (!req.body.folders.some(f => f.id === folder.id)) {
if (!req.body.folders.some((f) => f.id === folder.id)) {
// Remove library items in folder
const libraryItemsInFolder = await Database.libraryItemModel.findAll({
where: {
@ -195,7 +199,7 @@ class LibraryController {
for (const libraryItem of libraryItemsInFolder) {
let mediaItemIds = []
if (library.isPodcast) {
mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id)
mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id)
} else {
mediaItemIds.push(libraryItem.mediaId)
}
@ -267,7 +271,7 @@ class LibraryController {
for (const libraryItem of libraryItemsInLibrary) {
let mediaItemIds = []
if (library.isPodcast) {
mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id)
mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id)
} else {
mediaItemIds.push(libraryItem.mediaId)
}
@ -298,7 +302,10 @@ class LibraryController {
* @param {import('express').Response} res
*/
async getLibraryItems(req, res) {
const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
const include = (req.query.include || '')
.split(',')
.map((v) => v.trim().toLowerCase())
.filter((v) => !!v)
const payload = {
results: [],
@ -316,7 +323,9 @@ class LibraryController {
payload.offset = payload.page * payload.limit
// TODO: Temporary way of handling collapse sub-series. Either remove feature or handle through sql queries
if (payload.filterBy?.split('.')[0] === 'series' && payload.collapseseries) {
const filterByGroup = payload.filterBy?.split('.').shift()
const filterByValue = filterByGroup ? libraryFilters.decode(payload.filterBy.replace(`${filterByGroup}.`, '')) : null
if (filterByGroup === 'series' && filterByValue !== 'no-series' && payload.collapseseries) {
const seriesId = libraryFilters.decode(payload.filterBy.split('.')[1])
payload.results = await libraryHelpers.handleCollapseSubseries(payload, seriesId, req.user, req.library)
} else {
@ -369,7 +378,7 @@ class LibraryController {
for (const libraryItem of libraryItemsWithIssues) {
let mediaItemIds = []
if (req.library.isPodcast) {
mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id)
mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id)
} else {
mediaItemIds.push(libraryItem.mediaId)
}
@ -386,14 +395,17 @@ class LibraryController {
}
/**
* GET: /api/libraries/:id/series
* Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
* GET: /api/libraries/:id/series
* Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async getAllSeriesForLibrary(req, res) {
const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
const include = (req.query.include || '')
.split(',')
.map((v) => v.trim().toLowerCase())
.filter((v) => !!v)
const payload = {
results: [],
@ -426,7 +438,10 @@ class LibraryController {
* @param {import('express').Response} res - Series
*/
async getSeriesForLibrary(req, res) {
const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
const include = (req.query.include || '')
.split(',')
.map((v) => v.trim().toLowerCase())
.filter((v) => !!v)
const series = await Database.seriesModel.findByPk(req.params.seriesId)
if (!series) return res.sendStatus(404)
@ -436,10 +451,10 @@ class LibraryController {
const seriesJson = oldSeries.toJSON()
if (include.includes('progress')) {
const libraryItemsFinished = libraryItemsInSeries.filter(li => !!req.user.getMediaProgress(li.id)?.isFinished)
const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.id)?.isFinished)
seriesJson.progress = {
libraryItemIds: libraryItemsInSeries.map(li => li.id),
libraryItemIdsFinished: libraryItemsFinished.map(li => li.id),
libraryItemIds: libraryItemsInSeries.map((li) => li.id),
libraryItemIdsFinished: libraryItemsFinished.map((li) => li.id),
isFinished: libraryItemsFinished.length >= libraryItemsInSeries.length
}
}
@ -459,7 +474,10 @@ class LibraryController {
* @param {*} res
*/
async getCollectionsForLibrary(req, res) {
const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
const include = (req.query.include || '')
.split(',')
.map((v) => v.trim().toLowerCase())
.filter((v) => !!v)
const payload = {
results: [],
@ -495,7 +513,7 @@ class LibraryController {
*/
async getUserPlaylistsForLibrary(req, res) {
let playlistsForUser = await Database.playlistModel.getPlaylistsForUserAndLibrary(req.user.id, req.library.id)
playlistsForUser = await Promise.all(playlistsForUser.map(async p => p.getOldJsonExpanded()))
playlistsForUser = await Promise.all(playlistsForUser.map(async (p) => p.getOldJsonExpanded()))
const payload = {
results: [],
@ -531,7 +549,10 @@ class LibraryController {
*/
async getUserPersonalizedShelves(req, res) {
const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10
const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
const include = (req.query.include || '')
.split(',')
.map((v) => v.trim().toLowerCase())
.filter((v) => !!v)
const shelves = await Database.libraryItemModel.getPersonalizedShelves(req.library, req.user, include, limitPerShelf)
res.json(shelves)
}
@ -552,7 +573,7 @@ class LibraryController {
const orderdata = req.body
let hasUpdates = false
for (let i = 0; i < orderdata.length; i++) {
const library = libraries.find(lib => lib.id === orderdata[i].id)
const library = libraries.find((lib) => lib.id === orderdata[i].id)
if (!library) {
Logger.error(`[LibraryController] Invalid library not found in reorder ${orderdata[i].id}`)
return res.sendStatus(500)
@ -571,7 +592,7 @@ class LibraryController {
}
res.json({
libraries: libraries.map(lib => lib.toJSON())
libraries: libraries.map((lib) => lib.toJSON())
})
}
@ -657,9 +678,7 @@ class LibraryController {
attributes: []
}
},
order: [
[Sequelize.literal('name COLLATE NOCASE'), 'ASC']
]
order: [[Sequelize.literal('name COLLATE NOCASE'), 'ASC']]
})
const oldAuthors = []
@ -699,7 +718,7 @@ class LibraryController {
const narrators = {}
for (const book of booksWithNarrators) {
book.narrators.forEach(n => {
book.narrators.forEach((n) => {
if (typeof n !== 'string') {
Logger.error(`[LibraryController] getNarrators: Invalid narrator "${n}" on book "${book.title}"`)
} else if (!narrators[n]) {
@ -715,7 +734,7 @@ class LibraryController {
}
res.json({
narrators: naturalSort(Object.values(narrators)).asc(n => n.name)
narrators: naturalSort(Object.values(narrators)).asc((n) => n.name)
})
}
@ -747,7 +766,7 @@ class LibraryController {
const itemsWithNarrator = await libraryItemFilters.getAllLibraryItemsWithNarrators([narratorName])
for (const libraryItem of itemsWithNarrator) {
libraryItem.media.narrators = libraryItem.media.narrators.filter(n => n !== narratorName)
libraryItem.media.narrators = libraryItem.media.narrators.filter((n) => n !== narratorName)
if (!libraryItem.media.narrators.includes(updatedName)) {
libraryItem.media.narrators.push(updatedName)
}
@ -759,7 +778,10 @@ class LibraryController {
}
if (itemsUpdated.length) {
SocketAuthority.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded()))
SocketAuthority.emitter(
'items_updated',
itemsUpdated.map((li) => li.toJSONExpanded())
)
}
res.json({
@ -790,7 +812,7 @@ class LibraryController {
const itemsWithNarrator = await libraryItemFilters.getAllLibraryItemsWithNarrators([narratorName])
for (const libraryItem of itemsWithNarrator) {
libraryItem.media.narrators = libraryItem.media.narrators.filter(n => n !== narratorName)
libraryItem.media.narrators = libraryItem.media.narrators.filter((n) => n !== narratorName)
await libraryItem.media.update({
narrators: libraryItem.media.narrators
})
@ -799,7 +821,10 @@ class LibraryController {
}
if (itemsUpdated.length) {
SocketAuthority.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded()))
SocketAuthority.emitter(
'items_updated',
itemsUpdated.map((li) => li.toJSONExpanded())
)
}
res.json({
@ -859,7 +884,7 @@ class LibraryController {
const payload = {
episodes: [],
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0
}
const offset = payload.page * payload.limit
@ -929,10 +954,10 @@ class LibraryController {
let numRemoved = 0
for (const libraryItem of libraryItemsWithMetadata) {
const metadataFilepath = libraryItem.libraryFiles.find(lf => lf.metadata.filename === metadataFilename)?.metadata.path
const metadataFilepath = libraryItem.libraryFiles.find((lf) => lf.metadata.filename === metadataFilename)?.metadata.path
if (!metadataFilepath) continue
Logger.debug(`[LibraryController] Removing file "${metadataFilepath}"`)
if ((await fileUtils.removeFile(metadataFilepath))) {
if (await fileUtils.removeFile(metadataFilepath)) {
numRemoved++
}
}

View file

@ -32,7 +32,7 @@ class PodcastManager {
}
getEpisodeDownloadsInQueue(libraryItemId) {
return this.downloadQueue.filter(d => d.libraryItemId === libraryItemId)
return this.downloadQueue.filter((d) => d.libraryItemId === libraryItemId)
}
clearDownloadQueue(libraryItemId = null) {
@ -44,12 +44,12 @@ class PodcastManager {
} else {
var itemDownloads = this.getEpisodeDownloadsInQueue(libraryItemId)
Logger.info(`[PodcastManager] Clearing downloads in queue for item "${libraryItemId}" (${itemDownloads.length})`)
this.downloadQueue = this.downloadQueue.filter(d => d.libraryItemId !== libraryItemId)
this.downloadQueue = this.downloadQueue.filter((d) => d.libraryItemId !== libraryItemId)
}
}
async downloadPodcastEpisodes(libraryItem, episodesToDownload, isAutoDownload) {
let index = Math.max(...libraryItem.media.episodes.filter(ep => ep.index == null || isNaN(ep.index)).map(ep => Number(ep.index))) + 1
let index = Math.max(...libraryItem.media.episodes.filter((ep) => ep.index == null || isNaN(ep.index)).map((ep) => Number(ep.index))) + 1
for (const ep of episodesToDownload) {
const newPe = new PodcastEpisode()
newPe.setData(ep, index++)
@ -72,7 +72,7 @@ class PodcastManager {
const taskDescription = `Downloading episode "${podcastEpisodeDownload.podcastEpisode.title}".`
const taskData = {
libraryId: podcastEpisodeDownload.libraryId,
libraryItemId: podcastEpisodeDownload.libraryItemId,
libraryItemId: podcastEpisodeDownload.libraryItemId
}
const task = TaskManager.createAndAddTask('download-podcast-episode', 'Downloading Episode', taskDescription, false, taskData)
@ -104,10 +104,12 @@ class PodcastManager {
})
} else {
// Download episode only
success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath).then(() => true).catch((error) => {
Logger.error(`[PodcastManager] Podcast Episode download failed`, error)
return false
})
success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath)
.then(() => true)
.catch((error) => {
Logger.error(`[PodcastManager] Podcast Episode download failed`, error)
return false
})
}
if (success) {
@ -156,7 +158,7 @@ class PodcastManager {
podcastEpisode.audioFile = audioFile
if (audioFile.chapters?.length) {
podcastEpisode.chapters = audioFile.chapters.map(ch => ({ ...ch }))
podcastEpisode.chapters = audioFile.chapters.map((ch) => ({ ...ch }))
}
libraryItem.media.addPodcastEpisode(podcastEpisode)
@ -181,7 +183,8 @@ class PodcastManager {
podcastEpisodeExpanded.libraryItem = libraryItem.toJSONExpanded()
SocketAuthority.emitter('episode_added', podcastEpisodeExpanded)
if (this.currentDownload.isAutoDownload) { // Notifications only for auto downloaded episodes
if (this.currentDownload.isAutoDownload) {
// Notifications only for auto downloaded episodes
this.notificationManager.onPodcastEpisodeDownloaded(libraryItem, podcastEpisode)
}
@ -191,12 +194,14 @@ class PodcastManager {
async removeOldestEpisode(libraryItem, episodeIdJustDownloaded) {
var smallestPublishedAt = 0
var oldestEpisode = null
libraryItem.media.episodesWithPubDate.filter(ep => ep.id !== episodeIdJustDownloaded).forEach((ep) => {
if (!smallestPublishedAt || ep.publishedAt < smallestPublishedAt) {
smallestPublishedAt = ep.publishedAt
oldestEpisode = ep
}
})
libraryItem.media.episodesWithPubDate
.filter((ep) => ep.id !== episodeIdJustDownloaded)
.forEach((ep) => {
if (!smallestPublishedAt || ep.publishedAt < smallestPublishedAt) {
smallestPublishedAt = ep.publishedAt
oldestEpisode = ep
}
})
// TODO: Should we check for open playback sessions for this episode?
// TODO: remove all user progress for this episode
if (oldestEpisode?.audioFile) {
@ -246,7 +251,8 @@ class PodcastManager {
let newEpisodes = await this.checkPodcastForNewEpisodes(libraryItem, dateToCheckForEpisodesAfter, libraryItem.media.maxNewEpisodesToDownload)
Logger.debug(`[PodcastManager] runEpisodeCheck: ${newEpisodes?.length || 'N/A'} episodes found`)
if (!newEpisodes) { // Failed
if (!newEpisodes) {
// Failed
// Allow up to MaxFailedEpisodeChecks failed attempts before disabling auto download
if (!this.failedCheckMap[libraryItem.id]) this.failedCheckMap[libraryItem.id] = 0
this.failedCheckMap[libraryItem.id]++
@ -290,7 +296,7 @@ class PodcastManager {
}
// Filter new and not already has
let newEpisodes = feed.episodes.filter(ep => ep.publishedAt > dateToCheckForEpisodesAfter && !podcastLibraryItem.media.checkHasEpisodeByFeedUrl(ep.enclosure.url))
let newEpisodes = feed.episodes.filter((ep) => ep.publishedAt > dateToCheckForEpisodesAfter && !podcastLibraryItem.media.checkHasEpisodeByFeedEpisode(ep))
if (maxNewEpisodes > 0) {
newEpisodes = newEpisodes.slice(0, maxNewEpisodes)
@ -349,7 +355,7 @@ class PodcastManager {
}
const matches = []
feed.episodes.forEach(ep => {
feed.episodes.forEach((ep) => {
if (!ep.title) return
const epTitle = ep.title.toLowerCase().trim()
@ -410,7 +416,7 @@ class PodcastManager {
return {
currentDownload: _currentDownload?.toJSONForClient(),
queue: this.downloadQueue.filter(item => !libraryId || item.libraryId === libraryId).map(item => item.toJSONForClient())
queue: this.downloadQueue.filter((item) => !libraryId || item.libraryId === libraryId).map((item) => item.toJSONForClient())
}
}
}

View file

@ -1,4 +1,4 @@
const uuidv4 = require("uuid").v4
const uuidv4 = require('uuid').v4
const { areEquivalent, copyValue } = require('../../utils/index')
const AudioFile = require('../files/AudioFile')
const AudioTrack = require('../files/AudioTrack')
@ -47,7 +47,7 @@ class PodcastEpisode {
this.enclosure = episode.enclosure ? { ...episode.enclosure } : null
this.guid = episode.guid || null
this.pubDate = episode.pubDate
this.chapters = episode.chapters?.map(ch => ({ ...ch })) || []
this.chapters = episode.chapters?.map((ch) => ({ ...ch })) || []
this.audioFile = episode.audioFile ? new AudioFile(episode.audioFile) : null
this.publishedAt = episode.publishedAt
this.addedAt = episode.addedAt
@ -74,7 +74,7 @@ class PodcastEpisode {
enclosure: this.enclosure ? { ...this.enclosure } : null,
guid: this.guid,
pubDate: this.pubDate,
chapters: this.chapters.map(ch => ({ ...ch })),
chapters: this.chapters.map((ch) => ({ ...ch })),
audioFile: this.audioFile?.toJSON() || null,
publishedAt: this.publishedAt,
addedAt: this.addedAt,
@ -98,7 +98,7 @@ class PodcastEpisode {
enclosure: this.enclosure ? { ...this.enclosure } : null,
guid: this.guid,
pubDate: this.pubDate,
chapters: this.chapters.map(ch => ({ ...ch })),
chapters: this.chapters.map((ch) => ({ ...ch })),
audioFile: this.audioFile?.toJSON() || null,
audioTrack: this.audioTrack?.toJSON() || null,
publishedAt: this.publishedAt,
@ -121,7 +121,9 @@ class PodcastEpisode {
get duration() {
return this.audioFile?.duration || 0
}
get size() { return this.audioFile?.metadata.size || 0 }
get size() {
return this.audioFile?.metadata.size || 0
}
get enclosureUrl() {
return this.enclosure?.url || null
}
@ -151,9 +153,9 @@ class PodcastEpisode {
let hasUpdates = false
for (const key in this.toJSON()) {
let newValue = payload[key]
if (newValue === "") newValue = null
if (newValue === '') newValue = null
let existingValue = this[key]
if (existingValue === "") existingValue = null
if (existingValue === '') existingValue = null
if (newValue != undefined && !areEquivalent(newValue, existingValue)) {
this[key] = copyValue(newValue)
@ -177,7 +179,7 @@ class PodcastEpisode {
}
checkEqualsEnclosureUrl(url) {
if (!this.enclosure || !this.enclosure.url) return false
if (!this.enclosure?.url) return false
return this.enclosure.url == url
}
}

View file

@ -58,7 +58,7 @@ class Podcast {
metadata: this.metadata.toJSON(),
coverPath: this.coverPath,
tags: [...this.tags],
episodes: this.episodes.map(e => e.toJSON()),
episodes: this.episodes.map((e) => e.toJSON()),
autoDownloadEpisodes: this.autoDownloadEpisodes,
autoDownloadSchedule: this.autoDownloadSchedule,
lastEpisodeCheck: this.lastEpisodeCheck,
@ -90,7 +90,7 @@ class Podcast {
metadata: this.metadata.toJSONExpanded(),
coverPath: this.coverPath,
tags: [...this.tags],
episodes: this.episodes.map(e => e.toJSONExpanded()),
episodes: this.episodes.map((e) => e.toJSONExpanded()),
autoDownloadEpisodes: this.autoDownloadEpisodes,
autoDownloadSchedule: this.autoDownloadSchedule,
lastEpisodeCheck: this.lastEpisodeCheck,
@ -121,7 +121,7 @@ class Podcast {
get size() {
var total = 0
this.episodes.forEach((ep) => total += ep.size)
this.episodes.forEach((ep) => (total += ep.size))
return total
}
get hasMediaEntities() {
@ -129,7 +129,7 @@ class Podcast {
}
get duration() {
let total = 0
this.episodes.forEach((ep) => total += ep.duration)
this.episodes.forEach((ep) => (total += ep.duration))
return total
}
get numTracks() {
@ -145,7 +145,7 @@ class Podcast {
return largestPublishedAt
}
get episodesWithPubDate() {
return this.episodes.filter(ep => !!ep.publishedAt)
return this.episodes.filter((ep) => !!ep.publishedAt)
}
update(payload) {
@ -169,7 +169,7 @@ class Podcast {
}
updateEpisode(id, payload) {
var episode = this.episodes.find(ep => ep.id == id)
var episode = this.episodes.find((ep) => ep.id == id)
if (!episode) return false
return episode.update(payload)
}
@ -182,15 +182,15 @@ class Podcast {
}
removeFileWithInode(inode) {
const hasEpisode = this.episodes.some(ep => ep.audioFile.ino === inode)
const hasEpisode = this.episodes.some((ep) => ep.audioFile.ino === inode)
if (hasEpisode) {
this.episodes = this.episodes.filter(ep => ep.audioFile.ino !== inode)
this.episodes = this.episodes.filter((ep) => ep.audioFile.ino !== inode)
}
return hasEpisode
}
findFileWithInode(inode) {
var episode = this.episodes.find(ep => ep.audioFile.ino === inode)
var episode = this.episodes.find((ep) => ep.audioFile.ino === inode)
if (episode) return episode.audioFile
return null
}
@ -208,21 +208,23 @@ class Podcast {
}
checkHasEpisode(episodeId) {
return this.episodes.some(ep => ep.id === episodeId)
return this.episodes.some((ep) => ep.id === episodeId)
}
checkHasEpisodeByFeedUrl(url) {
return this.episodes.some(ep => ep.checkEqualsEnclosureUrl(url))
checkHasEpisodeByFeedEpisode(feedEpisode) {
const guid = feedEpisode.guid
const url = feedEpisode.enclosure.url
return this.episodes.some((ep) => (ep.guid && ep.guid === guid) || ep.checkEqualsEnclosureUrl(url))
}
// Only checks container format
checkCanDirectPlay(payload, episodeId) {
var episode = this.episodes.find(ep => ep.id === episodeId)
var episode = this.episodes.find((ep) => ep.id === episodeId)
if (!episode) return false
return episode.checkCanDirectPlay(payload)
}
getDirectPlayTracklist(episodeId) {
var episode = this.episodes.find(ep => ep.id === episodeId)
var episode = this.episodes.find((ep) => ep.id === episodeId)
if (!episode) return false
return episode.getDirectPlayTracklist()
}
@ -241,15 +243,15 @@ class Podcast {
}
removeEpisode(episodeId) {
const episode = this.episodes.find(ep => ep.id === episodeId)
const episode = this.episodes.find((ep) => ep.id === episodeId)
if (episode) {
this.episodes = this.episodes.filter(ep => ep.id !== episodeId)
this.episodes = this.episodes.filter((ep) => ep.id !== episodeId)
}
return episode
}
getPlaybackTitle(episodeId) {
var episode = this.episodes.find(ep => ep.id == episodeId)
var episode = this.episodes.find((ep) => ep.id == episodeId)
if (!episode) return this.metadata.title
return episode.title
}
@ -259,7 +261,7 @@ class Podcast {
}
getEpisodeDuration(episodeId) {
var episode = this.episodes.find(ep => ep.id == episodeId)
var episode = this.episodes.find((ep) => ep.id == episodeId)
if (!episode) return 0
return episode.duration
}
@ -268,13 +270,13 @@ class Podcast {
if (!episodeId) return null
// Support old episode ids for mobile downloads
if (episodeId.startsWith('ep_')) return this.episodes.find(ep => ep.oldEpisodeId == episodeId)
if (episodeId.startsWith('ep_')) return this.episodes.find((ep) => ep.oldEpisodeId == episodeId)
return this.episodes.find(ep => ep.id == episodeId)
return this.episodes.find((ep) => ep.id == episodeId)
}
getChapters(episodeId) {
return this.getEpisode(episodeId)?.chapters?.map(ch => ({ ...ch })) || []
return this.getEpisode(episodeId)?.chapters?.map((ch) => ({ ...ch })) || []
}
}
module.exports = Podcast

View file

@ -25,9 +25,11 @@ module.exports = {
if (user.permissions.selectedTagsNotAccessible) {
bookWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0))
} else {
bookWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), {
[Sequelize.Op.gte]: 1
}))
bookWhere.push(
Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), {
[Sequelize.Op.gte]: 1
})
)
}
}
return {
@ -170,7 +172,7 @@ module.exports = {
Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('audioFiles')), 0),
{
'$mediaProgresses.isFinished$': true,
'ebookFile': {
ebookFile: {
[Sequelize.Op.not]: null
}
}
@ -285,11 +287,7 @@ module.exports = {
*/
async getCollapseSeriesBooksToExclude(bookFindOptions, seriesWhere) {
const allSeries = await Database.seriesModel.findAll({
attributes: [
'id',
'name',
[Sequelize.literal('(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)'), 'numBooks']
],
attributes: ['id', 'name', [Sequelize.literal('(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)'), 'numBooks']],
distinct: true,
subQuery: false,
where: seriesWhere,
@ -304,23 +302,22 @@ module.exports = {
required: true
}
],
order: [
Sequelize.literal('CAST(`books.bookSeries.sequence` AS FLOAT) ASC NULLS LAST')
]
order: [Sequelize.literal('CAST(`books.bookSeries.sequence` AS FLOAT) ASC NULLS LAST')]
})
const bookSeriesToInclude = []
const booksToInclude = []
let booksToExclude = []
allSeries.forEach(s => {
allSeries.forEach((s) => {
let found = false
for (let book of s.books) {
if (!found && !booksToInclude.includes(book.id)) {
booksToInclude.push(book.id)
bookSeriesToInclude.push({
id: book.bookSeries.id,
numBooks: s.dataValues.numBooks
numBooks: s.dataValues.numBooks,
libraryItemIds: s.books?.map((b) => b.libraryItem.id) || []
})
booksToExclude = booksToExclude.filter(bid => bid !== book.id)
booksToExclude = booksToExclude.filter((bid) => bid !== book.id)
found = true
} else if (!booksToExclude.includes(book.id) && !booksToInclude.includes(book.id)) {
booksToExclude.push(book.id)
@ -363,15 +360,11 @@ module.exports = {
let bookAttributes = null
if (sortBy === 'media.metadata.authorNameLF') {
bookAttributes = {
include: [
[Sequelize.literal(`(SELECT group_concat(a.lastFirst, ", ") FROM authors AS a, bookAuthors as ba WHERE ba.authorId = a.id AND ba.bookId = book.id)`), 'author_name']
]
include: [[Sequelize.literal(`(SELECT group_concat(lastFirst, ", ") FROM (SELECT a.lastFirst FROM authors AS a, bookAuthors as ba WHERE ba.authorId = a.id AND ba.bookId = book.id ORDER BY ba.createdAt ASC))`), 'author_name']]
}
} else if (sortBy === 'media.metadata.authorName') {
bookAttributes = {
include: [
[Sequelize.literal(`(SELECT group_concat(a.name, ", ") FROM authors AS a, bookAuthors as ba WHERE ba.authorId = a.id AND ba.bookId = book.id)`), 'author_name']
]
include: [[Sequelize.literal(`(SELECT group_concat(name, ", ") FROM (SELECT a.name FROM authors AS a, bookAuthors as ba WHERE ba.authorId = a.id AND ba.bookId = book.id ORDER BY ba.createdAt ASC))`), 'author_name']]
}
}
@ -386,9 +379,7 @@ module.exports = {
model: Database.seriesModel,
attributes: ['id', 'name', 'nameIgnorePrefix']
},
order: [
['createdAt', 'ASC']
],
order: [['createdAt', 'ASC']],
separate: true
}
@ -399,9 +390,7 @@ module.exports = {
model: Database.authorModel,
attributes: ['id', 'name']
},
order: [
['createdAt', 'ASC']
],
order: [['createdAt', 'ASC']],
separate: true
}
@ -427,7 +416,7 @@ module.exports = {
}
} else if (filterGroup === 'ebooks' && filterValue === 'no-supplementary') {
libraryItemWhere['libraryFiles'] = {
[Sequelize.Op.notLike]: Sequelize.literal(`\'%"isSupplementary":true%\'`),
[Sequelize.Op.notLike]: Sequelize.literal(`\'%"isSupplementary":true%\'`)
}
} else if (filterGroup === 'missing' && filterValue === 'authors') {
authorInclude = {
@ -496,7 +485,7 @@ module.exports = {
})
} else if (filterGroup === 'recent') {
libraryItemWhere['createdAt'] = {
[Sequelize.Op.gte]: new Date(new Date() - (60 * 24 * 60 * 60 * 1000)) // 60 days ago
[Sequelize.Op.gte]: new Date(new Date() - 60 * 24 * 60 * 60 * 1000) // 60 days ago
}
}
@ -551,9 +540,9 @@ module.exports = {
// When collapsing series and sorting by title then use the series name instead of the book title
// for this set an attribute "display_title" to use in sorting
if (global.ServerSettings.sortingIgnorePrefix) {
bookAttributes.include.push([Sequelize.literal(`IFNULL((SELECT s.nameIgnorePrefix FROM bookSeries AS bs, series AS s WHERE bs.seriesId = s.id AND bs.bookId = book.id AND bs.id IN (${bookSeriesToInclude.map(v => `"${v.id}"`).join(', ')})), titleIgnorePrefix)`), 'display_title'])
bookAttributes.include.push([Sequelize.literal(`IFNULL((SELECT s.nameIgnorePrefix FROM bookSeries AS bs, series AS s WHERE bs.seriesId = s.id AND bs.bookId = book.id AND bs.id IN (${bookSeriesToInclude.map((v) => `"${v.id}"`).join(', ')})), titleIgnorePrefix)`), 'display_title'])
} else {
bookAttributes.include.push([Sequelize.literal(`IFNULL((SELECT s.name FROM bookSeries AS bs, series AS s WHERE bs.seriesId = s.id AND bs.bookId = book.id AND bs.id IN (${bookSeriesToInclude.map(v => `"${v.id}"`).join(', ')})), \`book\`.\`title\`)`), 'display_title'])
bookAttributes.include.push([Sequelize.literal(`IFNULL((SELECT s.name FROM bookSeries AS bs, series AS s WHERE bs.seriesId = s.id AND bs.bookId = book.id AND bs.id IN (${bookSeriesToInclude.map((v) => `"${v.id}"`).join(', ')})), \`book\`.\`title\`)`), 'display_title'])
}
}
@ -598,15 +587,16 @@ module.exports = {
// For showing details of collapsed series
if (collapseseries && book.bookSeries?.length) {
const collapsedSeries = book.bookSeries.find(bs => collapseSeriesBookSeries.some(cbs => cbs.id === bs.id))
const collapsedSeries = book.bookSeries.find((bs) => collapseSeriesBookSeries.some((cbs) => cbs.id === bs.id))
if (collapsedSeries) {
const collapseSeriesObj = collapseSeriesBookSeries.find(csbs => csbs.id === collapsedSeries.id)
const collapseSeriesObj = collapseSeriesBookSeries.find((csbs) => csbs.id === collapsedSeries.id)
libraryItem.collapsedSeries = {
id: collapsedSeries.series.id,
name: collapsedSeries.series.name,
nameIgnorePrefix: collapsedSeries.series.nameIgnorePrefix,
sequence: collapsedSeries.sequence,
numBooks: collapseSeriesObj?.numBooks || 0
numBooks: collapseSeriesObj?.numBooks || 0,
libraryItemIds: collapseSeriesObj?.libraryItemIds || []
}
}
}
@ -655,9 +645,7 @@ module.exports = {
const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user)
bookWhere.push(...userPermissionBookWhere.bookWhere)
let includeAttributes = [
[Sequelize.literal('(SELECT max(mp.updatedAt) FROM bookSeries bs, mediaProgresses mp WHERE mp.mediaItemId = bs.bookId AND mp.userId = :userId AND bs.seriesId = series.id)'), 'recent_progress'],
]
let includeAttributes = [[Sequelize.literal('(SELECT max(mp.updatedAt) FROM bookSeries bs, mediaProgresses mp WHERE mp.mediaItemId = bs.bookId AND mp.userId = :userId AND bs.seriesId = series.id)'), 'recent_progress']]
let booksNotFinishedQuery = `SELECT count(*) FROM bookSeries bs LEFT OUTER JOIN mediaProgresses mp ON mp.mediaItemId = bs.bookId AND mp.userId = :userId WHERE bs.seriesId = series.id AND (mp.isFinished = 0 OR mp.isFinished IS NULL)`
if (library.settings.onlyShowLaterBooksInContinueSeries) {
@ -699,9 +687,7 @@ module.exports = {
attributes: ['bookId', 'sequence'],
separate: true,
subQuery: false,
order: [
[Sequelize.literal('CAST(sequence AS FLOAT) ASC NULLS LAST')]
],
order: [[Sequelize.literal('CAST(sequence AS FLOAT) ASC NULLS LAST')]],
where: {
'$book.mediaProgresses.isFinished$': {
[Sequelize.Op.or]: [null, 0]
@ -731,44 +717,44 @@ module.exports = {
]
}
},
order: [
[Sequelize.literal('recent_progress DESC')]
],
order: [[Sequelize.literal('recent_progress DESC')]],
distinct: true,
subQuery: false,
limit,
offset
})
const libraryItems = series.map(s => {
if (!s.bookSeries.length) return null // this is only possible if user has restricted books in series
const libraryItems = series
.map((s) => {
if (!s.bookSeries.length) return null // this is only possible if user has restricted books in series
let bookIndex = 0
// if the library setting is toggled, only show later entries in series, otherwise skip
if (library.settings.onlyShowLaterBooksInContinueSeries) {
bookIndex = s.bookSeries.findIndex(function (b) {
return parseFloat(b.dataValues.sequence) > s.dataValues.maxSequence
})
if (bookIndex === -1) {
// no later books than maxSequence
return null
let bookIndex = 0
// if the library setting is toggled, only show later entries in series, otherwise skip
if (library.settings.onlyShowLaterBooksInContinueSeries) {
bookIndex = s.bookSeries.findIndex(function (b) {
return parseFloat(b.dataValues.sequence) > s.dataValues.maxSequence
})
if (bookIndex === -1) {
// no later books than maxSequence
return null
}
}
}
const libraryItem = s.bookSeries[bookIndex].book.libraryItem.toJSON()
const book = s.bookSeries[bookIndex].book.toJSON()
delete book.libraryItem
libraryItem.series = {
id: s.id,
name: s.name,
sequence: s.bookSeries[bookIndex].sequence
}
if (libraryItem.feeds?.length) {
libraryItem.rssFeed = libraryItem.feeds[0]
}
libraryItem.media = book
return libraryItem
}).filter(s => s)
const libraryItem = s.bookSeries[bookIndex].book.libraryItem.toJSON()
const book = s.bookSeries[bookIndex].book.toJSON()
delete book.libraryItem
libraryItem.series = {
id: s.id,
name: s.name,
sequence: s.bookSeries[bookIndex].sequence
}
if (libraryItem.feeds?.length) {
libraryItem.rssFeed = libraryItem.feeds[0]
}
libraryItem.media = book
return libraryItem
})
.filter((s) => s)
return {
libraryItems,
@ -811,9 +797,7 @@ module.exports = {
model: Database.bookModel,
where: userPermissionBookWhere.bookWhere
},
order: [
[Sequelize.literal('CAST(sequence AS FLOAT) ASC NULLS LAST')]
],
order: [[Sequelize.literal('CAST(sequence AS FLOAT) ASC NULLS LAST')]],
limit: 1
},
subQuery: false,
@ -821,7 +805,7 @@ module.exports = {
order: Database.sequelize.random()
})
const booksFromSeriesToInclude = seriesNotStarted.map(se => se.bookSeries?.[0]?.bookId).filter(bid => bid)
const booksFromSeriesToInclude = seriesNotStarted.map((se) => se.bookSeries?.[0]?.bookId).filter((bid) => bid)
// optional include rssFeed
const libraryItemIncludes = []
@ -963,7 +947,7 @@ module.exports = {
*/
async getLibraryItemsForSeries(oldSeries, oldUser) {
const { libraryItems } = await this.getFilteredLibraryItems(oldSeries.libraryId, oldUser, 'series', oldSeries.id, null, null, false, [], null, null)
return libraryItems.map(li => Database.libraryItemModel.getOldLibraryItem(li))
return libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li))
},
/**
@ -1201,12 +1185,10 @@ module.exports = {
libraryId
}
},
order: [
['duration', 'DESC']
],
order: [['duration', 'DESC']],
limit
})
return books.map(book => {
return books.map((book) => {
return {
id: book.libraryItem.id,
title: book.title,