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' }" /> <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"> <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"> <div v-if="userIsAdminOrUp" class="flex">
<ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn> <ui-btn to="/config" color="primary" class="w-52 mr-2">{{ $strings.ButtonConfigureScanner }}</ui-btn>
<ui-btn color="success" class="w-52" @click="scan">Scan Library</ui-btn> <ui-btn color="success" class="w-52" :loading="isScanningLibrary || tempIsScanning" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
</div> </div>
</div> </div>
<div v-else-if="loaded && !shelves.length && search" class="w-full h-40 flex items-center justify-center"> <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, scannerParseSubtitle: false,
wrapperClientWidth: 0, wrapperClientWidth: 0,
shelves: [], shelves: [],
lastItemIndexSelected: -1 lastItemIndexSelected: -1,
tempIsScanning: false
} }
}, },
computed: { computed: {
@ -97,6 +98,9 @@ export default {
}, },
streamLibraryItem() { streamLibraryItem() {
return this.$store.state.streamLibraryItem return this.$store.state.streamLibraryItem
},
isScanningLibrary() {
return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId)
} }
}, },
methods: { methods: {
@ -273,14 +277,15 @@ export default {
this.shelves = shelves this.shelves = shelves
}, },
scan() { scan() {
this.tempIsScanning = true
this.$store this.$store
.dispatch('libraries/requestLibraryScan', { libraryId: this.$store.state.libraries.currentLibraryId }) .dispatch('libraries/requestLibraryScan', { libraryId: this.$store.state.libraries.currentLibraryId })
.then(() => {
this.$toast.success('Library scan started')
})
.catch((error) => { .catch((error) => {
console.error('Failed to start scan', 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) { userUpdated(user) {

View file

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

View file

@ -6,9 +6,12 @@
</div> </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="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"> <div class="min-w-0">
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate"> <div class="flex items-center">
{{ title }} <nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
</nuxt-link> {{ title }}
</nuxt-link>
<widgets-explicit-indicator v-if="isExplicit" />
</div>
<div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center"> <div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center">
<span class="material-icons text-sm">person</span> <span class="material-icons text-sm">person</span>
<div class="flex items-center"> <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> <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>
<div v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">{{ $strings.LabelUnknown }}</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>
</div> </div>
@ -136,7 +138,7 @@ export default {
return this.streamLibraryItem?.mediaType === 'music' return this.streamLibraryItem?.mediaType === 'music'
}, },
isExplicit() { isExplicit() {
return this.mediaMetadata.explicit || false return !!this.mediaMetadata.explicit
}, },
mediaMetadata() { mediaMetadata() {
return this.media.metadata || {} return this.media.metadata || {}

View file

@ -13,9 +13,9 @@
<div class="flex-grow" /> <div class="flex-grow" />
<p class="text-sm md:text-base">{{ book.publishedYear }}</p> <p class="text-sm md:text-base">{{ book.publishedYear }}</p>
</div> </div>
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">by {{ book.author }}</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">Narrated by {{ book.narrator }}</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">Runtime: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</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-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"> <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"> <p class="leading-3 text-xs text-gray-400">
@ -29,9 +29,9 @@
</div> </div>
<div v-else class="px-4 flex-grow"> <div v-else class="px-4 flex-grow">
<h1> <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> </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 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> <p class="text-xs text-gray-400 leading-5">{{ book.trackCount }} Episodes</p>
</div> </div>
@ -75,11 +75,11 @@ export default {
let differenceInMinutes = currentBookDurationMinutes - this.book.duration let differenceInMinutes = currentBookDurationMinutes - this.book.duration
if (differenceInMinutes < 0) { if (differenceInMinutes < 0) {
differenceInMinutes = Math.abs(differenceInMinutes) 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) { } 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: { 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 === '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" /> <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" /> <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 === '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 === 'tags') return `<p class="truncate">${this.$strings.LabelTags}: ${html}</p>`
if (this.matchKey === 'subtitle') return `<p class="truncate">${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 === 'isbn') return `<p class="truncate">ISBN: ${html}</p>`
if (this.matchKey === 'asin') return `<p class="truncate">ASIN: ${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>` if (this.matchKey === 'series') return `<p class="truncate">${this.$strings.LabelSeries}: ${html}</p>`
@ -90,4 +90,4 @@ export default {
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
} }
</style> </style>

View file

@ -1,128 +1,124 @@
<template> <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 --> <!-- 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 class="absolute cover-bg" ref="coverBg" />
</div> </div>
<!-- Alternative bookshelf title/author/sort --> <!-- 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' }"> <div :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
<ui-tooltip v-if="displayTitle" :text="displayTitle" :disabled="!displayTitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center"> <ui-tooltip v-if="displayTitle" :text="displayTitle" :disabled="!displayTitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
<p ref="displayTitle" class="truncate">{{ displayTitle }}</p> <p cy-id="title" ref="displayTitle" class="truncate">{{ displayTitle }}</p>
<widgets-explicit-indicator :explicit="isExplicit" /> <widgets-explicit-indicator cy-id="explicitIndicator" v-if="isExplicit" />
</ui-tooltip> </ui-tooltip>
</div> </div>
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || '&nbsp;' }}</p> <p cy-id="line2" 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="line3" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
</div> </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> <p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequenceList }}</p>
</div> </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> <p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ booksInSeries }}</p>
</div> </div>
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10"> <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> <p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="text-gray-300 text-center">{{ title }}</p>
</div> </div>
<!-- Cover Image --> <!-- 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 --> <!-- 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> <div>
<p class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }"> <p cy-id="placeholderTitleText" class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
{{ titleCleaned }}
</p>
</div> </div>
</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' }"> <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 class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p> <p cy-id="placeholderAuthorText" class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
</div> </div>
</div> </div>
<!-- No progress shown for collapsed series in library and podcasts (unless showing podcast episode) --> <!-- No progress shown for 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> <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>
<!-- 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>
<!-- Overlay is not shown if collapsing series in library --> <!-- 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 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 v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none"> <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"> <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> <span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
</div> </div>
</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"> <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> <span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">auto_stories</span>
</div> </div>
</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> <span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
</div> </div>
<!-- Radio button --> <!-- 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> <span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
</div> </div>
<!-- More Menu Icon --> <!-- 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> <span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
</div> </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> <span class="text-white/80" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ ebookFormat }}</span>
</div> </div>
</div> </div>
<!-- Processing/loading spinner overlay --> <!-- 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" /> <widgets-loading-spinner size="la-lg" />
</div> </div>
<!-- Series name overlay --> <!-- 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> <p v-if="seriesName" class="text-gray-200 text-center" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">{{ seriesName }}</p>
</div> </div>
<!-- Error widget --> <!-- 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"> <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> <span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
</div> </div>
</ui-tooltip> </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> <span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.5 + 'rem' }">rss_feed</span>
</div> </div>
<!-- Series sequence --> <!-- 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> <p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
</div> </div>
<!-- Podcast Episode # --> <!-- 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' }"> <p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">
Episode<span v-if="recentEpisodeNumber"> #{{ recentEpisodeNumber }}</span> Episode<span v-if="recentEpisodeNumber"> #{{ recentEpisodeNumber }}</span>
</p> </p>
</div> </div>
<!-- Podcast Num Episodes --> <!-- 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> <p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
</div> </div>
<!-- Podcast Num Episodes --> <!-- 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> <p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodesIncomplete }}</p>
</div> </div>
</div> </div>
@ -343,11 +339,22 @@ export default {
if (!this.userProgress || this.userProgress.progress) return false if (!this.userProgress || this.userProgress.progress) return false
return this.userProgress.ebookProgress > 0 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() { userProgressPercent() {
if (this.useEBookProgress) return Math.max(Math.min(1, this.userProgress.ebookProgress), 0) let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0
return this.userProgress ? Math.max(Math.min(1, this.userProgress.progress), 0) || 0 : 0 return Math.max(Math.min(1, progressPercent), 0)
}, },
itemIsFinished() { itemIsFinished() {
if (this.booksInSeries) return this.seriesIsFinished
return this.userProgress ? !!this.userProgress.isFinished : false return this.userProgress ? !!this.userProgress.isFinished : false
}, },
seriesIsFinished() { seriesIsFinished() {

View file

@ -119,9 +119,13 @@ export default {
return this.seriesBookProgress.some((p) => !p.isFinished && p.progress > 0) return this.seriesBookProgress.some((p) => !p.isFinished && p.progress > 0)
}, },
seriesPercentInProgress() { seriesPercentInProgress() {
let totalFinishedAndInProgress = this.seriesBooksFinished.length if (!this.books.length) return 0
if (this.hasSeriesBookInProgress) totalFinishedAndInProgress += 1 let progressPercent = 0
return Math.min(1, Math.max(0, totalFinishedAndInProgress / this.books.length)) this.seriesBookProgress.forEach((progress) => {
progressPercent += progress.isFinished ? 1 : progress.progress || 0
})
progressPercent /= this.books.length
return Math.min(1, Math.max(0, progressPercent))
}, },
isSeriesFinished() { isSeriesFinished() {
return this.books.length === this.seriesBooksFinished.length 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 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"> <div class="flex items-center">
<p class="text-base text-gray-200">{{ _session.displayTitle }}</p> <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>
<div class="w-full h-px bg-white bg-opacity-10 my-4" /> <div class="w-full h-px bg-white bg-opacity-10 my-4" />

View file

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

View file

@ -42,13 +42,13 @@
<div class="flex py-2"> <div class="flex py-2">
<div> <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"> <a :href="selectedMatch.cover" target="_blank" class="bg-primary">
<covers-preview-cover :src="selectedMatch.cover" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" /> <covers-preview-cover :src="selectedMatch.cover" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</a> </a>
</div> </div>
<div v-if="media.coverPath" class="ml-0.5"> <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"> <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" /> <covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrc'](libraryItem, null, true)" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</a> </a>
@ -180,14 +180,14 @@
<ui-checkbox v-model="selectedMatchUsage.explicit" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.explicit" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4" :class="{ 'pt-4': mediaMetadata.explicit != null }"> <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" /> <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> </div>
<div v-if="selectedMatchOrig.abridged != null" class="flex items-center pb-2" :class="{ 'pt-2': mediaMetadata.abridged == null }"> <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" /> <ui-checkbox v-model="selectedMatchUsage.abridged" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4" :class="{ 'pt-4': mediaMetadata.abridged != null }"> <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" /> <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>
</div> </div>

View file

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

View file

@ -271,7 +271,7 @@ export default {
this.$emit('update:processing', true) this.$emit('update:processing', true)
this.yearStats = await this.$axios.$get(`/api/me/stats/year/${this.year}`).catch((err) => { this.yearStats = await this.$axios.$get(`/api/me/stats/year/${this.year}`).catch((err) => {
console.error('Failed to load stats for year', 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 return null
}) })
await this.initCanvas() await this.initCanvas()
@ -282,4 +282,4 @@ export default {
this.init() this.init()
} }
} }
</script> </script>

View file

@ -250,7 +250,7 @@ export default {
this.$emit('update:processing', true) this.$emit('update:processing', true)
this.yearStats = await this.$axios.$get(`/api/stats/year/${this.year}`).catch((err) => { this.yearStats = await this.$axios.$get(`/api/stats/year/${this.year}`).catch((err) => {
console.error('Failed to load stats for year', 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 return null
}) })
await this.initCanvas() await this.initCanvas()
@ -261,4 +261,4 @@ export default {
this.init() this.init()
} }
} }
</script> </script>

View file

@ -180,7 +180,7 @@ export default {
this.$emit('update:processing', true) this.$emit('update:processing', true)
this.yearStats = await this.$axios.$get(`/api/me/stats/year/${this.year}`).catch((err) => { this.yearStats = await this.$axios.$get(`/api/me/stats/year/${this.year}`).catch((err) => {
console.error('Failed to load stats for year', 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 return null
}) })
await this.initCanvas() await this.initCanvas()
@ -191,4 +191,4 @@ export default {
this.init() this.init()
} }
} }
</script> </script>

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@
<td class="px-4"> <td class="px-4">
<div class="flex items-center"> <div class="flex items-center">
<nuxt-link :to="`/item/${downloadQueued.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ downloadQueued.podcastTitle }}</nuxt-link> <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> </div>
</td> </td>
<td> <td>

View file

@ -1,5 +1,5 @@
<template> <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"> <svg xmlns="http://www.w3.org/2000/svg" width="12px" height="12px" viewBox="0 0 512 512" class="ml-1">
<path <path
fill="white" fill="white"
@ -40,9 +40,7 @@
<script> <script>
export default { export default {
props: { props: {},
explicit: Boolean
},
data() { data() {
return {} 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 charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="stylesheet" href="tailwind.compiled.css">
<title>Components App</title> <title>Components App</title>
</head> </head>
<body class="text-white bg-bg"> <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, isCategorized: false,
seriesMount: series, seriesMount: series,
sortingIgnorePrefix: false, sortingIgnorePrefix: false,
orderBy: 'addedAt', orderBy: 'addedAt'
} }
const stubs = { const stubs = {
@ -126,7 +126,7 @@ describe('LazySeriesCard', () => {
.and('have.class', 'bg-yellow-400') .and('have.class', 'bg-yellow-400')
.and(($el) => { .and(($el) => {
const width = $el.width() 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, ...mocks.$store,
getters: { getters: {
...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') cy.get('&detailBottomDisplayTitle').should('have.text', 'Lord of the Rings')
}) })
})
})

View file

@ -18,7 +18,7 @@
<div class="w-12 hidden lg:block" /> <div class="w-12 hidden lg:block" />
<p class="text-lg mb-4 font-semibold">{{ $strings.HeaderChapters }}</p> <p class="text-lg mb-4 font-semibold">{{ $strings.HeaderChapters }}</p>
<div class="flex-grow" /> <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 class="w-32 hidden lg:block" />
</div> </div>
<div class="flex items-center mb-3 py-1 -mx-1"> <div class="flex items-center mb-3 py-1 -mx-1">
@ -639,4 +639,4 @@ export default {
this.destroyAudioEl() this.destroyAudioEl()
} }
} }
</script> </script>

View file

@ -310,14 +310,14 @@ export default {
.then((data) => { .then((data) => {
this.$store.commit('setServerSettings', data.serverSettings) this.$store.commit('setServerSettings', data.serverSettings)
if (data.updated) { if (data.updated) {
this.$toast.success('Server settings updated') this.$toast.success(this.$strings.ToastServerSettingsUpdateSuccess)
} else { } else {
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary) this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
} }
}) })
.catch((error) => { .catch((error) => {
console.error('Failed to update server settings', error) console.error('Failed to update server settings', error)
this.$toast.error('Failed to update server settings') this.$toast.error(this.$strings.ToastServerSettingsUpdateFailed)
}) })
.finally(() => { .finally(() => {
this.savingSettings = false this.savingSettings = false
@ -347,4 +347,4 @@ export default {
padding: 2px 4px; padding: 2px 4px;
white-space: nowrap; white-space: nowrap;
} }
</style> </style>

View file

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

View file

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

View file

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

View file

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

View file

@ -142,7 +142,7 @@ export default {
this.loading = true this.loading = true
const notificationResponse = await this.$axios.$get('/api/notifications').catch((error) => { const notificationResponse = await this.$axios.$get('/api/notifications').catch((error) => {
console.error('Failed to get notification settings', 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 return null
}) })
this.loading = false this.loading = false
@ -172,4 +172,4 @@ export default {
this.$root.socket.off('notifications_updated', this.notificationsUpdated) this.$root.socket.off('notifications_updated', this.notificationsUpdated)
} }
} }
</script> </script>

View file

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

View file

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

View file

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

View file

@ -27,7 +27,7 @@
<h1 class="text-2xl md:text-3xl font-semibold"> <h1 class="text-2xl md:text-3xl font-semibold">
<div class="flex items-center"> <div class="flex items-center">
{{ title }} {{ title }}
<widgets-explicit-indicator :explicit="isExplicit" /> <widgets-explicit-indicator v-if="isExplicit" />
<widgets-abridged-indicator v-if="isAbridged" /> <widgets-abridged-indicator v-if="isAbridged" />
</div> </div>
</h1> </h1>
@ -40,7 +40,7 @@
</template> </template>
<template v-if="!isVideo"> <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"> <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> <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> </p>
@ -285,7 +285,7 @@ export default {
return this.mediaMetadata.subtitle return this.mediaMetadata.subtitle
}, },
podcastAuthor() { podcastAuthor() {
return this.mediaMetadata.author || '' return this.mediaMetadata.author || 'Unknown'
}, },
authors() { authors() {
return this.mediaMetadata.authors || [] return this.mediaMetadata.authors || []

View file

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

View file

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

View file

@ -21,10 +21,10 @@
<div class="flex-grow pl-4 max-w-2xl"> <div class="flex-grow pl-4 max-w-2xl">
<div class="flex items-center"> <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> <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" /> <widgets-already-in-library-indicator :already-in-library="podcast.alreadyInLibrary" />
</div> </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.genres.join(', ') }}</p>
<p class="text-xs text-gray-400 leading-5">{{ podcast.trackCount }} {{ $strings.HeaderEpisodes }}</p> <p class="text-xs text-gray-400 leading-5">{{ podcast.trackCount }} {{ $strings.HeaderEpisodes }}</p>
</div> </div>

View file

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

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Vaše statistiky", "HeaderYourStats": "Vaše statistiky",
"LabelAbridged": "Zkráceno", "LabelAbridged": "Zkráceno",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Typ účtu", "LabelAccountType": "Typ účtu",
"LabelAccountTypeAdmin": "Správce", "LabelAccountTypeAdmin": "Správce",
"LabelAccountTypeGuest": "Host", "LabelAccountTypeGuest": "Host",
@ -229,6 +231,7 @@
"LabelBitrate": "Datový tok", "LabelBitrate": "Datový tok",
"LabelBooks": "Knihy", "LabelBooks": "Knihy",
"LabelButtonText": "Button Text", "LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Změnit heslo", "LabelChangePassword": "Změnit heslo",
"LabelChannels": "Kanály", "LabelChannels": "Kanály",
"LabelChapters": "Kapitoly", "LabelChapters": "Kapitoly",
@ -266,6 +269,9 @@
"LabelDownload": "Stáhnout", "LabelDownload": "Stáhnout",
"LabelDownloadNEpisodes": "Stáhnout {0} epizody", "LabelDownloadNEpisodes": "Stáhnout {0} epizody",
"LabelDuration": "Doba trvání", "LabelDuration": "Doba trvání",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Doba trvání nalezena:", "LabelDurationFound": "Doba trvání nalezena:",
"LabelEbook": "Elektronická kniha", "LabelEbook": "Elektronická kniha",
"LabelEbooks": "Elektronické knihy", "LabelEbooks": "Elektronické knihy",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Typ epizody", "LabelEpisodeType": "Typ epizody",
"LabelExample": "Příklad", "LabelExample": "Příklad",
"LabelExplicit": "Explicitní", "LabelExplicit": "Explicitní",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "URL zdroje", "LabelFeedURL": "URL zdroje",
"LabelFetchingMetadata": "Fetching Metadata", "LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Soubor", "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", "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", "LabelSettingsTimeFormat": "Formát času",
"LabelShowAll": "Zobrazit vše", "LabelShowAll": "Zobrazit vše",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Velikost", "LabelSize": "Velikost",
"LabelSleepTimer": "Časovač vypnutí", "LabelSleepTimer": "Časovač vypnutí",
"LabelSlug": "Slug", "LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Opravdu chcete označit všechny epizody jako nedokončené?", "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é?", "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é?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Opravdu chcete odstranit všechny kapitoly?",
"MessageConfirmRemoveAuthor": "Opravdu chcete odstranit autora \"{0}\"?", "MessageConfirmRemoveAuthor": "Opravdu chcete odstranit autora \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Záložka odstraněna", "ToastBookmarkRemoveSuccess": "Záložka odstraněna",
"ToastBookmarkUpdateFailed": "Aktualizace záložky se nezdařila", "ToastBookmarkUpdateFailed": "Aktualizace záložky se nezdařila",
"ToastBookmarkUpdateSuccess": "Záložka aktualizována", "ToastBookmarkUpdateSuccess": "Záložka aktualizována",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Kapitoly obsahují chyby", "ToastChaptersHaveErrors": "Kapitoly obsahují chyby",
"ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy", "ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy",
"ToastCollectionItemsRemoveFailed": "Nepodařilo se odstranit položky z kolekce", "ToastCollectionItemsRemoveFailed": "Nepodařilo se odstranit položky z kolekce",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Kolekce odstraněna", "ToastCollectionRemoveSuccess": "Kolekce odstraněna",
"ToastCollectionUpdateFailed": "Aktualizace kolekce se nezdařila", "ToastCollectionUpdateFailed": "Aktualizace kolekce se nezdařila",
"ToastCollectionUpdateSuccess": "Kolekce aktualizována", "ToastCollectionUpdateSuccess": "Kolekce aktualizována",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Aktualizace obálky se nezdařila", "ToastItemCoverUpdateFailed": "Aktualizace obálky se nezdařila",
"ToastItemCoverUpdateSuccess": "Obálka předmětu byl aktualizována", "ToastItemCoverUpdateSuccess": "Obálka předmětu byl aktualizována",
"ToastItemDetailsUpdateFailed": "Nepodařilo se aktualizovat podrobnosti o položce", "ToastItemDetailsUpdateFailed": "Nepodařilo se aktualizovat podrobnosti o položce",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-kniha odeslána do zařízení \"{0}\"", "ToastSendEbookToDeviceSuccess": "E-kniha odeslána do zařízení \"{0}\"",
"ToastSeriesUpdateFailed": "Aktualizace série se nezdařila", "ToastSeriesUpdateFailed": "Aktualizace série se nezdařila",
"ToastSeriesUpdateSuccess": "Aktualizace série byla úspěšná", "ToastSeriesUpdateSuccess": "Aktualizace série byla úspěšná",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Nepodařilo se smazat relaci", "ToastSessionDeleteFailed": "Nepodařilo se smazat relaci",
"ToastSessionDeleteSuccess": "Relace smazána", "ToastSessionDeleteSuccess": "Relace smazána",
"ToastSocketConnected": "Socket připojen", "ToastSocketConnected": "Socket připojen",
"ToastSocketDisconnected": "Socket odpojen", "ToastSocketDisconnected": "Socket odpojen",
"ToastSocketFailedToConnect": "Socket se nepodařilo připojit", "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", "ToastUserDeleteFailed": "Nepodařilo se smazat uživatele",
"ToastUserDeleteSuccess": "Uživatel smazán" "ToastUserDeleteSuccess": "Uživatel smazán"
} }

View file

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

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Jahr {0} in Übersicht", "HeaderYearReview": "Jahr {0} in Übersicht",
"HeaderYourStats": "Eigene Statistiken", "HeaderYourStats": "Eigene Statistiken",
"LabelAbridged": "Gekürzt", "LabelAbridged": "Gekürzt",
"LabelAbridgedChecked": "Gekürzt (angehakt)",
"LabelAbridgedUnchecked": "Ungekürzt (nicht angehakt)",
"LabelAccountType": "Kontoart", "LabelAccountType": "Kontoart",
"LabelAccountTypeAdmin": "Admin", "LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Gast", "LabelAccountTypeGuest": "Gast",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate", "LabelBitrate": "Bitrate",
"LabelBooks": "Bücher", "LabelBooks": "Bücher",
"LabelButtonText": "Button Text", "LabelButtonText": "Button Text",
"LabelByAuthor": "von {0}",
"LabelChangePassword": "Passwort ändern", "LabelChangePassword": "Passwort ändern",
"LabelChannels": "Kanäle", "LabelChannels": "Kanäle",
"LabelChapters": "Kapitel", "LabelChapters": "Kapitel",
@ -266,6 +269,9 @@
"LabelDownload": "Herunterladen", "LabelDownload": "Herunterladen",
"LabelDownloadNEpisodes": "Download {0} Episoden", "LabelDownloadNEpisodes": "Download {0} Episoden",
"LabelDuration": "Laufzeit", "LabelDuration": "Laufzeit",
"LabelDurationComparisonExactMatch": "(genauer Treffer)",
"LabelDurationComparisonLonger": "({0} änger)",
"LabelDurationComparisonShorter": "({0} kürzer)",
"LabelDurationFound": "Gefundene Laufzeit:", "LabelDurationFound": "Gefundene Laufzeit:",
"LabelEbook": "E-Book", "LabelEbook": "E-Book",
"LabelEbooks": "E-Books", "LabelEbooks": "E-Books",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episodentyp", "LabelEpisodeType": "Episodentyp",
"LabelExample": "Beispiel", "LabelExample": "Beispiel",
"LabelExplicit": "Explizit (Altersbeschränkung)", "LabelExplicit": "Explizit (Altersbeschränkung)",
"LabelExplicitChecked": "Explicit (Altersbeschränkung) (angehakt)",
"LabelExplicitUnchecked": "Not Explicit (Altersbeschränkung) (nicht angehakt)",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Abholen der Metadaten", "LabelFetchingMetadata": "Abholen der Metadaten",
"LabelFile": "Datei", "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", "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", "LabelSettingsTimeFormat": "Zeitformat",
"LabelShowAll": "Alles anzeigen", "LabelShowAll": "Alles anzeigen",
"LabelShowSeconds": "Zeige Sekunden",
"LabelSize": "Größe", "LabelSize": "Größe",
"LabelSleepTimer": "Sleep-Timer", "LabelSleepTimer": "Sleep-Timer",
"LabelSlug": "URL Teil", "LabelSlug": "URL Teil",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Alle Episoden werden als nicht abgeschlossen markiert! Bist du dir sicher?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Alle Kapitel werden entfernt! Bist du dir sicher?",
"MessageConfirmRemoveAuthor": "Autor \"{0}\" wird enfernt! Bist du dir sicher?", "MessageConfirmRemoveAuthor": "Autor \"{0}\" wird enfernt! Bist du dir sicher?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Lesezeichen gelöscht", "ToastBookmarkRemoveSuccess": "Lesezeichen gelöscht",
"ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen", "ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
"ToastBookmarkUpdateSuccess": "Lesezeichen aktualisiert", "ToastBookmarkUpdateSuccess": "Lesezeichen aktualisiert",
"ToastCachePurgeFailed": "Cache leeren fehlgeschlagen",
"ToastCachePurgeSuccess": "Cache geleert",
"ToastChaptersHaveErrors": "Kapitel sind fehlerhaft", "ToastChaptersHaveErrors": "Kapitel sind fehlerhaft",
"ToastChaptersMustHaveTitles": "Kapitel benötigen eindeutige Namen", "ToastChaptersMustHaveTitles": "Kapitel benötigen eindeutige Namen",
"ToastCollectionItemsRemoveFailed": "Fehler beim Entfernen der Medien aus der Sammlung", "ToastCollectionItemsRemoveFailed": "Fehler beim Entfernen der Medien aus der Sammlung",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Sammlung gelöscht", "ToastCollectionRemoveSuccess": "Sammlung gelöscht",
"ToastCollectionUpdateFailed": "Sammlung konnte nicht aktualisiert werden", "ToastCollectionUpdateFailed": "Sammlung konnte nicht aktualisiert werden",
"ToastCollectionUpdateSuccess": "Sammlung aktualisiert", "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", "ToastItemCoverUpdateFailed": "Fehler bei der Aktualisierung des Titelbildes",
"ToastItemCoverUpdateSuccess": "Titelbild aktualisiert", "ToastItemCoverUpdateSuccess": "Titelbild aktualisiert",
"ToastItemDetailsUpdateFailed": "Fehler bei der Aktualisierung der Artikeldetails", "ToastItemDetailsUpdateFailed": "Fehler bei der Aktualisierung der Artikeldetails",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-Book an Gerät \"{0}\" gesendet", "ToastSendEbookToDeviceSuccess": "E-Book an Gerät \"{0}\" gesendet",
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen", "ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
"ToastSeriesUpdateSuccess": "Serien aktualisiert", "ToastSeriesUpdateSuccess": "Serien aktualisiert",
"ToastServerSettingsUpdateFailed": "Die Server-Einstellungen wurden nicht gespeichert.",
"ToastServerSettingsUpdateSuccess": "Die Server-Einstellungen wurden geupdated",
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden", "ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
"ToastSessionDeleteSuccess": "Sitzung gelöscht", "ToastSessionDeleteSuccess": "Sitzung gelöscht",
"ToastSocketConnected": "Verbindung zum WebSocket hergestellt", "ToastSocketConnected": "Verbindung zum WebSocket hergestellt",
"ToastSocketDisconnected": "Verbindung zum WebSocket verloren", "ToastSocketDisconnected": "Verbindung zum WebSocket verloren",
"ToastSocketFailedToConnect": "Verbindung zum WebSocket fehlgeschlagen", "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", "ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
"ToastUserDeleteSuccess": "Benutzer gelöscht" "ToastUserDeleteSuccess": "Benutzer gelöscht"
} }

View file

@ -193,6 +193,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Your Stats", "HeaderYourStats": "Your Stats",
"LabelAbridged": "Abridged", "LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Account Type", "LabelAccountType": "Account Type",
"LabelAccountTypeAdmin": "Admin", "LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Guest", "LabelAccountTypeGuest": "Guest",
@ -233,6 +235,7 @@
"LabelBitrate": "Bitrate", "LabelBitrate": "Bitrate",
"LabelBooks": "Books", "LabelBooks": "Books",
"LabelButtonText": "Button Text", "LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Change Password", "LabelChangePassword": "Change Password",
"LabelChannels": "Channels", "LabelChannels": "Channels",
"LabelChapters": "Chapters", "LabelChapters": "Chapters",
@ -271,6 +274,9 @@
"LabelDownload": "Download", "LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episodes", "LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Duration", "LabelDuration": "Duration",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Duration found:", "LabelDurationFound": "Duration found:",
"LabelEbook": "Ebook", "LabelEbook": "Ebook",
"LabelEbooks": "Ebooks", "LabelEbooks": "Ebooks",
@ -289,6 +295,8 @@
"LabelEpisodeType": "Episode Type", "LabelEpisodeType": "Episode Type",
"LabelExample": "Example", "LabelExample": "Example",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedHealthy": "Feed Healthy", "LabelFeedHealthy": "Feed Healthy",
"LabelFeedLastChecked": "Last Checked", "LabelFeedLastChecked": "Last Checked",
"LabelFeedLastSuccessfulCheck": "Last Successful Check", "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", "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", "LabelSettingsTimeFormat": "Time Format",
"LabelShowAll": "Show All", "LabelShowAll": "Show All",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Size", "LabelSize": "Size",
"LabelSleepTimer": "Sleep timer", "LabelSleepTimer": "Sleep timer",
"LabelSlug": "Slug", "LabelSlug": "Slug",
@ -612,6 +621,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -750,6 +760,8 @@
"ToastBookmarkRemoveSuccess": "Bookmark removed", "ToastBookmarkRemoveSuccess": "Bookmark removed",
"ToastBookmarkUpdateFailed": "Failed to update bookmark", "ToastBookmarkUpdateFailed": "Failed to update bookmark",
"ToastBookmarkUpdateSuccess": "Bookmark updated", "ToastBookmarkUpdateSuccess": "Bookmark updated",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors", "ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles", "ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection", "ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
@ -758,6 +770,9 @@
"ToastCollectionRemoveSuccess": "Collection removed", "ToastCollectionRemoveSuccess": "Collection removed",
"ToastCollectionUpdateFailed": "Failed to update collection", "ToastCollectionUpdateFailed": "Failed to update collection",
"ToastCollectionUpdateSuccess": "Collection updated", "ToastCollectionUpdateSuccess": "Collection updated",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Failed to update item cover", "ToastItemCoverUpdateFailed": "Failed to update item cover",
"ToastItemCoverUpdateSuccess": "Item cover updated", "ToastItemCoverUpdateSuccess": "Item cover updated",
"ToastItemDetailsUpdateFailed": "Failed to update item details", "ToastItemDetailsUpdateFailed": "Failed to update item details",
@ -791,11 +806,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"", "ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed", "ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success", "ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Failed to delete session", "ToastSessionDeleteFailed": "Failed to delete session",
"ToastSessionDeleteSuccess": "Session deleted", "ToastSessionDeleteSuccess": "Session deleted",
"ToastSocketConnected": "Socket connected", "ToastSocketConnected": "Socket connected",
"ToastSocketDisconnected": "Socket disconnected", "ToastSocketDisconnected": "Socket disconnected",
"ToastSocketFailedToConnect": "Socket failed to connect", "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", "ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted" "ToastUserDeleteSuccess": "User deleted"
} }

View file

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

View file

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

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Vos statistiques", "HeaderYourStats": "Vos statistiques",
"LabelAbridged": "Version courte", "LabelAbridged": "Version courte",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Type de compte", "LabelAccountType": "Type de compte",
"LabelAccountTypeAdmin": "Admin", "LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Invité", "LabelAccountTypeGuest": "Invité",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate", "LabelBitrate": "Bitrate",
"LabelBooks": "Livres", "LabelBooks": "Livres",
"LabelButtonText": "Texte du bouton", "LabelButtonText": "Texte du bouton",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Modifier le mot de passe", "LabelChangePassword": "Modifier le mot de passe",
"LabelChannels": "Canaux", "LabelChannels": "Canaux",
"LabelChapters": "Chapitres", "LabelChapters": "Chapitres",
@ -266,6 +269,9 @@
"LabelDownload": "Téléchargement", "LabelDownload": "Téléchargement",
"LabelDownloadNEpisodes": "Télécharger {0} épisode(s)", "LabelDownloadNEpisodes": "Télécharger {0} épisode(s)",
"LabelDuration": "Durée", "LabelDuration": "Durée",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Durée trouvée :", "LabelDurationFound": "Durée trouvée :",
"LabelEbook": "Livre numérique", "LabelEbook": "Livre numérique",
"LabelEbooks": "Livres numériques", "LabelEbooks": "Livres numériques",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Type de lépisode", "LabelEpisodeType": "Type de lépisode",
"LabelExample": "Exemple", "LabelExample": "Exemple",
"LabelExplicit": "Restriction", "LabelExplicit": "Restriction",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "URL du flux", "LabelFeedURL": "URL du flux",
"LabelFetchingMetadata": "Récupération des métadonnées", "LabelFetchingMetadata": "Récupération des métadonnées",
"LabelFile": "Fichier", "LabelFile": "Fichier",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Par défaut, les métadonnées sont enregistrées dans /metadata/items", "LabelSettingsStoreMetadataWithItemHelp": "Par défaut, les métadonnées sont enregistrées dans /metadata/items",
"LabelSettingsTimeFormat": "Format dheure", "LabelSettingsTimeFormat": "Format dheure",
"LabelShowAll": "Tout afficher", "LabelShowAll": "Tout afficher",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Taille", "LabelSize": "Taille",
"LabelSleepTimer": "Minuterie", "LabelSleepTimer": "Minuterie",
"LabelSlug": "Balise", "LabelSlug": "Balise",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Êtes-vous sûr de vouloir marquer tous les épisodes comme non terminés ?", "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 ?", "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 ?", "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 ?", "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 ?", "MessageConfirmRemoveAllChapters": "Êtes-vous sûr de vouloir supprimer tous les chapitres ?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Signet supprimé", "ToastBookmarkRemoveSuccess": "Signet supprimé",
"ToastBookmarkUpdateFailed": "Échec de la mise à jour de signet", "ToastBookmarkUpdateFailed": "Échec de la mise à jour de signet",
"ToastBookmarkUpdateSuccess": "Signet mis à jour", "ToastBookmarkUpdateSuccess": "Signet mis à jour",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs", "ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs",
"ToastChaptersMustHaveTitles": "Les chapitre doivent avoir un titre", "ToastChaptersMustHaveTitles": "Les chapitre doivent avoir un titre",
"ToastCollectionItemsRemoveFailed": "Échec de la suppression de(s) article(s) de la collection", "ToastCollectionItemsRemoveFailed": "Échec de la suppression de(s) article(s) de la collection",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Collection supprimée", "ToastCollectionRemoveSuccess": "Collection supprimée",
"ToastCollectionUpdateFailed": "Échec de la mise à jour de la collection", "ToastCollectionUpdateFailed": "Échec de la mise à jour de la collection",
"ToastCollectionUpdateSuccess": "Collection mise à jour", "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", "ToastItemCoverUpdateFailed": "Échec de la mise à jour de la couverture de larticle",
"ToastItemCoverUpdateSuccess": "Couverture de larticle mise à jour", "ToastItemCoverUpdateSuccess": "Couverture de larticle mise à jour",
"ToastItemDetailsUpdateFailed": "Échec de la mise à jour des détails de larticle", "ToastItemDetailsUpdateFailed": "Échec de la mise à jour des détails de larticle",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Livre numérique envoyé à lappareil : {0}", "ToastSendEbookToDeviceSuccess": "Livre numérique envoyé à lappareil : {0}",
"ToastSeriesUpdateFailed": "Échec de la mise à jour de la série", "ToastSeriesUpdateFailed": "Échec de la mise à jour de la série",
"ToastSeriesUpdateSuccess": "Mise à jour de la série réussie", "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", "ToastSessionDeleteFailed": "Échec de la suppression de session",
"ToastSessionDeleteSuccess": "Session supprimée", "ToastSessionDeleteSuccess": "Session supprimée",
"ToastSocketConnected": "WebSocket connecté", "ToastSocketConnected": "WebSocket connecté",
"ToastSocketDisconnected": "WebSocket déconnecté", "ToastSocketDisconnected": "WebSocket déconnecté",
"ToastSocketFailedToConnect": "Échec de la connexion WebSocket", "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", "ToastUserDeleteFailed": "Échec de la suppression de lutilisateur",
"ToastUserDeleteSuccess": "Utilisateur supprimé" "ToastUserDeleteSuccess": "Utilisateur supprimé"
} }

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Your Stats", "HeaderYourStats": "Your Stats",
"LabelAbridged": "Abridged", "LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Account Type", "LabelAccountType": "Account Type",
"LabelAccountTypeAdmin": "Admin", "LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Guest", "LabelAccountTypeGuest": "Guest",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate", "LabelBitrate": "Bitrate",
"LabelBooks": "Books", "LabelBooks": "Books",
"LabelButtonText": "Button Text", "LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Change Password", "LabelChangePassword": "Change Password",
"LabelChannels": "Channels", "LabelChannels": "Channels",
"LabelChapters": "Chapters", "LabelChapters": "Chapters",
@ -266,6 +269,9 @@
"LabelDownload": "Download", "LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episodes", "LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Duration", "LabelDuration": "Duration",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Duration found:", "LabelDurationFound": "Duration found:",
"LabelEbook": "Ebook", "LabelEbook": "Ebook",
"LabelEbooks": "Ebooks", "LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episode Type", "LabelEpisodeType": "Episode Type",
"LabelExample": "Example", "LabelExample": "Example",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata", "LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "File", "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", "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", "LabelSettingsTimeFormat": "Time Format",
"LabelShowAll": "Show All", "LabelShowAll": "Show All",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Size", "LabelSize": "Size",
"LabelSleepTimer": "Sleep timer", "LabelSleepTimer": "Sleep timer",
"LabelSlug": "Slug", "LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Bookmark removed", "ToastBookmarkRemoveSuccess": "Bookmark removed",
"ToastBookmarkUpdateFailed": "Failed to update bookmark", "ToastBookmarkUpdateFailed": "Failed to update bookmark",
"ToastBookmarkUpdateSuccess": "Bookmark updated", "ToastBookmarkUpdateSuccess": "Bookmark updated",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors", "ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles", "ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection", "ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Collection removed", "ToastCollectionRemoveSuccess": "Collection removed",
"ToastCollectionUpdateFailed": "Failed to update collection", "ToastCollectionUpdateFailed": "Failed to update collection",
"ToastCollectionUpdateSuccess": "Collection updated", "ToastCollectionUpdateSuccess": "Collection updated",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Failed to update item cover", "ToastItemCoverUpdateFailed": "Failed to update item cover",
"ToastItemCoverUpdateSuccess": "Item cover updated", "ToastItemCoverUpdateSuccess": "Item cover updated",
"ToastItemDetailsUpdateFailed": "Failed to update item details", "ToastItemDetailsUpdateFailed": "Failed to update item details",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"", "ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed", "ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success", "ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Failed to delete session", "ToastSessionDeleteFailed": "Failed to delete session",
"ToastSessionDeleteSuccess": "Session deleted", "ToastSessionDeleteSuccess": "Session deleted",
"ToastSocketConnected": "Socket connected", "ToastSocketConnected": "Socket connected",
"ToastSocketDisconnected": "Socket disconnected", "ToastSocketDisconnected": "Socket disconnected",
"ToastSocketFailedToConnect": "Socket failed to connect", "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", "ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted" "ToastUserDeleteSuccess": "User deleted"
} }

View file

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

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Your Stats", "HeaderYourStats": "Your Stats",
"LabelAbridged": "Abridged", "LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Account Type", "LabelAccountType": "Account Type",
"LabelAccountTypeAdmin": "Admin", "LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Guest", "LabelAccountTypeGuest": "Guest",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate", "LabelBitrate": "Bitrate",
"LabelBooks": "Books", "LabelBooks": "Books",
"LabelButtonText": "Button Text", "LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Change Password", "LabelChangePassword": "Change Password",
"LabelChannels": "Channels", "LabelChannels": "Channels",
"LabelChapters": "Chapters", "LabelChapters": "Chapters",
@ -266,6 +269,9 @@
"LabelDownload": "Download", "LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episodes", "LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Duration", "LabelDuration": "Duration",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Duration found:", "LabelDurationFound": "Duration found:",
"LabelEbook": "Ebook", "LabelEbook": "Ebook",
"LabelEbooks": "Ebooks", "LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episode Type", "LabelEpisodeType": "Episode Type",
"LabelExample": "Example", "LabelExample": "Example",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata", "LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "File", "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", "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", "LabelSettingsTimeFormat": "Time Format",
"LabelShowAll": "Show All", "LabelShowAll": "Show All",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Size", "LabelSize": "Size",
"LabelSleepTimer": "Sleep timer", "LabelSleepTimer": "Sleep timer",
"LabelSlug": "Slug", "LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Bookmark removed", "ToastBookmarkRemoveSuccess": "Bookmark removed",
"ToastBookmarkUpdateFailed": "Failed to update bookmark", "ToastBookmarkUpdateFailed": "Failed to update bookmark",
"ToastBookmarkUpdateSuccess": "Bookmark updated", "ToastBookmarkUpdateSuccess": "Bookmark updated",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors", "ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles", "ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection", "ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Collection removed", "ToastCollectionRemoveSuccess": "Collection removed",
"ToastCollectionUpdateFailed": "Failed to update collection", "ToastCollectionUpdateFailed": "Failed to update collection",
"ToastCollectionUpdateSuccess": "Collection updated", "ToastCollectionUpdateSuccess": "Collection updated",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Failed to update item cover", "ToastItemCoverUpdateFailed": "Failed to update item cover",
"ToastItemCoverUpdateSuccess": "Item cover updated", "ToastItemCoverUpdateSuccess": "Item cover updated",
"ToastItemDetailsUpdateFailed": "Failed to update item details", "ToastItemDetailsUpdateFailed": "Failed to update item details",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"", "ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed", "ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success", "ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Failed to delete session", "ToastSessionDeleteFailed": "Failed to delete session",
"ToastSessionDeleteSuccess": "Session deleted", "ToastSessionDeleteSuccess": "Session deleted",
"ToastSocketConnected": "Socket connected", "ToastSocketConnected": "Socket connected",
"ToastSocketDisconnected": "Socket disconnected", "ToastSocketDisconnected": "Socket disconnected",
"ToastSocketFailedToConnect": "Socket failed to connect", "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", "ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted" "ToastUserDeleteSuccess": "User deleted"
} }

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Tvoja statistika", "HeaderYourStats": "Tvoja statistika",
"LabelAbridged": "Abridged", "LabelAbridged": "Abridged",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Vrsta korisničkog računa", "LabelAccountType": "Vrsta korisničkog računa",
"LabelAccountTypeAdmin": "Administrator", "LabelAccountTypeAdmin": "Administrator",
"LabelAccountTypeGuest": "Gost", "LabelAccountTypeGuest": "Gost",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate", "LabelBitrate": "Bitrate",
"LabelBooks": "Knjige", "LabelBooks": "Knjige",
"LabelButtonText": "Button Text", "LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Promijeni lozinku", "LabelChangePassword": "Promijeni lozinku",
"LabelChannels": "Channels", "LabelChannels": "Channels",
"LabelChapters": "Chapters", "LabelChapters": "Chapters",
@ -266,6 +269,9 @@
"LabelDownload": "Preuzmi", "LabelDownload": "Preuzmi",
"LabelDownloadNEpisodes": "Download {0} episodes", "LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Trajanje", "LabelDuration": "Trajanje",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Pronađeno trajanje:", "LabelDurationFound": "Pronađeno trajanje:",
"LabelEbook": "Ebook", "LabelEbook": "Ebook",
"LabelEbooks": "Ebooks", "LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Vrsta epizode", "LabelEpisodeType": "Vrsta epizode",
"LabelExample": "Example", "LabelExample": "Example",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata", "LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Datoteka", "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", "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", "LabelSettingsTimeFormat": "Time Format",
"LabelShowAll": "Prikaži sve", "LabelShowAll": "Prikaži sve",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Veličina", "LabelSize": "Veličina",
"LabelSleepTimer": "Sleep timer", "LabelSleepTimer": "Sleep timer",
"LabelSlug": "Slug", "LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Knjižnja bilješka uklonjena", "ToastBookmarkRemoveSuccess": "Knjižnja bilješka uklonjena",
"ToastBookmarkUpdateFailed": "Aktualizacija knjižne bilješke neuspješna", "ToastBookmarkUpdateFailed": "Aktualizacija knjižne bilješke neuspješna",
"ToastBookmarkUpdateSuccess": "Knjižna bilješka aktualizirana", "ToastBookmarkUpdateSuccess": "Knjižna bilješka aktualizirana",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors", "ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles", "ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Neuspješno brisanje stavke/-i iz kolekcije", "ToastCollectionItemsRemoveFailed": "Neuspješno brisanje stavke/-i iz kolekcije",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Kolekcija obrisana", "ToastCollectionRemoveSuccess": "Kolekcija obrisana",
"ToastCollectionUpdateFailed": "Aktualiziranje kolekcije neuspješno", "ToastCollectionUpdateFailed": "Aktualiziranje kolekcije neuspješno",
"ToastCollectionUpdateSuccess": "Kolekcija aktualizirana", "ToastCollectionUpdateSuccess": "Kolekcija aktualizirana",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Aktualiziranje covera stavke neuspješna", "ToastItemCoverUpdateFailed": "Aktualiziranje covera stavke neuspješna",
"ToastItemCoverUpdateSuccess": "Cover stavke aktualiziran", "ToastItemCoverUpdateSuccess": "Cover stavke aktualiziran",
"ToastItemDetailsUpdateFailed": "Aktualiziranje detalja stavke neuspješno", "ToastItemDetailsUpdateFailed": "Aktualiziranje detalja stavke neuspješno",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"", "ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed", "ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success", "ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Neuspješno brisanje serije", "ToastSessionDeleteFailed": "Neuspješno brisanje serije",
"ToastSessionDeleteSuccess": "Sesija obrisana", "ToastSessionDeleteSuccess": "Sesija obrisana",
"ToastSocketConnected": "Socket connected", "ToastSocketConnected": "Socket connected",
"ToastSocketDisconnected": "Socket disconnected", "ToastSocketDisconnected": "Socket disconnected",
"ToastSocketFailedToConnect": "Socket failed to connect", "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", "ToastUserDeleteFailed": "Neuspješno brisanje korisnika",
"ToastUserDeleteSuccess": "Korisnik obrisan" "ToastUserDeleteSuccess": "Korisnik obrisan"
} }

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Saját statisztikák", "HeaderYourStats": "Saját statisztikák",
"LabelAbridged": "Tömörített", "LabelAbridged": "Tömörített",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Fióktípus", "LabelAccountType": "Fióktípus",
"LabelAccountTypeAdmin": "Admin", "LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Vendég", "LabelAccountTypeGuest": "Vendég",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitráta", "LabelBitrate": "Bitráta",
"LabelBooks": "Könyvek", "LabelBooks": "Könyvek",
"LabelButtonText": "Gomb szövege", "LabelButtonText": "Gomb szövege",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Jelszó megváltoztatása", "LabelChangePassword": "Jelszó megváltoztatása",
"LabelChannels": "Csatornák", "LabelChannels": "Csatornák",
"LabelChapters": "Fejezetek", "LabelChapters": "Fejezetek",
@ -266,6 +269,9 @@
"LabelDownload": "Letöltés", "LabelDownload": "Letöltés",
"LabelDownloadNEpisodes": "{0} epizód letöltése", "LabelDownloadNEpisodes": "{0} epizód letöltése",
"LabelDuration": "Időtartam", "LabelDuration": "Időtartam",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Megtalált időtartam:", "LabelDurationFound": "Megtalált időtartam:",
"LabelEbook": "E-könyv", "LabelEbook": "E-könyv",
"LabelEbooks": "E-könyvek", "LabelEbooks": "E-könyvek",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Epizód típusa", "LabelEpisodeType": "Epizód típusa",
"LabelExample": "Példa", "LabelExample": "Példa",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Hírcsatorna URL", "LabelFeedURL": "Hírcsatorna URL",
"LabelFetchingMetadata": "Metaadatok lekérése", "LabelFetchingMetadata": "Metaadatok lekérése",
"LabelFile": "Fájl", "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", "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", "LabelSettingsTimeFormat": "Időformátum",
"LabelShowAll": "Mindent mutat", "LabelShowAll": "Mindent mutat",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Méret", "LabelSize": "Méret",
"LabelSleepTimer": "Alvásidőzítő", "LabelSleepTimer": "Alvásidőzítő",
"LabelSlug": "Rövid cím", "LabelSlug": "Rövid cím",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Biztosan meg szeretné jelölni az összes epizódot nem befejezettnek?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Biztosan eltávolítja az összes fejezetet?",
"MessageConfirmRemoveAuthor": "Biztosan eltávolítja a(z) \"{0}\" szerzőt?", "MessageConfirmRemoveAuthor": "Biztosan eltávolítja a(z) \"{0}\" szerzőt?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Könyvjelző eltávolítva", "ToastBookmarkRemoveSuccess": "Könyvjelző eltávolítva",
"ToastBookmarkUpdateFailed": "Könyvjelző frissítése sikertelen", "ToastBookmarkUpdateFailed": "Könyvjelző frissítése sikertelen",
"ToastBookmarkUpdateSuccess": "Könyvjelző frissítve", "ToastBookmarkUpdateSuccess": "Könyvjelző frissítve",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "A fejezetek hibákat tartalmaznak", "ToastChaptersHaveErrors": "A fejezetek hibákat tartalmaznak",
"ToastChaptersMustHaveTitles": "A fejezeteknek címekkel kell rendelkezniük", "ToastChaptersMustHaveTitles": "A fejezeteknek címekkel kell rendelkezniük",
"ToastCollectionItemsRemoveFailed": "Elem(ek) eltávolítása a gyűjteményből sikertelen", "ToastCollectionItemsRemoveFailed": "Elem(ek) eltávolítása a gyűjteményből sikertelen",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Gyűjtemény eltávolítva", "ToastCollectionRemoveSuccess": "Gyűjtemény eltávolítva",
"ToastCollectionUpdateFailed": "Gyűjtemény frissítése sikertelen", "ToastCollectionUpdateFailed": "Gyűjtemény frissítése sikertelen",
"ToastCollectionUpdateSuccess": "Gyűjtemény frissítve", "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", "ToastItemCoverUpdateFailed": "Elem borítójának frissítése sikertelen",
"ToastItemCoverUpdateSuccess": "Elem borítója frissítve", "ToastItemCoverUpdateSuccess": "Elem borítója frissítve",
"ToastItemDetailsUpdateFailed": "Elem részleteinek frissítése sikertelen", "ToastItemDetailsUpdateFailed": "Elem részleteinek frissítése sikertelen",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-könyv elküldve az eszközre \"{0}\"", "ToastSendEbookToDeviceSuccess": "E-könyv elküldve az eszközre \"{0}\"",
"ToastSeriesUpdateFailed": "Sorozat frissítése sikertelen", "ToastSeriesUpdateFailed": "Sorozat frissítése sikertelen",
"ToastSeriesUpdateSuccess": "Sorozat frissítése sikeres", "ToastSeriesUpdateSuccess": "Sorozat frissítése sikeres",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Munkamenet törlése sikertelen", "ToastSessionDeleteFailed": "Munkamenet törlése sikertelen",
"ToastSessionDeleteSuccess": "Munkamenet törölve", "ToastSessionDeleteSuccess": "Munkamenet törölve",
"ToastSocketConnected": "Socket csatlakoztatva", "ToastSocketConnected": "Socket csatlakoztatva",
"ToastSocketDisconnected": "Socket lecsatlakoztatva", "ToastSocketDisconnected": "Socket lecsatlakoztatva",
"ToastSocketFailedToConnect": "A Socket csatlakoztatása sikertelen", "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", "ToastUserDeleteFailed": "Felhasználó törlése sikertelen",
"ToastUserDeleteSuccess": "Felhasználó törölve" "ToastUserDeleteSuccess": "Felhasználó törölve"
} }

View file

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

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Jūsų statistika", "HeaderYourStats": "Jūsų statistika",
"LabelAbridged": "Santrauka", "LabelAbridged": "Santrauka",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Paskyros tipas", "LabelAccountType": "Paskyros tipas",
"LabelAccountTypeAdmin": "Administratorius", "LabelAccountTypeAdmin": "Administratorius",
"LabelAccountTypeGuest": "Svečias", "LabelAccountTypeGuest": "Svečias",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitų sparta", "LabelBitrate": "Bitų sparta",
"LabelBooks": "Knygos", "LabelBooks": "Knygos",
"LabelButtonText": "Button Text", "LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Pakeisti slaptažodį", "LabelChangePassword": "Pakeisti slaptažodį",
"LabelChannels": "Kanalai", "LabelChannels": "Kanalai",
"LabelChapters": "Skyriai", "LabelChapters": "Skyriai",
@ -266,6 +269,9 @@
"LabelDownload": "Atsisiųsti", "LabelDownload": "Atsisiųsti",
"LabelDownloadNEpisodes": "Atsisiųsti {0} epizodų", "LabelDownloadNEpisodes": "Atsisiųsti {0} epizodų",
"LabelDuration": "Trukmė", "LabelDuration": "Trukmė",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Rasta trukmė:", "LabelDurationFound": "Rasta trukmė:",
"LabelEbook": "Elektroninė knyga", "LabelEbook": "Elektroninė knyga",
"LabelEbooks": "Elektroninės knygos", "LabelEbooks": "Elektroninės knygos",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Epizodo tipas", "LabelEpisodeType": "Epizodo tipas",
"LabelExample": "Pavyzdys", "LabelExample": "Pavyzdys",
"LabelExplicit": "Suaugusiems", "LabelExplicit": "Suaugusiems",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Srauto URL", "LabelFeedURL": "Srauto URL",
"LabelFetchingMetadata": "Fetching Metadata", "LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Failas", "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", "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", "LabelSettingsTimeFormat": "Laiko formatas",
"LabelShowAll": "Rodyti viską", "LabelShowAll": "Rodyti viską",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Dydis", "LabelSize": "Dydis",
"LabelSleepTimer": "Miego laikmatis", "LabelSleepTimer": "Miego laikmatis",
"LabelSlug": "Slug", "LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Ar tikrai norite pažymėti visus epizodus kaip nebaigtus?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Ar tikrai norite pašalinti visus skyrius?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Žyma pašalinta", "ToastBookmarkRemoveSuccess": "Žyma pašalinta",
"ToastBookmarkUpdateFailed": "Žymos atnaujinti nepavyko", "ToastBookmarkUpdateFailed": "Žymos atnaujinti nepavyko",
"ToastBookmarkUpdateSuccess": "Žyma atnaujinta", "ToastBookmarkUpdateSuccess": "Žyma atnaujinta",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Skyriai turi klaidų", "ToastChaptersHaveErrors": "Skyriai turi klaidų",
"ToastChaptersMustHaveTitles": "Skyriai turi turėti pavadinimus", "ToastChaptersMustHaveTitles": "Skyriai turi turėti pavadinimus",
"ToastCollectionItemsRemoveFailed": "Elementų pašalinti iš kolekcijos nepavyko", "ToastCollectionItemsRemoveFailed": "Elementų pašalinti iš kolekcijos nepavyko",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Kolekcija pašalinta", "ToastCollectionRemoveSuccess": "Kolekcija pašalinta",
"ToastCollectionUpdateFailed": "Kolekcijos atnaujinti nepavyko", "ToastCollectionUpdateFailed": "Kolekcijos atnaujinti nepavyko",
"ToastCollectionUpdateSuccess": "Kolekcija atnaujinta", "ToastCollectionUpdateSuccess": "Kolekcija atnaujinta",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Elemento viršelio atnaujinti nepavyko", "ToastItemCoverUpdateFailed": "Elemento viršelio atnaujinti nepavyko",
"ToastItemCoverUpdateSuccess": "Elemento viršelis atnaujintas", "ToastItemCoverUpdateSuccess": "Elemento viršelis atnaujintas",
"ToastItemDetailsUpdateFailed": "Elemento detalių atnaujinti nepavyko", "ToastItemDetailsUpdateFailed": "Elemento detalių atnaujinti nepavyko",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "E-knyga išsiųsta į įrenginį \"{0}\"", "ToastSendEbookToDeviceSuccess": "E-knyga išsiųsta į įrenginį \"{0}\"",
"ToastSeriesUpdateFailed": "Serijos atnaujinti nepavyko", "ToastSeriesUpdateFailed": "Serijos atnaujinti nepavyko",
"ToastSeriesUpdateSuccess": "Serijos atnaujintos", "ToastSeriesUpdateSuccess": "Serijos atnaujintos",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Sesijos ištrinti nepavyko", "ToastSessionDeleteFailed": "Sesijos ištrinti nepavyko",
"ToastSessionDeleteSuccess": "Sesija ištrinta", "ToastSessionDeleteSuccess": "Sesija ištrinta",
"ToastSocketConnected": "Serveris prijungtas", "ToastSocketConnected": "Serveris prijungtas",
"ToastSocketDisconnected": "Severis atjungtas", "ToastSocketDisconnected": "Severis atjungtas",
"ToastSocketFailedToConnect": "Nepavyko prisijungti prie serverio", "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", "ToastUserDeleteFailed": "Nepavyko ištrinti naudotojo",
"ToastUserDeleteSuccess": "Naudotojas ištrintas" "ToastUserDeleteSuccess": "Naudotojas ištrintas"
} }

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Je statistieken", "HeaderYourStats": "Je statistieken",
"LabelAbridged": "Verkort", "LabelAbridged": "Verkort",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Accounttype", "LabelAccountType": "Accounttype",
"LabelAccountTypeAdmin": "Beheerder", "LabelAccountTypeAdmin": "Beheerder",
"LabelAccountTypeGuest": "Gast", "LabelAccountTypeGuest": "Gast",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate", "LabelBitrate": "Bitrate",
"LabelBooks": "Boeken", "LabelBooks": "Boeken",
"LabelButtonText": "Button Text", "LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Wachtwoord wijzigen", "LabelChangePassword": "Wachtwoord wijzigen",
"LabelChannels": "Kanalen", "LabelChannels": "Kanalen",
"LabelChapters": "Hoofdstukken", "LabelChapters": "Hoofdstukken",
@ -266,6 +269,9 @@
"LabelDownload": "Download", "LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episodes", "LabelDownloadNEpisodes": "Download {0} episodes",
"LabelDuration": "Duur", "LabelDuration": "Duur",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Gevonden duur:", "LabelDurationFound": "Gevonden duur:",
"LabelEbook": "Ebook", "LabelEbook": "Ebook",
"LabelEbooks": "Ebooks", "LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Afleveringtype", "LabelEpisodeType": "Afleveringtype",
"LabelExample": "Voorbeeld", "LabelExample": "Voorbeeld",
"LabelExplicit": "Expliciet", "LabelExplicit": "Expliciet",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata", "LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Bestand", "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", "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", "LabelSettingsTimeFormat": "Tijdformat",
"LabelShowAll": "Toon alle", "LabelShowAll": "Toon alle",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Grootte", "LabelSize": "Grootte",
"LabelSleepTimer": "Slaaptimer", "LabelSleepTimer": "Slaaptimer",
"LabelSlug": "Slug", "LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Weet je zeker dat je alle afleveringen als niet-voltooid wil markeren?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Weet je zeker dat je alle hoofdstukken wil verwijderen?",
"MessageConfirmRemoveAuthor": "Weet je zeker dat je auteur \"{0}\" wil verwijderen?", "MessageConfirmRemoveAuthor": "Weet je zeker dat je auteur \"{0}\" wil verwijderen?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Boekwijzer verwijderd", "ToastBookmarkRemoveSuccess": "Boekwijzer verwijderd",
"ToastBookmarkUpdateFailed": "Bijwerken boekwijzer mislukt", "ToastBookmarkUpdateFailed": "Bijwerken boekwijzer mislukt",
"ToastBookmarkUpdateSuccess": "Boekwijzer bijgewerkt", "ToastBookmarkUpdateSuccess": "Boekwijzer bijgewerkt",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Hoofdstukken bevatten fouten", "ToastChaptersHaveErrors": "Hoofdstukken bevatten fouten",
"ToastChaptersMustHaveTitles": "Hoofdstukken moeten titels hebben", "ToastChaptersMustHaveTitles": "Hoofdstukken moeten titels hebben",
"ToastCollectionItemsRemoveFailed": "Verwijderen onderdeel (of onderdelen) uit collectie mislukt", "ToastCollectionItemsRemoveFailed": "Verwijderen onderdeel (of onderdelen) uit collectie mislukt",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Collectie verwijderd", "ToastCollectionRemoveSuccess": "Collectie verwijderd",
"ToastCollectionUpdateFailed": "Bijwerken collectie mislukt", "ToastCollectionUpdateFailed": "Bijwerken collectie mislukt",
"ToastCollectionUpdateSuccess": "Collectie bijgewerkt", "ToastCollectionUpdateSuccess": "Collectie bijgewerkt",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Bijwerken cover onderdeel mislukt", "ToastItemCoverUpdateFailed": "Bijwerken cover onderdeel mislukt",
"ToastItemCoverUpdateSuccess": "Cover onderdeel bijgewerkt", "ToastItemCoverUpdateSuccess": "Cover onderdeel bijgewerkt",
"ToastItemDetailsUpdateFailed": "Bijwerken details onderdeel mislukt", "ToastItemDetailsUpdateFailed": "Bijwerken details onderdeel mislukt",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook verstuurd naar apparaat \"{0}\"", "ToastSendEbookToDeviceSuccess": "Ebook verstuurd naar apparaat \"{0}\"",
"ToastSeriesUpdateFailed": "Bijwerken serie mislukt", "ToastSeriesUpdateFailed": "Bijwerken serie mislukt",
"ToastSeriesUpdateSuccess": "Bijwerken serie gelukt", "ToastSeriesUpdateSuccess": "Bijwerken serie gelukt",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Verwijderen sessie mislukt", "ToastSessionDeleteFailed": "Verwijderen sessie mislukt",
"ToastSessionDeleteSuccess": "Sessie verwijderd", "ToastSessionDeleteSuccess": "Sessie verwijderd",
"ToastSocketConnected": "Socket verbonden", "ToastSocketConnected": "Socket verbonden",
"ToastSocketDisconnected": "Socket niet verbonden", "ToastSocketDisconnected": "Socket niet verbonden",
"ToastSocketFailedToConnect": "Verbinding Socket mislukt", "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", "ToastUserDeleteFailed": "Verwijderen gebruiker mislukt",
"ToastUserDeleteSuccess": "Gebruiker verwijderd" "ToastUserDeleteSuccess": "Gebruiker verwijderd"
} }

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Year {0} in Review", "HeaderYearReview": "Year {0} in Review",
"HeaderYourStats": "Din statistikk", "HeaderYourStats": "Din statistikk",
"LabelAbridged": "Forkortet", "LabelAbridged": "Forkortet",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Kontotype", "LabelAccountType": "Kontotype",
"LabelAccountTypeAdmin": "Admin", "LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Gjest", "LabelAccountTypeGuest": "Gjest",
@ -229,6 +231,7 @@
"LabelBitrate": "Bithastighet", "LabelBitrate": "Bithastighet",
"LabelBooks": "Bøker", "LabelBooks": "Bøker",
"LabelButtonText": "Button Text", "LabelButtonText": "Button Text",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Endre passord", "LabelChangePassword": "Endre passord",
"LabelChannels": "Kanaler", "LabelChannels": "Kanaler",
"LabelChapters": "Kapitler", "LabelChapters": "Kapitler",
@ -266,6 +269,9 @@
"LabelDownload": "Last ned", "LabelDownload": "Last ned",
"LabelDownloadNEpisodes": "Last ned {0} episoder", "LabelDownloadNEpisodes": "Last ned {0} episoder",
"LabelDuration": "Varighet", "LabelDuration": "Varighet",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Varighet funnet:", "LabelDurationFound": "Varighet funnet:",
"LabelEbook": "Ebok", "LabelEbook": "Ebok",
"LabelEbooks": "Ebøker", "LabelEbooks": "Ebøker",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Episode type", "LabelEpisodeType": "Episode type",
"LabelExample": "Eksempel", "LabelExample": "Eksempel",
"LabelExplicit": "Eksplisitt", "LabelExplicit": "Eksplisitt",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "Feed Adresse", "LabelFeedURL": "Feed Adresse",
"LabelFetchingMetadata": "Fetching Metadata", "LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Fil", "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", "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", "LabelSettingsTimeFormat": "Tid format",
"LabelShowAll": "Vis alt", "LabelShowAll": "Vis alt",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Størrelse", "LabelSize": "Størrelse",
"LabelSleepTimer": "Sove-timer", "LabelSleepTimer": "Sove-timer",
"LabelSlug": "Slug", "LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Er du sikker på at du vil markere alle episodene som ikke fullført?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Er du sikker på at du vil fjerne alle kapitler?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Bokmerke fjernet", "ToastBookmarkRemoveSuccess": "Bokmerke fjernet",
"ToastBookmarkUpdateFailed": "Misslykkes å oppdatere bokmerke", "ToastBookmarkUpdateFailed": "Misslykkes å oppdatere bokmerke",
"ToastBookmarkUpdateSuccess": "Bokmerke oppdatert", "ToastBookmarkUpdateSuccess": "Bokmerke oppdatert",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Kapittel har feil", "ToastChaptersHaveErrors": "Kapittel har feil",
"ToastChaptersMustHaveTitles": "Kapittel må ha titler", "ToastChaptersMustHaveTitles": "Kapittel må ha titler",
"ToastCollectionItemsRemoveFailed": "Misslykkes å fjerne gjenstand(er) fra samling", "ToastCollectionItemsRemoveFailed": "Misslykkes å fjerne gjenstand(er) fra samling",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Samling fjernet", "ToastCollectionRemoveSuccess": "Samling fjernet",
"ToastCollectionUpdateFailed": "Misslykkes å oppdatere samling", "ToastCollectionUpdateFailed": "Misslykkes å oppdatere samling",
"ToastCollectionUpdateSuccess": "samlingupdated", "ToastCollectionUpdateSuccess": "samlingupdated",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastFailedToLoadData": "Failed to load data",
"ToastItemCoverUpdateFailed": "Misslykkes å oppdatere omslag", "ToastItemCoverUpdateFailed": "Misslykkes å oppdatere omslag",
"ToastItemCoverUpdateSuccess": "Omslag oppdatert", "ToastItemCoverUpdateSuccess": "Omslag oppdatert",
"ToastItemDetailsUpdateFailed": "Misslykkes å oppdatere detaljer", "ToastItemDetailsUpdateFailed": "Misslykkes å oppdatere detaljer",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebok sendt til \"{0}\"", "ToastSendEbookToDeviceSuccess": "Ebok sendt til \"{0}\"",
"ToastSeriesUpdateFailed": "Misslykkes å oppdatere serie", "ToastSeriesUpdateFailed": "Misslykkes å oppdatere serie",
"ToastSeriesUpdateSuccess": "Serie oppdatert", "ToastSeriesUpdateSuccess": "Serie oppdatert",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionDeleteFailed": "Misslykkes å slette sesjon", "ToastSessionDeleteFailed": "Misslykkes å slette sesjon",
"ToastSessionDeleteSuccess": "Sesjon slettet", "ToastSessionDeleteSuccess": "Sesjon slettet",
"ToastSocketConnected": "Socket koblet til", "ToastSocketConnected": "Socket koblet til",
"ToastSocketDisconnected": "Socket koblet fra", "ToastSocketDisconnected": "Socket koblet fra",
"ToastSocketFailedToConnect": "Misslykkes å koble til Socket", "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", "ToastUserDeleteFailed": "Misslykkes å slette bruker",
"ToastUserDeleteSuccess": "Bruker slettet" "ToastUserDeleteSuccess": "Bruker slettet"
} }

View file

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

View file

@ -61,8 +61,8 @@
"ButtonQueueRemoveItem": "Remover da Lista", "ButtonQueueRemoveItem": "Remover da Lista",
"ButtonQuickMatch": "Consulta rápida", "ButtonQuickMatch": "Consulta rápida",
"ButtonRead": "Ler", "ButtonRead": "Ler",
"ButtonReadLess": "Read less", "ButtonReadLess": "Ler menos",
"ButtonReadMore": "Read more", "ButtonReadMore": "Ler mais",
"ButtonRefresh": "Atualizar", "ButtonRefresh": "Atualizar",
"ButtonRemove": "Remover", "ButtonRemove": "Remover",
"ButtonRemoveAll": "Remover Todos", "ButtonRemoveAll": "Remover Todos",
@ -189,6 +189,8 @@
"HeaderYearReview": "Retrospectiva de {0} ", "HeaderYearReview": "Retrospectiva de {0} ",
"HeaderYourStats": "Suas Estatísticas", "HeaderYourStats": "Suas Estatísticas",
"LabelAbridged": "Versão Abreviada", "LabelAbridged": "Versão Abreviada",
"LabelAbridgedChecked": "Abreviada (verificada)",
"LabelAbridgedUnchecked": "Não Abreviada (não verificada)",
"LabelAccountType": "Tipo de Conta", "LabelAccountType": "Tipo de Conta",
"LabelAccountTypeAdmin": "Administrador", "LabelAccountTypeAdmin": "Administrador",
"LabelAccountTypeGuest": "Convidado", "LabelAccountTypeGuest": "Convidado",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate", "LabelBitrate": "Bitrate",
"LabelBooks": "Livros", "LabelBooks": "Livros",
"LabelButtonText": "Texto do botão", "LabelButtonText": "Texto do botão",
"LabelByAuthor": "por {0}",
"LabelChangePassword": "Trocar Senha", "LabelChangePassword": "Trocar Senha",
"LabelChannels": "Canais", "LabelChannels": "Canais",
"LabelChapters": "Capítulos", "LabelChapters": "Capítulos",
@ -266,6 +269,9 @@
"LabelDownload": "Download", "LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download de {0} Episódios", "LabelDownloadNEpisodes": "Download de {0} Episódios",
"LabelDuration": "Duração", "LabelDuration": "Duração",
"LabelDurationComparisonExactMatch": "(exato)",
"LabelDurationComparisonLonger": "({0} maior)",
"LabelDurationComparisonShorter": "({0} menor)",
"LabelDurationFound": "Duração comprovada:", "LabelDurationFound": "Duração comprovada:",
"LabelEbook": "Ebook", "LabelEbook": "Ebook",
"LabelEbooks": "Ebooks", "LabelEbooks": "Ebooks",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Tipo do Episódio", "LabelEpisodeType": "Tipo do Episódio",
"LabelExample": "Exemplo", "LabelExample": "Exemplo",
"LabelExplicit": "Explícito", "LabelExplicit": "Explícito",
"LabelExplicitChecked": "Explícito (verificado)",
"LabelExplicitUnchecked": "Não explícito (não verificado)",
"LabelFeedURL": "URL do Feed", "LabelFeedURL": "URL do Feed",
"LabelFetchingMetadata": "Buscando Metadados", "LabelFetchingMetadata": "Buscando Metadados",
"LabelFile": "Arquivo", "LabelFile": "Arquivo",
@ -439,7 +447,7 @@
"LabelSearchTitle": "Busca por Título", "LabelSearchTitle": "Busca por Título",
"LabelSearchTitleOrASIN": "Busca por Título ou ASIN", "LabelSearchTitleOrASIN": "Busca por Título ou ASIN",
"LabelSeason": "Temporada", "LabelSeason": "Temporada",
"LabelSelectAll": "Select all", "LabelSelectAll": "Selecionar todos",
"LabelSelectAllEpisodes": "Selecionar todos os Episódios", "LabelSelectAllEpisodes": "Selecionar todos os Episódios",
"LabelSelectEpisodesShowing": "Selecionar os {0} Episódios Visíveis", "LabelSelectEpisodesShowing": "Selecionar os {0} Episódios Visíveis",
"LabelSelectUsers": "Selecionar usuários", "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", "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", "LabelSettingsTimeFormat": "Formato da Tempo",
"LabelShowAll": "Exibir Todos", "LabelShowAll": "Exibir Todos",
"LabelShowSeconds": "Exibir segundos",
"LabelSize": "Tamanho", "LabelSize": "Tamanho",
"LabelSleepTimer": "Timer", "LabelSleepTimer": "Timer",
"LabelSlug": "Slug", "LabelSlug": "Slug",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Tem certeza de que deseja marcar todos os episódios como não concluídos?", "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?", "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?", "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?", "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?", "MessageConfirmRemoveAllChapters": "Tem certeza de que deseja remover todos os capítulos?",
"MessageConfirmRemoveAuthor": "Tem certeza de que deseja remover o autor \"{0}\"?", "MessageConfirmRemoveAuthor": "Tem certeza de que deseja remover o autor \"{0}\"?",
@ -631,7 +641,7 @@
"MessageListeningSessionsInTheLastYear": "{0} sessões de escuta no ano anterior", "MessageListeningSessionsInTheLastYear": "{0} sessões de escuta no ano anterior",
"MessageLoading": "Carregando...", "MessageLoading": "Carregando...",
"MessageLoadingFolders": "Carregando pastas...", "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!", "MessageM4BFailed": "Falha no M4B!",
"MessageM4BFinished": "M4B Concluído!", "MessageM4BFinished": "M4B Concluído!",
"MessageMapChapterTitles": "Designar títulos de capítulos a partir dos capítulos existentes no audiobook sem ajustar seus tempos", "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", "ToastBookmarkRemoveSuccess": "Marcador removido",
"ToastBookmarkUpdateFailed": "Falha ao atualizar o marcador", "ToastBookmarkUpdateFailed": "Falha ao atualizar o marcador",
"ToastBookmarkUpdateSuccess": "Marcador atualizado", "ToastBookmarkUpdateSuccess": "Marcador atualizado",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Capítulos com erro", "ToastChaptersHaveErrors": "Capítulos com erro",
"ToastChaptersMustHaveTitles": "Capítulos precisam ter títulos", "ToastChaptersMustHaveTitles": "Capítulos precisam ter títulos",
"ToastCollectionItemsRemoveFailed": "Falha ao remover item(ns) da coleção", "ToastCollectionItemsRemoveFailed": "Falha ao remover item(ns) da coleção",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Coleção removida", "ToastCollectionRemoveSuccess": "Coleção removida",
"ToastCollectionUpdateFailed": "Falha ao atualizar coleção", "ToastCollectionUpdateFailed": "Falha ao atualizar coleção",
"ToastCollectionUpdateSuccess": "Coleção atualizada", "ToastCollectionUpdateSuccess": "Coleção atualizada",
"ToastDeleteFileFailed": "Falha ao apagar arquivo",
"ToastDeleteFileSuccess": "Arquivo apagado",
"ToastFailedToLoadData": "Falha ao carregar dados",
"ToastItemCoverUpdateFailed": "Falha ao atualizar capa do item", "ToastItemCoverUpdateFailed": "Falha ao atualizar capa do item",
"ToastItemCoverUpdateSuccess": "Capa do item atualizada", "ToastItemCoverUpdateSuccess": "Capa do item atualizada",
"ToastItemDetailsUpdateFailed": "Falha ao atualizar detalhes do item", "ToastItemDetailsUpdateFailed": "Falha ao atualizar detalhes do item",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Ebook enviado para o dispositivo \"{0}\"", "ToastSendEbookToDeviceSuccess": "Ebook enviado para o dispositivo \"{0}\"",
"ToastSeriesUpdateFailed": "Falha ao atualizar série", "ToastSeriesUpdateFailed": "Falha ao atualizar série",
"ToastSeriesUpdateSuccess": "Série atualizada", "ToastSeriesUpdateSuccess": "Série atualizada",
"ToastServerSettingsUpdateFailed": "Falha ao atualizar configurações do servidor",
"ToastServerSettingsUpdateSuccess": "Configurações do servidor atualizadas",
"ToastSessionDeleteFailed": "Falha ao apagar sessão", "ToastSessionDeleteFailed": "Falha ao apagar sessão",
"ToastSessionDeleteSuccess": "Sessão apagada", "ToastSessionDeleteSuccess": "Sessão apagada",
"ToastSocketConnected": "Socket conectado", "ToastSocketConnected": "Socket conectado",
"ToastSocketDisconnected": "Socket desconectado", "ToastSocketDisconnected": "Socket desconectado",
"ToastSocketFailedToConnect": "Falha na conexão do socket", "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", "ToastUserDeleteFailed": "Falha ao apagar usuário",
"ToastUserDeleteSuccess": "Usuário apagado" "ToastUserDeleteSuccess": "Usuário apagado"
} }

View file

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

View file

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

View file

@ -18,7 +18,7 @@
"ButtonChooseFiles": "Обрати файли", "ButtonChooseFiles": "Обрати файли",
"ButtonClearFilter": "Очистити фільтр", "ButtonClearFilter": "Очистити фільтр",
"ButtonCloseFeed": "Закрити стрічку", "ButtonCloseFeed": "Закрити стрічку",
"ButtonCollections": "Колекції", "ButtonCollections": "Добірки",
"ButtonConfigureScanner": "Налаштувати сканер", "ButtonConfigureScanner": "Налаштувати сканер",
"ButtonCreate": "Створити", "ButtonCreate": "Створити",
"ButtonCreateBackup": "Створити резервну копію", "ButtonCreateBackup": "Створити резервну копію",
@ -61,8 +61,8 @@
"ButtonQueueRemoveItem": "Вилучити з черги", "ButtonQueueRemoveItem": "Вилучити з черги",
"ButtonQuickMatch": "Швидкий пошук", "ButtonQuickMatch": "Швидкий пошук",
"ButtonRead": "Читати", "ButtonRead": "Читати",
"ButtonReadLess": "Read less", "ButtonReadLess": "Згорнути",
"ButtonReadMore": "Read more", "ButtonReadMore": "Читати далі",
"ButtonRefresh": "Оновити", "ButtonRefresh": "Оновити",
"ButtonRemove": "Видалити", "ButtonRemove": "Видалити",
"ButtonRemoveAll": "Видалити все", "ButtonRemoveAll": "Видалити все",
@ -111,8 +111,8 @@
"HeaderChangePassword": "Змінити пароль", "HeaderChangePassword": "Змінити пароль",
"HeaderChapters": "Глави", "HeaderChapters": "Глави",
"HeaderChooseAFolder": "Обрати теку", "HeaderChooseAFolder": "Обрати теку",
"HeaderCollection": "Колекція", "HeaderCollection": "Добірка",
"HeaderCollectionItems": "Елементи колекції", "HeaderCollectionItems": "Елементи добірки",
"HeaderCover": "Обкладинка", "HeaderCover": "Обкладинка",
"HeaderCurrentDownloads": "Поточні завантаження", "HeaderCurrentDownloads": "Поточні завантаження",
"HeaderCustomMetadataProviders": "Постачальники метаданих", "HeaderCustomMetadataProviders": "Постачальники метаданих",
@ -189,6 +189,8 @@
"HeaderYearReview": "Підсумки {0} року", "HeaderYearReview": "Підсумки {0} року",
"HeaderYourStats": "Ваша статистика", "HeaderYourStats": "Ваша статистика",
"LabelAbridged": "Скорочена", "LabelAbridged": "Скорочена",
"LabelAbridgedChecked": "Скорочена (з прапорцем)",
"LabelAbridgedUnchecked": "Нескорочена (без прапорця)",
"LabelAccountType": "Тип профілю", "LabelAccountType": "Тип профілю",
"LabelAccountTypeAdmin": "Адміністратор", "LabelAccountTypeAdmin": "Адміністратор",
"LabelAccountTypeGuest": "Гість", "LabelAccountTypeGuest": "Гість",
@ -229,6 +231,7 @@
"LabelBitrate": "Бітрейт", "LabelBitrate": "Бітрейт",
"LabelBooks": "Книги", "LabelBooks": "Книги",
"LabelButtonText": "Текст кнопки", "LabelButtonText": "Текст кнопки",
"LabelByAuthor": "від {0}",
"LabelChangePassword": "Змінити пароль", "LabelChangePassword": "Змінити пароль",
"LabelChannels": "Канали", "LabelChannels": "Канали",
"LabelChapters": "Глави", "LabelChapters": "Глави",
@ -251,7 +254,7 @@
"LabelCronExpression": "Команда cron", "LabelCronExpression": "Команда cron",
"LabelCurrent": "Поточне", "LabelCurrent": "Поточне",
"LabelCurrently": "Поточний:", "LabelCurrently": "Поточний:",
"LabelCustomCronExpression": "Спецільна команда cron:", "LabelCustomCronExpression": "Спеціальна команда cron:",
"LabelDatetime": "Дата й час", "LabelDatetime": "Дата й час",
"LabelDeleteFromFileSystemCheckbox": "Видалити з файлової системи (зніміть прапорець, щоб видалити лише з бази даних)", "LabelDeleteFromFileSystemCheckbox": "Видалити з файлової системи (зніміть прапорець, щоб видалити лише з бази даних)",
"LabelDescription": "Опис", "LabelDescription": "Опис",
@ -266,6 +269,9 @@
"LabelDownload": "Завантажити", "LabelDownload": "Завантажити",
"LabelDownloadNEpisodes": "Завантажити епізодів: {0}", "LabelDownloadNEpisodes": "Завантажити епізодів: {0}",
"LabelDuration": "Тривалість", "LabelDuration": "Тривалість",
"LabelDurationComparisonExactMatch": "(повний збіг)",
"LabelDurationComparisonLonger": "(на {0} довше)",
"LabelDurationComparisonShorter": "(на {0} коротше)",
"LabelDurationFound": "Виявлена тривалість:", "LabelDurationFound": "Виявлена тривалість:",
"LabelEbook": "Електронна книга", "LabelEbook": "Електронна книга",
"LabelEbooks": "Електронні книги", "LabelEbooks": "Електронні книги",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Тип епізоду", "LabelEpisodeType": "Тип епізоду",
"LabelExample": "Приклад", "LabelExample": "Приклад",
"LabelExplicit": "Відверта", "LabelExplicit": "Відверта",
"LabelExplicitChecked": "Відверта (з прапорцем)",
"LabelExplicitUnchecked": "Не відверта (без прапорця)",
"LabelFeedURL": "Адреса стрічки", "LabelFeedURL": "Адреса стрічки",
"LabelFetchingMetadata": "Отримання метаданих", "LabelFetchingMetadata": "Отримання метаданих",
"LabelFile": "Файл", "LabelFile": "Файл",
@ -387,9 +395,9 @@
"LabelNotStarted": "Не розпочато", "LabelNotStarted": "Не розпочато",
"LabelNumberOfBooks": "Кількість книг", "LabelNumberOfBooks": "Кількість книг",
"LabelNumberOfEpisodes": "Кількість епізодів", "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:", "LabelOpenIDAdvancedPermsClaimDescription": "Назва OpenID claim, що містить розширені дозволи на дії користувачів у додатку, які будуть застосовуватися до ролей, що не є адміністраторами (<b>якщо налаштовано</b>). Якщо у відповіді нема claim, у доступі до Audiobookshelf буде відмовлено. Якщо відсутня хоча б одна опція, відповідь буде вважатися <code>хибною</code>. Переконайтеся, що запит постачальника ідентифікаційних даних відповідає очікуваній структурі:",
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.", "LabelOpenIDClaims": "Не змінюйте наступні параметри, аби вимкнути розширене призначення груп і дозволів, автоматично призначаючи групу 'Користувач'.",
"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.", "LabelOpenIDGroupClaimDescription": "Ім'я OpenID claim, що містить список груп користувачів. Зазвичай їх називають <code>групами</code>. <b>Якщо налаштовано</b>, застосунок автоматично призначатиме ролі на основі членства користувача в групах, за умови, що ці групи названі в claim'і без урахування реєстру 'admin', 'user' або 'guest'. Claim мусить містити список, і якщо користувач належить до кількох груп, програма призначить йому роль, що відповідає найвищому рівню доступу. Якщо жодна група не збігається, у доступі буде відмовлено.",
"LabelOpenRSSFeed": "Відкрити RSS-канал", "LabelOpenRSSFeed": "Відкрити RSS-канал",
"LabelOverwrite": "Перезаписати", "LabelOverwrite": "Перезаписати",
"LabelPassword": "Пароль", "LabelPassword": "Пароль",
@ -439,7 +447,7 @@
"LabelSearchTitle": "Пошук за назвою", "LabelSearchTitle": "Пошук за назвою",
"LabelSearchTitleOrASIN": "Пошук назви або ASIN", "LabelSearchTitleOrASIN": "Пошук назви або ASIN",
"LabelSeason": "Сезон", "LabelSeason": "Сезон",
"LabelSelectAll": "Select all", "LabelSelectAll": "Вибрати все",
"LabelSelectAllEpisodes": "Вибрати всі серії", "LabelSelectAllEpisodes": "Вибрати всі серії",
"LabelSelectEpisodesShowing": "Обрати показані епізоди: {0}", "LabelSelectEpisodesShowing": "Обрати показані епізоди: {0}",
"LabelSelectUsers": "Вибрати користувачів", "LabelSelectUsers": "Вибрати користувачів",
@ -488,6 +496,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "За замовчуванням файли метаданих зберігаються у /metadata/items. Цей параметр увімкне збереження метаданих у теці елемента бібліотеки", "LabelSettingsStoreMetadataWithItemHelp": "За замовчуванням файли метаданих зберігаються у /metadata/items. Цей параметр увімкне збереження метаданих у теці елемента бібліотеки",
"LabelSettingsTimeFormat": "Формат часу", "LabelSettingsTimeFormat": "Формат часу",
"LabelShowAll": "Показати все", "LabelShowAll": "Показати все",
"LabelShowSeconds": "Показувати секунди",
"LabelSize": "Розмір", "LabelSize": "Розмір",
"LabelSleepTimer": "Таймер вимкнення", "LabelSleepTimer": "Таймер вимкнення",
"LabelSlug": "Назва", "LabelSlug": "Назва",
@ -527,7 +536,7 @@
"LabelTimeBase": "Шкала часу", "LabelTimeBase": "Шкала часу",
"LabelTimeListened": "Часу прослухано", "LabelTimeListened": "Часу прослухано",
"LabelTimeListenedToday": "Сьогодні прослухано", "LabelTimeListenedToday": "Сьогодні прослухано",
"LabelTimeRemaining": "Залишилося: {0}", "LabelTimeRemaining": "Лишилося: {0}",
"LabelTimeToShift": "На скільки секунд зсунути", "LabelTimeToShift": "На скільки секунд зсунути",
"LabelTitle": "Назва", "LabelTitle": "Назва",
"LabelToolsEmbedMetadata": "Вбудувати метадані", "LabelToolsEmbedMetadata": "Вбудувати метадані",
@ -599,6 +608,7 @@
"MessageConfirmMarkAllEpisodesNotFinished": "Ви дійсно бажаєте позначити усі епізоди незавершеними?", "MessageConfirmMarkAllEpisodesNotFinished": "Ви дійсно бажаєте позначити усі епізоди незавершеними?",
"MessageConfirmMarkSeriesFinished": "Ви дійсно бажаєте позначити усі книги серії завершеними?", "MessageConfirmMarkSeriesFinished": "Ви дійсно бажаєте позначити усі книги серії завершеними?",
"MessageConfirmMarkSeriesNotFinished": "Ви дійсно бажаєте позначити всі книги серії незавершеними?", "MessageConfirmMarkSeriesNotFinished": "Ви дійсно бажаєте позначити всі книги серії незавершеними?",
"MessageConfirmPurgeCache": "Очищення кешу видалить усю теку <code>/metadata/cache</code>. <br /><br />Ви дійсно бажаєте видалити теку кешу?",
"MessageConfirmQuickEmbed": "Увага! Швидке вбудування не створює резервних копій ваших аудіо. Переконайтеся, що маєте копію ваших файлів.<br><br>Продовжити?", "MessageConfirmQuickEmbed": "Увага! Швидке вбудування не створює резервних копій ваших аудіо. Переконайтеся, що маєте копію ваших файлів.<br><br>Продовжити?",
"MessageConfirmRemoveAllChapters": "Ви дійсно бажаєте видалити усі глави?", "MessageConfirmRemoveAllChapters": "Ви дійсно бажаєте видалити усі глави?",
"MessageConfirmRemoveAuthor": "Ви дійсно бажаєте видалити автора \"{0}\"?", "MessageConfirmRemoveAuthor": "Ви дійсно бажаєте видалити автора \"{0}\"?",
@ -631,7 +641,7 @@
"MessageListeningSessionsInTheLastYear": "Сесій прослуховування минулого року: {0}", "MessageListeningSessionsInTheLastYear": "Сесій прослуховування минулого року: {0}",
"MessageLoading": "Завантаження...", "MessageLoading": "Завантаження...",
"MessageLoadingFolders": "Завантаження тек...", "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!", "MessageM4BFailed": "Помилка M4B!",
"MessageM4BFinished": "M4B створено!", "MessageM4BFinished": "M4B створено!",
"MessageMapChapterTitles": "Встановіть назви глав вашої аудіокниги без визначення налаштувань тривалості", "MessageMapChapterTitles": "Встановіть назви глав вашої аудіокниги без визначення налаштувань тривалості",
@ -735,6 +745,8 @@
"ToastBookmarkRemoveSuccess": "Закладку видалено", "ToastBookmarkRemoveSuccess": "Закладку видалено",
"ToastBookmarkUpdateFailed": "Не вдалося оновити закладку", "ToastBookmarkUpdateFailed": "Не вдалося оновити закладку",
"ToastBookmarkUpdateSuccess": "Закладку оновлено", "ToastBookmarkUpdateSuccess": "Закладку оновлено",
"ToastCachePurgeFailed": "Не вдалося очистити кеш",
"ToastCachePurgeSuccess": "Кеш очищено",
"ToastChaptersHaveErrors": "Глави містять помилки", "ToastChaptersHaveErrors": "Глави містять помилки",
"ToastChaptersMustHaveTitles": "Глави повинні мати назви", "ToastChaptersMustHaveTitles": "Глави повинні мати назви",
"ToastCollectionItemsRemoveFailed": "Не вдалося видалити елемент(и) з добірки", "ToastCollectionItemsRemoveFailed": "Не вдалося видалити елемент(и) з добірки",
@ -743,6 +755,9 @@
"ToastCollectionRemoveSuccess": "Добірку видалено", "ToastCollectionRemoveSuccess": "Добірку видалено",
"ToastCollectionUpdateFailed": "Не вдалося оновити добірку", "ToastCollectionUpdateFailed": "Не вдалося оновити добірку",
"ToastCollectionUpdateSuccess": "Добірку оновлено", "ToastCollectionUpdateSuccess": "Добірку оновлено",
"ToastDeleteFileFailed": "Не вдалося видалити файл",
"ToastDeleteFileSuccess": "Файл видалено",
"ToastFailedToLoadData": "Не вдалося завантажити дані",
"ToastItemCoverUpdateFailed": "Не вдалося оновити обкладинку", "ToastItemCoverUpdateFailed": "Не вдалося оновити обкладинку",
"ToastItemCoverUpdateSuccess": "Обкладинку елемента оновлено", "ToastItemCoverUpdateSuccess": "Обкладинку елемента оновлено",
"ToastItemDetailsUpdateFailed": "Не вдалося оновити подробиці елемента", "ToastItemDetailsUpdateFailed": "Не вдалося оновити подробиці елемента",
@ -776,11 +791,16 @@
"ToastSendEbookToDeviceSuccess": "Електронну книгу надіслано на пристрій \"{0}\"", "ToastSendEbookToDeviceSuccess": "Електронну книгу надіслано на пристрій \"{0}\"",
"ToastSeriesUpdateFailed": "Не вдалося оновити серію", "ToastSeriesUpdateFailed": "Не вдалося оновити серію",
"ToastSeriesUpdateSuccess": "Серію успішно оновлено", "ToastSeriesUpdateSuccess": "Серію успішно оновлено",
"ToastServerSettingsUpdateFailed": "Не вдалося оновити налаштування сервера",
"ToastServerSettingsUpdateSuccess": "Налаштування сервера оновлено",
"ToastSessionDeleteFailed": "Не вдалося видалити сесію", "ToastSessionDeleteFailed": "Не вдалося видалити сесію",
"ToastSessionDeleteSuccess": "Сесію видалено", "ToastSessionDeleteSuccess": "Сесію видалено",
"ToastSocketConnected": "Сокет під'єднано", "ToastSocketConnected": "Сокет під'єднано",
"ToastSocketDisconnected": "Сокет від'єднано", "ToastSocketDisconnected": "Сокет від'єднано",
"ToastSocketFailedToConnect": "Не вдалося під'єднатися до сокета", "ToastSocketFailedToConnect": "Не вдалося під'єднатися до сокета",
"ToastSortingPrefixesEmptyError": "Мусить мати хоча б 1 префікс сортування",
"ToastSortingPrefixesUpdateFailed": "Не вдалося оновити префікси сортування",
"ToastSortingPrefixesUpdateSuccess": "Префікси сортування оновлено ({0})",
"ToastUserDeleteFailed": "Не вдалося видалити користувача", "ToastUserDeleteFailed": "Не вдалося видалити користувача",
"ToastUserDeleteSuccess": "Користувача видалено" "ToastUserDeleteSuccess": "Користувача видалено"
} }

View file

@ -189,6 +189,8 @@
"HeaderYearReview": "Năm {0} trong Xem Xét", "HeaderYearReview": "Năm {0} trong Xem Xét",
"HeaderYourStats": "Thống Kê Của Bạn", "HeaderYourStats": "Thống Kê Của Bạn",
"LabelAbridged": "Rút Gọn", "LabelAbridged": "Rút Gọn",
"LabelAbridgedChecked": "Abridged (checked)",
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
"LabelAccountType": "Loại Tài Khoản", "LabelAccountType": "Loại Tài Khoản",
"LabelAccountTypeAdmin": "Quản Trị Viên", "LabelAccountTypeAdmin": "Quản Trị Viên",
"LabelAccountTypeGuest": "Khách", "LabelAccountTypeGuest": "Khách",
@ -229,6 +231,7 @@
"LabelBitrate": "Bitrate", "LabelBitrate": "Bitrate",
"LabelBooks": "Sách", "LabelBooks": "Sách",
"LabelButtonText": "Nút Văn Bản", "LabelButtonText": "Nút Văn Bản",
"LabelByAuthor": "by {0}",
"LabelChangePassword": "Đổi Mật Khẩu", "LabelChangePassword": "Đổi Mật Khẩu",
"LabelChannels": "Kênh", "LabelChannels": "Kênh",
"LabelChapters": "Chương", "LabelChapters": "Chương",
@ -266,6 +269,9 @@
"LabelDownload": "Tải Xuống", "LabelDownload": "Tải Xuống",
"LabelDownloadNEpisodes": "Tải Xuống {0} Tập", "LabelDownloadNEpisodes": "Tải Xuống {0} Tập",
"LabelDuration": "Thời Lượng", "LabelDuration": "Thời Lượng",
"LabelDurationComparisonExactMatch": "(exact match)",
"LabelDurationComparisonLonger": "({0} longer)",
"LabelDurationComparisonShorter": "({0} shorter)",
"LabelDurationFound": "Thời lượng được tìm thấy:", "LabelDurationFound": "Thời lượng được tìm thấy:",
"LabelEbook": "Ebook", "LabelEbook": "Ebook",
"LabelEbooks": "Các Ebook", "LabelEbooks": "Các Ebook",
@ -283,6 +289,8 @@
"LabelEpisodeType": "Loại Tập", "LabelEpisodeType": "Loại Tập",
"LabelExample": "Ví Dụ", "LabelExample": "Ví Dụ",
"LabelExplicit": "Rõ Ràng", "LabelExplicit": "Rõ Ràng",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
"LabelFeedURL": "URL Feed", "LabelFeedURL": "URL Feed",
"LabelFetchingMetadata": "Đang Lấy Metadata", "LabelFetchingMetadata": "Đang Lấy Metadata",
"LabelFile": "Tệp", "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", "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", "LabelSettingsTimeFormat": "Định dạng Thời gian",
"LabelShowAll": "Hiển thị Tất cả", "LabelShowAll": "Hiển thị Tất cả",
"LabelShowSeconds": "Show seconds",
"LabelSize": "Kích thước", "LabelSize": "Kích thước",
"LabelSleepTimer": "Hẹn giờ tắt", "LabelSleepTimer": "Hẹn giờ tắt",
"LabelSlug": "Slug", "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?", "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?", "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?", "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?", "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?", "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?", "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", "ToastBookmarkRemoveSuccess": "Đánh dấu đã được xóa",
"ToastBookmarkUpdateFailed": "Cập nhật đánh dấu thất bại", "ToastBookmarkUpdateFailed": "Cập nhật đánh dấu thất bại",
"ToastBookmarkUpdateSuccess": "Đánh dấu đã được cập nhật", "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", "ToastChaptersHaveErrors": "Các chương có lỗi",
"ToastChaptersMustHaveTitles": "Các chương phải có tiêu đề", "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", "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", "ToastCollectionRemoveSuccess": "Bộ sưu tập đã được xóa",
"ToastCollectionUpdateFailed": "Cập nhật bộ sưu tập thất bại", "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", "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", "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", "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", "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}\"", "ToastSendEbookToDeviceSuccess": "Ebook đã được gửi đến thiết bị \"{0}\"",
"ToastSeriesUpdateFailed": "Cập nhật loạt truyện thất bại", "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", "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", "ToastSessionDeleteFailed": "Xóa phiên thất bại",
"ToastSessionDeleteSuccess": "Phiên đã được xóa", "ToastSessionDeleteSuccess": "Phiên đã được xóa",
"ToastSocketConnected": "Kết nối socket", "ToastSocketConnected": "Kết nối socket",
"ToastSocketDisconnected": "Ngắt kết nối socket", "ToastSocketDisconnected": "Ngắt kết nối socket",
"ToastSocketFailedToConnect": "Không thể 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", "ToastUserDeleteFailed": "Xóa người dùng thất bại",
"ToastUserDeleteSuccess": "Người dùng đã được xóa" "ToastUserDeleteSuccess": "Người dùng đã được xóa"
} }

View file

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

View file

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

View file

@ -23,7 +23,7 @@ const libraryItemsPodcastFilters = require('../utils/queries/libraryItemsPodcast
const authorFilters = require('../utils/queries/authorFilters') const authorFilters = require('../utils/queries/authorFilters')
class LibraryController { class LibraryController {
constructor() { } constructor() {}
async create(req, res) { async create(req, res) {
const newLibraryPayload = { const newLibraryPayload = {
@ -35,7 +35,7 @@ class LibraryController {
// Validate that the custom provider exists if given any // Validate that the custom provider exists if given any
if (newLibraryPayload.provider?.startsWith('custom-')) { 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`) Logger.error(`[LibraryController] Custom metadata provider "${newLibraryPayload.provider}" does not exist`)
return res.status(400).send('Custom metadata 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 // Validate folder paths exist or can be created & resolve rel paths
// returns 400 if a folder fails to access // 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)) f.fullPath = fileUtils.filePathToPOSIX(Path.resolve(f.fullPath))
return f return f
}) })
for (const folder of newLibraryPayload.folders) { for (const folder of newLibraryPayload.folders) {
try { try {
const direxists = await fs.pathExists(folder.fullPath) 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) await fs.mkdir(folder.fullPath)
} }
} catch (error) { } catch (error) {
@ -85,20 +86,20 @@ class LibraryController {
const librariesAccessible = req.user.librariesAccessible || [] const librariesAccessible = req.user.librariesAccessible || []
if (librariesAccessible.length) { if (librariesAccessible.length) {
return res.json({ 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({ res.json({
libraries: libraries.map(lib => lib.toJSON()) libraries: libraries.map((lib) => lib.toJSON())
}) })
} }
/** /**
* GET: /api/libraries/:id * GET: /api/libraries/:id
* *
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async findOne(req, res) { async findOne(req, res) {
const includeArray = (req.query.include || '').split(',') const includeArray = (req.query.include || '').split(',')
@ -120,8 +121,8 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/episode-downloads * GET: /api/libraries/:id/episode-downloads
* Get podcast episodes in download queue * Get podcast episodes in download queue
* @param {*} req * @param {*} req
* @param {*} res * @param {*} res
*/ */
async getEpisodeDownloadQueue(req, res) { async getEpisodeDownloadQueue(req, res) {
const libraryDownloadQueueDetails = this.podcastManager.getDownloadQueueDetails(req.library.id) const libraryDownloadQueueDetails = this.podcastManager.getDownloadQueueDetails(req.library.id)
@ -130,9 +131,9 @@ class LibraryController {
/** /**
* PATCH: /api/libraries/:id * PATCH: /api/libraries/:id
* *
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async update(req, res) { async update(req, res) {
/** @type {import('../objects/Library')} */ /** @type {import('../objects/Library')} */
@ -140,7 +141,7 @@ class LibraryController {
// Validate that the custom provider exists if given any // Validate that the custom provider exists if given any
if (req.body.provider?.startsWith('custom-')) { 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`) Logger.error(`[LibraryController] Custom metadata provider "${req.body.provider}" does not exist`)
return res.status(400).send('Custom metadata 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 // returns 400 if a new folder fails to access
if (req.body.folders) { if (req.body.folders) {
const newFolderPaths = [] const newFolderPaths = []
req.body.folders = req.body.folders.map(f => { req.body.folders = req.body.folders.map((f) => {
if (!f.id) { if (!f.id) {
f.fullPath = fileUtils.filePathToPOSIX(Path.resolve(f.fullPath)) f.fullPath = fileUtils.filePathToPOSIX(Path.resolve(f.fullPath))
newFolderPaths.push(f.fullPath) newFolderPaths.push(f.fullPath)
@ -161,10 +162,13 @@ class LibraryController {
const pathExists = await fs.pathExists(path) const pathExists = await fs.pathExists(path)
if (!pathExists) { if (!pathExists) {
// Ensure dir will recursively create directories which might be preferred over mkdir // Ensure dir will recursively create directories which might be preferred over mkdir
const success = await fs.ensureDir(path).then(() => true).catch((error) => { const success = await fs
Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error) .ensureDir(path)
return false .then(() => true)
}) .catch((error) => {
Logger.error(`[LibraryController] Failed to ensure folder dir "${path}"`, error)
return false
})
if (!success) { if (!success) {
return res.status(400).send(`Invalid folder directory "${path}"`) return res.status(400).send(`Invalid folder directory "${path}"`)
} }
@ -173,7 +177,7 @@ class LibraryController {
// Handle removing folders // Handle removing folders
for (const folder of library.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 // Remove library items in folder
const libraryItemsInFolder = await Database.libraryItemModel.findAll({ const libraryItemsInFolder = await Database.libraryItemModel.findAll({
where: { where: {
@ -195,7 +199,7 @@ class LibraryController {
for (const libraryItem of libraryItemsInFolder) { for (const libraryItem of libraryItemsInFolder) {
let mediaItemIds = [] let mediaItemIds = []
if (library.isPodcast) { if (library.isPodcast) {
mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id) mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id)
} else { } else {
mediaItemIds.push(libraryItem.mediaId) mediaItemIds.push(libraryItem.mediaId)
} }
@ -231,8 +235,8 @@ class LibraryController {
/** /**
* DELETE: /api/libraries/:id * DELETE: /api/libraries/:id
* Delete a library * Delete a library
* @param {*} req * @param {*} req
* @param {*} res * @param {*} res
*/ */
async delete(req, res) { async delete(req, res) {
const library = req.library const library = req.library
@ -267,7 +271,7 @@ class LibraryController {
for (const libraryItem of libraryItemsInLibrary) { for (const libraryItem of libraryItemsInLibrary) {
let mediaItemIds = [] let mediaItemIds = []
if (library.isPodcast) { if (library.isPodcast) {
mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id) mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id)
} else { } else {
mediaItemIds.push(libraryItem.mediaId) mediaItemIds.push(libraryItem.mediaId)
} }
@ -293,12 +297,15 @@ class LibraryController {
/** /**
* GET /api/libraries/:id/items * GET /api/libraries/:id/items
* *
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async getLibraryItems(req, 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 = { const payload = {
results: [], results: [],
@ -316,7 +323,9 @@ class LibraryController {
payload.offset = payload.page * payload.limit payload.offset = payload.page * payload.limit
// TODO: Temporary way of handling collapse sub-series. Either remove feature or handle through sql queries // 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]) const seriesId = libraryFilters.decode(payload.filterBy.split('.')[1])
payload.results = await libraryHelpers.handleCollapseSubseries(payload, seriesId, req.user, req.library) payload.results = await libraryHelpers.handleCollapseSubseries(payload, seriesId, req.user, req.library)
} else { } else {
@ -331,8 +340,8 @@ class LibraryController {
/** /**
* DELETE: /libraries/:id/issues * DELETE: /libraries/:id/issues
* Remove all library items missing or invalid * Remove all library items missing or invalid
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async removeLibraryItemsWithIssues(req, res) { async removeLibraryItemsWithIssues(req, res) {
const libraryItemsWithIssues = await Database.libraryItemModel.findAll({ const libraryItemsWithIssues = await Database.libraryItemModel.findAll({
@ -369,7 +378,7 @@ class LibraryController {
for (const libraryItem of libraryItemsWithIssues) { for (const libraryItem of libraryItemsWithIssues) {
let mediaItemIds = [] let mediaItemIds = []
if (req.library.isPodcast) { if (req.library.isPodcast) {
mediaItemIds = libraryItem.media.podcastEpisodes.map(pe => pe.id) mediaItemIds = libraryItem.media.podcastEpisodes.map((pe) => pe.id)
} else { } else {
mediaItemIds.push(libraryItem.mediaId) mediaItemIds.push(libraryItem.mediaId)
} }
@ -386,14 +395,17 @@ class LibraryController {
} }
/** /**
* GET: /api/libraries/:id/series * GET: /api/libraries/:id/series
* Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open * Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open
* *
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async getAllSeriesForLibrary(req, 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 = { const payload = {
results: [], results: [],
@ -421,12 +433,15 @@ class LibraryController {
* Optional includes (e.g. `?include=rssfeed,progress`) * Optional includes (e.g. `?include=rssfeed,progress`)
* rssfeed: adds `rssFeed` to series object if a feed is open * rssfeed: adds `rssFeed` to series object if a feed is open
* progress: adds `progress` to series object with { libraryItemIds:Array<llid>, libraryItemIdsFinished:Array<llid>, isFinished:boolean } * progress: adds `progress` to series object with { libraryItemIds:Array<llid>, libraryItemIdsFinished:Array<llid>, isFinished:boolean }
* *
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res - Series * @param {import('express').Response} res - Series
*/ */
async getSeriesForLibrary(req, res) { 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) const series = await Database.seriesModel.findByPk(req.params.seriesId)
if (!series) return res.sendStatus(404) if (!series) return res.sendStatus(404)
@ -436,10 +451,10 @@ class LibraryController {
const seriesJson = oldSeries.toJSON() const seriesJson = oldSeries.toJSON()
if (include.includes('progress')) { 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 = { seriesJson.progress = {
libraryItemIds: libraryItemsInSeries.map(li => li.id), libraryItemIds: libraryItemsInSeries.map((li) => li.id),
libraryItemIdsFinished: libraryItemsFinished.map(li => li.id), libraryItemIdsFinished: libraryItemsFinished.map((li) => li.id),
isFinished: libraryItemsFinished.length >= libraryItemsInSeries.length isFinished: libraryItemsFinished.length >= libraryItemsInSeries.length
} }
} }
@ -455,11 +470,14 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/collections * GET: /api/libraries/:id/collections
* Get all collections for library * Get all collections for library
* @param {*} req * @param {*} req
* @param {*} res * @param {*} res
*/ */
async getCollectionsForLibrary(req, 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 = { const payload = {
results: [], results: [],
@ -490,12 +508,12 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/playlists * GET: /api/libraries/:id/playlists
* Get playlists for user in library * Get playlists for user in library
* @param {*} req * @param {*} req
* @param {*} res * @param {*} res
*/ */
async getUserPlaylistsForLibrary(req, res) { async getUserPlaylistsForLibrary(req, res) {
let playlistsForUser = await Database.playlistModel.getPlaylistsForUserAndLibrary(req.user.id, req.library.id) 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 = { const payload = {
results: [], results: [],
@ -515,8 +533,8 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/filterdata * GET: /api/libraries/:id/filterdata
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async getLibraryFilterData(req, res) { async getLibraryFilterData(req, res) {
const filterData = await libraryFilters.getFilterData(req.library.mediaType, req.library.id) const filterData = await libraryFilters.getFilterData(req.library.mediaType, req.library.id)
@ -526,12 +544,15 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/personalized * GET: /api/libraries/:id/personalized
* Home page shelves * Home page shelves
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async getUserPersonalizedShelves(req, res) { async getUserPersonalizedShelves(req, res) {
const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10 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) const shelves = await Database.libraryItemModel.getPersonalizedShelves(req.library, req.user, include, limitPerShelf)
res.json(shelves) res.json(shelves)
} }
@ -539,8 +560,8 @@ class LibraryController {
/** /**
* POST: /api/libraries/order * POST: /api/libraries/order
* Change the display order of libraries * Change the display order of libraries
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async reorder(req, res) { async reorder(req, res) {
if (!req.user.isAdminOrUp) { if (!req.user.isAdminOrUp) {
@ -552,7 +573,7 @@ class LibraryController {
const orderdata = req.body const orderdata = req.body
let hasUpdates = false let hasUpdates = false
for (let i = 0; i < orderdata.length; i++) { 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) { if (!library) {
Logger.error(`[LibraryController] Invalid library not found in reorder ${orderdata[i].id}`) Logger.error(`[LibraryController] Invalid library not found in reorder ${orderdata[i].id}`)
return res.sendStatus(500) return res.sendStatus(500)
@ -571,7 +592,7 @@ class LibraryController {
} }
res.json({ res.json({
libraries: libraries.map(lib => lib.toJSON()) libraries: libraries.map((lib) => lib.toJSON())
}) })
} }
@ -579,8 +600,8 @@ class LibraryController {
* GET: /api/libraries/:id/search * GET: /api/libraries/:id/search
* Search library items with query * Search library items with query
* ?q=search * ?q=search
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async search(req, res) { async search(req, res) {
if (!req.query.q || typeof req.query.q !== 'string') { if (!req.query.q || typeof req.query.q !== 'string') {
@ -596,8 +617,8 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/stats * GET: /api/libraries/:id/stats
* Get stats for library * Get stats for library
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async stats(req, res) { async stats(req, res) {
const stats = { const stats = {
@ -638,8 +659,8 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/authors * GET: /api/libraries/:id/authors
* Get authors for library * Get authors for library
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async getAuthors(req, res) { async getAuthors(req, res) {
const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user) const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user)
@ -657,9 +678,7 @@ class LibraryController {
attributes: [] attributes: []
} }
}, },
order: [ order: [[Sequelize.literal('name COLLATE NOCASE'), 'ASC']]
[Sequelize.literal('name COLLATE NOCASE'), 'ASC']
]
}) })
const oldAuthors = [] const oldAuthors = []
@ -678,8 +697,8 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/narrators * GET: /api/libraries/:id/narrators
* @param {*} req * @param {*} req
* @param {*} res * @param {*} res
*/ */
async getNarrators(req, res) { async getNarrators(req, res) {
// Get all books with narrators // Get all books with narrators
@ -699,7 +718,7 @@ class LibraryController {
const narrators = {} const narrators = {}
for (const book of booksWithNarrators) { for (const book of booksWithNarrators) {
book.narrators.forEach(n => { book.narrators.forEach((n) => {
if (typeof n !== 'string') { if (typeof n !== 'string') {
Logger.error(`[LibraryController] getNarrators: Invalid narrator "${n}" on book "${book.title}"`) Logger.error(`[LibraryController] getNarrators: Invalid narrator "${n}" on book "${book.title}"`)
} else if (!narrators[n]) { } else if (!narrators[n]) {
@ -715,7 +734,7 @@ class LibraryController {
} }
res.json({ res.json({
narrators: naturalSort(Object.values(narrators)).asc(n => n.name) narrators: naturalSort(Object.values(narrators)).asc((n) => n.name)
}) })
} }
@ -724,8 +743,8 @@ class LibraryController {
* Update narrator name * Update narrator name
* :narratorId is base64 encoded name * :narratorId is base64 encoded name
* req.body { name } * req.body { name }
* @param {*} req * @param {*} req
* @param {*} res * @param {*} res
*/ */
async updateNarrator(req, res) { async updateNarrator(req, res) {
if (!req.user.canUpdate) { if (!req.user.canUpdate) {
@ -747,7 +766,7 @@ class LibraryController {
const itemsWithNarrator = await libraryItemFilters.getAllLibraryItemsWithNarrators([narratorName]) const itemsWithNarrator = await libraryItemFilters.getAllLibraryItemsWithNarrators([narratorName])
for (const libraryItem of itemsWithNarrator) { 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)) { if (!libraryItem.media.narrators.includes(updatedName)) {
libraryItem.media.narrators.push(updatedName) libraryItem.media.narrators.push(updatedName)
} }
@ -759,7 +778,10 @@ class LibraryController {
} }
if (itemsUpdated.length) { if (itemsUpdated.length) {
SocketAuthority.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded())) SocketAuthority.emitter(
'items_updated',
itemsUpdated.map((li) => li.toJSONExpanded())
)
} }
res.json({ res.json({
@ -771,8 +793,8 @@ class LibraryController {
* DELETE: /api/libraries/:id/narrators/:narratorId * DELETE: /api/libraries/:id/narrators/:narratorId
* Remove narrator * Remove narrator
* :narratorId is base64 encoded name * :narratorId is base64 encoded name
* @param {*} req * @param {*} req
* @param {*} res * @param {*} res
*/ */
async removeNarrator(req, res) { async removeNarrator(req, res) {
if (!req.user.canUpdate) { if (!req.user.canUpdate) {
@ -790,7 +812,7 @@ class LibraryController {
const itemsWithNarrator = await libraryItemFilters.getAllLibraryItemsWithNarrators([narratorName]) const itemsWithNarrator = await libraryItemFilters.getAllLibraryItemsWithNarrators([narratorName])
for (const libraryItem of itemsWithNarrator) { 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({ await libraryItem.media.update({
narrators: libraryItem.media.narrators narrators: libraryItem.media.narrators
}) })
@ -799,7 +821,10 @@ class LibraryController {
} }
if (itemsUpdated.length) { if (itemsUpdated.length) {
SocketAuthority.emitter('items_updated', itemsUpdated.map(li => li.toJSONExpanded())) SocketAuthority.emitter(
'items_updated',
itemsUpdated.map((li) => li.toJSONExpanded())
)
} }
res.json({ res.json({
@ -810,9 +835,9 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/matchall * GET: /api/libraries/:id/matchall
* Quick match all library items. Book libraries only. * Quick match all library items. Book libraries only.
* *
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async matchAll(req, res) { async matchAll(req, res) {
if (!req.user.isAdminOrUp) { if (!req.user.isAdminOrUp) {
@ -827,9 +852,9 @@ class LibraryController {
* POST: /api/libraries/:id/scan * POST: /api/libraries/:id/scan
* Optional query: * Optional query:
* ?force=1 * ?force=1
* *
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async scan(req, res) { async scan(req, res) {
if (!req.user.isAdminOrUp) { if (!req.user.isAdminOrUp) {
@ -848,8 +873,8 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/recent-episodes * GET: /api/libraries/:id/recent-episodes
* Used for latest page * Used for latest page
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async getRecentEpisodes(req, res) { async getRecentEpisodes(req, res) {
if (!req.library.isPodcast) { if (!req.library.isPodcast) {
@ -859,7 +884,7 @@ class LibraryController {
const payload = { const payload = {
episodes: [], episodes: [],
limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0, 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 const offset = payload.page * payload.limit
@ -870,8 +895,8 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/opml * GET: /api/libraries/:id/opml
* Get OPML file for a podcast library * Get OPML file for a podcast library
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async getOPMLFile(req, res) { async getOPMLFile(req, res) {
const userPermissionPodcastWhere = libraryItemsPodcastFilters.getUserPermissionPodcastWhereQuery(req.user) const userPermissionPodcastWhere = libraryItemsPodcastFilters.getUserPermissionPodcastWhereQuery(req.user)
@ -895,9 +920,9 @@ class LibraryController {
/** /**
* Remove all metadata.json or metadata.abs files in library item folders * Remove all metadata.json or metadata.abs files in library item folders
* *
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
async removeAllMetadataFiles(req, res) { async removeAllMetadataFiles(req, res) {
if (!req.user.isAdminOrUp) { if (!req.user.isAdminOrUp) {
@ -929,10 +954,10 @@ class LibraryController {
let numRemoved = 0 let numRemoved = 0
for (const libraryItem of libraryItemsWithMetadata) { 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 if (!metadataFilepath) continue
Logger.debug(`[LibraryController] Removing file "${metadataFilepath}"`) Logger.debug(`[LibraryController] Removing file "${metadataFilepath}"`)
if ((await fileUtils.removeFile(metadataFilepath))) { if (await fileUtils.removeFile(metadataFilepath)) {
numRemoved++ numRemoved++
} }
} }
@ -945,9 +970,9 @@ class LibraryController {
/** /**
* Middleware that is not using libraryItems from memory * Middleware that is not using libraryItems from memory
* @param {import('express').Request} req * @param {import('express').Request} req
* @param {import('express').Response} res * @param {import('express').Response} res
* @param {import('express').NextFunction} next * @param {import('express').NextFunction} next
*/ */
async middleware(req, res, next) { async middleware(req, res, next) {
if (!req.user.checkCanAccessLibrary(req.params.id)) { if (!req.user.checkCanAccessLibrary(req.params.id)) {

View file

@ -32,7 +32,7 @@ class PodcastManager {
} }
getEpisodeDownloadsInQueue(libraryItemId) { getEpisodeDownloadsInQueue(libraryItemId) {
return this.downloadQueue.filter(d => d.libraryItemId === libraryItemId) return this.downloadQueue.filter((d) => d.libraryItemId === libraryItemId)
} }
clearDownloadQueue(libraryItemId = null) { clearDownloadQueue(libraryItemId = null) {
@ -44,12 +44,12 @@ class PodcastManager {
} else { } else {
var itemDownloads = this.getEpisodeDownloadsInQueue(libraryItemId) var itemDownloads = this.getEpisodeDownloadsInQueue(libraryItemId)
Logger.info(`[PodcastManager] Clearing downloads in queue for item "${libraryItemId}" (${itemDownloads.length})`) 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) { 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) { for (const ep of episodesToDownload) {
const newPe = new PodcastEpisode() const newPe = new PodcastEpisode()
newPe.setData(ep, index++) newPe.setData(ep, index++)
@ -72,7 +72,7 @@ class PodcastManager {
const taskDescription = `Downloading episode "${podcastEpisodeDownload.podcastEpisode.title}".` const taskDescription = `Downloading episode "${podcastEpisodeDownload.podcastEpisode.title}".`
const taskData = { const taskData = {
libraryId: podcastEpisodeDownload.libraryId, libraryId: podcastEpisodeDownload.libraryId,
libraryItemId: podcastEpisodeDownload.libraryItemId, libraryItemId: podcastEpisodeDownload.libraryItemId
} }
const task = TaskManager.createAndAddTask('download-podcast-episode', 'Downloading Episode', taskDescription, false, taskData) const task = TaskManager.createAndAddTask('download-podcast-episode', 'Downloading Episode', taskDescription, false, taskData)
@ -104,10 +104,12 @@ class PodcastManager {
}) })
} else { } else {
// Download episode only // Download episode only
success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath).then(() => true).catch((error) => { success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath)
Logger.error(`[PodcastManager] Podcast Episode download failed`, error) .then(() => true)
return false .catch((error) => {
}) Logger.error(`[PodcastManager] Podcast Episode download failed`, error)
return false
})
} }
if (success) { if (success) {
@ -156,7 +158,7 @@ class PodcastManager {
podcastEpisode.audioFile = audioFile podcastEpisode.audioFile = audioFile
if (audioFile.chapters?.length) { if (audioFile.chapters?.length) {
podcastEpisode.chapters = audioFile.chapters.map(ch => ({ ...ch })) podcastEpisode.chapters = audioFile.chapters.map((ch) => ({ ...ch }))
} }
libraryItem.media.addPodcastEpisode(podcastEpisode) libraryItem.media.addPodcastEpisode(podcastEpisode)
@ -181,7 +183,8 @@ class PodcastManager {
podcastEpisodeExpanded.libraryItem = libraryItem.toJSONExpanded() podcastEpisodeExpanded.libraryItem = libraryItem.toJSONExpanded()
SocketAuthority.emitter('episode_added', podcastEpisodeExpanded) 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) this.notificationManager.onPodcastEpisodeDownloaded(libraryItem, podcastEpisode)
} }
@ -191,12 +194,14 @@ class PodcastManager {
async removeOldestEpisode(libraryItem, episodeIdJustDownloaded) { async removeOldestEpisode(libraryItem, episodeIdJustDownloaded) {
var smallestPublishedAt = 0 var smallestPublishedAt = 0
var oldestEpisode = null var oldestEpisode = null
libraryItem.media.episodesWithPubDate.filter(ep => ep.id !== episodeIdJustDownloaded).forEach((ep) => { libraryItem.media.episodesWithPubDate
if (!smallestPublishedAt || ep.publishedAt < smallestPublishedAt) { .filter((ep) => ep.id !== episodeIdJustDownloaded)
smallestPublishedAt = ep.publishedAt .forEach((ep) => {
oldestEpisode = ep if (!smallestPublishedAt || ep.publishedAt < smallestPublishedAt) {
} smallestPublishedAt = ep.publishedAt
}) oldestEpisode = ep
}
})
// TODO: Should we check for open playback sessions for this episode? // TODO: Should we check for open playback sessions for this episode?
// TODO: remove all user progress for this episode // TODO: remove all user progress for this episode
if (oldestEpisode?.audioFile) { if (oldestEpisode?.audioFile) {
@ -246,7 +251,8 @@ class PodcastManager {
let newEpisodes = await this.checkPodcastForNewEpisodes(libraryItem, dateToCheckForEpisodesAfter, libraryItem.media.maxNewEpisodesToDownload) let newEpisodes = await this.checkPodcastForNewEpisodes(libraryItem, dateToCheckForEpisodesAfter, libraryItem.media.maxNewEpisodesToDownload)
Logger.debug(`[PodcastManager] runEpisodeCheck: ${newEpisodes?.length || 'N/A'} episodes found`) 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 // Allow up to MaxFailedEpisodeChecks failed attempts before disabling auto download
if (!this.failedCheckMap[libraryItem.id]) this.failedCheckMap[libraryItem.id] = 0 if (!this.failedCheckMap[libraryItem.id]) this.failedCheckMap[libraryItem.id] = 0
this.failedCheckMap[libraryItem.id]++ this.failedCheckMap[libraryItem.id]++
@ -290,7 +296,7 @@ class PodcastManager {
} }
// Filter new and not already has // 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) { if (maxNewEpisodes > 0) {
newEpisodes = newEpisodes.slice(0, maxNewEpisodes) newEpisodes = newEpisodes.slice(0, maxNewEpisodes)
@ -349,7 +355,7 @@ class PodcastManager {
} }
const matches = [] const matches = []
feed.episodes.forEach(ep => { feed.episodes.forEach((ep) => {
if (!ep.title) return if (!ep.title) return
const epTitle = ep.title.toLowerCase().trim() const epTitle = ep.title.toLowerCase().trim()
@ -410,7 +416,7 @@ class PodcastManager {
return { return {
currentDownload: _currentDownload?.toJSONForClient(), 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 { areEquivalent, copyValue } = require('../../utils/index')
const AudioFile = require('../files/AudioFile') const AudioFile = require('../files/AudioFile')
const AudioTrack = require('../files/AudioTrack') const AudioTrack = require('../files/AudioTrack')
@ -47,7 +47,7 @@ class PodcastEpisode {
this.enclosure = episode.enclosure ? { ...episode.enclosure } : null this.enclosure = episode.enclosure ? { ...episode.enclosure } : null
this.guid = episode.guid || null this.guid = episode.guid || null
this.pubDate = episode.pubDate 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.audioFile = episode.audioFile ? new AudioFile(episode.audioFile) : null
this.publishedAt = episode.publishedAt this.publishedAt = episode.publishedAt
this.addedAt = episode.addedAt this.addedAt = episode.addedAt
@ -74,7 +74,7 @@ class PodcastEpisode {
enclosure: this.enclosure ? { ...this.enclosure } : null, enclosure: this.enclosure ? { ...this.enclosure } : null,
guid: this.guid, guid: this.guid,
pubDate: this.pubDate, pubDate: this.pubDate,
chapters: this.chapters.map(ch => ({ ...ch })), chapters: this.chapters.map((ch) => ({ ...ch })),
audioFile: this.audioFile?.toJSON() || null, audioFile: this.audioFile?.toJSON() || null,
publishedAt: this.publishedAt, publishedAt: this.publishedAt,
addedAt: this.addedAt, addedAt: this.addedAt,
@ -98,7 +98,7 @@ class PodcastEpisode {
enclosure: this.enclosure ? { ...this.enclosure } : null, enclosure: this.enclosure ? { ...this.enclosure } : null,
guid: this.guid, guid: this.guid,
pubDate: this.pubDate, pubDate: this.pubDate,
chapters: this.chapters.map(ch => ({ ...ch })), chapters: this.chapters.map((ch) => ({ ...ch })),
audioFile: this.audioFile?.toJSON() || null, audioFile: this.audioFile?.toJSON() || null,
audioTrack: this.audioTrack?.toJSON() || null, audioTrack: this.audioTrack?.toJSON() || null,
publishedAt: this.publishedAt, publishedAt: this.publishedAt,
@ -121,7 +121,9 @@ class PodcastEpisode {
get duration() { get duration() {
return this.audioFile?.duration || 0 return this.audioFile?.duration || 0
} }
get size() { return this.audioFile?.metadata.size || 0 } get size() {
return this.audioFile?.metadata.size || 0
}
get enclosureUrl() { get enclosureUrl() {
return this.enclosure?.url || null return this.enclosure?.url || null
} }
@ -151,9 +153,9 @@ class PodcastEpisode {
let hasUpdates = false let hasUpdates = false
for (const key in this.toJSON()) { for (const key in this.toJSON()) {
let newValue = payload[key] let newValue = payload[key]
if (newValue === "") newValue = null if (newValue === '') newValue = null
let existingValue = this[key] let existingValue = this[key]
if (existingValue === "") existingValue = null if (existingValue === '') existingValue = null
if (newValue != undefined && !areEquivalent(newValue, existingValue)) { if (newValue != undefined && !areEquivalent(newValue, existingValue)) {
this[key] = copyValue(newValue) this[key] = copyValue(newValue)
@ -177,7 +179,7 @@ class PodcastEpisode {
} }
checkEqualsEnclosureUrl(url) { checkEqualsEnclosureUrl(url) {
if (!this.enclosure || !this.enclosure.url) return false if (!this.enclosure?.url) return false
return this.enclosure.url == url return this.enclosure.url == url
} }
} }

View file

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

View file

@ -7,7 +7,7 @@ const { asciiOnlyToLowerCase } = require('../index')
module.exports = { module.exports = {
/** /**
* User permissions to restrict books for explicit content & tags * User permissions to restrict books for explicit content & tags
* @param {import('../../objects/user/User')} user * @param {import('../../objects/user/User')} user
* @returns {{ bookWhere:Sequelize.WhereOptions, replacements:object }} * @returns {{ bookWhere:Sequelize.WhereOptions, replacements:object }}
*/ */
getUserPermissionBookWhereQuery(user) { getUserPermissionBookWhereQuery(user) {
@ -25,9 +25,11 @@ module.exports = {
if (user.permissions.selectedTagsNotAccessible) { 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)) bookWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0))
} else { } else {
bookWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), { bookWhere.push(
[Sequelize.Op.gte]: 1 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 { return {
@ -39,8 +41,8 @@ module.exports = {
/** /**
* When collapsing series and filtering by progress * When collapsing series and filtering by progress
* different where options are required * different where options are required
* *
* @param {string} value * @param {string} value
* @returns {Sequelize.WhereOptions} * @returns {Sequelize.WhereOptions}
*/ */
getCollapseSeriesMediaProgressFilter(value) { getCollapseSeriesMediaProgressFilter(value) {
@ -90,8 +92,8 @@ module.exports = {
/** /**
* Get where options for Book model * Get where options for Book model
* @param {string} group * @param {string} group
* @param {[string]} value * @param {[string]} value
* @returns {object} { Sequelize.WhereOptions, string[] } * @returns {object} { Sequelize.WhereOptions, string[] }
*/ */
getMediaGroupQuery(group, value) { getMediaGroupQuery(group, value) {
@ -170,7 +172,7 @@ module.exports = {
Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('audioFiles')), 0), Sequelize.where(Sequelize.fn('json_array_length', Sequelize.col('audioFiles')), 0),
{ {
'$mediaProgresses.isFinished$': true, '$mediaProgresses.isFinished$': true,
'ebookFile': { ebookFile: {
[Sequelize.Op.not]: null [Sequelize.Op.not]: null
} }
} }
@ -232,8 +234,8 @@ module.exports = {
/** /**
* Get sequelize order * Get sequelize order
* @param {string} sortBy * @param {string} sortBy
* @param {boolean} sortDesc * @param {boolean} sortDesc
* @param {boolean} collapseseries * @param {boolean} collapseseries
* @returns {Sequelize.order} * @returns {Sequelize.order}
*/ */
@ -278,18 +280,14 @@ module.exports = {
* When collapsing series get first book in each series * When collapsing series get first book in each series
* to know which books to exclude from primary query. * to know which books to exclude from primary query.
* Additionally use this query to get the number of books in each series * Additionally use this query to get the number of books in each series
* *
* @param {Sequelize.ModelStatic} bookFindOptions * @param {Sequelize.ModelStatic} bookFindOptions
* @param {Sequelize.WhereOptions} seriesWhere * @param {Sequelize.WhereOptions} seriesWhere
* @returns {object} { booksToExclude, bookSeriesToInclude } * @returns {object} { booksToExclude, bookSeriesToInclude }
*/ */
async getCollapseSeriesBooksToExclude(bookFindOptions, seriesWhere) { async getCollapseSeriesBooksToExclude(bookFindOptions, seriesWhere) {
const allSeries = await Database.seriesModel.findAll({ const allSeries = await Database.seriesModel.findAll({
attributes: [ attributes: ['id', 'name', [Sequelize.literal('(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)'), 'numBooks']],
'id',
'name',
[Sequelize.literal('(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)'), 'numBooks']
],
distinct: true, distinct: true,
subQuery: false, subQuery: false,
where: seriesWhere, where: seriesWhere,
@ -304,23 +302,22 @@ module.exports = {
required: true required: true
} }
], ],
order: [ order: [Sequelize.literal('CAST(`books.bookSeries.sequence` AS FLOAT) ASC NULLS LAST')]
Sequelize.literal('CAST(`books.bookSeries.sequence` AS FLOAT) ASC NULLS LAST')
]
}) })
const bookSeriesToInclude = [] const bookSeriesToInclude = []
const booksToInclude = [] const booksToInclude = []
let booksToExclude = [] let booksToExclude = []
allSeries.forEach(s => { allSeries.forEach((s) => {
let found = false let found = false
for (let book of s.books) { for (let book of s.books) {
if (!found && !booksToInclude.includes(book.id)) { if (!found && !booksToInclude.includes(book.id)) {
booksToInclude.push(book.id) booksToInclude.push(book.id)
bookSeriesToInclude.push({ bookSeriesToInclude.push({
id: book.bookSeries.id, 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 found = true
} else if (!booksToExclude.includes(book.id) && !booksToInclude.includes(book.id)) { } else if (!booksToExclude.includes(book.id) && !booksToInclude.includes(book.id)) {
booksToExclude.push(book.id) booksToExclude.push(book.id)
@ -332,16 +329,16 @@ module.exports = {
/** /**
* Get library items for book media type using filter and sort * Get library items for book media type using filter and sort
* @param {string} libraryId * @param {string} libraryId
* @param {[oldUser]} user * @param {[oldUser]} user
* @param {[string]} filterGroup * @param {[string]} filterGroup
* @param {[string]} filterValue * @param {[string]} filterValue
* @param {string} sortBy * @param {string} sortBy
* @param {string} sortDesc * @param {string} sortDesc
* @param {boolean} collapseseries * @param {boolean} collapseseries
* @param {string[]} include * @param {string[]} include
* @param {number} limit * @param {number} limit
* @param {number} offset * @param {number} offset
* @param {boolean} isHomePage for home page shelves * @param {boolean} isHomePage for home page shelves
* @returns {object} { libraryItems:LibraryItem[], count:number } * @returns {object} { libraryItems:LibraryItem[], count:number }
*/ */
@ -363,15 +360,11 @@ module.exports = {
let bookAttributes = null let bookAttributes = null
if (sortBy === 'media.metadata.authorNameLF') { if (sortBy === 'media.metadata.authorNameLF') {
bookAttributes = { bookAttributes = {
include: [ 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']]
[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']
]
} }
} else if (sortBy === 'media.metadata.authorName') { } else if (sortBy === 'media.metadata.authorName') {
bookAttributes = { bookAttributes = {
include: [ 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']]
[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']
]
} }
} }
@ -386,9 +379,7 @@ module.exports = {
model: Database.seriesModel, model: Database.seriesModel,
attributes: ['id', 'name', 'nameIgnorePrefix'] attributes: ['id', 'name', 'nameIgnorePrefix']
}, },
order: [ order: [['createdAt', 'ASC']],
['createdAt', 'ASC']
],
separate: true separate: true
} }
@ -399,9 +390,7 @@ module.exports = {
model: Database.authorModel, model: Database.authorModel,
attributes: ['id', 'name'] attributes: ['id', 'name']
}, },
order: [ order: [['createdAt', 'ASC']],
['createdAt', 'ASC']
],
separate: true separate: true
} }
@ -427,7 +416,7 @@ module.exports = {
} }
} else if (filterGroup === 'ebooks' && filterValue === 'no-supplementary') { } else if (filterGroup === 'ebooks' && filterValue === 'no-supplementary') {
libraryItemWhere['libraryFiles'] = { libraryItemWhere['libraryFiles'] = {
[Sequelize.Op.notLike]: Sequelize.literal(`\'%"isSupplementary":true%\'`), [Sequelize.Op.notLike]: Sequelize.literal(`\'%"isSupplementary":true%\'`)
} }
} else if (filterGroup === 'missing' && filterValue === 'authors') { } else if (filterGroup === 'missing' && filterValue === 'authors') {
authorInclude = { authorInclude = {
@ -496,7 +485,7 @@ module.exports = {
}) })
} else if (filterGroup === 'recent') { } else if (filterGroup === 'recent') {
libraryItemWhere['createdAt'] = { 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 // 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 // for this set an attribute "display_title" to use in sorting
if (global.ServerSettings.sortingIgnorePrefix) { 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 { } 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 // For showing details of collapsed series
if (collapseseries && book.bookSeries?.length) { 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) { if (collapsedSeries) {
const collapseSeriesObj = collapseSeriesBookSeries.find(csbs => csbs.id === collapsedSeries.id) const collapseSeriesObj = collapseSeriesBookSeries.find((csbs) => csbs.id === collapsedSeries.id)
libraryItem.collapsedSeries = { libraryItem.collapsedSeries = {
id: collapsedSeries.series.id, id: collapsedSeries.series.id,
name: collapsedSeries.series.name, name: collapsedSeries.series.name,
nameIgnorePrefix: collapsedSeries.series.nameIgnorePrefix, nameIgnorePrefix: collapsedSeries.series.nameIgnorePrefix,
sequence: collapsedSeries.sequence, sequence: collapsedSeries.sequence,
numBooks: collapseSeriesObj?.numBooks || 0 numBooks: collapseSeriesObj?.numBooks || 0,
libraryItemIds: collapseSeriesObj?.libraryItemIds || []
} }
} }
} }
@ -633,11 +623,11 @@ module.exports = {
* 2. Has no books in progress * 2. Has no books in progress
* 3. Has at least 1 unfinished book * 3. Has at least 1 unfinished book
* TODO: Reduce queries * TODO: Reduce queries
* @param {import('../../objects/Library')} library * @param {import('../../objects/Library')} library
* @param {import('../../objects/user/User')} user * @param {import('../../objects/user/User')} user
* @param {string[]} include * @param {string[]} include
* @param {number} limit * @param {number} limit
* @param {number} offset * @param {number} offset
* @returns {{ libraryItems:import('../../models/LibraryItem')[], count:number }} * @returns {{ libraryItems:import('../../models/LibraryItem')[], count:number }}
*/ */
async getContinueSeriesLibraryItems(library, user, include, limit, offset) { async getContinueSeriesLibraryItems(library, user, include, limit, offset) {
@ -655,15 +645,13 @@ module.exports = {
const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user) const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user)
bookWhere.push(...userPermissionBookWhere.bookWhere) bookWhere.push(...userPermissionBookWhere.bookWhere)
let includeAttributes = [ 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']]
[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)` 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) { if (library.settings.onlyShowLaterBooksInContinueSeries) {
const maxSequenceQuery = `(SELECT CAST(max(bs.sequence) as FLOAT) FROM bookSeries bs, mediaProgresses mp WHERE mp.mediaItemId = bs.bookId AND mp.isFinished = 1 AND mp.userId = :userId AND bs.seriesId = series.id)` const maxSequenceQuery = `(SELECT CAST(max(bs.sequence) as FLOAT) FROM bookSeries bs, mediaProgresses mp WHERE mp.mediaItemId = bs.bookId AND mp.isFinished = 1 AND mp.userId = :userId AND bs.seriesId = series.id)`
includeAttributes.push([Sequelize.literal(`${maxSequenceQuery}`), 'maxSequence']) includeAttributes.push([Sequelize.literal(`${maxSequenceQuery}`), 'maxSequence'])
booksNotFinishedQuery = booksNotFinishedQuery + ` AND CAST(bs.sequence as FLOAT) > ${maxSequenceQuery}` booksNotFinishedQuery = booksNotFinishedQuery + ` AND CAST(bs.sequence as FLOAT) > ${maxSequenceQuery}`
} }
@ -699,9 +687,7 @@ module.exports = {
attributes: ['bookId', 'sequence'], attributes: ['bookId', 'sequence'],
separate: true, separate: true,
subQuery: false, subQuery: false,
order: [ order: [[Sequelize.literal('CAST(sequence AS FLOAT) ASC NULLS LAST')]],
[Sequelize.literal('CAST(sequence AS FLOAT) ASC NULLS LAST')]
],
where: { where: {
'$book.mediaProgresses.isFinished$': { '$book.mediaProgresses.isFinished$': {
[Sequelize.Op.or]: [null, 0] [Sequelize.Op.or]: [null, 0]
@ -731,44 +717,44 @@ module.exports = {
] ]
} }
}, },
order: [ order: [[Sequelize.literal('recent_progress DESC')]],
[Sequelize.literal('recent_progress DESC')]
],
distinct: true, distinct: true,
subQuery: false, subQuery: false,
limit, limit,
offset offset
}) })
const libraryItems = series.map(s => { const libraryItems = series
if (!s.bookSeries.length) return null // this is only possible if user has restricted books in series .map((s) => {
if (!s.bookSeries.length) return null // this is only possible if user has restricted books in series
let bookIndex = 0 let bookIndex = 0
// if the library setting is toggled, only show later entries in series, otherwise skip // if the library setting is toggled, only show later entries in series, otherwise skip
if (library.settings.onlyShowLaterBooksInContinueSeries) { if (library.settings.onlyShowLaterBooksInContinueSeries) {
bookIndex = s.bookSeries.findIndex(function (b) { bookIndex = s.bookSeries.findIndex(function (b) {
return parseFloat(b.dataValues.sequence) > s.dataValues.maxSequence return parseFloat(b.dataValues.sequence) > s.dataValues.maxSequence
}) })
if (bookIndex === -1) { if (bookIndex === -1) {
// no later books than maxSequence // no later books than maxSequence
return null return null
}
} }
}
const libraryItem = s.bookSeries[bookIndex].book.libraryItem.toJSON() const libraryItem = s.bookSeries[bookIndex].book.libraryItem.toJSON()
const book = s.bookSeries[bookIndex].book.toJSON() const book = s.bookSeries[bookIndex].book.toJSON()
delete book.libraryItem delete book.libraryItem
libraryItem.series = { libraryItem.series = {
id: s.id, id: s.id,
name: s.name, name: s.name,
sequence: s.bookSeries[bookIndex].sequence sequence: s.bookSeries[bookIndex].sequence
} }
if (libraryItem.feeds?.length) { if (libraryItem.feeds?.length) {
libraryItem.rssFeed = libraryItem.feeds[0] libraryItem.rssFeed = libraryItem.feeds[0]
} }
libraryItem.media = book libraryItem.media = book
return libraryItem return libraryItem
}).filter(s => s) })
.filter((s) => s)
return { return {
libraryItems, libraryItems,
@ -780,10 +766,10 @@ module.exports = {
* Get book library items for the "Discover" shelf * Get book library items for the "Discover" shelf
* Random selection of books that are not started * Random selection of books that are not started
* - only includes the first book of a not-started series * - only includes the first book of a not-started series
* @param {string} libraryId * @param {string} libraryId
* @param {oldUser} user * @param {oldUser} user
* @param {string[]} include * @param {string[]} include
* @param {number} limit * @param {number} limit
* @returns {object} {libraryItems:LibraryItem, count:number} * @returns {object} {libraryItems:LibraryItem, count:number}
*/ */
async getDiscoverLibraryItems(libraryId, user, include, limit) { async getDiscoverLibraryItems(libraryId, user, include, limit) {
@ -811,9 +797,7 @@ module.exports = {
model: Database.bookModel, model: Database.bookModel,
where: userPermissionBookWhere.bookWhere where: userPermissionBookWhere.bookWhere
}, },
order: [ order: [[Sequelize.literal('CAST(sequence AS FLOAT) ASC NULLS LAST')]],
[Sequelize.literal('CAST(sequence AS FLOAT) ASC NULLS LAST')]
],
limit: 1 limit: 1
}, },
subQuery: false, subQuery: false,
@ -821,7 +805,7 @@ module.exports = {
order: Database.sequelize.random() 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 // optional include rssFeed
const libraryItemIncludes = [] const libraryItemIncludes = []
@ -913,7 +897,7 @@ module.exports = {
/** /**
* Get book library items in a collection * Get book library items in a collection
* @param {oldCollection} collection * @param {oldCollection} collection
* @returns {Promise<LibraryItem[]>} * @returns {Promise<LibraryItem[]>}
*/ */
async getLibraryItemsForCollection(collection) { async getLibraryItemsForCollection(collection) {
@ -957,22 +941,22 @@ module.exports = {
/** /**
* Get library items for series * Get library items for series
* @param {import('../../objects/entities/Series')} oldSeries * @param {import('../../objects/entities/Series')} oldSeries
* @param {import('../../objects/user/User')} [oldUser] * @param {import('../../objects/user/User')} [oldUser]
* @returns {Promise<import('../../objects/LibraryItem')[]>} * @returns {Promise<import('../../objects/LibraryItem')[]>}
*/ */
async getLibraryItemsForSeries(oldSeries, oldUser) { async getLibraryItemsForSeries(oldSeries, oldUser) {
const { libraryItems } = await this.getFilteredLibraryItems(oldSeries.libraryId, oldUser, 'series', oldSeries.id, null, null, false, [], null, null) 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))
}, },
/** /**
* Search books, authors, series * Search books, authors, series
* @param {import('../../objects/user/User')} oldUser * @param {import('../../objects/user/User')} oldUser
* @param {import('../../objects/Library')} oldLibrary * @param {import('../../objects/Library')} oldLibrary
* @param {string} query * @param {string} query
* @param {number} limit * @param {number} limit
* @param {number} offset * @param {number} offset
* @returns {{book:object[], narrators:object[], authors:object[], tags:object[], series:object[]}} * @returns {{book:object[], narrators:object[], authors:object[], tags:object[], series:object[]}}
*/ */
async search(oldUser, oldLibrary, query, limit, offset) { async search(oldUser, oldLibrary, query, limit, offset) {
@ -1151,7 +1135,7 @@ module.exports = {
/** /**
* Genres with num books * Genres with num books
* @param {string} libraryId * @param {string} libraryId
* @returns {{genre:string, count:number}[]} * @returns {{genre:string, count:number}[]}
*/ */
async getGenresWithCount(libraryId) { async getGenresWithCount(libraryId) {
@ -1173,7 +1157,7 @@ module.exports = {
/** /**
* Get stats for book library * Get stats for book library
* @param {string} libraryId * @param {string} libraryId
* @returns {Promise<{ totalSize:number, totalDuration:number, numAudioFiles:number, totalItems:number}>} * @returns {Promise<{ totalSize:number, totalDuration:number, numAudioFiles:number, totalItems:number}>}
*/ */
async getBookLibraryStats(libraryId) { async getBookLibraryStats(libraryId) {
@ -1187,8 +1171,8 @@ module.exports = {
/** /**
* Get longest books in library * Get longest books in library
* @param {string} libraryId * @param {string} libraryId
* @param {number} limit * @param {number} limit
* @returns {Promise<{ id:string, title:string, duration:number }[]>} * @returns {Promise<{ id:string, title:string, duration:number }[]>}
*/ */
async getLongestBooks(libraryId, limit) { async getLongestBooks(libraryId, limit) {
@ -1201,12 +1185,10 @@ module.exports = {
libraryId libraryId
} }
}, },
order: [ order: [['duration', 'DESC']],
['duration', 'DESC']
],
limit limit
}) })
return books.map(book => { return books.map((book) => {
return { return {
id: book.libraryItem.id, id: book.libraryItem.id,
title: book.title, title: book.title,
@ -1214,4 +1196,4 @@ module.exports = {
} }
}) })
} }
} }