mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-04 18:00:45 +02:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce213c3d89 | |||
| 32cd0360e6 | |||
| 1ec23a5699 | |||
| 48330f6432 | |||
| 28358debbc | |||
| 54b7ed6117 | |||
| 0cfd2ee63b | |||
| 37a0990741 | |||
| 7a0cd1eb34 | |||
| ac3277da09 | |||
| 65d1e7be56 | |||
| 80685afa7e | |||
| f892453892 | |||
| 422bb8c31c | |||
| 4ddd2788f0 | |||
| 423a2129d1 | |||
| a338097514 | |||
| 84b67abb03 | |||
| 5ec8406653 | |||
| 0344a63b48 | |||
| 24923c0009 | |||
| a9036c9738 | |||
| f9f7fbed33 | |||
| 53b5bee736 | |||
| d0b3726905 | |||
| 7a6864507e | |||
| e20563f2e1 | |||
| fea5f8f3d4 | |||
| f9bb529b85 | |||
| 60e348fcc1 | |||
| f194c5be0e | |||
| 47712e63f1 | |||
| 790c1fb34a | |||
| 9cca731acc | |||
| 48f232790a | |||
| 3c55aa5f43 | |||
| 8c1edb30a6 | |||
| 5e64af4448 | |||
| 9f60017cfe | |||
| b6a86d11d2 | |||
| db86bfd63d | |||
| 7ff72a8920 | |||
| 2c4f86d148 | |||
| 1a9f26e804 | |||
| 42f8194bde | |||
| 8634b7058c | |||
| fc276b330a |
@@ -264,7 +264,6 @@ export default {
|
|||||||
libraryItems.forEach((item) => {
|
libraryItems.forEach((item) => {
|
||||||
let subtitle = ''
|
let subtitle = ''
|
||||||
if (item.mediaType === 'book') subtitle = item.media.metadata.authors.map((au) => au.name).join(', ')
|
if (item.mediaType === 'book') subtitle = item.media.metadata.authors.map((au) => au.name).join(', ')
|
||||||
else if (item.mediaType === 'music') subtitle = item.media.metadata.artists.join(', ')
|
|
||||||
queueItems.push({
|
queueItems.push({
|
||||||
libraryItemId: item.id,
|
libraryItemId: item.id,
|
||||||
libraryId: item.libraryId,
|
libraryId: item.libraryId,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
{{ seriesName }}
|
{{ seriesName }}
|
||||||
</p>
|
</p>
|
||||||
<div class="w-6 h-6 rounded-full bg-black bg-opacity-30 flex items-center justify-center ml-3">
|
<div class="w-6 h-6 rounded-full bg-black bg-opacity-30 flex items-center justify-center ml-3">
|
||||||
<span class="font-mono">{{ numShowing }}</span>
|
<span class="font-mono">{{ $formatNumber(numShowing) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- library & collections page -->
|
<!-- library & collections page -->
|
||||||
<template v-else-if="page !== 'search' && page !== 'podcast-search' && page !== 'recent-episodes' && !isHome">
|
<template v-else-if="page !== 'search' && page !== 'podcast-search' && page !== 'recent-episodes' && !isHome">
|
||||||
<p class="hidden md:block">{{ numShowing }} {{ entityName }}</p>
|
<p class="hidden md:block">{{ $formatNumber(numShowing) }} {{ entityName }}</p>
|
||||||
|
|
||||||
<div class="flex-grow hidden sm:inline-block" />
|
<div class="flex-grow hidden sm:inline-block" />
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<controls-sort-select v-if="isSeriesPage && !isBatchSelecting" v-model="settings.seriesSortBy" :descending.sync="settings.seriesSortDesc" :items="seriesSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateSeriesSort" />
|
<controls-sort-select v-if="isSeriesPage && !isBatchSelecting" v-model="settings.seriesSortBy" :descending.sync="settings.seriesSortDesc" :items="seriesSortItems" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateSeriesSort" />
|
||||||
|
|
||||||
<!-- issues page remove all button -->
|
<!-- issues page remove all button -->
|
||||||
<ui-btn v-if="isIssuesFilter && userCanDelete && !isBatchSelecting" :loading="processingIssues" color="error" small class="ml-4" @click="removeAllIssues">{{ $strings.ButtonRemoveAll }} {{ numShowing }} {{ entityName }}</ui-btn>
|
<ui-btn v-if="isIssuesFilter && userCanDelete && !isBatchSelecting" :loading="processingIssues" color="error" small class="ml-4" @click="removeAllIssues">{{ $strings.ButtonRemoveAll }} {{ $formatNumber(numShowing) }} {{ entityName }}</ui-btn>
|
||||||
|
|
||||||
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
|
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" :menu-width="110" class="ml-2" @action="contextMenuAction" />
|
||||||
</template>
|
</template>
|
||||||
@@ -246,9 +246,6 @@ export default {
|
|||||||
isPodcastLibrary() {
|
isPodcastLibrary() {
|
||||||
return this.currentLibraryMediaType === 'podcast'
|
return this.currentLibraryMediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusicLibrary() {
|
|
||||||
return this.currentLibraryMediaType === 'music'
|
|
||||||
},
|
|
||||||
isLibraryPage() {
|
isLibraryPage() {
|
||||||
return this.page === ''
|
return this.page === ''
|
||||||
},
|
},
|
||||||
@@ -281,7 +278,6 @@ export default {
|
|||||||
},
|
},
|
||||||
entityName() {
|
entityName() {
|
||||||
if (this.isAlbumsPage) return 'Albums'
|
if (this.isAlbumsPage) return 'Albums'
|
||||||
if (this.isMusicLibrary) return 'Tracks'
|
|
||||||
|
|
||||||
if (this.isPodcastLibrary) return this.$strings.LabelPodcasts
|
if (this.isPodcastLibrary) return this.$strings.LabelPodcasts
|
||||||
if (!this.page) return this.$strings.LabelBooks
|
if (!this.page) return this.$strings.LabelBooks
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="streamLibraryItem" id="mediaPlayerContainer" class="w-full fixed bottom-0 left-0 right-0 h-48 lg:h-40 z-50 bg-primary px-2 lg:px-4 pb-1 lg:pb-4 pt-2">
|
<div v-if="streamLibraryItem" id="mediaPlayerContainer" class="w-full fixed bottom-0 left-0 right-0 h-48 lg:h-40 z-50 bg-primary px-2 lg:px-4 pb-1 lg:pb-4 pt-2">
|
||||||
<div id="videoDock" />
|
|
||||||
<div class="absolute left-2 top-2 lg:left-4 cursor-pointer">
|
<div class="absolute left-2 top-2 lg:left-4 cursor-pointer">
|
||||||
<covers-book-cover expand-on-click :library-item="streamLibraryItem" :width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" />
|
<covers-book-cover expand-on-click :library-item="streamLibraryItem" :width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" />
|
||||||
</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="isSquareCover ? 'pl-18 sm:pl-24' : 'pl-12 sm:pl-16'">
|
||||||
<div class="min-w-0 w-full">
|
<div class="min-w-0 w-full">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
|
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
|
||||||
@@ -12,10 +11,9 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<widgets-explicit-indicator v-if="isExplicit" />
|
<widgets-explicit-indicator v-if="isExplicit" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center w-1/2 sm:w-4/5 lg:w-2/5">
|
<div class="text-gray-400 flex items-center w-1/2 sm:w-4/5 lg:w-2/5">
|
||||||
<span class="material-symbols text-sm">person</span>
|
<span class="material-symbols text-sm">person</span>
|
||||||
<div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</div>
|
<div v-if="podcastAuthor" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ podcastAuthor }}</div>
|
||||||
<div v-else-if="musicArtists" class="pl-1 sm:pl-1.5 text-xs sm:text-base">{{ musicArtists }}</div>
|
|
||||||
<div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base truncate">
|
<div v-else-if="authors.length" class="pl-1 sm:pl-1.5 text-xs sm:text-base truncate">
|
||||||
<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">, </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">, </span></nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,9 +138,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.streamLibraryItem?.mediaType === 'podcast'
|
return this.streamLibraryItem?.mediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusic() {
|
|
||||||
return this.streamLibraryItem?.mediaType === 'music'
|
|
||||||
},
|
|
||||||
isExplicit() {
|
isExplicit() {
|
||||||
return !!this.mediaMetadata.explicit
|
return !!this.mediaMetadata.explicit
|
||||||
},
|
},
|
||||||
@@ -174,10 +169,6 @@ export default {
|
|||||||
if (!this.isPodcast) return null
|
if (!this.isPodcast) return null
|
||||||
return this.mediaMetadata.author || 'Unknown'
|
return this.mediaMetadata.author || 'Unknown'
|
||||||
},
|
},
|
||||||
musicArtists() {
|
|
||||||
if (!this.isMusic) return null
|
|
||||||
return this.mediaMetadata.artists.join(', ')
|
|
||||||
},
|
|
||||||
hasNextItemInQueue() {
|
hasNextItemInQueue() {
|
||||||
return this.currentPlayerQueueIndex < this.playerQueueItems.length - 1
|
return this.currentPlayerQueueIndex < this.playerQueueItems.length - 1
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -95,14 +95,6 @@
|
|||||||
<div v-show="isPodcastSearchPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
<div v-show="isPodcastSearchPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="isMusicLibrary" :to="`/library/${currentLibraryId}/bookshelf/albums`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isMusicAlbumsPage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
|
||||||
<span class="material-symbols text-xl">album</span>
|
|
||||||
|
|
||||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">Albums</p>
|
|
||||||
|
|
||||||
<div v-show="isMusicAlbumsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
|
||||||
</nuxt-link>
|
|
||||||
|
|
||||||
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/download-queue`" class="w-full h-20 flex flex-col items-center justify-center text-white text-opacity-80 border-b border-primary border-opacity-70 hover:bg-primary cursor-pointer relative" :class="isPodcastDownloadQueuePage ? 'bg-primary bg-opacity-80' : 'bg-bg bg-opacity-60'">
|
||||||
<span class="material-symbols text-2xl"></span>
|
<span class="material-symbols text-2xl"></span>
|
||||||
|
|
||||||
@@ -172,9 +164,6 @@ export default {
|
|||||||
isPodcastLibrary() {
|
isPodcastLibrary() {
|
||||||
return this.currentLibraryMediaType === 'podcast'
|
return this.currentLibraryMediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusicLibrary() {
|
|
||||||
return this.currentLibraryMediaType === 'music'
|
|
||||||
},
|
|
||||||
isPodcastDownloadQueuePage() {
|
isPodcastDownloadQueuePage() {
|
||||||
return this.$route.name === 'library-library-podcast-download-queue'
|
return this.$route.name === 'library-library-podcast-download-queue'
|
||||||
},
|
},
|
||||||
@@ -184,9 +173,6 @@ export default {
|
|||||||
isPodcastLatestPage() {
|
isPodcastLatestPage() {
|
||||||
return this.$route.name === 'library-library-podcast-latest'
|
return this.$route.name === 'library-library-podcast-latest'
|
||||||
},
|
},
|
||||||
isMusicAlbumsPage() {
|
|
||||||
return this.paramId === 'albums'
|
|
||||||
},
|
|
||||||
homePage() {
|
homePage() {
|
||||||
return this.$route.name === 'library-library'
|
return this.$route.name === 'library-library'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -226,9 +226,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.mediaType === 'podcast' || this.store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
|
return this.mediaType === 'podcast' || this.store.getters['libraries/getCurrentLibraryMediaType'] === 'podcast'
|
||||||
},
|
},
|
||||||
isMusic() {
|
|
||||||
return this.mediaType === 'music'
|
|
||||||
},
|
|
||||||
isExplicit() {
|
isExplicit() {
|
||||||
return this.mediaMetadata.explicit || false
|
return this.mediaMetadata.explicit || false
|
||||||
},
|
},
|
||||||
@@ -336,7 +333,6 @@ export default {
|
|||||||
displayLineTwo() {
|
displayLineTwo() {
|
||||||
if (this.recentEpisode) return this.title
|
if (this.recentEpisode) return this.title
|
||||||
if (this.isPodcast) return this.author
|
if (this.isPodcast) return this.author
|
||||||
if (this.isMusic) return this.artist
|
|
||||||
if (this.collapsedSeries) return ''
|
if (this.collapsedSeries) return ''
|
||||||
if (this.isAuthorBookshelfView) {
|
if (this.isAuthorBookshelfView) {
|
||||||
return this.mediaMetadata.publishedYear || ''
|
return this.mediaMetadata.publishedYear || ''
|
||||||
@@ -364,7 +360,6 @@ export default {
|
|||||||
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId, this.recentEpisode.id)
|
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId, this.recentEpisode.id)
|
||||||
},
|
},
|
||||||
userProgress() {
|
userProgress() {
|
||||||
if (this.isMusic) return null
|
|
||||||
if (this.episodeProgress) return this.episodeProgress
|
if (this.episodeProgress) return this.episodeProgress
|
||||||
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||||
},
|
},
|
||||||
@@ -420,7 +415,7 @@ export default {
|
|||||||
return !this.isSelectionMode && !this.showPlayButton && this.ebookFormat
|
return !this.isSelectionMode && !this.showPlayButton && this.ebookFormat
|
||||||
},
|
},
|
||||||
showPlayButton() {
|
showPlayButton() {
|
||||||
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode || this.isMusic)
|
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && !this.isStreaming && (this.numTracks || this.recentEpisode)
|
||||||
},
|
},
|
||||||
showSmallEBookIcon() {
|
showSmallEBookIcon() {
|
||||||
return !this.isSelectionMode && this.ebookFormat
|
return !this.isSelectionMode && this.ebookFormat
|
||||||
@@ -464,8 +459,6 @@ export default {
|
|||||||
return this.store.getters['user/getIsAdminOrUp']
|
return this.store.getters['user/getIsAdminOrUp']
|
||||||
},
|
},
|
||||||
moreMenuItems() {
|
moreMenuItems() {
|
||||||
if (this.isMusic) return []
|
|
||||||
|
|
||||||
if (this.recentEpisode) {
|
if (this.recentEpisode) {
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,38 +27,6 @@
|
|||||||
<nuxt-link :to="`/library/${libraryId}/bookshelf?filter=publishers.${$encode(publisher)}`" class="hover:underline">{{ publisher }}</nuxt-link>
|
<nuxt-link :to="`/library/${libraryId}/bookshelf?filter=publishers.${$encode(publisher)}`" class="hover:underline">{{ publisher }}</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="musicAlbum" class="flex py-0.5">
|
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Album</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ musicAlbum }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="musicAlbumArtist" class="flex py-0.5">
|
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Album Artist</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ musicAlbumArtist }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="musicTrackPretty" class="flex py-0.5">
|
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Track</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ musicTrackPretty }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="musicDiscPretty" class="flex py-0.5">
|
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">Disc</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ musicDiscPretty }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="podcastType" class="flex py-0.5">
|
<div v-if="podcastType" class="flex py-0.5">
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelPodcastType }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelPodcastType }}</span>
|
||||||
@@ -97,7 +65,7 @@
|
|||||||
<nuxt-link :to="`/library/${libraryId}/bookshelf?filter=languages.${$encode(language)}`" class="hover:underline">{{ language }}</nuxt-link>
|
<nuxt-link :to="`/library/${libraryId}/bookshelf?filter=languages.${$encode(language)}`" class="hover:underline">{{ language }}</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tracks.length || audioFile || (isPodcast && totalPodcastDuration)" class="flex py-0.5">
|
<div v-if="tracks.length || (isPodcast && totalPodcastDuration)" class="flex py-0.5">
|
||||||
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
<div class="w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||||
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelDuration }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelDuration }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,10 +102,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.libraryItem.mediaType === 'podcast'
|
return this.libraryItem.mediaType === 'podcast'
|
||||||
},
|
},
|
||||||
audioFile() {
|
|
||||||
// Music track
|
|
||||||
return this.media.audioFile
|
|
||||||
},
|
|
||||||
media() {
|
media() {
|
||||||
return this.libraryItem.media || {}
|
return this.libraryItem.media || {}
|
||||||
},
|
},
|
||||||
@@ -168,25 +132,6 @@ export default {
|
|||||||
publisher() {
|
publisher() {
|
||||||
return this.mediaMetadata.publisher || ''
|
return this.mediaMetadata.publisher || ''
|
||||||
},
|
},
|
||||||
musicArtists() {
|
|
||||||
return this.mediaMetadata.artists || []
|
|
||||||
},
|
|
||||||
musicAlbum() {
|
|
||||||
return this.mediaMetadata.album || ''
|
|
||||||
},
|
|
||||||
musicAlbumArtist() {
|
|
||||||
return this.mediaMetadata.albumArtist || ''
|
|
||||||
},
|
|
||||||
musicTrackPretty() {
|
|
||||||
if (!this.mediaMetadata.trackNumber) return null
|
|
||||||
if (!this.mediaMetadata.trackTotal) return this.mediaMetadata.trackNumber
|
|
||||||
return `${this.mediaMetadata.trackNumber} / ${this.mediaMetadata.trackTotal}`
|
|
||||||
},
|
|
||||||
musicDiscPretty() {
|
|
||||||
if (!this.mediaMetadata.discNumber) return null
|
|
||||||
if (!this.mediaMetadata.discTotal) return this.mediaMetadata.discNumber
|
|
||||||
return `${this.mediaMetadata.discNumber} / ${this.mediaMetadata.discTotal}`
|
|
||||||
},
|
|
||||||
narrators() {
|
narrators() {
|
||||||
return this.mediaMetadata.narrators || []
|
return this.mediaMetadata.narrators || []
|
||||||
},
|
},
|
||||||
@@ -220,4 +165,4 @@ export default {
|
|||||||
methods: {},
|
methods: {},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -98,9 +98,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.libraryMediaType === 'podcast'
|
return this.libraryMediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusic() {
|
|
||||||
return this.libraryMediaType === 'music'
|
|
||||||
},
|
|
||||||
seriesItems() {
|
seriesItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -274,35 +271,9 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
musicItems() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelAll,
|
|
||||||
value: 'all'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelGenre,
|
|
||||||
textPlural: this.$strings.LabelGenres,
|
|
||||||
value: 'genres',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelTag,
|
|
||||||
textPlural: this.$strings.LabelTags,
|
|
||||||
value: 'tags',
|
|
||||||
sublist: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.ButtonIssues,
|
|
||||||
value: 'issues',
|
|
||||||
sublist: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
selectItems() {
|
selectItems() {
|
||||||
if (this.isSeries) return this.seriesItems
|
if (this.isSeries) return this.seriesItems
|
||||||
if (this.isPodcast) return this.podcastItems
|
if (this.isPodcast) return this.podcastItems
|
||||||
if (this.isMusic) return this.musicItems
|
|
||||||
return this.bookItems
|
return this.bookItems
|
||||||
},
|
},
|
||||||
selectedItemSublist() {
|
selectedItemSublist() {
|
||||||
|
|||||||
@@ -56,9 +56,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.libraryMediaType === 'podcast'
|
return this.libraryMediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isMusic() {
|
|
||||||
return this.libraryMediaType === 'music'
|
|
||||||
},
|
|
||||||
podcastItems() {
|
podcastItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -148,40 +145,10 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
musicItems() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelTitle,
|
|
||||||
value: 'media.metadata.title'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelAddedAt,
|
|
||||||
value: 'addedAt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelSize,
|
|
||||||
value: 'size'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelDuration,
|
|
||||||
value: 'media.duration'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelFileBirthtime,
|
|
||||||
value: 'birthtimeMs'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.LabelFileModified,
|
|
||||||
value: 'mtimeMs'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
selectItems() {
|
selectItems() {
|
||||||
let items = null
|
let items = null
|
||||||
if (this.isPodcast) {
|
if (this.isPodcast) {
|
||||||
items = this.podcastItems
|
items = this.podcastItems
|
||||||
} else if (this.isMusic) {
|
|
||||||
items = this.musicItems
|
|
||||||
} else if (this.$store.getters['user/getUserSetting']('filterBy').startsWith('series.')) {
|
} else if (this.$store.getters['user/getUserSetting']('filterBy').startsWith('series.')) {
|
||||||
items = this.seriesItems
|
items = this.seriesItems
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -108,9 +108,9 @@ export default {
|
|||||||
if (res.warning) {
|
if (res.warning) {
|
||||||
this.$toast.warning(res.warning)
|
this.$toast.warning(res.warning)
|
||||||
} else if (res.updated) {
|
} else if (res.updated) {
|
||||||
this.$toast.success(this.$strings.ToastNoUpdatesNecessary)
|
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info(this.$strings.ToastItemDetailsUpdateUnneeded)
|
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -170,7 +170,7 @@ export default {
|
|||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
if (updateResult) {
|
if (updateResult) {
|
||||||
if (updateResult.updated) {
|
if (updateResult.updated) {
|
||||||
this.$toast.success(this.$strings.MessageItemDetailsUpdated)
|
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
|
|||||||
@@ -178,22 +178,6 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
toggleFullscreen(isFullscreen) {
|
toggleFullscreen(isFullscreen) {
|
||||||
this.$store.commit('setPlayerIsFullscreen', isFullscreen)
|
this.$store.commit('setPlayerIsFullscreen', isFullscreen)
|
||||||
|
|
||||||
var videoPlayerEl = document.getElementById('video-player')
|
|
||||||
if (videoPlayerEl) {
|
|
||||||
if (isFullscreen) {
|
|
||||||
videoPlayerEl.style.width = '100vw'
|
|
||||||
videoPlayerEl.style.height = '100vh'
|
|
||||||
videoPlayerEl.style.top = '0px'
|
|
||||||
videoPlayerEl.style.left = '0px'
|
|
||||||
} else {
|
|
||||||
videoPlayerEl.style.width = '384px'
|
|
||||||
videoPlayerEl.style.height = '216px'
|
|
||||||
videoPlayerEl.style.top = 'unset'
|
|
||||||
videoPlayerEl.style.bottom = '80px'
|
|
||||||
videoPlayerEl.style.left = '16px'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setDuration(duration) {
|
setDuration(duration) {
|
||||||
this.duration = duration
|
this.duration = duration
|
||||||
|
|||||||
@@ -3,67 +3,67 @@
|
|||||||
<form class="w-full h-full px-2 md:px-4 py-6" @submit.prevent="submitForm">
|
<form class="w-full h-full px-2 md:px-4 py-6" @submit.prevent="submitForm">
|
||||||
<div class="flex flex-wrap -mx-1">
|
<div class="flex flex-wrap -mx-1">
|
||||||
<div class="w-full md:w-1/2 px-1">
|
<div class="w-full md:w-1/2 px-1">
|
||||||
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" />
|
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1 mt-2 md:mt-0">
|
<div class="flex-grow px-1 mt-2 md:mt-0">
|
||||||
<ui-text-input-with-label ref="subtitleInput" v-model="details.subtitle" :label="$strings.LabelSubtitle" />
|
<ui-text-input-with-label ref="subtitleInput" v-model="details.subtitle" :label="$strings.LabelSubtitle" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap mt-2 -mx-1">
|
<div class="flex flex-wrap mt-2 -mx-1">
|
||||||
<div class="w-full md:w-3/4 px-1">
|
<div class="w-full md:w-3/4 px-1">
|
||||||
<!-- Authors filter only contains authors in this library, uses filter data -->
|
<!-- Authors filter only contains authors in this library, uses filter data -->
|
||||||
<ui-multi-select-query-input ref="authorsSelect" v-model="details.authors" :label="$strings.LabelAuthors" filter-key="authors" />
|
<ui-multi-select-query-input ref="authorsSelect" v-model="details.authors" :label="$strings.LabelAuthors" filter-key="authors" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1 mt-2 md:mt-0">
|
<div class="flex-grow px-1 mt-2 md:mt-0">
|
||||||
<ui-text-input-with-label ref="publishYearInput" v-model="details.publishedYear" type="number" :label="$strings.LabelPublishYear" />
|
<ui-text-input-with-label ref="publishYearInput" v-model="details.publishedYear" type="number" :label="$strings.LabelPublishYear" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex mt-2 -mx-1">
|
<div class="flex mt-2 -mx-1">
|
||||||
<div class="flex-grow px-1">
|
<div class="flex-grow px-1">
|
||||||
<widgets-series-input-widget v-model="details.series" />
|
<widgets-series-input-widget v-model="details.series" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" />
|
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" @input="handleInputChange" />
|
||||||
|
|
||||||
<div class="flex flex-wrap mt-2 -mx-1">
|
<div class="flex flex-wrap mt-2 -mx-1">
|
||||||
<div class="w-full md:w-1/2 px-1">
|
<div class="w-full md:w-1/2 px-1">
|
||||||
<ui-multi-select ref="genresSelect" v-model="details.genres" :label="$strings.LabelGenres" :items="genres" />
|
<ui-multi-select ref="genresSelect" v-model="details.genres" :label="$strings.LabelGenres" :items="genres" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1 mt-2 md:mt-0">
|
<div class="flex-grow px-1 mt-2 md:mt-0">
|
||||||
<ui-multi-select ref="tagsSelect" v-model="newTags" :label="$strings.LabelTags" :items="tags" />
|
<ui-multi-select ref="tagsSelect" v-model="newTags" :label="$strings.LabelTags" :items="tags" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap mt-2 -mx-1">
|
<div class="flex flex-wrap mt-2 -mx-1">
|
||||||
<div class="w-full md:w-1/2 px-1">
|
<div class="w-full md:w-1/2 px-1">
|
||||||
<ui-multi-select ref="narratorsSelect" v-model="details.narrators" :label="$strings.LabelNarrators" :items="narrators" />
|
<ui-multi-select ref="narratorsSelect" v-model="details.narrators" :label="$strings.LabelNarrators" :items="narrators" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
|
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
|
||||||
<ui-text-input-with-label ref="isbnInput" v-model="details.isbn" label="ISBN" />
|
<ui-text-input-with-label ref="isbnInput" v-model="details.isbn" label="ISBN" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
|
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
|
||||||
<ui-text-input-with-label ref="asinInput" v-model="details.asin" label="ASIN" />
|
<ui-text-input-with-label ref="asinInput" v-model="details.asin" label="ASIN" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap mt-2 -mx-1">
|
<div class="flex flex-wrap mt-2 -mx-1">
|
||||||
<div class="w-full md:w-1/4 px-1">
|
<div class="w-full md:w-1/4 px-1">
|
||||||
<ui-text-input-with-label ref="publisherInput" v-model="details.publisher" :label="$strings.LabelPublisher" />
|
<ui-text-input-with-label ref="publisherInput" v-model="details.publisher" :label="$strings.LabelPublisher" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
|
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
|
||||||
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" />
|
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1 pt-6 mt-2 md:mt-0">
|
<div class="flex-grow px-1 pt-6 mt-2 md:mt-0">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<ui-checkbox v-model="details.explicit" :label="$strings.LabelExplicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
<ui-checkbox v-model="details.explicit" :label="$strings.LabelExplicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1 pt-6 mt-2 md:mt-0">
|
<div class="flex-grow px-1 pt-6 mt-2 md:mt-0">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<ui-checkbox v-model="details.abridged" :label="$strings.LabelAbridged" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
<ui-checkbox v-model="details.abridged" :label="$strings.LabelAbridged" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,6 +132,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleInputChange() {
|
||||||
|
this.$emit('change', {
|
||||||
|
libraryItemId: this.libraryItem.id,
|
||||||
|
hasChanges: this.checkForChanges().hasChanges
|
||||||
|
})
|
||||||
|
},
|
||||||
getDetails() {
|
getDetails() {
|
||||||
this.forceBlur()
|
this.forceBlur()
|
||||||
return this.checkForChanges()
|
return this.checkForChanges()
|
||||||
@@ -172,6 +178,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.handleInputChange()
|
||||||
},
|
},
|
||||||
forceBlur() {
|
forceBlur() {
|
||||||
if (this.$refs.titleInput) this.$refs.titleInput.blur()
|
if (this.$refs.titleInput) this.$refs.titleInput.blur()
|
||||||
@@ -286,4 +293,4 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,45 +3,45 @@
|
|||||||
<form class="w-full h-full px-4 py-6" @submit.prevent="submitForm">
|
<form class="w-full h-full px-4 py-6" @submit.prevent="submitForm">
|
||||||
<div class="flex -mx-1">
|
<div class="flex -mx-1">
|
||||||
<div class="w-1/2 px-1">
|
<div class="w-1/2 px-1">
|
||||||
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" />
|
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1">
|
<div class="flex-grow px-1">
|
||||||
<ui-text-input-with-label ref="authorInput" v-model="details.author" :label="$strings.LabelAuthor" />
|
<ui-text-input-with-label ref="authorInput" v-model="details.author" :label="$strings.LabelAuthor" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui-text-input-with-label ref="feedUrlInput" v-model="details.feedUrl" :label="$strings.LabelRSSFeedURL" class="mt-2" />
|
<ui-text-input-with-label ref="feedUrlInput" v-model="details.feedUrl" :label="$strings.LabelRSSFeedURL" class="mt-2" @input="handleInputChange" />
|
||||||
|
|
||||||
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" />
|
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" @input="handleInputChange" />
|
||||||
|
|
||||||
<div class="flex mt-2 -mx-1">
|
<div class="flex mt-2 -mx-1">
|
||||||
<div class="w-1/2 px-1">
|
<div class="w-1/2 px-1">
|
||||||
<ui-multi-select ref="genresSelect" v-model="details.genres" :label="$strings.LabelGenres" :items="genres" />
|
<ui-multi-select ref="genresSelect" v-model="details.genres" :label="$strings.LabelGenres" :items="genres" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1">
|
<div class="flex-grow px-1">
|
||||||
<ui-multi-select ref="tagsSelect" v-model="newTags" :label="$strings.LabelTags" :items="tags" />
|
<ui-multi-select ref="tagsSelect" v-model="newTags" :label="$strings.LabelTags" :items="tags" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex mt-2 -mx-1">
|
<div class="flex mt-2 -mx-1">
|
||||||
<div class="w-1/4 px-1">
|
<div class="w-1/4 px-1">
|
||||||
<ui-text-input-with-label ref="releaseDateInput" v-model="details.releaseDate" :label="$strings.LabelReleaseDate" />
|
<ui-text-input-with-label ref="releaseDateInput" v-model="details.releaseDate" :label="$strings.LabelReleaseDate" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/4 px-1">
|
<div class="w-1/4 px-1">
|
||||||
<ui-text-input-with-label ref="itunesIdInput" v-model="details.itunesId" label="iTunes ID" />
|
<ui-text-input-with-label ref="itunesIdInput" v-model="details.itunesId" label="iTunes ID" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/4 px-1">
|
<div class="w-1/4 px-1">
|
||||||
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" />
|
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-1 pt-6">
|
<div class="flex-grow px-1 pt-6">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<ui-checkbox v-model="details.explicit" :label="$strings.LabelExplicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
<ui-checkbox v-model="details.explicit" :label="$strings.LabelExplicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mt-2 -mx-1">
|
<div class="flex mt-2 -mx-1">
|
||||||
<div class="w-1/4 px-1">
|
<div class="w-1/4 px-1">
|
||||||
<ui-dropdown :label="$strings.LabelPodcastType" v-model="details.type" :items="podcastTypes" small class="max-w-52" />
|
<ui-dropdown :label="$strings.LabelPodcastType" v-model="details.type" :items="podcastTypes" small class="max-w-52" @input="handleInputChange" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -105,6 +105,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleInputChange() {
|
||||||
|
this.$emit('change', {
|
||||||
|
libraryItemId: this.libraryItem.id,
|
||||||
|
hasChanges: this.checkForChanges().hasChanges
|
||||||
|
})
|
||||||
|
},
|
||||||
getDetails() {
|
getDetails() {
|
||||||
this.forceBlur()
|
this.forceBlur()
|
||||||
return this.checkForChanges()
|
return this.checkForChanges()
|
||||||
@@ -136,6 +142,8 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.handleInputChange()
|
||||||
},
|
},
|
||||||
forceBlur() {
|
forceBlur() {
|
||||||
if (this.$refs.titleInput) this.$refs.titleInput.blur()
|
if (this.$refs.titleInput) this.$refs.titleInput.blur()
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.13.0",
|
"version": "2.13.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.13.0",
|
"version": "2.13.4",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.13.0",
|
"version": "2.13.4",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
@@ -97,8 +97,8 @@
|
|||||||
<div class="flex justify-center flex-wrap">
|
<div class="flex justify-center flex-wrap">
|
||||||
<template v-for="libraryItem in libraryItemCopies">
|
<template v-for="libraryItem in libraryItemCopies">
|
||||||
<div :key="libraryItem.id" class="w-full max-w-3xl border border-black-300 p-6 -ml-px -mt-px">
|
<div :key="libraryItem.id" class="w-full max-w-3xl border border-black-300 p-6 -ml-px -mt-px">
|
||||||
<widgets-book-details-edit v-if="libraryItem.mediaType === 'book'" :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" />
|
<widgets-book-details-edit v-if="libraryItem.mediaType === 'book'" :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" />
|
||||||
<widgets-podcast-details-edit v-else :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" />
|
<widgets-podcast-details-edit v-else :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
|
|
||||||
<div :class="isScrollable ? 'fixed left-0 box-shadow-lg-up bg-primary' : ''" class="w-full h-20 px-4 flex items-center border-t border-bg z-40" :style="{ bottom: streamLibraryItem ? '165px' : '0px' }">
|
<div :class="isScrollable ? 'fixed left-0 box-shadow-lg-up bg-primary' : ''" class="w-full h-20 px-4 flex items-center border-t border-bg z-40" :style="{ bottom: streamLibraryItem ? '165px' : '0px' }">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn color="success" :padding-x="8" class="text-lg" :loading="isProcessing" @click.prevent="saveClick">{{ $strings.ButtonSave }}</ui-btn>
|
<ui-btn color="success" :padding-x="8" class="text-lg" :loading="isProcessing" :disabled="!hasChanges" @click.prevent="saveClick">{{ $strings.ButtonSave }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -170,7 +170,8 @@ export default {
|
|||||||
abridged: false
|
abridged: false
|
||||||
},
|
},
|
||||||
appendableKeys: ['authors', 'genres', 'tags', 'narrators', 'series'],
|
appendableKeys: ['authors', 'genres', 'tags', 'narrators', 'series'],
|
||||||
openMapOptions: false
|
openMapOptions: false,
|
||||||
|
itemsWithChanges: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -221,9 +222,19 @@ export default {
|
|||||||
},
|
},
|
||||||
hasSelectedBatchUsage() {
|
hasSelectedBatchUsage() {
|
||||||
return Object.values(this.selectedBatchUsage).some((b) => !!b)
|
return Object.values(this.selectedBatchUsage).some((b) => !!b)
|
||||||
|
},
|
||||||
|
hasChanges() {
|
||||||
|
return this.itemsWithChanges.length > 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleItemChange(itemChange) {
|
||||||
|
if (!itemChange.hasChanges) {
|
||||||
|
this.itemsWithChanges = this.itemsWithChanges.filter((id) => id !== itemChange.libraryItemId)
|
||||||
|
} else if (!this.itemsWithChanges.includes(itemChange.libraryItemId)) {
|
||||||
|
this.itemsWithChanges.push(itemChange.libraryItemId)
|
||||||
|
}
|
||||||
|
},
|
||||||
blurBatchForm() {
|
blurBatchForm() {
|
||||||
if (this.$refs.seriesSelect && this.$refs.seriesSelect.isFocused) {
|
if (this.$refs.seriesSelect && this.$refs.seriesSelect.isFocused) {
|
||||||
this.$refs.seriesSelect.forceBlur()
|
this.$refs.seriesSelect.forceBlur()
|
||||||
@@ -283,38 +294,10 @@ export default {
|
|||||||
removedSeriesItem(item) {},
|
removedSeriesItem(item) {},
|
||||||
newNarratorItem(item) {},
|
newNarratorItem(item) {},
|
||||||
removedNarratorItem(item) {},
|
removedNarratorItem(item) {},
|
||||||
newTagItem(item) {
|
newTagItem(item) {},
|
||||||
// if (item && !this.newTagItems.includes(item)) {
|
removedTagItem(item) {},
|
||||||
// this.newTagItems.push(item)
|
newGenreItem(item) {},
|
||||||
// }
|
removedGenreItem(item) {},
|
||||||
},
|
|
||||||
removedTagItem(item) {
|
|
||||||
// If newly added, remove if not used on any other items
|
|
||||||
// if (item && this.newTagItems.includes(item)) {
|
|
||||||
// var usedByOtherAb = this.libraryItemCopies.find((ab) => {
|
|
||||||
// return ab.tags && ab.tags.includes(item)
|
|
||||||
// })
|
|
||||||
// if (!usedByOtherAb) {
|
|
||||||
// this.newTagItems = this.newTagItems.filter((t) => t !== item)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
newGenreItem(item) {
|
|
||||||
// if (item && !this.newGenreItems.includes(item)) {
|
|
||||||
// this.newGenreItems.push(item)
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
removedGenreItem(item) {
|
|
||||||
// If newly added, remove if not used on any other items
|
|
||||||
// if (item && this.newGenreItems.includes(item)) {
|
|
||||||
// var usedByOtherAb = this.libraryItemCopies.find((ab) => {
|
|
||||||
// return ab.book.genres && ab.book.genres.includes(item)
|
|
||||||
// })
|
|
||||||
// if (!usedByOtherAb) {
|
|
||||||
// this.newGenreItems = this.newGenreItems.filter((t) => t !== item)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
init() {
|
init() {
|
||||||
// TODO: Better deep cloning of library items
|
// TODO: Better deep cloning of library items
|
||||||
this.libraryItemCopies = this.libraryItems.map((li) => {
|
this.libraryItemCopies = this.libraryItems.map((li) => {
|
||||||
@@ -376,6 +359,7 @@ export default {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
if (data.updates) {
|
if (data.updates) {
|
||||||
|
this.itemsWithChanges = []
|
||||||
this.$toast.success(`Successfully updated ${data.updates} items`)
|
this.$toast.success(`Successfully updated ${data.updates} items`)
|
||||||
this.$router.replace(`/library/${this.currentLibraryId}/bookshelf`)
|
this.$router.replace(`/library/${this.currentLibraryId}/bookshelf`)
|
||||||
} else {
|
} else {
|
||||||
@@ -387,10 +371,28 @@ export default {
|
|||||||
this.$toast.error('Failed to batch update')
|
this.$toast.error('Failed to batch update')
|
||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
beforeUnload(e) {
|
||||||
|
if (!e || !this.hasChanges) return
|
||||||
|
e.preventDefault()
|
||||||
|
e.returnValue = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeRouteLeave(to, from, next) {
|
||||||
|
if (this.hasChanges) {
|
||||||
|
next(false)
|
||||||
|
window.location = to.path
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init()
|
this.init()
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', this.beforeUnload)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('beforeunload', this.beforeUnload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -39,16 +39,11 @@
|
|||||||
><span :key="index" v-if="index < seriesList.length - 1">, </span>
|
><span :key="index" v-if="index < seriesList.length - 1">, </span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="!isVideo">
|
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">{{ $getString('LabelByAuthor', [podcastAuthor]) }}</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="authors.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">
|
by <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">, </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">, </span></nuxt-link>
|
</p>
|
||||||
</p>
|
<p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p>
|
||||||
<p v-else-if="authors.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
|
|
||||||
by <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">, </span></nuxt-link>
|
|
||||||
</p>
|
|
||||||
<p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<content-library-item-details :library-item="libraryItem" />
|
<content-library-item-details :library-item="libraryItem" />
|
||||||
</div>
|
</div>
|
||||||
@@ -109,7 +104,7 @@
|
|||||||
<ui-icon-btn icon="" outlined class="mx-0.5" @click="editClick" />
|
<ui-icon-btn icon="" outlined class="mx-0.5" @click="editClick" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<ui-tooltip v-if="!isPodcast && !isMusic" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
<ui-tooltip v-if="!isPodcast" :text="userIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="top">
|
||||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
|
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
@@ -220,12 +215,6 @@ export default {
|
|||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.libraryItem.mediaType === 'podcast'
|
return this.libraryItem.mediaType === 'podcast'
|
||||||
},
|
},
|
||||||
isVideo() {
|
|
||||||
return this.libraryItem.mediaType === 'video'
|
|
||||||
},
|
|
||||||
isMusic() {
|
|
||||||
return this.libraryItem.mediaType === 'music'
|
|
||||||
},
|
|
||||||
isMissing() {
|
isMissing() {
|
||||||
return this.libraryItem.isMissing
|
return this.libraryItem.isMissing
|
||||||
},
|
},
|
||||||
@@ -240,8 +229,6 @@ export default {
|
|||||||
},
|
},
|
||||||
showPlayButton() {
|
showPlayButton() {
|
||||||
if (this.isMissing || this.isInvalid) return false
|
if (this.isMissing || this.isInvalid) return false
|
||||||
if (this.isMusic) return !!this.audioFile
|
|
||||||
if (this.isVideo) return !!this.videoFile
|
|
||||||
if (this.isPodcast) return this.podcastEpisodes.length
|
if (this.isPodcast) return this.podcastEpisodes.length
|
||||||
return this.tracks.length
|
return this.tracks.length
|
||||||
},
|
},
|
||||||
@@ -292,9 +279,6 @@ export default {
|
|||||||
authors() {
|
authors() {
|
||||||
return this.mediaMetadata.authors || []
|
return this.mediaMetadata.authors || []
|
||||||
},
|
},
|
||||||
musicArtists() {
|
|
||||||
return this.mediaMetadata.artists || []
|
|
||||||
},
|
|
||||||
series() {
|
series() {
|
||||||
return this.mediaMetadata.series || []
|
return this.mediaMetadata.series || []
|
||||||
},
|
},
|
||||||
@@ -309,7 +293,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
duration() {
|
duration() {
|
||||||
if (!this.tracks.length && !this.audioFile) return 0
|
if (!this.tracks.length) return 0
|
||||||
return this.media.duration
|
return this.media.duration
|
||||||
},
|
},
|
||||||
libraryFiles() {
|
libraryFiles() {
|
||||||
@@ -321,18 +305,10 @@ export default {
|
|||||||
ebookFile() {
|
ebookFile() {
|
||||||
return this.media.ebookFile
|
return this.media.ebookFile
|
||||||
},
|
},
|
||||||
videoFile() {
|
|
||||||
return this.media.videoFile
|
|
||||||
},
|
|
||||||
audioFile() {
|
|
||||||
// Music track
|
|
||||||
return this.media.audioFile
|
|
||||||
},
|
|
||||||
description() {
|
description() {
|
||||||
return this.mediaMetadata.description || ''
|
return this.mediaMetadata.description || ''
|
||||||
},
|
},
|
||||||
userMediaProgress() {
|
userMediaProgress() {
|
||||||
if (this.isMusic) return null
|
|
||||||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||||
},
|
},
|
||||||
userIsFinished() {
|
userIsFinished() {
|
||||||
|
|||||||
@@ -1,260 +0,0 @@
|
|||||||
import Hls from 'hls.js'
|
|
||||||
import EventEmitter from 'events'
|
|
||||||
|
|
||||||
export default class LocalVideoPlayer extends EventEmitter {
|
|
||||||
constructor(ctx) {
|
|
||||||
super()
|
|
||||||
|
|
||||||
this.ctx = ctx
|
|
||||||
this.player = null
|
|
||||||
|
|
||||||
this.libraryItem = null
|
|
||||||
this.videoTrack = null
|
|
||||||
this.isHlsTranscode = null
|
|
||||||
this.hlsInstance = null
|
|
||||||
this.usingNativeplayer = false
|
|
||||||
this.startTime = 0
|
|
||||||
this.playWhenReady = false
|
|
||||||
this.defaultPlaybackRate = 1
|
|
||||||
|
|
||||||
this.playableMimeTypes = []
|
|
||||||
|
|
||||||
this.initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
if (document.getElementById('video-player')) {
|
|
||||||
document.getElementById('video-player').remove()
|
|
||||||
}
|
|
||||||
var videoEl = document.createElement('video')
|
|
||||||
videoEl.id = 'video-player'
|
|
||||||
// videoEl.style.display = 'none'
|
|
||||||
videoEl.className = 'absolute bg-black z-50'
|
|
||||||
videoEl.style.height = '216px'
|
|
||||||
videoEl.style.width = '384px'
|
|
||||||
videoEl.style.bottom = '80px'
|
|
||||||
videoEl.style.left = '16px'
|
|
||||||
document.body.appendChild(videoEl)
|
|
||||||
this.player = videoEl
|
|
||||||
|
|
||||||
this.player.addEventListener('play', this.evtPlay.bind(this))
|
|
||||||
this.player.addEventListener('pause', this.evtPause.bind(this))
|
|
||||||
this.player.addEventListener('progress', this.evtProgress.bind(this))
|
|
||||||
this.player.addEventListener('ended', this.evtEnded.bind(this))
|
|
||||||
this.player.addEventListener('error', this.evtError.bind(this))
|
|
||||||
this.player.addEventListener('loadedmetadata', this.evtLoadedMetadata.bind(this))
|
|
||||||
this.player.addEventListener('timeupdate', this.evtTimeupdate.bind(this))
|
|
||||||
|
|
||||||
var mimeTypes = ['video/mp4']
|
|
||||||
var mimeTypeCanPlayMap = {}
|
|
||||||
mimeTypes.forEach((mt) => {
|
|
||||||
var canPlay = this.player.canPlayType(mt)
|
|
||||||
mimeTypeCanPlayMap[mt] = canPlay
|
|
||||||
if (canPlay) this.playableMimeTypes.push(mt)
|
|
||||||
})
|
|
||||||
console.log(`[LocalVideoPlayer] Supported mime types`, mimeTypeCanPlayMap, this.playableMimeTypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
evtPlay() {
|
|
||||||
this.emit('stateChange', 'PLAYING')
|
|
||||||
}
|
|
||||||
evtPause() {
|
|
||||||
this.emit('stateChange', 'PAUSED')
|
|
||||||
}
|
|
||||||
evtProgress() {
|
|
||||||
var lastBufferTime = this.getLastBufferedTime()
|
|
||||||
this.emit('buffertimeUpdate', lastBufferTime)
|
|
||||||
}
|
|
||||||
evtEnded() {
|
|
||||||
console.log(`[LocalVideoPlayer] Ended`)
|
|
||||||
this.emit('finished')
|
|
||||||
}
|
|
||||||
evtError(error) {
|
|
||||||
console.error('Player error', error)
|
|
||||||
this.emit('error', error)
|
|
||||||
}
|
|
||||||
evtLoadedMetadata(data) {
|
|
||||||
if (!this.isHlsTranscode) {
|
|
||||||
this.player.currentTime = this.startTime
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('stateChange', 'LOADED')
|
|
||||||
if (this.playWhenReady) {
|
|
||||||
this.playWhenReady = false
|
|
||||||
this.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
evtTimeupdate() {
|
|
||||||
if (this.player.paused) {
|
|
||||||
this.emit('timeupdate', this.getCurrentTime())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
this.destroyHlsInstance()
|
|
||||||
if (this.player) {
|
|
||||||
this.player.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set(libraryItem, videoTrack, isHlsTranscode, startTime, playWhenReady = false) {
|
|
||||||
this.libraryItem = libraryItem
|
|
||||||
this.videoTrack = videoTrack
|
|
||||||
this.isHlsTranscode = isHlsTranscode
|
|
||||||
this.playWhenReady = playWhenReady
|
|
||||||
this.startTime = startTime
|
|
||||||
|
|
||||||
if (this.hlsInstance) {
|
|
||||||
this.destroyHlsInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isHlsTranscode) {
|
|
||||||
this.setHlsStream()
|
|
||||||
} else {
|
|
||||||
this.setDirectPlay()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setHlsStream() {
|
|
||||||
// iOS does not support Media Elements but allows for HLS in the native video player
|
|
||||||
if (!Hls.isSupported()) {
|
|
||||||
console.warn('HLS is not supported - fallback to using video element')
|
|
||||||
this.usingNativeplayer = true
|
|
||||||
this.player.src = this.videoTrack.relativeContentUrl
|
|
||||||
this.player.currentTime = this.startTime
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var hlsOptions = {
|
|
||||||
startPosition: this.startTime || -1
|
|
||||||
// No longer needed because token is put in a query string
|
|
||||||
// xhrSetup: (xhr) => {
|
|
||||||
// xhr.setRequestHeader('Authorization', `Bearer ${this.token}`)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
this.hlsInstance = new Hls(hlsOptions)
|
|
||||||
|
|
||||||
this.hlsInstance.attachMedia(this.player)
|
|
||||||
this.hlsInstance.on(Hls.Events.MEDIA_ATTACHED, () => {
|
|
||||||
this.hlsInstance.loadSource(this.videoTrack.relativeContentUrl)
|
|
||||||
|
|
||||||
this.hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
||||||
console.log('[HLS] Manifest Parsed')
|
|
||||||
})
|
|
||||||
|
|
||||||
this.hlsInstance.on(Hls.Events.ERROR, (e, data) => {
|
|
||||||
console.error('[HLS] Error', data.type, data.details, data)
|
|
||||||
if (data.details === Hls.ErrorDetails.BUFFER_STALLED_ERROR) {
|
|
||||||
console.error('[HLS] BUFFER STALLED ERROR')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.hlsInstance.on(Hls.Events.DESTROYING, () => {
|
|
||||||
console.log('[HLS] Destroying HLS Instance')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
setDirectPlay() {
|
|
||||||
this.player.src = this.videoTrack.relativeContentUrl
|
|
||||||
console.log(`[LocalVideoPlayer] Loading track src ${this.videoTrack.relativeContentUrl}`)
|
|
||||||
this.player.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
destroyHlsInstance() {
|
|
||||||
if (!this.hlsInstance) return
|
|
||||||
if (this.hlsInstance.destroy) {
|
|
||||||
var temp = this.hlsInstance
|
|
||||||
temp.destroy()
|
|
||||||
}
|
|
||||||
this.hlsInstance = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async resetStream(startTime) {
|
|
||||||
this.destroyHlsInstance()
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
this.set(this.libraryItem, this.videoTrack, this.isHlsTranscode, startTime, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
playPause() {
|
|
||||||
if (!this.player) return
|
|
||||||
if (this.player.paused) this.play()
|
|
||||||
else this.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
play() {
|
|
||||||
if (this.player) this.player.play()
|
|
||||||
}
|
|
||||||
|
|
||||||
pause() {
|
|
||||||
if (this.player) this.player.pause()
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentTime() {
|
|
||||||
return this.player ? this.player.currentTime : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
getDuration() {
|
|
||||||
return this.videoTrack.duration
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlaybackRate(playbackRate) {
|
|
||||||
if (!this.player) return
|
|
||||||
this.defaultPlaybackRate = playbackRate
|
|
||||||
this.player.playbackRate = playbackRate
|
|
||||||
}
|
|
||||||
|
|
||||||
seek(time) {
|
|
||||||
if (!this.player) return
|
|
||||||
this.player.currentTime = Math.max(0, time)
|
|
||||||
}
|
|
||||||
|
|
||||||
setVolume(volume) {
|
|
||||||
if (!this.player) return
|
|
||||||
this.player.volume = volume
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
isValidDuration(duration) {
|
|
||||||
if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
getBufferedRanges() {
|
|
||||||
if (!this.player) return []
|
|
||||||
const ranges = []
|
|
||||||
const seekable = this.player.buffered || []
|
|
||||||
|
|
||||||
let offset = 0
|
|
||||||
|
|
||||||
for (let i = 0, length = seekable.length; i < length; i++) {
|
|
||||||
let start = seekable.start(i)
|
|
||||||
let end = seekable.end(i)
|
|
||||||
if (!this.isValidDuration(start)) {
|
|
||||||
start = 0
|
|
||||||
}
|
|
||||||
if (!this.isValidDuration(end)) {
|
|
||||||
end = 0
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges.push({
|
|
||||||
start: start + offset,
|
|
||||||
end: end + offset
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return ranges
|
|
||||||
}
|
|
||||||
|
|
||||||
getLastBufferedTime() {
|
|
||||||
var bufferedRanges = this.getBufferedRanges()
|
|
||||||
if (!bufferedRanges.length) return 0
|
|
||||||
|
|
||||||
var buff = bufferedRanges.find((buff) => buff.start < this.player.currentTime && buff.end > this.player.currentTime)
|
|
||||||
if (buff) return buff.end
|
|
||||||
|
|
||||||
var last = bufferedRanges[bufferedRanges.length - 1]
|
|
||||||
return last.end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import LocalAudioPlayer from './LocalAudioPlayer'
|
import LocalAudioPlayer from './LocalAudioPlayer'
|
||||||
import LocalVideoPlayer from './LocalVideoPlayer'
|
|
||||||
import CastPlayer from './CastPlayer'
|
import CastPlayer from './CastPlayer'
|
||||||
import AudioTrack from './AudioTrack'
|
import AudioTrack from './AudioTrack'
|
||||||
import VideoTrack from './VideoTrack'
|
|
||||||
|
|
||||||
export default class PlayerHandler {
|
export default class PlayerHandler {
|
||||||
constructor(ctx) {
|
constructor(ctx) {
|
||||||
@@ -16,8 +14,6 @@ export default class PlayerHandler {
|
|||||||
this.player = null
|
this.player = null
|
||||||
this.playerState = 'IDLE'
|
this.playerState = 'IDLE'
|
||||||
this.isHlsTranscode = false
|
this.isHlsTranscode = false
|
||||||
this.isVideo = false
|
|
||||||
this.isMusic = false
|
|
||||||
this.currentSessionId = null
|
this.currentSessionId = null
|
||||||
this.startTimeOverride = undefined // Used for starting playback at a specific time (i.e. clicking bookmark from library item page)
|
this.startTimeOverride = undefined // Used for starting playback at a specific time (i.e. clicking bookmark from library item page)
|
||||||
this.startTime = 0
|
this.startTime = 0
|
||||||
@@ -65,12 +61,10 @@ export default class PlayerHandler {
|
|||||||
|
|
||||||
load(libraryItem, episodeId, playWhenReady, playbackRate, startTimeOverride = undefined) {
|
load(libraryItem, episodeId, playWhenReady, playbackRate, startTimeOverride = undefined) {
|
||||||
this.libraryItem = libraryItem
|
this.libraryItem = libraryItem
|
||||||
this.isVideo = libraryItem.mediaType === 'video'
|
|
||||||
this.isMusic = libraryItem.mediaType === 'music'
|
|
||||||
|
|
||||||
this.episodeId = episodeId
|
this.episodeId = episodeId
|
||||||
this.playWhenReady = playWhenReady
|
this.playWhenReady = playWhenReady
|
||||||
this.initialPlaybackRate = this.isMusic ? 1 : playbackRate
|
this.initialPlaybackRate = playbackRate
|
||||||
|
|
||||||
this.startTimeOverride = startTimeOverride == null || isNaN(startTimeOverride) ? undefined : Number(startTimeOverride)
|
this.startTimeOverride = startTimeOverride == null || isNaN(startTimeOverride) ? undefined : Number(startTimeOverride)
|
||||||
|
|
||||||
@@ -97,7 +91,7 @@ export default class PlayerHandler {
|
|||||||
this.playWhenReady = playWhenReady
|
this.playWhenReady = playWhenReady
|
||||||
this.prepare()
|
this.prepare()
|
||||||
}
|
}
|
||||||
} else if (!this.isCasting && !(this.player instanceof LocalAudioPlayer) && !(this.player instanceof LocalVideoPlayer)) {
|
} else if (!this.isCasting && !(this.player instanceof LocalAudioPlayer)) {
|
||||||
console.log('[PlayerHandler] Switching to local player')
|
console.log('[PlayerHandler] Switching to local player')
|
||||||
|
|
||||||
this.stopPlayInterval()
|
this.stopPlayInterval()
|
||||||
@@ -107,11 +101,7 @@ export default class PlayerHandler {
|
|||||||
this.player.destroy()
|
this.player.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isVideo) {
|
this.player = new LocalAudioPlayer(this.ctx)
|
||||||
this.player = new LocalVideoPlayer(this.ctx)
|
|
||||||
} else {
|
|
||||||
this.player = new LocalAudioPlayer(this.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setPlayerListeners()
|
this.setPlayerListeners()
|
||||||
|
|
||||||
@@ -203,7 +193,7 @@ export default class PlayerHandler {
|
|||||||
supportedMimeTypes: this.player.playableMimeTypes,
|
supportedMimeTypes: this.player.playableMimeTypes,
|
||||||
mediaPlayer: this.isCasting ? 'chromecast' : 'html5',
|
mediaPlayer: this.isCasting ? 'chromecast' : 'html5',
|
||||||
forceTranscode,
|
forceTranscode,
|
||||||
forceDirectPlay: this.isCasting || this.isVideo // TODO: add transcode support for chromecast
|
forceDirectPlay: this.isCasting // TODO: add transcode support for chromecast
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = this.episodeId ? `/api/items/${this.libraryItem.id}/play/${this.episodeId}` : `/api/items/${this.libraryItem.id}/play`
|
const path = this.episodeId ? `/api/items/${this.libraryItem.id}/play/${this.episodeId}` : `/api/items/${this.libraryItem.id}/play`
|
||||||
@@ -218,7 +208,6 @@ export default class PlayerHandler {
|
|||||||
if (!this.player) this.switchPlayer() // Must set player first for open sessions
|
if (!this.player) this.switchPlayer() // Must set player first for open sessions
|
||||||
|
|
||||||
this.libraryItem = session.libraryItem
|
this.libraryItem = session.libraryItem
|
||||||
this.isVideo = session.libraryItem.mediaType === 'video'
|
|
||||||
this.playWhenReady = false
|
this.playWhenReady = false
|
||||||
this.initialPlaybackRate = playbackRate
|
this.initialPlaybackRate = playbackRate
|
||||||
this.startTimeOverride = undefined
|
this.startTimeOverride = undefined
|
||||||
@@ -237,28 +226,16 @@ export default class PlayerHandler {
|
|||||||
|
|
||||||
console.log('[PlayerHandler] Preparing Session', session)
|
console.log('[PlayerHandler] Preparing Session', session)
|
||||||
|
|
||||||
if (session.videoTrack) {
|
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken))
|
||||||
var videoTrack = new VideoTrack(session.videoTrack, this.userToken)
|
|
||||||
|
|
||||||
this.ctx.playerLoading = true
|
this.ctx.playerLoading = true
|
||||||
this.isHlsTranscode = true
|
this.isHlsTranscode = true
|
||||||
if (session.playMethod === this.ctx.$constants.PlayMethod.DIRECTPLAY) {
|
if (session.playMethod === this.ctx.$constants.PlayMethod.DIRECTPLAY) {
|
||||||
this.isHlsTranscode = false
|
this.isHlsTranscode = false
|
||||||
}
|
|
||||||
|
|
||||||
this.player.set(this.libraryItem, videoTrack, this.isHlsTranscode, this.startTime, this.playWhenReady)
|
|
||||||
} else {
|
|
||||||
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken))
|
|
||||||
|
|
||||||
this.ctx.playerLoading = true
|
|
||||||
this.isHlsTranscode = true
|
|
||||||
if (session.playMethod === this.ctx.$constants.PlayMethod.DIRECTPLAY) {
|
|
||||||
this.isHlsTranscode = false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.player.set(this.libraryItem, audioTracks, this.isHlsTranscode, this.startTime, this.playWhenReady)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.player.set(this.libraryItem, audioTracks, this.isHlsTranscode, this.startTime, this.playWhenReady)
|
||||||
|
|
||||||
// browser media session api
|
// browser media session api
|
||||||
this.ctx.setMediaSession()
|
this.ctx.setMediaSession()
|
||||||
}
|
}
|
||||||
@@ -333,8 +310,6 @@ export default class PlayerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendProgressSync(currentTime) {
|
sendProgressSync(currentTime) {
|
||||||
if (this.isMusic) return
|
|
||||||
|
|
||||||
const diffSinceLastSync = Math.abs(this.lastSyncTime - currentTime)
|
const diffSinceLastSync = Math.abs(this.lastSyncTime - currentTime)
|
||||||
if (diffSinceLastSync < 1) return
|
if (diffSinceLastSync < 1) return
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
export default class VideoTrack {
|
|
||||||
constructor(track, userToken) {
|
|
||||||
this.index = track.index || 0
|
|
||||||
this.startOffset = track.startOffset || 0 // Total time of all previous tracks
|
|
||||||
this.duration = track.duration || 0
|
|
||||||
this.title = track.title || ''
|
|
||||||
this.contentUrl = track.contentUrl || null
|
|
||||||
this.mimeType = track.mimeType
|
|
||||||
this.metadata = track.metadata || {}
|
|
||||||
|
|
||||||
this.userToken = userToken
|
|
||||||
}
|
|
||||||
|
|
||||||
get fullContentUrl() {
|
|
||||||
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
|
|
||||||
}
|
|
||||||
return `${window.location.origin}${this.contentUrl}?token=${this.userToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
get relativeContentUrl() {
|
|
||||||
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.contentUrl + `?token=${this.userToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,6 +25,7 @@ const languageCodeMap = {
|
|||||||
pl: { label: 'Polski', dateFnsLocale: 'pl' },
|
pl: { label: 'Polski', dateFnsLocale: 'pl' },
|
||||||
'pt-br': { label: 'Português (Brasil)', dateFnsLocale: 'ptBR' },
|
'pt-br': { label: 'Português (Brasil)', dateFnsLocale: 'ptBR' },
|
||||||
ru: { label: 'Русский', dateFnsLocale: 'ru' },
|
ru: { label: 'Русский', dateFnsLocale: 'ru' },
|
||||||
|
sl: { label: 'Slovenščina', dateFnsLocale: 'sl' },
|
||||||
sv: { label: 'Svenska', dateFnsLocale: 'sv' },
|
sv: { label: 'Svenska', dateFnsLocale: 'sv' },
|
||||||
uk: { label: 'Українська', dateFnsLocale: 'uk' },
|
uk: { label: 'Українська', dateFnsLocale: 'uk' },
|
||||||
'vi-vn': { label: 'Tiếng Việt', dateFnsLocale: 'vi' },
|
'vi-vn': { label: 'Tiếng Việt', dateFnsLocale: 'vi' },
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Vue.prototype.$bytesPretty = (bytes, decimals = 2) => {
|
|||||||
if (isNaN(bytes) || bytes == 0) {
|
if (isNaN(bytes) || bytes == 0) {
|
||||||
return '0 Bytes'
|
return '0 Bytes'
|
||||||
}
|
}
|
||||||
const k = 1024
|
const k = 1000
|
||||||
const dm = decimals < 0 ? 0 : decimals
|
const dm = decimals < 0 ? 0 : decimals
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
|||||||
+209
-10
@@ -9,6 +9,7 @@
|
|||||||
"ButtonApply": "প্রয়োগ করুন",
|
"ButtonApply": "প্রয়োগ করুন",
|
||||||
"ButtonApplyChapters": "অধ্যায় প্রয়োগ করুন",
|
"ButtonApplyChapters": "অধ্যায় প্রয়োগ করুন",
|
||||||
"ButtonAuthors": "লেখক",
|
"ButtonAuthors": "লেখক",
|
||||||
|
"ButtonBack": "পেছনে যান",
|
||||||
"ButtonBrowseForFolder": "ফোল্ডারের জন্য ব্রাউজ করুন",
|
"ButtonBrowseForFolder": "ফোল্ডারের জন্য ব্রাউজ করুন",
|
||||||
"ButtonCancel": "বাতিল করুন",
|
"ButtonCancel": "বাতিল করুন",
|
||||||
"ButtonCancelEncode": "এনকোড বাতিল করুন",
|
"ButtonCancelEncode": "এনকোড বাতিল করুন",
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
"ButtonChooseFiles": "ফাইল চয়ন করুন",
|
"ButtonChooseFiles": "ফাইল চয়ন করুন",
|
||||||
"ButtonClearFilter": "ফিল্টার পরিষ্কার করুন",
|
"ButtonClearFilter": "ফিল্টার পরিষ্কার করুন",
|
||||||
"ButtonCloseFeed": "ফিড বন্ধ করুন",
|
"ButtonCloseFeed": "ফিড বন্ধ করুন",
|
||||||
|
"ButtonCloseSession": "খোলা সেশন বন্ধ করুন",
|
||||||
"ButtonCollections": "সংগ্রহ",
|
"ButtonCollections": "সংগ্রহ",
|
||||||
"ButtonConfigureScanner": "স্ক্যানার কনফিগার করুন",
|
"ButtonConfigureScanner": "স্ক্যানার কনফিগার করুন",
|
||||||
"ButtonCreate": "তৈরি করুন",
|
"ButtonCreate": "তৈরি করুন",
|
||||||
@@ -27,6 +29,9 @@
|
|||||||
"ButtonEdit": "সম্পাদনা করুন",
|
"ButtonEdit": "সম্পাদনা করুন",
|
||||||
"ButtonEditChapters": "অধ্যায় সম্পাদনা করুন",
|
"ButtonEditChapters": "অধ্যায় সম্পাদনা করুন",
|
||||||
"ButtonEditPodcast": "পডকাস্ট সম্পাদনা করুন",
|
"ButtonEditPodcast": "পডকাস্ট সম্পাদনা করুন",
|
||||||
|
"ButtonEnable": "সক্রিয় করুন",
|
||||||
|
"ButtonFireAndFail": "সক্রিয় এবং ব্যর্থ",
|
||||||
|
"ButtonFireOnTest": "পরীক্ষামূলক ইভেন্টে সক্রিয় করুন",
|
||||||
"ButtonForceReScan": "জোরপূর্বক পুনরায় স্ক্যান করুন",
|
"ButtonForceReScan": "জোরপূর্বক পুনরায় স্ক্যান করুন",
|
||||||
"ButtonFullPath": "সম্পূর্ণ পথ",
|
"ButtonFullPath": "সম্পূর্ণ পথ",
|
||||||
"ButtonHide": "লুকান",
|
"ButtonHide": "লুকান",
|
||||||
@@ -45,6 +50,7 @@
|
|||||||
"ButtonNevermind": "কিছু মনে করবেন না",
|
"ButtonNevermind": "কিছু মনে করবেন না",
|
||||||
"ButtonNext": "পরবর্তী",
|
"ButtonNext": "পরবর্তী",
|
||||||
"ButtonNextChapter": "পরবর্তী অধ্যায়",
|
"ButtonNextChapter": "পরবর্তী অধ্যায়",
|
||||||
|
"ButtonNextItemInQueue": "সারিতে পরের আইটেম",
|
||||||
"ButtonOk": "ঠিক আছে",
|
"ButtonOk": "ঠিক আছে",
|
||||||
"ButtonOpenFeed": "ফিড খুলুন",
|
"ButtonOpenFeed": "ফিড খুলুন",
|
||||||
"ButtonOpenManager": "ম্যানেজার খুলুন",
|
"ButtonOpenManager": "ম্যানেজার খুলুন",
|
||||||
@@ -54,13 +60,17 @@
|
|||||||
"ButtonPlaylists": "প্লেলিস্ট",
|
"ButtonPlaylists": "প্লেলিস্ট",
|
||||||
"ButtonPrevious": "পূর্ববর্তী",
|
"ButtonPrevious": "পূর্ববর্তী",
|
||||||
"ButtonPreviousChapter": "আগের অধ্যায়",
|
"ButtonPreviousChapter": "আগের অধ্যায়",
|
||||||
|
"ButtonProbeAudioFile": "প্রোব অডিও ফাইল",
|
||||||
"ButtonPurgeAllCache": "সমস্ত ক্যাশে পরিষ্কার করুন",
|
"ButtonPurgeAllCache": "সমস্ত ক্যাশে পরিষ্কার করুন",
|
||||||
"ButtonPurgeItemsCache": "আইটেম ক্যাশে পরিষ্কার করুন",
|
"ButtonPurgeItemsCache": "আইটেম ক্যাশে পরিষ্কার করুন",
|
||||||
"ButtonQueueAddItem": "সারিতে যোগ করুন",
|
"ButtonQueueAddItem": "সারিতে যোগ করুন",
|
||||||
"ButtonQueueRemoveItem": "সারি থেকে মুছে ফেলুন",
|
"ButtonQueueRemoveItem": "সারি থেকে মুছে ফেলুন",
|
||||||
|
"ButtonQuickEmbedMetadata": "মেটাডেটা দ্রুত এম্বেড করুন",
|
||||||
"ButtonQuickMatch": "দ্রুত ম্যাচ",
|
"ButtonQuickMatch": "দ্রুত ম্যাচ",
|
||||||
"ButtonReScan": "পুনরায় স্ক্যান",
|
"ButtonReScan": "পুনরায় স্ক্যান",
|
||||||
"ButtonRead": "পড়ুন",
|
"ButtonRead": "পড়ুন",
|
||||||
|
"ButtonReadLess": "সংক্ষিপ্ত",
|
||||||
|
"ButtonReadMore": "বিস্তারিত পড়ুন",
|
||||||
"ButtonRefresh": "রিফ্রেশ",
|
"ButtonRefresh": "রিফ্রেশ",
|
||||||
"ButtonRemove": "মুছে ফেলুন",
|
"ButtonRemove": "মুছে ফেলুন",
|
||||||
"ButtonRemoveAll": "সব মুছে ফেলুন",
|
"ButtonRemoveAll": "সব মুছে ফেলুন",
|
||||||
@@ -85,8 +95,10 @@
|
|||||||
"ButtonShow": "দেখান",
|
"ButtonShow": "দেখান",
|
||||||
"ButtonStartM4BEncode": "M4B এনকোড শুরু করুন",
|
"ButtonStartM4BEncode": "M4B এনকোড শুরু করুন",
|
||||||
"ButtonStartMetadataEmbed": "মেটাডেটা এম্বেড শুরু করুন",
|
"ButtonStartMetadataEmbed": "মেটাডেটা এম্বেড শুরু করুন",
|
||||||
|
"ButtonStats": "পরিসংখ্যান",
|
||||||
"ButtonSubmit": "জমা দিন",
|
"ButtonSubmit": "জমা দিন",
|
||||||
"ButtonTest": "পরীক্ষা",
|
"ButtonTest": "পরীক্ষা",
|
||||||
|
"ButtonUnlinkOpenId": "ওপেন আইডি লিঙ্কমুক্ত করুন",
|
||||||
"ButtonUpload": "আপলোড",
|
"ButtonUpload": "আপলোড",
|
||||||
"ButtonUploadBackup": "আপলোড ব্যাকআপ",
|
"ButtonUploadBackup": "আপলোড ব্যাকআপ",
|
||||||
"ButtonUploadCover": "কভার আপলোড করুন",
|
"ButtonUploadCover": "কভার আপলোড করুন",
|
||||||
@@ -99,9 +111,10 @@
|
|||||||
"ErrorUploadFetchMetadataNoResults": "মেটাডেটা আনা যায়নি - শিরোনাম এবং/অথবা লেখক আপডেট করার চেষ্টা করুন",
|
"ErrorUploadFetchMetadataNoResults": "মেটাডেটা আনা যায়নি - শিরোনাম এবং/অথবা লেখক আপডেট করার চেষ্টা করুন",
|
||||||
"ErrorUploadLacksTitle": "একটি শিরোনাম থাকতে হবে",
|
"ErrorUploadLacksTitle": "একটি শিরোনাম থাকতে হবে",
|
||||||
"HeaderAccount": "অ্যাকাউন্ট",
|
"HeaderAccount": "অ্যাকাউন্ট",
|
||||||
|
"HeaderAddCustomMetadataProvider": "কাস্টম মেটাডেটা সরবরাহকারী যোগ করুন",
|
||||||
"HeaderAdvanced": "অ্যাডভান্সড",
|
"HeaderAdvanced": "অ্যাডভান্সড",
|
||||||
"HeaderAppriseNotificationSettings": "বিজ্ঞপ্তি সেটিংস অবহিত করুন",
|
"HeaderAppriseNotificationSettings": "বিজ্ঞপ্তি সেটিংস অবহিত করুন",
|
||||||
"HeaderAudioTracks": "অডিও ট্র্যাকস",
|
"HeaderAudioTracks": "অডিও ট্র্যাকসগুলো",
|
||||||
"HeaderAudiobookTools": "অডিওবই ফাইল ম্যানেজমেন্ট টুলস",
|
"HeaderAudiobookTools": "অডিওবই ফাইল ম্যানেজমেন্ট টুলস",
|
||||||
"HeaderAuthentication": "প্রমাণীকরণ",
|
"HeaderAuthentication": "প্রমাণীকরণ",
|
||||||
"HeaderBackups": "ব্যাকআপ",
|
"HeaderBackups": "ব্যাকআপ",
|
||||||
@@ -112,6 +125,7 @@
|
|||||||
"HeaderCollectionItems": "সংগ্রহ আইটেম",
|
"HeaderCollectionItems": "সংগ্রহ আইটেম",
|
||||||
"HeaderCover": "কভার",
|
"HeaderCover": "কভার",
|
||||||
"HeaderCurrentDownloads": "বর্তমান ডাউনলোডগুলি",
|
"HeaderCurrentDownloads": "বর্তমান ডাউনলোডগুলি",
|
||||||
|
"HeaderCustomMessageOnLogin": "লগইন এ কাস্টম বার্তা",
|
||||||
"HeaderCustomMetadataProviders": "কাস্টম মেটাডেটা প্রদানকারী",
|
"HeaderCustomMetadataProviders": "কাস্টম মেটাডেটা প্রদানকারী",
|
||||||
"HeaderDetails": "বিস্তারিত",
|
"HeaderDetails": "বিস্তারিত",
|
||||||
"HeaderDownloadQueue": "ডাউনলোড সারি",
|
"HeaderDownloadQueue": "ডাউনলোড সারি",
|
||||||
@@ -143,6 +157,8 @@
|
|||||||
"HeaderMetadataToEmbed": "এম্বেড করার জন্য মেটাডেটা",
|
"HeaderMetadataToEmbed": "এম্বেড করার জন্য মেটাডেটা",
|
||||||
"HeaderNewAccount": "নতুন অ্যাকাউন্ট",
|
"HeaderNewAccount": "নতুন অ্যাকাউন্ট",
|
||||||
"HeaderNewLibrary": "নতুন লাইব্রেরি",
|
"HeaderNewLibrary": "নতুন লাইব্রেরি",
|
||||||
|
"HeaderNotificationCreate": "বিজ্ঞপ্তি তৈরি করুন",
|
||||||
|
"HeaderNotificationUpdate": "বিজ্ঞপ্তি আপডেট করুন",
|
||||||
"HeaderNotifications": "বিজ্ঞপ্তি",
|
"HeaderNotifications": "বিজ্ঞপ্তি",
|
||||||
"HeaderOpenIDConnectAuthentication": "ওপেনআইডি সংযোগ প্রমাণীকরণ",
|
"HeaderOpenIDConnectAuthentication": "ওপেনআইডি সংযোগ প্রমাণীকরণ",
|
||||||
"HeaderOpenRSSFeed": "আরএসএস ফিড খুলুন",
|
"HeaderOpenRSSFeed": "আরএসএস ফিড খুলুন",
|
||||||
@@ -150,6 +166,7 @@
|
|||||||
"HeaderPasswordAuthentication": "পাসওয়ার্ড প্রমাণীকরণ",
|
"HeaderPasswordAuthentication": "পাসওয়ার্ড প্রমাণীকরণ",
|
||||||
"HeaderPermissions": "অনুমতি",
|
"HeaderPermissions": "অনুমতি",
|
||||||
"HeaderPlayerQueue": "প্লেয়ার সারি",
|
"HeaderPlayerQueue": "প্লেয়ার সারি",
|
||||||
|
"HeaderPlayerSettings": "প্লেয়ার সেটিংস",
|
||||||
"HeaderPlaylist": "প্লেলিস্ট",
|
"HeaderPlaylist": "প্লেলিস্ট",
|
||||||
"HeaderPlaylistItems": "প্লেলিস্ট আইটেম",
|
"HeaderPlaylistItems": "প্লেলিস্ট আইটেম",
|
||||||
"HeaderPodcastsToAdd": "যোগ করার জন্য পডকাস্ট",
|
"HeaderPodcastsToAdd": "যোগ করার জন্য পডকাস্ট",
|
||||||
@@ -186,6 +203,9 @@
|
|||||||
"HeaderYearReview": "বাৎসরিক পর্যালোচনা {0}",
|
"HeaderYearReview": "বাৎসরিক পর্যালোচনা {0}",
|
||||||
"HeaderYourStats": "আপনার পরিসংখ্যান",
|
"HeaderYourStats": "আপনার পরিসংখ্যান",
|
||||||
"LabelAbridged": "সংক্ষিপ্ত",
|
"LabelAbridged": "সংক্ষিপ্ত",
|
||||||
|
"LabelAbridgedChecked": "সংক্ষিপ্ত (চেক)",
|
||||||
|
"LabelAbridgedUnchecked": "অসংক্ষেপিত (চেক করা হয়নি)",
|
||||||
|
"LabelAccessibleBy": "দ্বারা প্রবেশযোগ্য",
|
||||||
"LabelAccountType": "অ্যাকাউন্টের প্রকার",
|
"LabelAccountType": "অ্যাকাউন্টের প্রকার",
|
||||||
"LabelAccountTypeAdmin": "প্রশাসন",
|
"LabelAccountTypeAdmin": "প্রশাসন",
|
||||||
"LabelAccountTypeGuest": "অতিথি",
|
"LabelAccountTypeGuest": "অতিথি",
|
||||||
@@ -196,6 +216,7 @@
|
|||||||
"LabelAddToPlaylist": "প্লেলিস্টে যোগ করুন",
|
"LabelAddToPlaylist": "প্লেলিস্টে যোগ করুন",
|
||||||
"LabelAddToPlaylistBatch": "প্লেলিস্টে {0}টি আইটেম যোগ করুন",
|
"LabelAddToPlaylistBatch": "প্লেলিস্টে {0}টি আইটেম যোগ করুন",
|
||||||
"LabelAddedAt": "এতে যোগ করা হয়েছে",
|
"LabelAddedAt": "এতে যোগ করা হয়েছে",
|
||||||
|
"LabelAddedDate": "যোগ করা হয়েছে {0}",
|
||||||
"LabelAdminUsersOnly": "শুধু অ্যাডমিন ব্যবহারকারী",
|
"LabelAdminUsersOnly": "শুধু অ্যাডমিন ব্যবহারকারী",
|
||||||
"LabelAll": "সব",
|
"LabelAll": "সব",
|
||||||
"LabelAllUsers": "সমস্ত ব্যবহারকারী",
|
"LabelAllUsers": "সমস্ত ব্যবহারকারী",
|
||||||
@@ -218,13 +239,14 @@
|
|||||||
"LabelBackupLocation": "ব্যাকআপ অবস্থান",
|
"LabelBackupLocation": "ব্যাকআপ অবস্থান",
|
||||||
"LabelBackupsEnableAutomaticBackups": "স্বয়ংক্রিয় ব্যাকআপ সক্ষম করুন",
|
"LabelBackupsEnableAutomaticBackups": "স্বয়ংক্রিয় ব্যাকআপ সক্ষম করুন",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "ব্যাকআপগুলি /মেটাডাটা/ব্যাকআপে সংরক্ষিত",
|
"LabelBackupsEnableAutomaticBackupsHelp": "ব্যাকআপগুলি /মেটাডাটা/ব্যাকআপে সংরক্ষিত",
|
||||||
"LabelBackupsMaxBackupSize": "সর্বোচ্চ ব্যাকআপ আকার (GB-তে)",
|
"LabelBackupsMaxBackupSize": "সর্বোচ্চ ব্যাকআপ আকার (GB-তে) (অসীমের জন্য 0)",
|
||||||
"LabelBackupsMaxBackupSizeHelp": "ভুল কনফিগারেশনের বিরুদ্ধে সুরক্ষা হিসেবে ব্যাকআপগুলি ব্যর্থ হবে যদি তারা কনফিগার করা আকার অতিক্রম করে।",
|
"LabelBackupsMaxBackupSizeHelp": "ভুল কনফিগারেশনের বিরুদ্ধে সুরক্ষা হিসেবে ব্যাকআপগুলি ব্যর্থ হবে যদি তারা কনফিগার করা আকার অতিক্রম করে।",
|
||||||
"LabelBackupsNumberToKeep": "ব্যাকআপের সংখ্যা রাখুন",
|
"LabelBackupsNumberToKeep": "ব্যাকআপের সংখ্যা রাখুন",
|
||||||
"LabelBackupsNumberToKeepHelp": "এক সময়ে শুধুমাত্র ১ টি ব্যাকআপ সরানো হবে তাই যদি আপনার কাছে ইতিমধ্যে এর চেয়ে বেশি ব্যাকআপ থাকে তাহলে আপনাকে ম্যানুয়ালি সেগুলি সরিয়ে ফেলতে হবে।",
|
"LabelBackupsNumberToKeepHelp": "এক সময়ে শুধুমাত্র ১ টি ব্যাকআপ সরানো হবে তাই যদি আপনার কাছে ইতিমধ্যে এর চেয়ে বেশি ব্যাকআপ থাকে তাহলে আপনাকে ম্যানুয়ালি সেগুলি সরিয়ে ফেলতে হবে।",
|
||||||
"LabelBitrate": "বিটরেট",
|
"LabelBitrate": "বিটরেট",
|
||||||
"LabelBooks": "বইগুলো",
|
"LabelBooks": "বইগুলো",
|
||||||
"LabelButtonText": "ঘর পাঠ্য",
|
"LabelButtonText": "ঘর পাঠ্য",
|
||||||
|
"LabelByAuthor": "দ্বারা {0}",
|
||||||
"LabelChangePassword": "পাসওয়ার্ড পরিবর্তন করুন",
|
"LabelChangePassword": "পাসওয়ার্ড পরিবর্তন করুন",
|
||||||
"LabelChannels": "চ্যানেল",
|
"LabelChannels": "চ্যানেল",
|
||||||
"LabelChapterTitle": "অধ্যায়ের শিরোনাম",
|
"LabelChapterTitle": "অধ্যায়ের শিরোনাম",
|
||||||
@@ -234,6 +256,7 @@
|
|||||||
"LabelClosePlayer": "প্লেয়ার বন্ধ করুন",
|
"LabelClosePlayer": "প্লেয়ার বন্ধ করুন",
|
||||||
"LabelCodec": "কোডেক",
|
"LabelCodec": "কোডেক",
|
||||||
"LabelCollapseSeries": "সিরিজ সঙ্কুচিত করুন",
|
"LabelCollapseSeries": "সিরিজ সঙ্কুচিত করুন",
|
||||||
|
"LabelCollapseSubSeries": "উপ-সিরিজ সঙ্কুচিত করুন",
|
||||||
"LabelCollection": "সংগ্রহ",
|
"LabelCollection": "সংগ্রহ",
|
||||||
"LabelCollections": "সংগ্রহ",
|
"LabelCollections": "সংগ্রহ",
|
||||||
"LabelComplete": "সম্পূর্ণ",
|
"LabelComplete": "সম্পূর্ণ",
|
||||||
@@ -249,6 +272,7 @@
|
|||||||
"LabelCurrently": "বর্তমানে:",
|
"LabelCurrently": "বর্তমানে:",
|
||||||
"LabelCustomCronExpression": "কাস্টম Cron এক্সপ্রেশন:",
|
"LabelCustomCronExpression": "কাস্টম Cron এক্সপ্রেশন:",
|
||||||
"LabelDatetime": "তারিখ সময়",
|
"LabelDatetime": "তারিখ সময়",
|
||||||
|
"LabelDays": "দিনগুলো",
|
||||||
"LabelDeleteFromFileSystemCheckbox": "ফাইল সিস্টেম থেকে মুছে ফেলুন (শুধু ডাটাবেস থেকে সরাতে টিক চিহ্ন মুক্ত করুন)",
|
"LabelDeleteFromFileSystemCheckbox": "ফাইল সিস্টেম থেকে মুছে ফেলুন (শুধু ডাটাবেস থেকে সরাতে টিক চিহ্ন মুক্ত করুন)",
|
||||||
"LabelDescription": "বিবরণ",
|
"LabelDescription": "বিবরণ",
|
||||||
"LabelDeselectAll": "সমস্ত অনির্বাচিত করুন",
|
"LabelDeselectAll": "সমস্ত অনির্বাচিত করুন",
|
||||||
@@ -262,29 +286,42 @@
|
|||||||
"LabelDownload": "ডাউনলোড করুন",
|
"LabelDownload": "ডাউনলোড করুন",
|
||||||
"LabelDownloadNEpisodes": "{0}টি পর্ব ডাউনলোড করুন",
|
"LabelDownloadNEpisodes": "{0}টি পর্ব ডাউনলোড করুন",
|
||||||
"LabelDuration": "সময়কাল",
|
"LabelDuration": "সময়কাল",
|
||||||
|
"LabelDurationComparisonExactMatch": "(সঠিক মিল)",
|
||||||
|
"LabelDurationComparisonLonger": "({0} দীর্ঘ)",
|
||||||
|
"LabelDurationComparisonShorter": "({0} ছোট)",
|
||||||
"LabelDurationFound": "সময়কাল পাওয়া গেছে:",
|
"LabelDurationFound": "সময়কাল পাওয়া গেছে:",
|
||||||
"LabelEbook": "ই-বই",
|
"LabelEbook": "ই-বই",
|
||||||
"LabelEbooks": "ই-বইগুলো",
|
"LabelEbooks": "ই-বইগুলো",
|
||||||
"LabelEdit": "সম্পাদনা করুন",
|
"LabelEdit": "সম্পাদনা করুন",
|
||||||
"LabelEmail": "ইমেইল",
|
"LabelEmail": "ইমেইল",
|
||||||
"LabelEmailSettingsFromAddress": "ঠিকানা থেকে",
|
"LabelEmailSettingsFromAddress": "ঠিকানা থেকে",
|
||||||
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to।",
|
"LabelEmailSettingsRejectUnauthorized": "অননুমোদিত সার্টিফিকেট প্রত্যাখ্যান করুন",
|
||||||
|
"LabelEmailSettingsRejectUnauthorizedHelp": "SSL প্রমাণপত্রের বৈধতা নিষ্ক্রিয় করা আপনার সংযোগকে নিরাপত্তা ঝুঁকিতে ফেলতে পারে, যেমন ম্যান-ইন-দ্য-মিডল আক্রমণ। শুধুমাত্র এই বিকল্পটি নিষ্ক্রিয় করুন যদি আপনি এর প্রভাবগুলি বুঝতে পারেন এবং আপনি যে মেইল সার্ভারের সাথে সংযোগ করছেন তাকে বিশ্বাস করেন।",
|
||||||
"LabelEmailSettingsSecure": "নিরাপদ",
|
"LabelEmailSettingsSecure": "নিরাপদ",
|
||||||
"LabelEmailSettingsSecureHelp": "যদি সত্য হয় সার্ভারের সাথে সংযোগ করার সময় সংযোগটি TLS ব্যবহার করবে। মিথ্যা হলে TLS ব্যবহার করা হবে যদি সার্ভার STARTTLS এক্সটেনশন সমর্থন করে। বেশিরভাগ ক্ষেত্রে এই মানটিকে সত্য হিসাবে সেট করুন যদি আপনি পোর্ট 465-এর সাথে সংযোগ করছেন। পোর্ট 587 বা পোর্টের জন্য 25 এটি মিথ্যা রাখুন। (nodemailer.com/smtp/#authentication থেকে)",
|
"LabelEmailSettingsSecureHelp": "যদি সত্য হয় সার্ভারের সাথে সংযোগ করার সময় সংযোগটি TLS ব্যবহার করবে। মিথ্যা হলে TLS ব্যবহার করা হবে যদি সার্ভার STARTTLS এক্সটেনশন সমর্থন করে। বেশিরভাগ ক্ষেত্রে এই মানটিকে সত্য হিসাবে সেট করুন যদি আপনি পোর্ট 465-এর সাথে সংযোগ করছেন। পোর্ট 587 বা পোর্টের জন্য 25 এটি মিথ্যা রাখুন। (nodemailer.com/smtp/#authentication থেকে)",
|
||||||
"LabelEmailSettingsTestAddress": "পরীক্ষার ঠিকানা",
|
"LabelEmailSettingsTestAddress": "পরীক্ষার ঠিকানা",
|
||||||
"LabelEmbeddedCover": "এম্বেডেড কভার",
|
"LabelEmbeddedCover": "এম্বেডেড কভার",
|
||||||
"LabelEnable": "সক্ষম করুন",
|
"LabelEnable": "সক্ষম করুন",
|
||||||
"LabelEnd": "সমাপ্ত",
|
"LabelEnd": "সমাপ্ত",
|
||||||
|
"LabelEndOfChapter": "অধ্যায়ের সমাপ্তি",
|
||||||
"LabelEpisode": "পর্ব",
|
"LabelEpisode": "পর্ব",
|
||||||
"LabelEpisodeTitle": "পর্বের শিরোনাম",
|
"LabelEpisodeTitle": "পর্বের শিরোনাম",
|
||||||
"LabelEpisodeType": "পর্বের ধরন",
|
"LabelEpisodeType": "পর্বের ধরন",
|
||||||
|
"LabelEpisodes": "পর্বগুলো",
|
||||||
"LabelExample": "উদাহরণ",
|
"LabelExample": "উদাহরণ",
|
||||||
|
"LabelExpandSeries": "সিরিজ প্রসারিত করুন",
|
||||||
|
"LabelExpandSubSeries": "সাব সিরিজ প্রসারিত করুন",
|
||||||
"LabelExplicit": "বিশদ",
|
"LabelExplicit": "বিশদ",
|
||||||
|
"LabelExplicitChecked": "সুস্পষ্ট (পরীক্ষিত)",
|
||||||
|
"LabelExplicitUnchecked": "অস্পষ্ট (অপরিক্ষীত)",
|
||||||
|
"LabelExportOPML": "OPML এক্সপোর্ট করুন",
|
||||||
"LabelFeedURL": "ফিড ইউআরএল",
|
"LabelFeedURL": "ফিড ইউআরএল",
|
||||||
"LabelFetchingMetadata": "মেটাডেটা আনা হচ্ছে",
|
"LabelFetchingMetadata": "মেটাডেটা আনা হচ্ছে",
|
||||||
"LabelFile": "ফাইল",
|
"LabelFile": "ফাইল",
|
||||||
"LabelFileBirthtime": "ফাইল জন্মের সময়",
|
"LabelFileBirthtime": "ফাইল জন্মের সময়",
|
||||||
|
"LabelFileBornDate": "জন্ম {0}",
|
||||||
"LabelFileModified": "ফাইল পরিবর্তিত",
|
"LabelFileModified": "ফাইল পরিবর্তিত",
|
||||||
|
"LabelFileModifiedDate": "পরিবর্তিত {0}",
|
||||||
"LabelFilename": "ফাইলের নাম",
|
"LabelFilename": "ফাইলের নাম",
|
||||||
"LabelFilterByUser": "ব্যবহারকারী দ্বারা ফিল্টারকৃত",
|
"LabelFilterByUser": "ব্যবহারকারী দ্বারা ফিল্টারকৃত",
|
||||||
"LabelFindEpisodes": "পর্বগুলো খুঁজুন",
|
"LabelFindEpisodes": "পর্বগুলো খুঁজুন",
|
||||||
@@ -292,7 +329,8 @@
|
|||||||
"LabelFolder": "ফোল্ডার",
|
"LabelFolder": "ফোল্ডার",
|
||||||
"LabelFolders": "ফোল্ডারগুলো",
|
"LabelFolders": "ফোল্ডারগুলো",
|
||||||
"LabelFontBold": "বোল্ড",
|
"LabelFontBold": "বোল্ড",
|
||||||
"LabelFontFamily": "ফন্ট পরিবার",
|
"LabelFontBoldness": "হরফ বোল্ডনেস",
|
||||||
|
"LabelFontFamily": "হরফ পরিবার",
|
||||||
"LabelFontItalic": "ইটালিক",
|
"LabelFontItalic": "ইটালিক",
|
||||||
"LabelFontScale": "ফন্ট স্কেল",
|
"LabelFontScale": "ফন্ট স্কেল",
|
||||||
"LabelFontStrikethrough": "অবচ্ছেদন রেখা",
|
"LabelFontStrikethrough": "অবচ্ছেদন রেখা",
|
||||||
@@ -302,9 +340,11 @@
|
|||||||
"LabelHardDeleteFile": "জোরপূর্বক ফাইল মুছে ফেলুন",
|
"LabelHardDeleteFile": "জোরপূর্বক ফাইল মুছে ফেলুন",
|
||||||
"LabelHasEbook": "ই-বই আছে",
|
"LabelHasEbook": "ই-বই আছে",
|
||||||
"LabelHasSupplementaryEbook": "পরিপূরক ই-বই আছে",
|
"LabelHasSupplementaryEbook": "পরিপূরক ই-বই আছে",
|
||||||
|
"LabelHideSubtitles": "সাবটাইটেল লুকান",
|
||||||
"LabelHighestPriority": "সর্বোচ্চ অগ্রাধিকার",
|
"LabelHighestPriority": "সর্বোচ্চ অগ্রাধিকার",
|
||||||
"LabelHost": "নিমন্ত্রণকর্তা",
|
"LabelHost": "নিমন্ত্রণকর্তা",
|
||||||
"LabelHour": "ঘন্টা",
|
"LabelHour": "ঘন্টা",
|
||||||
|
"LabelHours": "ঘন্টা",
|
||||||
"LabelIcon": "আইকন",
|
"LabelIcon": "আইকন",
|
||||||
"LabelImageURLFromTheWeb": "ওয়েব থেকে ছবির ইউআরএল",
|
"LabelImageURLFromTheWeb": "ওয়েব থেকে ছবির ইউআরএল",
|
||||||
"LabelInProgress": "প্রগতিতে আছে",
|
"LabelInProgress": "প্রগতিতে আছে",
|
||||||
@@ -321,8 +361,11 @@
|
|||||||
"LabelIntervalEveryHour": "প্রতি ঘন্টা",
|
"LabelIntervalEveryHour": "প্রতি ঘন্টা",
|
||||||
"LabelInvert": "উল্টানো",
|
"LabelInvert": "উল্টানো",
|
||||||
"LabelItem": "আইটেম",
|
"LabelItem": "আইটেম",
|
||||||
|
"LabelJumpBackwardAmount": "পিছন দিকে ঝাঁপের পরিমাণ",
|
||||||
|
"LabelJumpForwardAmount": "সামনের দিকে ঝাঁপের পরিমাণ",
|
||||||
"LabelLanguage": "ভাষা",
|
"LabelLanguage": "ভাষা",
|
||||||
"LabelLanguageDefaultServer": "সার্ভারের ডিফল্ট ভাষা",
|
"LabelLanguageDefaultServer": "সার্ভারের ডিফল্ট ভাষা",
|
||||||
|
"LabelLanguages": "ভাষাসমূহ",
|
||||||
"LabelLastBookAdded": "শেষ বই যোগ করা হয়েছে",
|
"LabelLastBookAdded": "শেষ বই যোগ করা হয়েছে",
|
||||||
"LabelLastBookUpdated": "শেষ বই আপডেট করা হয়েছে",
|
"LabelLastBookUpdated": "শেষ বই আপডেট করা হয়েছে",
|
||||||
"LabelLastSeen": "শেষ দেখা",
|
"LabelLastSeen": "শেষ দেখা",
|
||||||
@@ -334,6 +377,7 @@
|
|||||||
"LabelLess": "কম",
|
"LabelLess": "কম",
|
||||||
"LabelLibrariesAccessibleToUser": "ব্যবহারকারীর কাছে অ্যাক্সেসযোগ্য লাইব্রেরি",
|
"LabelLibrariesAccessibleToUser": "ব্যবহারকারীর কাছে অ্যাক্সেসযোগ্য লাইব্রেরি",
|
||||||
"LabelLibrary": "লাইব্রেরি",
|
"LabelLibrary": "লাইব্রেরি",
|
||||||
|
"LabelLibraryFilterSublistEmpty": "না {0}",
|
||||||
"LabelLibraryItem": "লাইব্রেরি আইটেম",
|
"LabelLibraryItem": "লাইব্রেরি আইটেম",
|
||||||
"LabelLibraryName": "লাইব্রেরির নাম",
|
"LabelLibraryName": "লাইব্রেরির নাম",
|
||||||
"LabelLimit": "সীমা",
|
"LabelLimit": "সীমা",
|
||||||
@@ -353,6 +397,7 @@
|
|||||||
"LabelMetadataOrderOfPrecedenceDescription": "উচ্চ অগ্রাধিকারের মেটাডেটার উৎসগুলো নিম্ন অগ্রাধিকারের মেটাডেটা উৎসগুলোকে ওভাররাইড করবে",
|
"LabelMetadataOrderOfPrecedenceDescription": "উচ্চ অগ্রাধিকারের মেটাডেটার উৎসগুলো নিম্ন অগ্রাধিকারের মেটাডেটা উৎসগুলোকে ওভাররাইড করবে",
|
||||||
"LabelMetadataProvider": "মেটাডেটা প্রদানকারী",
|
"LabelMetadataProvider": "মেটাডেটা প্রদানকারী",
|
||||||
"LabelMinute": "মিনিট",
|
"LabelMinute": "মিনিট",
|
||||||
|
"LabelMinutes": "মিনিটস",
|
||||||
"LabelMissing": "নিখোঁজ",
|
"LabelMissing": "নিখোঁজ",
|
||||||
"LabelMissingEbook": "কোনও ই-বই নেই",
|
"LabelMissingEbook": "কোনও ই-বই নেই",
|
||||||
"LabelMissingSupplementaryEbook": "কোনও সম্পূরক ই-বই নেই",
|
"LabelMissingSupplementaryEbook": "কোনও সম্পূরক ই-বই নেই",
|
||||||
@@ -369,6 +414,7 @@
|
|||||||
"LabelNewestEpisodes": "নতুনতম পর্ব",
|
"LabelNewestEpisodes": "নতুনতম পর্ব",
|
||||||
"LabelNextBackupDate": "পরবর্তী ব্যাকআপ তারিখ",
|
"LabelNextBackupDate": "পরবর্তী ব্যাকআপ তারিখ",
|
||||||
"LabelNextScheduledRun": "পরবর্তী নির্ধারিত দৌড়",
|
"LabelNextScheduledRun": "পরবর্তী নির্ধারিত দৌড়",
|
||||||
|
"LabelNoCustomMetadataProviders": "কোনো কাস্টম মেটাডেটা প্রদানকারী নেই",
|
||||||
"LabelNoEpisodesSelected": "কোন পর্ব নির্বাচন করা হয়নি",
|
"LabelNoEpisodesSelected": "কোন পর্ব নির্বাচন করা হয়নি",
|
||||||
"LabelNotFinished": "সমাপ্ত হয়নি",
|
"LabelNotFinished": "সমাপ্ত হয়নি",
|
||||||
"LabelNotStarted": "শুরু হয়নি",
|
"LabelNotStarted": "শুরু হয়নি",
|
||||||
@@ -391,6 +437,7 @@
|
|||||||
"LabelOverwrite": "পুনঃলিখিত",
|
"LabelOverwrite": "পুনঃলিখিত",
|
||||||
"LabelPassword": "পাসওয়ার্ড",
|
"LabelPassword": "পাসওয়ার্ড",
|
||||||
"LabelPath": "পথ",
|
"LabelPath": "পথ",
|
||||||
|
"LabelPermanent": "স্থায়ী",
|
||||||
"LabelPermissionsAccessAllLibraries": "সমস্ত লাইব্রেরি অ্যাক্সেস করতে পারবে",
|
"LabelPermissionsAccessAllLibraries": "সমস্ত লাইব্রেরি অ্যাক্সেস করতে পারবে",
|
||||||
"LabelPermissionsAccessAllTags": "সমস্ত ট্যাগ অ্যাক্সেস করতে পারবে",
|
"LabelPermissionsAccessAllTags": "সমস্ত ট্যাগ অ্যাক্সেস করতে পারবে",
|
||||||
"LabelPermissionsAccessExplicitContent": "স্পষ্ট বিষয়বস্তু অ্যাক্সেস করতে পারে",
|
"LabelPermissionsAccessExplicitContent": "স্পষ্ট বিষয়বস্তু অ্যাক্সেস করতে পারে",
|
||||||
@@ -401,6 +448,7 @@
|
|||||||
"LabelPersonalYearReview": "আপনার বছরের পর্যালোচনা ({0})",
|
"LabelPersonalYearReview": "আপনার বছরের পর্যালোচনা ({0})",
|
||||||
"LabelPhotoPathURL": "ছবি পথ/ইউআরএল",
|
"LabelPhotoPathURL": "ছবি পথ/ইউআরএল",
|
||||||
"LabelPlayMethod": "প্লে পদ্ধতি",
|
"LabelPlayMethod": "প্লে পদ্ধতি",
|
||||||
|
"LabelPlayerChapterNumberMarker": "{1} এর মধ্যে {0}",
|
||||||
"LabelPlaylists": "প্লেলিস্ট",
|
"LabelPlaylists": "প্লেলিস্ট",
|
||||||
"LabelPodcast": "পডকাস্ট",
|
"LabelPodcast": "পডকাস্ট",
|
||||||
"LabelPodcastSearchRegion": "পডকাস্ট অনুসন্ধান অঞ্চল",
|
"LabelPodcastSearchRegion": "পডকাস্ট অনুসন্ধান অঞ্চল",
|
||||||
@@ -412,15 +460,20 @@
|
|||||||
"LabelPrimaryEbook": "প্রাথমিক ই-বই",
|
"LabelPrimaryEbook": "প্রাথমিক ই-বই",
|
||||||
"LabelProgress": "প্রগতি",
|
"LabelProgress": "প্রগতি",
|
||||||
"LabelProvider": "প্রদানকারী",
|
"LabelProvider": "প্রদানকারী",
|
||||||
|
"LabelProviderAuthorizationValue": "অনুমোদন শিরোনামের মান",
|
||||||
"LabelPubDate": "প্রকাশের তারিখ",
|
"LabelPubDate": "প্রকাশের তারিখ",
|
||||||
"LabelPublishYear": "প্রকাশের বছর",
|
"LabelPublishYear": "প্রকাশের বছর",
|
||||||
|
"LabelPublishedDate": "প্রকাশিত {0}",
|
||||||
"LabelPublisher": "প্রকাশক",
|
"LabelPublisher": "প্রকাশক",
|
||||||
|
"LabelPublishers": "প্রকাশকরা",
|
||||||
"LabelRSSFeedCustomOwnerEmail": "কাস্টম মালিকের ইমেইল",
|
"LabelRSSFeedCustomOwnerEmail": "কাস্টম মালিকের ইমেইল",
|
||||||
"LabelRSSFeedCustomOwnerName": "কাস্টম মালিকের নাম",
|
"LabelRSSFeedCustomOwnerName": "কাস্টম মালিকের নাম",
|
||||||
"LabelRSSFeedOpen": "আরএসএস ফিড খুলুন",
|
"LabelRSSFeedOpen": "আরএসএস ফিড খুলুন",
|
||||||
"LabelRSSFeedPreventIndexing": "সূচীকরণ প্রতিরোধ করুন",
|
"LabelRSSFeedPreventIndexing": "সূচীকরণ প্রতিরোধ করুন",
|
||||||
"LabelRSSFeedSlug": "আরএসএস ফিড স্লাগ",
|
"LabelRSSFeedSlug": "আরএসএস ফিড স্লাগ",
|
||||||
"LabelRSSFeedURL": "আরএসএস ফিড ইউআরএল",
|
"LabelRSSFeedURL": "আরএসএস ফিড ইউআরএল",
|
||||||
|
"LabelRandomly": "এলোমেলোভাবে",
|
||||||
|
"LabelReAddSeriesToContinueListening": "শোনা চালিয়ে যেতে সিরিজ পুনরায় যোগ করুন",
|
||||||
"LabelRead": "পড়ুন",
|
"LabelRead": "পড়ুন",
|
||||||
"LabelReadAgain": "আবার পড়ুন",
|
"LabelReadAgain": "আবার পড়ুন",
|
||||||
"LabelReadEbookWithoutProgress": "প্রগতি না রেখে ই-বই পড়ুন",
|
"LabelReadEbookWithoutProgress": "প্রগতি না রেখে ই-বই পড়ুন",
|
||||||
@@ -436,6 +489,7 @@
|
|||||||
"LabelSearchTitle": "অনুসন্ধান শিরোনাম",
|
"LabelSearchTitle": "অনুসন্ধান শিরোনাম",
|
||||||
"LabelSearchTitleOrASIN": "অনুসন্ধান শিরোনাম বা ASIN",
|
"LabelSearchTitleOrASIN": "অনুসন্ধান শিরোনাম বা ASIN",
|
||||||
"LabelSeason": "সেশন",
|
"LabelSeason": "সেশন",
|
||||||
|
"LabelSelectAll": "সব নির্বাচন করুন",
|
||||||
"LabelSelectAllEpisodes": "সমস্ত পর্ব নির্বাচন করুন",
|
"LabelSelectAllEpisodes": "সমস্ত পর্ব নির্বাচন করুন",
|
||||||
"LabelSelectEpisodesShowing": "দেখানো {0}টি পর্ব নির্বাচন করুন",
|
"LabelSelectEpisodesShowing": "দেখানো {0}টি পর্ব নির্বাচন করুন",
|
||||||
"LabelSelectUsers": "ব্যবহারকারী নির্বাচন করুন",
|
"LabelSelectUsers": "ব্যবহারকারী নির্বাচন করুন",
|
||||||
@@ -458,7 +512,8 @@
|
|||||||
"LabelSettingsEnableWatcher": "প্রহরী সক্ষম করুন",
|
"LabelSettingsEnableWatcher": "প্রহরী সক্ষম করুন",
|
||||||
"LabelSettingsEnableWatcherForLibrary": "লাইব্রেরির জন্য ফোল্ডার প্রহরী সক্ষম করুন",
|
"LabelSettingsEnableWatcherForLibrary": "লাইব্রেরির জন্য ফোল্ডার প্রহরী সক্ষম করুন",
|
||||||
"LabelSettingsEnableWatcherHelp": "ফাইলের পরিবর্তন শনাক্ত হলে আইটেমগুলির স্বয়ংক্রিয় যোগ/আপডেট সক্ষম করবে। *সার্ভার পুনরায় চালু করতে হবে",
|
"LabelSettingsEnableWatcherHelp": "ফাইলের পরিবর্তন শনাক্ত হলে আইটেমগুলির স্বয়ংক্রিয় যোগ/আপডেট সক্ষম করবে। *সার্ভার পুনরায় চালু করতে হবে",
|
||||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files।",
|
"LabelSettingsEpubsAllowScriptedContent": "ইপাবে স্ক্রিপ্ট করা বিষয়বস্তুর অনুমতি দিন",
|
||||||
|
"LabelSettingsEpubsAllowScriptedContentHelp": "ইপাব ফাইলগুলিকে স্ক্রিপ্ট চালানোর অনুমতি দিন। আপনি ইপাব ফাইলগুলির উৎসকে বিশ্বাস না করলে এই সেটিংটি নিষ্ক্রিয় রাখার সুপারিশ করা হলো।",
|
||||||
"LabelSettingsExperimentalFeatures": "পরীক্ষামূলক বৈশিষ্ট্য",
|
"LabelSettingsExperimentalFeatures": "পরীক্ষামূলক বৈশিষ্ট্য",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "ফিচারের বৈশিষ্ট্য যা আপনার প্রতিক্রিয়া ব্যবহার করতে পারে এবং পরীক্ষায় সহায়তা করতে পারে। গিটহাব আলোচনা খুলতে ক্লিক করুন।",
|
"LabelSettingsExperimentalFeaturesHelp": "ফিচারের বৈশিষ্ট্য যা আপনার প্রতিক্রিয়া ব্যবহার করতে পারে এবং পরীক্ষায় সহায়তা করতে পারে। গিটহাব আলোচনা খুলতে ক্লিক করুন।",
|
||||||
"LabelSettingsFindCovers": "কভার খুঁজুন",
|
"LabelSettingsFindCovers": "কভার খুঁজুন",
|
||||||
@@ -468,7 +523,7 @@
|
|||||||
"LabelSettingsHomePageBookshelfView": "নীড় পেজে বুকশেলফ ভিউ ব্যবহার করুন",
|
"LabelSettingsHomePageBookshelfView": "নীড় পেজে বুকশেলফ ভিউ ব্যবহার করুন",
|
||||||
"LabelSettingsLibraryBookshelfView": "লাইব্রেরি বুকশেলফ ভিউ ব্যবহার করুন",
|
"LabelSettingsLibraryBookshelfView": "লাইব্রেরি বুকশেলফ ভিউ ব্যবহার করুন",
|
||||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "কন্টিনিউ সিরিজে আগের বইগুলো এড়িয়ে যান",
|
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "কন্টিনিউ সিরিজে আগের বইগুলো এড়িয়ে যান",
|
||||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "কন্টিনিউ সিরিজের হোম পেজ শেল্ফ প্রথম বইটি দেখায় যেটি সিরিজে শুরু হয়নি যেটিতে অন্তত একটি বই শেষ হয়েছে এবং কোনো বই চলছে না। এই সেটিংটি সক্ষম করা হলে তা শুরু না হওয়া প্রথম বইটির পরিবর্তে সবচেয়ে দূরের সম্পূর্ণ বই থেকে সিরিজ চালিয়ে যাবে।",
|
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "কন্টিনিউ সিরিজের নীড় পেজ শেল্ফ দেখায় যে সিরিজে শুরু হয়নি এমন প্রথম বই যার অন্তত একটি বই শেষ হয়েছে এবং কোনো বই চলছে না। এই সেটিংটি সক্ষম করলে শুরু না হওয়া প্রথম বইটির পরিবর্তে সবচেয়ে দূরের সম্পূর্ণ বই থেকে সিরিজ চলতে থাকবে।",
|
||||||
"LabelSettingsParseSubtitles": "সাবটাইটেল পার্স করুন",
|
"LabelSettingsParseSubtitles": "সাবটাইটেল পার্স করুন",
|
||||||
"LabelSettingsParseSubtitlesHelp": "অডিওবুক ফোল্ডারের নাম থেকে সাবটাইটেল বের করুন৷<br>সাবটাইটেল অবশ্যই \" - \"<br>অর্থাৎ \"বুকের শিরোনাম - এখানে একটি সাবটাইটেল\" এর সাবটাইটেল আছে \"এখানে একটি সাবটাইটেল\"",
|
"LabelSettingsParseSubtitlesHelp": "অডিওবুক ফোল্ডারের নাম থেকে সাবটাইটেল বের করুন৷<br>সাবটাইটেল অবশ্যই \" - \"<br>অর্থাৎ \"বুকের শিরোনাম - এখানে একটি সাবটাইটেল\" এর সাবটাইটেল আছে \"এখানে একটি সাবটাইটেল\"",
|
||||||
"LabelSettingsPreferMatchedMetadata": "মিলিত মেটাডেটা পছন্দ করুন",
|
"LabelSettingsPreferMatchedMetadata": "মিলিত মেটাডেটা পছন্দ করুন",
|
||||||
@@ -484,7 +539,12 @@
|
|||||||
"LabelSettingsStoreMetadataWithItem": "আইটেমের সাথে মেটাডেটা সংরক্ষণ করুন",
|
"LabelSettingsStoreMetadataWithItem": "আইটেমের সাথে মেটাডেটা সংরক্ষণ করুন",
|
||||||
"LabelSettingsStoreMetadataWithItemHelp": "ডিফল্টরূপে মেটাডেটা ফাইলগুলি /মেটাডাটা/আইটেমগুলি -এ সংরক্ষণ করা হয়, এই সেটিংটি সক্ষম করলে মেটাডেটা ফাইলগুলি আপনার লাইব্রেরি আইটেম ফোল্ডারে সংরক্ষণ করা হবে",
|
"LabelSettingsStoreMetadataWithItemHelp": "ডিফল্টরূপে মেটাডেটা ফাইলগুলি /মেটাডাটা/আইটেমগুলি -এ সংরক্ষণ করা হয়, এই সেটিংটি সক্ষম করলে মেটাডেটা ফাইলগুলি আপনার লাইব্রেরি আইটেম ফোল্ডারে সংরক্ষণ করা হবে",
|
||||||
"LabelSettingsTimeFormat": "সময় বিন্যাস",
|
"LabelSettingsTimeFormat": "সময় বিন্যাস",
|
||||||
|
"LabelShare": "শেয়ার করুন",
|
||||||
|
"LabelShareOpen": "শেয়ার খোলা",
|
||||||
|
"LabelShareURL": "শেয়ার ইউআরএল",
|
||||||
"LabelShowAll": "সব দেখান",
|
"LabelShowAll": "সব দেখান",
|
||||||
|
"LabelShowSeconds": "সেকেন্ড দেখান",
|
||||||
|
"LabelShowSubtitles": "সহ-শিরোনাম দেখান",
|
||||||
"LabelSize": "আকার",
|
"LabelSize": "আকার",
|
||||||
"LabelSleepTimer": "স্লিপ টাইমার",
|
"LabelSleepTimer": "স্লিপ টাইমার",
|
||||||
"LabelSlug": "স্লাগ",
|
"LabelSlug": "স্লাগ",
|
||||||
@@ -522,6 +582,10 @@
|
|||||||
"LabelThemeDark": "অন্ধকার",
|
"LabelThemeDark": "অন্ধকার",
|
||||||
"LabelThemeLight": "আলো",
|
"LabelThemeLight": "আলো",
|
||||||
"LabelTimeBase": "সময় বেস",
|
"LabelTimeBase": "সময় বেস",
|
||||||
|
"LabelTimeDurationXHours": "{0} ঘণ্টা",
|
||||||
|
"LabelTimeDurationXMinutes": "{0} মিনিট",
|
||||||
|
"LabelTimeDurationXSeconds": "{0} সেকেন্ড",
|
||||||
|
"LabelTimeInMinutes": "মিনিটে সময়",
|
||||||
"LabelTimeListened": "সময় শোনা হয়েছে",
|
"LabelTimeListened": "সময় শোনা হয়েছে",
|
||||||
"LabelTimeListenedToday": "আজ শোনার সময়",
|
"LabelTimeListenedToday": "আজ শোনার সময়",
|
||||||
"LabelTimeRemaining": "{0}টি অবশিষ্ট",
|
"LabelTimeRemaining": "{0}টি অবশিষ্ট",
|
||||||
@@ -545,6 +609,7 @@
|
|||||||
"LabelUnabridged": "অসংলগ্ন",
|
"LabelUnabridged": "অসংলগ্ন",
|
||||||
"LabelUndo": "পূর্বাবস্থা",
|
"LabelUndo": "পূর্বাবস্থা",
|
||||||
"LabelUnknown": "অজানা",
|
"LabelUnknown": "অজানা",
|
||||||
|
"LabelUnknownPublishDate": "প্রকাশের তারিখ অজানা",
|
||||||
"LabelUpdateCover": "কভার আপডেট করুন",
|
"LabelUpdateCover": "কভার আপডেট করুন",
|
||||||
"LabelUpdateCoverHelp": "একটি মিল থাকা অবস্থায় নির্বাচিত বইগুলির বিদ্যমান কভারগুলি ওভাররাইট করার অনুমতি দিন",
|
"LabelUpdateCoverHelp": "একটি মিল থাকা অবস্থায় নির্বাচিত বইগুলির বিদ্যমান কভারগুলি ওভাররাইট করার অনুমতি দিন",
|
||||||
"LabelUpdateDetails": "বিশদ আপডেট করুন",
|
"LabelUpdateDetails": "বিশদ আপডেট করুন",
|
||||||
@@ -561,9 +626,12 @@
|
|||||||
"LabelVersion": "সংস্করণ",
|
"LabelVersion": "সংস্করণ",
|
||||||
"LabelViewBookmarks": "বুকমার্ক দেখুন",
|
"LabelViewBookmarks": "বুকমার্ক দেখুন",
|
||||||
"LabelViewChapters": "অধ্যায় দেখুন",
|
"LabelViewChapters": "অধ্যায় দেখুন",
|
||||||
|
"LabelViewPlayerSettings": "প্লেয়ার সেটিংস দেখুন",
|
||||||
"LabelViewQueue": "প্লেয়ার সারি দেখুন",
|
"LabelViewQueue": "প্লেয়ার সারি দেখুন",
|
||||||
"LabelVolume": "ভলিউম",
|
"LabelVolume": "ভলিউম",
|
||||||
"LabelWeekdaysToRun": "চলতে হবে সপ্তাহের দিন",
|
"LabelWeekdaysToRun": "চলতে হবে সপ্তাহের দিন",
|
||||||
|
"LabelXBooks": "{0}টি বই",
|
||||||
|
"LabelXItems": "{0}টি আইটেম",
|
||||||
"LabelYearReviewHide": "পর্যালোচনার বছর লুকান",
|
"LabelYearReviewHide": "পর্যালোচনার বছর লুকান",
|
||||||
"LabelYearReviewShow": "পর্যালোচনার বছর দেখুন",
|
"LabelYearReviewShow": "পর্যালোচনার বছর দেখুন",
|
||||||
"LabelYourAudiobookDuration": "আপনার অডিওবুকের সময়কাল",
|
"LabelYourAudiobookDuration": "আপনার অডিওবুকের সময়কাল",
|
||||||
@@ -571,12 +639,16 @@
|
|||||||
"LabelYourPlaylists": "আপনার প্লেলিস্ট",
|
"LabelYourPlaylists": "আপনার প্লেলিস্ট",
|
||||||
"LabelYourProgress": "আপনার অগ্রগতি",
|
"LabelYourProgress": "আপনার অগ্রগতি",
|
||||||
"MessageAddToPlayerQueue": "প্লেয়ার সারিতে যোগ করুন",
|
"MessageAddToPlayerQueue": "প্লেয়ার সারিতে যোগ করুন",
|
||||||
"MessageAppriseDescription": "এই বৈশিষ্ট্যটি ব্যবহার করার জন্য আপনাকে <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</-এর একটি উদাহরণ থাকতে হবে a> চলমান বা একটি এপিআই যা সেই একই অনুরোধগুলি পরিচালনা করবে৷ <br /> বিজ্ঞপ্তি পাঠানোর জন্য Apprise API Url সম্পূর্ণ URL পাথ হওয়া উচিত, যেমন, যদি আপনার API উদাহরণ <code>http://192.168 এ পরিবেশিত হয়৷ 1.1:8337</code> তারপর আপনি <code>http://192.168.1.1:8337/notify</code> লিখবেন।",
|
"MessageAppriseDescription": "এই বৈশিষ্ট্যটি ব্যবহার করার জন্য আপনাকে <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> চালানোর একটি উদাহরণ বা একটি এপিআই পরিচালনা করতে হবে যে একই অনুরোধ পরিচালনা করবে। <br />অ্যাপ্রাইজ এপিআই ইউআরএলটি বিজ্ঞপ্তি পাঠানোর জন্য সম্পূর্ণ ইউআরএল পথ হওয়া উচিত, যেমন, যদি আপনার API ইনস্ট্যান্স <code>http://192.168.1.1:8337</code> এ পরিবেশিত হয় তাহলে আপনি <code> রাখবেন >http://192.168.1.1:8337/notify</code>।",
|
||||||
"MessageBackupsDescription": "ব্যাকআপের মধ্যে রয়েছে ব্যবহারকারী, ব্যবহারকারীর অগ্রগতি, লাইব্রেরি আইটেমের বিবরণ, সার্ভার সেটিংস এবং <code>/metadata/items</code> & <code>/metadata/authors</code>-এ সংরক্ষিত ছবি। ব্যাকআপগুলি <strong> আপনার লাইব্রেরি ফোল্ডারে সঞ্চিত কোনো ফাইল >অন্তর্ভুক্ত করবেন না</strong>।",
|
"MessageBackupsDescription": "ব্যাকআপের মধ্যে রয়েছে ব্যবহারকারী, ব্যবহারকারীর অগ্রগতি, লাইব্রেরি আইটেমের বিবরণ, সার্ভার সেটিংস এবং <code>/metadata/items</code> & <code>/metadata/authors</code>-এ সংরক্ষিত ছবি। ব্যাকআপগুলি <strong> আপনার লাইব্রেরি ফোল্ডারে সঞ্চিত কোনো ফাইল >অন্তর্ভুক্ত করবেন না</strong>।",
|
||||||
|
"MessageBackupsLocationEditNote": "দ্রষ্টব্য: ব্যাকআপ অবস্থান আপডেট করলে বিদ্যমান ব্যাকআপগুলি সরানো বা সংশোধন করা হবে না",
|
||||||
|
"MessageBackupsLocationNoEditNote": "দ্রষ্টব্য: ব্যাকআপ অবস্থান একটি পরিবেশ পরিবর্তনশীল মাধ্যমে স্থির করা হয়েছে এবং এখানে পরিবর্তন করা যাবে না।",
|
||||||
|
"MessageBackupsLocationPathEmpty": "ব্যাকআপ অবস্থানের পথ খালি থাকতে পারবে না",
|
||||||
"MessageBatchQuickMatchDescription": "কুইক ম্যাচ নির্বাচিত আইটেমগুলির জন্য অনুপস্থিত কভার এবং মেটাডেটা যোগ করার চেষ্টা করবে। বিদ্যমান কভার এবং/অথবা মেটাডেটা ওভাররাইট করার জন্য দ্রুত ম্যাচকে অনুমতি দিতে নীচের বিকল্পগুলি সক্ষম করুন।",
|
"MessageBatchQuickMatchDescription": "কুইক ম্যাচ নির্বাচিত আইটেমগুলির জন্য অনুপস্থিত কভার এবং মেটাডেটা যোগ করার চেষ্টা করবে। বিদ্যমান কভার এবং/অথবা মেটাডেটা ওভাররাইট করার জন্য দ্রুত ম্যাচকে অনুমতি দিতে নীচের বিকল্পগুলি সক্ষম করুন।",
|
||||||
"MessageBookshelfNoCollections": "আপনি এখনও কোনো সংগ্রহ করেননি",
|
"MessageBookshelfNoCollections": "আপনি এখনও কোনো সংগ্রহ করেননি",
|
||||||
"MessageBookshelfNoRSSFeeds": "কোনও RSS ফিড খোলা নেই",
|
"MessageBookshelfNoRSSFeeds": "কোনও RSS ফিড খোলা নেই",
|
||||||
"MessageBookshelfNoResultsForFilter": "ফিল্টার \"{0}: {1}\" এর জন্য কোন ফলাফল নেই",
|
"MessageBookshelfNoResultsForFilter": "ফিল্টার \"{0}: {1}\" এর জন্য কোন ফলাফল নেই",
|
||||||
|
"MessageBookshelfNoResultsForQuery": "প্রশ্নের জন্য কোন ফলাফল নেই",
|
||||||
"MessageBookshelfNoSeries": "আপনার কোনো সিরিজ নেই",
|
"MessageBookshelfNoSeries": "আপনার কোনো সিরিজ নেই",
|
||||||
"MessageChapterEndIsAfter": "অধ্যায়ের সমাপ্তি আপনার অডিওবুকের শেষে",
|
"MessageChapterEndIsAfter": "অধ্যায়ের সমাপ্তি আপনার অডিওবুকের শেষে",
|
||||||
"MessageChapterErrorFirstNotZero": "প্রথম অধ্যায় 0 এ শুরু হতে হবে",
|
"MessageChapterErrorFirstNotZero": "প্রথম অধ্যায় 0 এ শুরু হতে হবে",
|
||||||
@@ -586,16 +658,24 @@
|
|||||||
"MessageCheckingCron": "ক্রন পরীক্ষা করা হচ্ছে...",
|
"MessageCheckingCron": "ক্রন পরীক্ষা করা হচ্ছে...",
|
||||||
"MessageConfirmCloseFeed": "আপনি কি নিশ্চিত যে আপনি এই ফিডটি বন্ধ করতে চান?",
|
"MessageConfirmCloseFeed": "আপনি কি নিশ্চিত যে আপনি এই ফিডটি বন্ধ করতে চান?",
|
||||||
"MessageConfirmDeleteBackup": "আপনি কি নিশ্চিত যে আপনি {0} এর ব্যাকআপ মুছে ফেলতে চান?",
|
"MessageConfirmDeleteBackup": "আপনি কি নিশ্চিত যে আপনি {0} এর ব্যাকআপ মুছে ফেলতে চান?",
|
||||||
|
"MessageConfirmDeleteDevice": "আপনি কি নিশ্চিতভাবে ই-রিডার ডিভাইস \"{0}\" মুছতে চান?",
|
||||||
"MessageConfirmDeleteFile": "এটি আপনার ফাইল সিস্টেম থেকে ফাইলটি মুছে দেবে। আপনি কি নিশ্চিত?",
|
"MessageConfirmDeleteFile": "এটি আপনার ফাইল সিস্টেম থেকে ফাইলটি মুছে দেবে। আপনি কি নিশ্চিত?",
|
||||||
"MessageConfirmDeleteLibrary": "আপনি কি নিশ্চিত যে আপনি স্থায়ীভাবে লাইব্রেরি \"{0}\" মুছে ফেলতে চান?",
|
"MessageConfirmDeleteLibrary": "আপনি কি নিশ্চিত যে আপনি স্থায়ীভাবে লাইব্রেরি \"{0}\" মুছে ফেলতে চান?",
|
||||||
"MessageConfirmDeleteLibraryItem": "এটি ডাটাবেস এবং আপনার ফাইল সিস্টেম থেকে লাইব্রেরি আইটেমটি মুছে ফেলবে। আপনি কি নিশ্চিত?",
|
"MessageConfirmDeleteLibraryItem": "এটি ডাটাবেস এবং আপনার ফাইল সিস্টেম থেকে লাইব্রেরি আইটেমটি মুছে ফেলবে। আপনি কি নিশ্চিত?",
|
||||||
"MessageConfirmDeleteLibraryItems": "এটি ডাটাবেস এবং আপনার ফাইল সিস্টেম থেকে {0}টি লাইব্রেরি আইটেম মুছে ফেলবে। আপনি কি নিশ্চিত?",
|
"MessageConfirmDeleteLibraryItems": "এটি ডাটাবেস এবং আপনার ফাইল সিস্টেম থেকে {0}টি লাইব্রেরি আইটেম মুছে ফেলবে। আপনি কি নিশ্চিত?",
|
||||||
|
"MessageConfirmDeleteMetadataProvider": "আপনি কি নিশ্চিতভাবে কাস্টম মেটাডেটা প্রদানকারী \"{0}\" মুছতে চান?",
|
||||||
|
"MessageConfirmDeleteNotification": "আপনি কি নিশ্চিতভাবে এই বিজ্ঞপ্তিটি মুছতে চান?",
|
||||||
"MessageConfirmDeleteSession": "আপনি কি নিশ্চিত আপনি এই অধিবেশন মুছে দিতে চান?",
|
"MessageConfirmDeleteSession": "আপনি কি নিশ্চিত আপনি এই অধিবেশন মুছে দিতে চান?",
|
||||||
"MessageConfirmForceReScan": "আপনি কি নিশ্চিত যে আপনি জোর করে পুনরায় স্ক্যান করতে চান?",
|
"MessageConfirmForceReScan": "আপনি কি নিশ্চিত যে আপনি জোর করে পুনরায় স্ক্যান করতে চান?",
|
||||||
"MessageConfirmMarkAllEpisodesFinished": "আপনি কি নিশ্চিত যে আপনি সমস্ত পর্ব সমাপ্ত হিসাবে চিহ্নিত করতে চান?",
|
"MessageConfirmMarkAllEpisodesFinished": "আপনি কি নিশ্চিত যে আপনি সমস্ত পর্ব সমাপ্ত হিসাবে চিহ্নিত করতে চান?",
|
||||||
"MessageConfirmMarkAllEpisodesNotFinished": "আপনি কি নিশ্চিত যে আপনি সমস্ত পর্বকে শেষ হয়নি বলে চিহ্নিত করতে চান?",
|
"MessageConfirmMarkAllEpisodesNotFinished": "আপনি কি নিশ্চিত যে আপনি সমস্ত পর্বকে শেষ হয়নি বলে চিহ্নিত করতে চান?",
|
||||||
|
"MessageConfirmMarkItemFinished": "আপনি কি \"{0}\" কে সমাপ্ত হিসাবে চিহ্নিত করার বিষয়ে নিশ্চিত?",
|
||||||
|
"MessageConfirmMarkItemNotFinished": "আপনি কি \"{0}\" শেষ হয়নি বলে চিহ্নিত করার বিষয়ে নিশ্চিত?",
|
||||||
"MessageConfirmMarkSeriesFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে সমাপ্ত হিসাবে চিহ্নিত করতে চান?",
|
"MessageConfirmMarkSeriesFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে সমাপ্ত হিসাবে চিহ্নিত করতে চান?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে শেষ হয়নি বলে চিহ্নিত করতে চান?",
|
"MessageConfirmMarkSeriesNotFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে শেষ হয়নি বলে চিহ্নিত করতে চান?",
|
||||||
|
"MessageConfirmNotificationTestTrigger": "পরীক্ষার তথ্য দিয়ে এই বিজ্ঞপ্তিটি ট্রিগার করবেন?",
|
||||||
|
"MessageConfirmPurgeCache": "ক্যাশে পরিষ্কারক <code>/metadata/cache</code>-এ সম্পূর্ণ ডিরেক্টরি মুছে ফেলবে। <br /><br />আপনি কি নিশ্চিত আপনি ক্যাশে ডিরেক্টরি সরাতে চান?",
|
||||||
|
"MessageConfirmPurgeItemsCache": "আইটেম ক্যাশে পরিষ্কারক <code>/metadata/cache/items</code>-এ সম্পূর্ণ ডিরেক্টরি মুছে ফেলবে।<br />আপনি কি নিশ্চিত?",
|
||||||
"MessageConfirmQuickEmbed": "সতর্কতা! দ্রুত এম্বেড আপনার অডিও ফাইলের ব্যাকআপ করবে না। নিশ্চিত করুন যে আপনার অডিও ফাইলগুলির একটি ব্যাকআপ আছে। <br><br>আপনি কি চালিয়ে যেতে চান?",
|
"MessageConfirmQuickEmbed": "সতর্কতা! দ্রুত এম্বেড আপনার অডিও ফাইলের ব্যাকআপ করবে না। নিশ্চিত করুন যে আপনার অডিও ফাইলগুলির একটি ব্যাকআপ আছে। <br><br>আপনি কি চালিয়ে যেতে চান?",
|
||||||
"MessageConfirmReScanLibraryItems": "আপনি কি নিশ্চিত যে আপনি {0}টি আইটেম পুনরায় স্ক্যান করতে চান?",
|
"MessageConfirmReScanLibraryItems": "আপনি কি নিশ্চিত যে আপনি {0}টি আইটেম পুনরায় স্ক্যান করতে চান?",
|
||||||
"MessageConfirmRemoveAllChapters": "আপনি কি নিশ্চিত যে আপনি সমস্ত অধ্যায় সরাতে চান?",
|
"MessageConfirmRemoveAllChapters": "আপনি কি নিশ্চিত যে আপনি সমস্ত অধ্যায় সরাতে চান?",
|
||||||
@@ -612,12 +692,15 @@
|
|||||||
"MessageConfirmRenameTag": "আপনি কি সব আইটেমের জন্য \"{0}\" ট্যাগের নাম পরিবর্তন করে \"{1}\" করার বিষয়ে নিশ্চিত?",
|
"MessageConfirmRenameTag": "আপনি কি সব আইটেমের জন্য \"{0}\" ট্যাগের নাম পরিবর্তন করে \"{1}\" করার বিষয়ে নিশ্চিত?",
|
||||||
"MessageConfirmRenameTagMergeNote": "দ্রষ্টব্য: এই ট্যাগটি আগে থেকেই বিদ্যমান তাই সেগুলিকে একত্র করা হবে।",
|
"MessageConfirmRenameTagMergeNote": "দ্রষ্টব্য: এই ট্যাগটি আগে থেকেই বিদ্যমান তাই সেগুলিকে একত্র করা হবে।",
|
||||||
"MessageConfirmRenameTagWarning": "সতর্কতা! একটি ভিন্ন কেসিং সহ একটি অনুরূপ ট্যাগ ইতিমধ্যেই বিদ্যমান \"{0}\"।",
|
"MessageConfirmRenameTagWarning": "সতর্কতা! একটি ভিন্ন কেসিং সহ একটি অনুরূপ ট্যাগ ইতিমধ্যেই বিদ্যমান \"{0}\"।",
|
||||||
|
"MessageConfirmResetProgress": "আপনি কি আপনার অগ্রগতি রিসেট করার বিষয়ে নিশ্চিত?",
|
||||||
"MessageConfirmSendEbookToDevice": "আপনি কি নিশ্চিত যে আপনি \"{2}\" ডিভাইসে {0} ইবুক \"{1}\" পাঠাতে চান?",
|
"MessageConfirmSendEbookToDevice": "আপনি কি নিশ্চিত যে আপনি \"{2}\" ডিভাইসে {0} ইবুক \"{1}\" পাঠাতে চান?",
|
||||||
|
"MessageConfirmUnlinkOpenId": "আপনি কি এই ব্যবহারকারীকে ওপেনআইডি থেকে লিঙ্কমুক্ত করার বিষয়ে নিশ্চিত?",
|
||||||
"MessageDownloadingEpisode": "ডাউনলোডিং পর্ব",
|
"MessageDownloadingEpisode": "ডাউনলোডিং পর্ব",
|
||||||
"MessageDragFilesIntoTrackOrder": "সঠিক ট্র্যাক অর্ডারে ফাইল টেনে আনুন",
|
"MessageDragFilesIntoTrackOrder": "সঠিক ট্র্যাক অর্ডারে ফাইল টেনে আনুন",
|
||||||
|
"MessageEmbedFailed": "এম্বেড ব্যর্থ হয়েছে!",
|
||||||
"MessageEmbedFinished": "এম্বেড করা শেষ!",
|
"MessageEmbedFinished": "এম্বেড করা শেষ!",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} পর্ব(গুলি) ডাউনলোডের জন্য সারিবদ্ধ",
|
"MessageEpisodesQueuedForDownload": "{0} পর্ব(গুলি) ডাউনলোডের জন্য সারিবদ্ধ",
|
||||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below।",
|
"MessageEreaderDevices": "ই-বুক সরবরাহ নিশ্চিত করতে, আপনাকে নীচে তালিকাভুক্ত প্রতিটি ডিভাইসের জন্য একটি বৈধ প্রেরক হিসাবে উপরের ইমেল ঠিকানাটি যুক্ত করতে হতে পারে।",
|
||||||
"MessageFeedURLWillBe": "ফিড URL হবে {0}",
|
"MessageFeedURLWillBe": "ফিড URL হবে {0}",
|
||||||
"MessageFetching": "আনয় হচ্ছে...",
|
"MessageFetching": "আনয় হচ্ছে...",
|
||||||
"MessageForceReScanDescription": "সকল ফাইল আবার নতুন স্ক্যানের মত স্ক্যান করবে। অডিও ফাইল ID3 ট্যাগ, OPF ফাইল, এবং টেক্সট ফাইলগুলি নতুন হিসাবে স্ক্যান করা হবে।",
|
"MessageForceReScanDescription": "সকল ফাইল আবার নতুন স্ক্যানের মত স্ক্যান করবে। অডিও ফাইল ID3 ট্যাগ, OPF ফাইল, এবং টেক্সট ফাইলগুলি নতুন হিসাবে স্ক্যান করা হবে।",
|
||||||
@@ -629,7 +712,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": "লগগুলি JSON ফাইল হিসাবে <code>/metadata/logs</code>-এ সংরক্ষণ করা হয়। ক্র্যাশ লগগুলি <code>/metadata/logs/crash_logs.txt</code>-এ সংরক্ষণ করা হয়।",
|
||||||
"MessageM4BFailed": "M4B ব্যর্থ!",
|
"MessageM4BFailed": "M4B ব্যর্থ!",
|
||||||
"MessageM4BFinished": "M4B সমাপ্ত!",
|
"MessageM4BFinished": "M4B সমাপ্ত!",
|
||||||
"MessageMapChapterTitles": "টাইমস্ট্যাম্প সামঞ্জস্য না করে আপনার বিদ্যমান অডিওবুক অধ্যায়গুলিতে অধ্যায়ের শিরোনাম ম্যাপ করুন",
|
"MessageMapChapterTitles": "টাইমস্ট্যাম্প সামঞ্জস্য না করে আপনার বিদ্যমান অডিওবুক অধ্যায়গুলিতে অধ্যায়ের শিরোনাম ম্যাপ করুন",
|
||||||
@@ -646,6 +729,7 @@
|
|||||||
"MessageNoCollections": "কোন সংগ্রহ নেই",
|
"MessageNoCollections": "কোন সংগ্রহ নেই",
|
||||||
"MessageNoCoversFound": "কোন কভার পাওয়া যায়নি",
|
"MessageNoCoversFound": "কোন কভার পাওয়া যায়নি",
|
||||||
"MessageNoDescription": "কোন বর্ণনা নেই",
|
"MessageNoDescription": "কোন বর্ণনা নেই",
|
||||||
|
"MessageNoDevices": "কোনো ডিভাইস নেই",
|
||||||
"MessageNoDownloadsInProgress": "বর্তমানে কোনো ডাউনলোড চলছে না",
|
"MessageNoDownloadsInProgress": "বর্তমানে কোনো ডাউনলোড চলছে না",
|
||||||
"MessageNoDownloadsQueued": "কোনও ডাউনলোড সারি নেই",
|
"MessageNoDownloadsQueued": "কোনও ডাউনলোড সারি নেই",
|
||||||
"MessageNoEpisodeMatchesFound": "কোন পর্বের মিল পাওয়া যায়নি",
|
"MessageNoEpisodeMatchesFound": "কোন পর্বের মিল পাওয়া যায়নি",
|
||||||
@@ -668,10 +752,12 @@
|
|||||||
"MessageNoUpdatesWereNecessary": "কোন আপডেটের প্রয়োজন ছিল না",
|
"MessageNoUpdatesWereNecessary": "কোন আপডেটের প্রয়োজন ছিল না",
|
||||||
"MessageNoUserPlaylists": "আপনার কোনো প্লেলিস্ট নেই",
|
"MessageNoUserPlaylists": "আপনার কোনো প্লেলিস্ট নেই",
|
||||||
"MessageNotYetImplemented": "এখনও বাস্তবায়িত হয়নি",
|
"MessageNotYetImplemented": "এখনও বাস্তবায়িত হয়নি",
|
||||||
|
"MessageOpmlPreviewNote": "দ্রষ্টব্য: এটি পার্স করা OPML ফাইলের একটি পূর্বরূপ। প্রকৃত পডকাস্ট শিরোনাম RSS ফিড থেকে নেওয়া হবে।",
|
||||||
"MessageOr": "বা",
|
"MessageOr": "বা",
|
||||||
"MessagePauseChapter": "পজ অধ্যায় প্লেব্যাক",
|
"MessagePauseChapter": "পজ অধ্যায় প্লেব্যাক",
|
||||||
"MessagePlayChapter": "অধ্যায়ের শুরুতে শুনুন",
|
"MessagePlayChapter": "অধ্যায়ের শুরুতে শুনুন",
|
||||||
"MessagePlaylistCreateFromCollection": "সংগ্রহ থেকে প্লেলিস্ট তৈরি করুন",
|
"MessagePlaylistCreateFromCollection": "সংগ্রহ থেকে প্লেলিস্ট তৈরি করুন",
|
||||||
|
"MessagePleaseWait": "অনুগ্রহ করে অপেক্ষা করুন..।",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "পডকাস্টের সাথে মিলের জন্য ব্যবহার করার জন্য কোন RSS ফিড ইউআরএল নেই",
|
"MessagePodcastHasNoRSSFeedForMatching": "পডকাস্টের সাথে মিলের জন্য ব্যবহার করার জন্য কোন RSS ফিড ইউআরএল নেই",
|
||||||
"MessageQuickMatchDescription": "খালি আইটেমের বিশদ বিবরণ এবং '{0}' থেকে প্রথম ম্যাচের ফলাফলের সাথে কভার করুন। সার্ভার সেটিং সক্ষম না থাকলে বিশদ ওভাররাইট করে না।",
|
"MessageQuickMatchDescription": "খালি আইটেমের বিশদ বিবরণ এবং '{0}' থেকে প্রথম ম্যাচের ফলাফলের সাথে কভার করুন। সার্ভার সেটিং সক্ষম না থাকলে বিশদ ওভাররাইট করে না।",
|
||||||
"MessageRemoveChapter": "অধ্যায় সরান",
|
"MessageRemoveChapter": "অধ্যায় সরান",
|
||||||
@@ -686,6 +772,9 @@
|
|||||||
"MessageSelected": "{0}টি নির্বাচিত",
|
"MessageSelected": "{0}টি নির্বাচিত",
|
||||||
"MessageServerCouldNotBeReached": "সার্ভারে পৌঁছানো যায়নি",
|
"MessageServerCouldNotBeReached": "সার্ভারে পৌঁছানো যায়নি",
|
||||||
"MessageSetChaptersFromTracksDescription": "প্রতিটি অডিও ফাইলকে অধ্যায় হিসেবে ব্যবহার করে অধ্যায় সেট করুন এবং অডিও ফাইলের নাম হিসেবে অধ্যায়ের শিরোনাম করুন",
|
"MessageSetChaptersFromTracksDescription": "প্রতিটি অডিও ফাইলকে অধ্যায় হিসেবে ব্যবহার করে অধ্যায় সেট করুন এবং অডিও ফাইলের নাম হিসেবে অধ্যায়ের শিরোনাম করুন",
|
||||||
|
"MessageShareExpirationWillBe": "মেয়াদ শেষ হবে <strong>{0}</strong>",
|
||||||
|
"MessageShareExpiresIn": "মেয়াদ শেষ হবে {0}",
|
||||||
|
"MessageShareURLWillBe": "শেয়ার করা ইউআরএল হবে <strong>{0}</strong>",
|
||||||
"MessageStartPlaybackAtTime": "\"{0}\" এর জন্য {1} এ প্লেব্যাক শুরু করবেন?",
|
"MessageStartPlaybackAtTime": "\"{0}\" এর জন্য {1} এ প্লেব্যাক শুরু করবেন?",
|
||||||
"MessageThinking": "চিন্তা করছি...",
|
"MessageThinking": "চিন্তা করছি...",
|
||||||
"MessageUploaderItemFailed": "আপলোড করতে ব্যর্থ",
|
"MessageUploaderItemFailed": "আপলোড করতে ব্যর্থ",
|
||||||
@@ -709,20 +798,48 @@
|
|||||||
"PlaceholderNewPlaylist": "নতুন প্লেলিস্টের নাম",
|
"PlaceholderNewPlaylist": "নতুন প্লেলিস্টের নাম",
|
||||||
"PlaceholderSearch": "অনুসন্ধান..",
|
"PlaceholderSearch": "অনুসন্ধান..",
|
||||||
"PlaceholderSearchEpisode": "অনুসন্ধান পর্ব..",
|
"PlaceholderSearchEpisode": "অনুসন্ধান পর্ব..",
|
||||||
|
"StatsAuthorsAdded": "লেখক যোগ করা হয়েছে",
|
||||||
|
"StatsBooksAdded": "বই যোগ করা হয়েছে",
|
||||||
|
"StatsBooksAdditional": "কিছু সংযোজনের মধ্যে রয়েছে…",
|
||||||
|
"StatsBooksFinished": "বই সমাপ্ত",
|
||||||
|
"StatsBooksFinishedThisYear": "এ বছর শেষ হওয়া কিছু বই …",
|
||||||
|
"StatsBooksListenedTo": "বই শোনা হয়েছে",
|
||||||
|
"StatsCollectionGrewTo": "আপনার বইয়ের সংগ্রহ বেড়েছে…",
|
||||||
|
"StatsSessions": "অধিবেশনসমূহ",
|
||||||
|
"StatsSpentListening": "শুনে কাটিয়েছেন",
|
||||||
|
"StatsTopAuthor": "শীর্ষস্থানীয় লেখক",
|
||||||
|
"StatsTopAuthors": "শীর্ষস্থানীয় লেখকগণ",
|
||||||
|
"StatsTopGenre": "শীর্ষ ঘরানা",
|
||||||
|
"StatsTopGenres": "শীর্ষ ঘরানাগুলো",
|
||||||
|
"StatsTopMonth": "সেরা মাস",
|
||||||
|
"StatsTopNarrator": "শীর্ষ কথক",
|
||||||
|
"StatsTopNarrators": "শীর্ষ কথকগণ",
|
||||||
|
"StatsTotalDuration": "মোট সময়কাল…",
|
||||||
|
"StatsYearInReview": "বাৎসরিক পর্যালোচনা",
|
||||||
"ToastAccountUpdateFailed": "অ্যাকাউন্ট আপডেট করতে ব্যর্থ",
|
"ToastAccountUpdateFailed": "অ্যাকাউন্ট আপডেট করতে ব্যর্থ",
|
||||||
"ToastAccountUpdateSuccess": "অ্যাকাউন্ট আপডেট করা হয়েছে",
|
"ToastAccountUpdateSuccess": "অ্যাকাউন্ট আপডেট করা হয়েছে",
|
||||||
|
"ToastAppriseUrlRequired": "একটি Apprise ইউআরএল লিখতে হবে",
|
||||||
"ToastAuthorImageRemoveSuccess": "লেখকের ছবি সরানো হয়েছে",
|
"ToastAuthorImageRemoveSuccess": "লেখকের ছবি সরানো হয়েছে",
|
||||||
|
"ToastAuthorNotFound": "লেখক \"{0}\" খুঁজে পাওয়া যায়নি",
|
||||||
|
"ToastAuthorRemoveSuccess": "লেখক সরানো হয়েছে",
|
||||||
|
"ToastAuthorSearchNotFound": "লেখক পাওয়া যায়নি",
|
||||||
"ToastAuthorUpdateFailed": "লেখক আপডেট করতে ব্যর্থ",
|
"ToastAuthorUpdateFailed": "লেখক আপডেট করতে ব্যর্থ",
|
||||||
"ToastAuthorUpdateMerged": "লেখক একত্রিত হয়েছে",
|
"ToastAuthorUpdateMerged": "লেখক একত্রিত হয়েছে",
|
||||||
"ToastAuthorUpdateSuccess": "লেখক আপডেট করেছেন",
|
"ToastAuthorUpdateSuccess": "লেখক আপডেট করেছেন",
|
||||||
"ToastAuthorUpdateSuccessNoImageFound": "লেখক আপডেট করেছেন (কোন ছবি পাওয়া যায়নি)",
|
"ToastAuthorUpdateSuccessNoImageFound": "লেখক আপডেট করেছেন (কোন ছবি পাওয়া যায়নি)",
|
||||||
|
"ToastBackupAppliedSuccess": "ব্যাকআপ প্রয়োগ করা হয়েছে",
|
||||||
"ToastBackupCreateFailed": "ব্যাকআপ তৈরি করতে ব্যর্থ",
|
"ToastBackupCreateFailed": "ব্যাকআপ তৈরি করতে ব্যর্থ",
|
||||||
"ToastBackupCreateSuccess": "ব্যাকআপ তৈরি করা হয়েছে",
|
"ToastBackupCreateSuccess": "ব্যাকআপ তৈরি করা হয়েছে",
|
||||||
"ToastBackupDeleteFailed": "ব্যাকআপ মুছে ফেলতে ব্যর্থ",
|
"ToastBackupDeleteFailed": "ব্যাকআপ মুছে ফেলতে ব্যর্থ",
|
||||||
"ToastBackupDeleteSuccess": "ব্যাকআপ মুছে ফেলা হয়েছে",
|
"ToastBackupDeleteSuccess": "ব্যাকআপ মুছে ফেলা হয়েছে",
|
||||||
|
"ToastBackupInvalidMaxKeep": "রাখার জন্য অকার্যকর ব্যাকআপের সংখ্যা",
|
||||||
|
"ToastBackupInvalidMaxSize": "অকার্যকর সর্বোচ্চ ব্যাকআপ আকার",
|
||||||
|
"ToastBackupPathUpdateFailed": "ব্যাকআপ পথ আপডেট করতে ব্যর্থ হয়েছে",
|
||||||
"ToastBackupRestoreFailed": "ব্যাকআপ পুনরুদ্ধার করতে ব্যর্থ",
|
"ToastBackupRestoreFailed": "ব্যাকআপ পুনরুদ্ধার করতে ব্যর্থ",
|
||||||
"ToastBackupUploadFailed": "ব্যাকআপ আপলোড করতে ব্যর্থ",
|
"ToastBackupUploadFailed": "ব্যাকআপ আপলোড করতে ব্যর্থ",
|
||||||
"ToastBackupUploadSuccess": "ব্যাকআপ আপলোড হয়েছে",
|
"ToastBackupUploadSuccess": "ব্যাকআপ আপলোড হয়েছে",
|
||||||
|
"ToastBatchDeleteFailed": "ব্যাচ মুছে ফেলতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastBatchDeleteSuccess": "ব্যাচ মুছে ফেলা সফল হয়েছে",
|
||||||
"ToastBatchUpdateFailed": "ব্যাচ আপডেট ব্যর্থ হয়েছে",
|
"ToastBatchUpdateFailed": "ব্যাচ আপডেট ব্যর্থ হয়েছে",
|
||||||
"ToastBatchUpdateSuccess": "ব্যাচ আপডেট সাফল্য",
|
"ToastBatchUpdateSuccess": "ব্যাচ আপডেট সাফল্য",
|
||||||
"ToastBookmarkCreateFailed": "বুকমার্ক তৈরি করতে ব্যর্থ",
|
"ToastBookmarkCreateFailed": "বুকমার্ক তৈরি করতে ব্যর্থ",
|
||||||
@@ -730,20 +847,50 @@
|
|||||||
"ToastBookmarkRemoveSuccess": "বুকমার্ক সরানো হয়েছে",
|
"ToastBookmarkRemoveSuccess": "বুকমার্ক সরানো হয়েছে",
|
||||||
"ToastBookmarkUpdateFailed": "বুকমার্ক আপডেট করতে ব্যর্থ",
|
"ToastBookmarkUpdateFailed": "বুকমার্ক আপডেট করতে ব্যর্থ",
|
||||||
"ToastBookmarkUpdateSuccess": "বুকমার্ক আপডেট করা হয়েছে",
|
"ToastBookmarkUpdateSuccess": "বুকমার্ক আপডেট করা হয়েছে",
|
||||||
|
"ToastCachePurgeFailed": "ক্যাশে পরিষ্কার করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastCachePurgeSuccess": "ক্যাশে সফলভাবে পরিষ্কার করা হয়েছে",
|
||||||
"ToastChaptersHaveErrors": "অধ্যায়ে ত্রুটি আছে",
|
"ToastChaptersHaveErrors": "অধ্যায়ে ত্রুটি আছে",
|
||||||
"ToastChaptersMustHaveTitles": "অধ্যায়ের শিরোনাম থাকতে হবে",
|
"ToastChaptersMustHaveTitles": "অধ্যায়ের শিরোনাম থাকতে হবে",
|
||||||
|
"ToastChaptersRemoved": "অধ্যায়গুলো মুছে ফেলা হয়েছে",
|
||||||
|
"ToastCollectionItemsAddFailed": "আইটেম(গুলি) সংগ্রহে যোগ করা ব্যর্থ হয়েছে",
|
||||||
|
"ToastCollectionItemsAddSuccess": "আইটেম(গুলি) সংগ্রহে যোগ করা সফল হয়েছে",
|
||||||
"ToastCollectionItemsRemoveSuccess": "আইটেম(গুলি) সংগ্রহ থেকে সরানো হয়েছে",
|
"ToastCollectionItemsRemoveSuccess": "আইটেম(গুলি) সংগ্রহ থেকে সরানো হয়েছে",
|
||||||
"ToastCollectionRemoveSuccess": "সংগ্রহ সরানো হয়েছে",
|
"ToastCollectionRemoveSuccess": "সংগ্রহ সরানো হয়েছে",
|
||||||
"ToastCollectionUpdateFailed": "সংগ্রহ আপডেট করতে ব্যর্থ",
|
"ToastCollectionUpdateFailed": "সংগ্রহ আপডেট করতে ব্যর্থ",
|
||||||
"ToastCollectionUpdateSuccess": "সংগ্রহ আপডেট করা হয়েছে",
|
"ToastCollectionUpdateSuccess": "সংগ্রহ আপডেট করা হয়েছে",
|
||||||
|
"ToastCoverUpdateFailed": "কভার আপডেট ব্যর্থ হয়েছে",
|
||||||
|
"ToastDeleteFileFailed": "ফাইল মুছে ফেলতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastDeleteFileSuccess": "ফাইল মুছে ফেলা হয়েছে",
|
||||||
|
"ToastDeviceAddFailed": "ডিভাইস যোগ করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastDeviceNameAlreadyExists": "এই নামের ইরিডার ডিভাইস ইতিমধ্যেই বিদ্যমান",
|
||||||
|
"ToastDeviceTestEmailFailed": "পরীক্ষামূলক ইমেল পাঠাতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastDeviceTestEmailSuccess": "পরীক্ষামূলক ইমেল পাঠানো হয়েছে",
|
||||||
|
"ToastDeviceUpdateFailed": "ডিভাইস আপডেট করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastEmailSettingsUpdateFailed": "ইমেল সেটিংস আপডেট করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastEmailSettingsUpdateSuccess": "ইমেল সেটিংস আপডেট করা হয়েছে",
|
||||||
|
"ToastEncodeCancelFailed": "এনকোড বাতিল করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastEncodeCancelSucces": "এনকোড বাতিল করা হয়েছে",
|
||||||
|
"ToastEpisodeDownloadQueueClearFailed": "সারি সাফ করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastEpisodeDownloadQueueClearSuccess": "পর্ব ডাউনলোড সারি পরিষ্কার করা হয়েছে",
|
||||||
|
"ToastErrorCannotShare": "এই ডিভাইসে স্থানীয়ভাবে শেয়ার করা যাবে না",
|
||||||
|
"ToastFailedToLoadData": "ডেটা লোড করা যায়নি",
|
||||||
|
"ToastFailedToShare": "শেয়ার করতে ব্যর্থ",
|
||||||
|
"ToastFailedToUpdateAccount": "অ্যাকাউন্ট আপডেট করতে ব্যর্থ",
|
||||||
|
"ToastFailedToUpdateUser": "ব্যবহারকারী আপডেট করতে ব্যর্থ",
|
||||||
|
"ToastInvalidImageUrl": "অকার্যকর ছবির ইউআরএল",
|
||||||
|
"ToastInvalidUrl": "অকার্যকর ইউআরএল",
|
||||||
"ToastItemCoverUpdateFailed": "আইটেম কভার আপডেট করতে ব্যর্থ হয়েছে",
|
"ToastItemCoverUpdateFailed": "আইটেম কভার আপডেট করতে ব্যর্থ হয়েছে",
|
||||||
"ToastItemCoverUpdateSuccess": "আইটেম কভার আপডেট করা হয়েছে",
|
"ToastItemCoverUpdateSuccess": "আইটেম কভার আপডেট করা হয়েছে",
|
||||||
|
"ToastItemDeletedFailed": "আইটেম মুছে ফেলতে ব্যর্থ",
|
||||||
|
"ToastItemDeletedSuccess": "মুছে ফেলা আইটেম",
|
||||||
"ToastItemDetailsUpdateFailed": "আইটেমের বিবরণ আপডেট করতে ব্যর্থ",
|
"ToastItemDetailsUpdateFailed": "আইটেমের বিবরণ আপডেট করতে ব্যর্থ",
|
||||||
"ToastItemDetailsUpdateSuccess": "আইটেমের বিবরণ আপডেট করা হয়েছে",
|
"ToastItemDetailsUpdateSuccess": "আইটেমের বিবরণ আপডেট করা হয়েছে",
|
||||||
"ToastItemMarkedAsFinishedFailed": "সমাপ্ত হিসাবে চিহ্নিত করতে ব্যর্থ",
|
"ToastItemMarkedAsFinishedFailed": "সমাপ্ত হিসাবে চিহ্নিত করতে ব্যর্থ",
|
||||||
"ToastItemMarkedAsFinishedSuccess": "আইটেম সমাপ্ত হিসাবে চিহ্নিত",
|
"ToastItemMarkedAsFinishedSuccess": "আইটেম সমাপ্ত হিসাবে চিহ্নিত",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "সমাপ্ত হয়নি হিসাবে চিহ্নিত করতে ব্যর্থ",
|
"ToastItemMarkedAsNotFinishedFailed": "সমাপ্ত হয়নি হিসাবে চিহ্নিত করতে ব্যর্থ",
|
||||||
"ToastItemMarkedAsNotFinishedSuccess": "আইটেম সমাপ্ত হয়নি বলে চিহ্নিত",
|
"ToastItemMarkedAsNotFinishedSuccess": "আইটেম সমাপ্ত হয়নি বলে চিহ্নিত",
|
||||||
|
"ToastItemUpdateFailed": "আইটেম আপডেট করতে ব্যর্থ",
|
||||||
|
"ToastItemUpdateSuccess": "আইটেম আপডেট করা হয়েছে",
|
||||||
"ToastLibraryCreateFailed": "লাইব্রেরি তৈরি করতে ব্যর্থ",
|
"ToastLibraryCreateFailed": "লাইব্রেরি তৈরি করতে ব্যর্থ",
|
||||||
"ToastLibraryCreateSuccess": "লাইব্রেরি \"{0}\" তৈরি করা হয়েছে",
|
"ToastLibraryCreateSuccess": "লাইব্রেরি \"{0}\" তৈরি করা হয়েছে",
|
||||||
"ToastLibraryDeleteFailed": "লাইব্রেরি মুছে ফেলতে ব্যর্থ",
|
"ToastLibraryDeleteFailed": "লাইব্রেরি মুছে ফেলতে ব্যর্থ",
|
||||||
@@ -752,6 +899,25 @@
|
|||||||
"ToastLibraryScanStarted": "লাইব্রেরি স্ক্যান শুরু হয়েছে",
|
"ToastLibraryScanStarted": "লাইব্রেরি স্ক্যান শুরু হয়েছে",
|
||||||
"ToastLibraryUpdateFailed": "লাইব্রেরি আপডেট করতে ব্যর্থ",
|
"ToastLibraryUpdateFailed": "লাইব্রেরি আপডেট করতে ব্যর্থ",
|
||||||
"ToastLibraryUpdateSuccess": "লাইব্রেরি \"{0}\" আপডেট করা হয়েছে",
|
"ToastLibraryUpdateSuccess": "লাইব্রেরি \"{0}\" আপডেট করা হয়েছে",
|
||||||
|
"ToastNameEmailRequired": "নাম এবং ইমেইল আবশ্যক",
|
||||||
|
"ToastNameRequired": "নাম আবশ্যক",
|
||||||
|
"ToastNewUserCreatedFailed": "অ্যাকাউন্ট তৈরি করতে ব্যর্থ: \"{0}\"",
|
||||||
|
"ToastNewUserCreatedSuccess": "নতুন একাউন্ট তৈরি হয়েছে",
|
||||||
|
"ToastNewUserLibraryError": "অন্তত একটি লাইব্রেরি নির্বাচন করতে হবে",
|
||||||
|
"ToastNewUserPasswordError": "অন্তত একটি পাসওয়ার্ড থাকতে হবে, শুধুমাত্র রুট ব্যবহারকারীর একটি খালি পাসওয়ার্ড থাকতে পারে",
|
||||||
|
"ToastNewUserTagError": "অন্তত একটি ট্যাগ নির্বাচন করতে হবে",
|
||||||
|
"ToastNewUserUsernameError": "একটি ব্যবহারকারীর নাম লিখুন",
|
||||||
|
"ToastNoUpdatesNecessary": "কোন আপডেটের প্রয়োজন নেই",
|
||||||
|
"ToastNotificationCreateFailed": "বিজ্ঞপ্তি তৈরি করতে ব্যর্থ",
|
||||||
|
"ToastNotificationDeleteFailed": "বিজ্ঞপ্তি মুছে ফেলতে ব্যর্থ",
|
||||||
|
"ToastNotificationFailedMaximum": "সর্বাধিক ব্যর্থ প্রচেষ্টা >= 0 হতে হবে",
|
||||||
|
"ToastNotificationQueueMaximum": "সর্বাধিক বিজ্ঞপ্তি সারি >= 0 হতে হবে",
|
||||||
|
"ToastNotificationSettingsUpdateFailed": "বিজ্ঞপ্তি সেটিংস আপডেট করতে ব্যর্থ",
|
||||||
|
"ToastNotificationSettingsUpdateSuccess": "বিজ্ঞপ্তি সেটিংস আপডেট করা হয়েছে",
|
||||||
|
"ToastNotificationTestTriggerFailed": "পরীক্ষামূলক বিজ্ঞপ্তি ট্রিগার করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastNotificationTestTriggerSuccess": "পরীক্ষামুলক বিজ্ঞপ্তি ট্রিগার হয়েছে",
|
||||||
|
"ToastNotificationUpdateFailed": "বিজ্ঞপ্তি আপডেট করতে ব্যর্থ",
|
||||||
|
"ToastNotificationUpdateSuccess": "বিজ্ঞপ্তি আপডেট হয়েছে",
|
||||||
"ToastPlaylistCreateFailed": "প্লেলিস্ট তৈরি করতে ব্যর্থ",
|
"ToastPlaylistCreateFailed": "প্লেলিস্ট তৈরি করতে ব্যর্থ",
|
||||||
"ToastPlaylistCreateSuccess": "প্লেলিস্ট তৈরি করা হয়েছে",
|
"ToastPlaylistCreateSuccess": "প্লেলিস্ট তৈরি করা হয়েছে",
|
||||||
"ToastPlaylistRemoveSuccess": "প্লেলিস্ট সরানো হয়েছে",
|
"ToastPlaylistRemoveSuccess": "প্লেলিস্ট সরানো হয়েছে",
|
||||||
@@ -759,19 +925,52 @@
|
|||||||
"ToastPlaylistUpdateSuccess": "প্লেলিস্ট আপডেট করা হয়েছে",
|
"ToastPlaylistUpdateSuccess": "প্লেলিস্ট আপডেট করা হয়েছে",
|
||||||
"ToastPodcastCreateFailed": "পডকাস্ট তৈরি করতে ব্যর্থ",
|
"ToastPodcastCreateFailed": "পডকাস্ট তৈরি করতে ব্যর্থ",
|
||||||
"ToastPodcastCreateSuccess": "পডকাস্ট সফলভাবে তৈরি করা হয়েছে",
|
"ToastPodcastCreateSuccess": "পডকাস্ট সফলভাবে তৈরি করা হয়েছে",
|
||||||
|
"ToastPodcastGetFeedFailed": "পডকাস্ট ফিড পেতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastPodcastNoEpisodesInFeed": "আরএসএস ফিডে কোনো পর্ব পাওয়া যায়নি",
|
||||||
|
"ToastPodcastNoRssFeed": "পডকাস্টের কোন আরএসএস ফিড নেই",
|
||||||
|
"ToastProviderCreatedFailed": "প্রদানকারী যোগ করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastProviderCreatedSuccess": "নতুন প্রদানকারী যোগ করা হয়েছে",
|
||||||
|
"ToastProviderNameAndUrlRequired": "নাম এবং ইউআরএল আবশ্যক",
|
||||||
|
"ToastProviderRemoveSuccess": "প্রদানকারী সরানো হয়েছে",
|
||||||
"ToastRSSFeedCloseFailed": "RSS ফিড বন্ধ করতে ব্যর্থ",
|
"ToastRSSFeedCloseFailed": "RSS ফিড বন্ধ করতে ব্যর্থ",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS ফিড বন্ধ",
|
"ToastRSSFeedCloseSuccess": "RSS ফিড বন্ধ",
|
||||||
|
"ToastRemoveFailed": "মুছে ফেলতে ব্যর্থ হয়েছে",
|
||||||
"ToastRemoveItemFromCollectionFailed": "সংগ্রহ থেকে আইটেম সরাতে ব্যর্থ",
|
"ToastRemoveItemFromCollectionFailed": "সংগ্রহ থেকে আইটেম সরাতে ব্যর্থ",
|
||||||
"ToastRemoveItemFromCollectionSuccess": "সংগ্রহ থেকে আইটেম সরানো হয়েছে",
|
"ToastRemoveItemFromCollectionSuccess": "সংগ্রহ থেকে আইটেম সরানো হয়েছে",
|
||||||
|
"ToastRemoveItemsWithIssuesFailed": "সমস্যাযুক্ত লাইব্রেরি আইটেমগুলি সরাতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastRemoveItemsWithIssuesSuccess": "সমস্যাযুক্ত লাইব্রেরি আইটেম সরানো হয়েছে",
|
||||||
|
"ToastRenameFailed": "পুনঃনামকরণ ব্যর্থ হয়েছে",
|
||||||
|
"ToastRescanFailed": "{0} এর জন্য পুনরায় স্ক্যান করা ব্যর্থ হয়েছে",
|
||||||
|
"ToastRescanRemoved": "পুনরায় স্ক্যান সম্পূর্ণ,আইটেম সরানো হয়েছে",
|
||||||
|
"ToastRescanUpToDate": "পুনরায় স্ক্যান সম্পূর্ণ, আইটেম সাম্প্রতিক ছিল",
|
||||||
|
"ToastRescanUpdated": "পুনরায় স্ক্যান সম্পূর্ণ, আইটেম আপডেট করা হয়েছে",
|
||||||
|
"ToastScanFailed": "লাইব্রেরি আইটেম স্ক্যান করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastSelectAtLeastOneUser": "অন্তত একজন ব্যবহারকারী নির্বাচন করুন",
|
||||||
"ToastSendEbookToDeviceFailed": "ডিভাইসে ইবুক পাঠাতে ব্যর্থ",
|
"ToastSendEbookToDeviceFailed": "ডিভাইসে ইবুক পাঠাতে ব্যর্থ",
|
||||||
"ToastSendEbookToDeviceSuccess": "ইবুক \"{0}\" ডিভাইসে পাঠানো হয়েছে",
|
"ToastSendEbookToDeviceSuccess": "ইবুক \"{0}\" ডিভাইসে পাঠানো হয়েছে",
|
||||||
"ToastSeriesUpdateFailed": "সিরিজ আপডেট ব্যর্থ হয়েছে",
|
"ToastSeriesUpdateFailed": "সিরিজ আপডেট ব্যর্থ হয়েছে",
|
||||||
"ToastSeriesUpdateSuccess": "সিরিজ আপডেট সাফল্য",
|
"ToastSeriesUpdateSuccess": "সিরিজ আপডেট সাফল্য",
|
||||||
|
"ToastServerSettingsUpdateFailed": "সার্ভার সেটিংস আপডেট করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastServerSettingsUpdateSuccess": "সার্ভার সেটিংস আপডেট করা হয়েছে",
|
||||||
|
"ToastSessionCloseFailed": "অধিবেশন বন্ধ করতে ব্যর্থ হয়েছে",
|
||||||
"ToastSessionDeleteFailed": "সেশন মুছে ফেলতে ব্যর্থ",
|
"ToastSessionDeleteFailed": "সেশন মুছে ফেলতে ব্যর্থ",
|
||||||
"ToastSessionDeleteSuccess": "সেশন মুছে ফেলা হয়েছে",
|
"ToastSessionDeleteSuccess": "সেশন মুছে ফেলা হয়েছে",
|
||||||
|
"ToastSlugMustChange": "স্লাগে অবৈধ অক্ষর রয়েছে",
|
||||||
|
"ToastSlugRequired": "স্লাগ আবশ্যক",
|
||||||
"ToastSocketConnected": "সকেট সংযুক্ত",
|
"ToastSocketConnected": "সকেট সংযুক্ত",
|
||||||
"ToastSocketDisconnected": "সকেট সংযোগ বিচ্ছিন্ন",
|
"ToastSocketDisconnected": "সকেট সংযোগ বিচ্ছিন্ন",
|
||||||
"ToastSocketFailedToConnect": "সকেট সংযোগ করতে ব্যর্থ হয়েছে",
|
"ToastSocketFailedToConnect": "সকেট সংযোগ করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastSortingPrefixesEmptyError": "কমপক্ষে ১ টি সাজানোর উপসর্গ থাকতে হবে",
|
||||||
|
"ToastSortingPrefixesUpdateFailed": "বাছাই উপসর্গ আপডেট করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastSortingPrefixesUpdateSuccess": "বাছাই করা উপসর্গ আপডেট করা হয়েছে ({0}টি আইটেম)",
|
||||||
|
"ToastTitleRequired": "শিরোনাম আবশ্যক",
|
||||||
|
"ToastUnknownError": "অজানা ত্রুটি",
|
||||||
|
"ToastUnlinkOpenIdFailed": "OpenID থেকে ব্যবহারকারীকে আনলিঙ্ক করতে ব্যর্থ হয়েছে",
|
||||||
|
"ToastUnlinkOpenIdSuccess": "OpenID থেকে ব্যবহারকারীকে লিঙ্কমুক্ত করা হয়েছে",
|
||||||
"ToastUserDeleteFailed": "ব্যবহারকারী মুছতে ব্যর্থ",
|
"ToastUserDeleteFailed": "ব্যবহারকারী মুছতে ব্যর্থ",
|
||||||
"ToastUserDeleteSuccess": "ব্যবহারকারী মুছে ফেলা হয়েছে"
|
"ToastUserDeleteSuccess": "ব্যবহারকারী মুছে ফেলা হয়েছে",
|
||||||
|
"ToastUserPasswordChangeSuccess": "পাসওয়ার্ড সফলভাবে পরিবর্তন করা হয়েছে",
|
||||||
|
"ToastUserPasswordMismatch": "পাসওয়ার্ড মিলছে না",
|
||||||
|
"ToastUserPasswordMustChange": "নতুন পাসওয়ার্ড পুরানো পাসওয়ার্ডের সাথে মিলতে পারবে না",
|
||||||
|
"ToastUserRootRequireName": "একটি রুট ব্যবহারকারীর নাম লিখতে হবে"
|
||||||
}
|
}
|
||||||
|
|||||||
+90
-6
@@ -51,8 +51,10 @@
|
|||||||
"ButtonNext": "Vor",
|
"ButtonNext": "Vor",
|
||||||
"ButtonNextChapter": "Nächstes Kapitel",
|
"ButtonNextChapter": "Nächstes Kapitel",
|
||||||
"ButtonNextItemInQueue": "Das nächste Element in der Warteschlange",
|
"ButtonNextItemInQueue": "Das nächste Element in der Warteschlange",
|
||||||
|
"ButtonOk": "Ok",
|
||||||
"ButtonOpenFeed": "Feed öffnen",
|
"ButtonOpenFeed": "Feed öffnen",
|
||||||
"ButtonOpenManager": "Manager öffnen",
|
"ButtonOpenManager": "Manager öffnen",
|
||||||
|
"ButtonPause": "Pausieren",
|
||||||
"ButtonPlay": "Abspielen",
|
"ButtonPlay": "Abspielen",
|
||||||
"ButtonPlaying": "Spielt",
|
"ButtonPlaying": "Spielt",
|
||||||
"ButtonPlaylists": "Wiedergabelisten",
|
"ButtonPlaylists": "Wiedergabelisten",
|
||||||
@@ -95,7 +97,8 @@
|
|||||||
"ButtonStartMetadataEmbed": "Metadateneinbettung starten",
|
"ButtonStartMetadataEmbed": "Metadateneinbettung starten",
|
||||||
"ButtonStats": "Statistiken",
|
"ButtonStats": "Statistiken",
|
||||||
"ButtonSubmit": "Ok",
|
"ButtonSubmit": "Ok",
|
||||||
"ButtonUnlinkOpedId": "OpenID trennen",
|
"ButtonTest": "Test",
|
||||||
|
"ButtonUnlinkOpenId": "OpenID trennen",
|
||||||
"ButtonUpload": "Hochladen",
|
"ButtonUpload": "Hochladen",
|
||||||
"ButtonUploadBackup": "Sicherung hochladen",
|
"ButtonUploadBackup": "Sicherung hochladen",
|
||||||
"ButtonUploadCover": "Titelbild hochladen",
|
"ButtonUploadCover": "Titelbild hochladen",
|
||||||
@@ -124,8 +127,10 @@
|
|||||||
"HeaderCurrentDownloads": "Aktuelle Downloads",
|
"HeaderCurrentDownloads": "Aktuelle Downloads",
|
||||||
"HeaderCustomMessageOnLogin": "Benutzerdefinierte Nachricht für den Login",
|
"HeaderCustomMessageOnLogin": "Benutzerdefinierte Nachricht für den Login",
|
||||||
"HeaderCustomMetadataProviders": "Benutzerdefinierte Metadata Anbieter",
|
"HeaderCustomMetadataProviders": "Benutzerdefinierte Metadata Anbieter",
|
||||||
|
"HeaderDetails": "Details",
|
||||||
"HeaderDownloadQueue": "Download Warteschlange",
|
"HeaderDownloadQueue": "Download Warteschlange",
|
||||||
"HeaderEbookFiles": "E-Buch-Dateien",
|
"HeaderEbookFiles": "E-Buch-Dateien",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Einstellungen",
|
"HeaderEmailSettings": "Email Einstellungen",
|
||||||
"HeaderEpisodes": "Episoden",
|
"HeaderEpisodes": "Episoden",
|
||||||
"HeaderEreaderDevices": "E-Reader Geräte",
|
"HeaderEreaderDevices": "E-Reader Geräte",
|
||||||
@@ -166,6 +171,7 @@
|
|||||||
"HeaderPlaylistItems": "Einträge in der Wiedergabeliste",
|
"HeaderPlaylistItems": "Einträge in der Wiedergabeliste",
|
||||||
"HeaderPodcastsToAdd": "Podcasts zum Hinzufügen",
|
"HeaderPodcastsToAdd": "Podcasts zum Hinzufügen",
|
||||||
"HeaderPreviewCover": "Vorschau Titelbild",
|
"HeaderPreviewCover": "Vorschau Titelbild",
|
||||||
|
"HeaderRSSFeedGeneral": "RSS Details",
|
||||||
"HeaderRSSFeedIsOpen": "RSS-Feed ist geöffnet",
|
"HeaderRSSFeedIsOpen": "RSS-Feed ist geöffnet",
|
||||||
"HeaderRSSFeeds": "RSS-Feeds",
|
"HeaderRSSFeeds": "RSS-Feeds",
|
||||||
"HeaderRemoveEpisode": "Episode entfernen",
|
"HeaderRemoveEpisode": "Episode entfernen",
|
||||||
@@ -179,6 +185,7 @@
|
|||||||
"HeaderSettingsDisplay": "Anzeige",
|
"HeaderSettingsDisplay": "Anzeige",
|
||||||
"HeaderSettingsExperimental": "Experimentelle Funktionen",
|
"HeaderSettingsExperimental": "Experimentelle Funktionen",
|
||||||
"HeaderSettingsGeneral": "Allgemein",
|
"HeaderSettingsGeneral": "Allgemein",
|
||||||
|
"HeaderSettingsScanner": "Scanner",
|
||||||
"HeaderSleepTimer": "Sleep-Timer",
|
"HeaderSleepTimer": "Sleep-Timer",
|
||||||
"HeaderStatsLargestItems": "Größte Medien",
|
"HeaderStatsLargestItems": "Größte Medien",
|
||||||
"HeaderStatsLongestItems": "Längste Medien (h)",
|
"HeaderStatsLongestItems": "Längste Medien (h)",
|
||||||
@@ -200,6 +207,7 @@
|
|||||||
"LabelAbridgedUnchecked": "Ungekürzt (nicht angehakt)",
|
"LabelAbridgedUnchecked": "Ungekürzt (nicht angehakt)",
|
||||||
"LabelAccessibleBy": "Zugänglich für",
|
"LabelAccessibleBy": "Zugänglich für",
|
||||||
"LabelAccountType": "Kontoart",
|
"LabelAccountType": "Kontoart",
|
||||||
|
"LabelAccountTypeAdmin": "Admin",
|
||||||
"LabelAccountTypeGuest": "Gast",
|
"LabelAccountTypeGuest": "Gast",
|
||||||
"LabelAccountTypeUser": "Benutzer",
|
"LabelAccountTypeUser": "Benutzer",
|
||||||
"LabelActivity": "Aktivitäten",
|
"LabelActivity": "Aktivitäten",
|
||||||
@@ -235,7 +243,9 @@
|
|||||||
"LabelBackupsMaxBackupSizeHelp": "Zum Schutz vor Fehlkonfigurationen schlagen Sicherungen fehl, wenn sie die konfigurierte Größe überschreiten.",
|
"LabelBackupsMaxBackupSizeHelp": "Zum Schutz vor Fehlkonfigurationen schlagen Sicherungen fehl, wenn sie die konfigurierte Größe überschreiten.",
|
||||||
"LabelBackupsNumberToKeep": "Anzahl der aufzubewahrenden Sicherungen",
|
"LabelBackupsNumberToKeep": "Anzahl der aufzubewahrenden Sicherungen",
|
||||||
"LabelBackupsNumberToKeepHelp": "Es wird immer nur 1 Sicherung auf einmal entfernt. Wenn du bereits mehrere Sicherungen als die definierte max. Anzahl hast, solltest du diese manuell entfernen.",
|
"LabelBackupsNumberToKeepHelp": "Es wird immer nur 1 Sicherung auf einmal entfernt. Wenn du bereits mehrere Sicherungen als die definierte max. Anzahl hast, solltest du diese manuell entfernen.",
|
||||||
|
"LabelBitrate": "Bitrate",
|
||||||
"LabelBooks": "Bücher",
|
"LabelBooks": "Bücher",
|
||||||
|
"LabelButtonText": "Button Text",
|
||||||
"LabelByAuthor": "von {0}",
|
"LabelByAuthor": "von {0}",
|
||||||
"LabelChangePassword": "Passwort ändern",
|
"LabelChangePassword": "Passwort ändern",
|
||||||
"LabelChannels": "Kanäle",
|
"LabelChannels": "Kanäle",
|
||||||
@@ -244,6 +254,7 @@
|
|||||||
"LabelChaptersFound": "Gefundene Kapitel",
|
"LabelChaptersFound": "Gefundene Kapitel",
|
||||||
"LabelClickForMoreInfo": "Klicken für mehr Informationen",
|
"LabelClickForMoreInfo": "Klicken für mehr Informationen",
|
||||||
"LabelClosePlayer": "Player schließen",
|
"LabelClosePlayer": "Player schließen",
|
||||||
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Serien einklappen",
|
"LabelCollapseSeries": "Serien einklappen",
|
||||||
"LabelCollapseSubSeries": "Unterserien einklappen",
|
"LabelCollapseSubSeries": "Unterserien einklappen",
|
||||||
"LabelCollection": "Sammlung",
|
"LabelCollection": "Sammlung",
|
||||||
@@ -282,6 +293,7 @@
|
|||||||
"LabelEbook": "E-Buch",
|
"LabelEbook": "E-Buch",
|
||||||
"LabelEbooks": "E-Bücher",
|
"LabelEbooks": "E-Bücher",
|
||||||
"LabelEdit": "Bearbeiten",
|
"LabelEdit": "Bearbeiten",
|
||||||
|
"LabelEmail": "Email",
|
||||||
"LabelEmailSettingsFromAddress": "Von Adresse",
|
"LabelEmailSettingsFromAddress": "Von Adresse",
|
||||||
"LabelEmailSettingsRejectUnauthorized": "Nicht autorisierte Zertifikate ablehnen",
|
"LabelEmailSettingsRejectUnauthorized": "Nicht autorisierte Zertifikate ablehnen",
|
||||||
"LabelEmailSettingsRejectUnauthorizedHelp": "Durch das Deaktivieren der SSL-Zertifikatsüberprüfung kann deine Verbindung Sicherheitsrisiken wie Man-in-the-Middle-Angriffen ausgesetzt sein. Deaktiviere diese Option nur, wenn due die Auswirkungen verstehst und dem Mailserver vertraust, mit dem eine Verbindung hergestellt wird.",
|
"LabelEmailSettingsRejectUnauthorizedHelp": "Durch das Deaktivieren der SSL-Zertifikatsüberprüfung kann deine Verbindung Sicherheitsrisiken wie Man-in-the-Middle-Angriffen ausgesetzt sein. Deaktiviere diese Option nur, wenn due die Auswirkungen verstehst und dem Mailserver vertraust, mit dem eine Verbindung hergestellt wird.",
|
||||||
@@ -292,6 +304,7 @@
|
|||||||
"LabelEnable": "Aktivieren",
|
"LabelEnable": "Aktivieren",
|
||||||
"LabelEnd": "Ende",
|
"LabelEnd": "Ende",
|
||||||
"LabelEndOfChapter": "Ende des Kapitels",
|
"LabelEndOfChapter": "Ende des Kapitels",
|
||||||
|
"LabelEpisode": "Episode",
|
||||||
"LabelEpisodeTitle": "Episodentitel",
|
"LabelEpisodeTitle": "Episodentitel",
|
||||||
"LabelEpisodeType": "Episodentyp",
|
"LabelEpisodeType": "Episodentyp",
|
||||||
"LabelEpisodes": "Episoden",
|
"LabelEpisodes": "Episoden",
|
||||||
@@ -302,6 +315,7 @@
|
|||||||
"LabelExplicitChecked": "Explicit (Altersbeschränkung) (angehakt)",
|
"LabelExplicitChecked": "Explicit (Altersbeschränkung) (angehakt)",
|
||||||
"LabelExplicitUnchecked": "Not Explicit (Altersbeschränkung) (nicht angehakt)",
|
"LabelExplicitUnchecked": "Not Explicit (Altersbeschränkung) (nicht angehakt)",
|
||||||
"LabelExportOPML": "OPML exportieren",
|
"LabelExportOPML": "OPML exportieren",
|
||||||
|
"LabelFeedURL": "Feed URL",
|
||||||
"LabelFetchingMetadata": "Abholen der Metadaten",
|
"LabelFetchingMetadata": "Abholen der Metadaten",
|
||||||
"LabelFile": "Datei",
|
"LabelFile": "Datei",
|
||||||
"LabelFileBirthtime": "Datei erstellt",
|
"LabelFileBirthtime": "Datei erstellt",
|
||||||
@@ -320,6 +334,7 @@
|
|||||||
"LabelFontItalic": "Kursiv",
|
"LabelFontItalic": "Kursiv",
|
||||||
"LabelFontScale": "Schriftgröße",
|
"LabelFontScale": "Schriftgröße",
|
||||||
"LabelFontStrikethrough": "Durchgestrichen",
|
"LabelFontStrikethrough": "Durchgestrichen",
|
||||||
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Kategorie",
|
"LabelGenre": "Kategorie",
|
||||||
"LabelGenres": "Kategorien",
|
"LabelGenres": "Kategorien",
|
||||||
"LabelHardDeleteFile": "Datei dauerhaft löschen",
|
"LabelHardDeleteFile": "Datei dauerhaft löschen",
|
||||||
@@ -327,6 +342,7 @@
|
|||||||
"LabelHasSupplementaryEbook": "Ergänzendes E-Book verfügbar",
|
"LabelHasSupplementaryEbook": "Ergänzendes E-Book verfügbar",
|
||||||
"LabelHideSubtitles": "Untertitel ausblenden",
|
"LabelHideSubtitles": "Untertitel ausblenden",
|
||||||
"LabelHighestPriority": "Höchste Priorität",
|
"LabelHighestPriority": "Höchste Priorität",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Stunde",
|
"LabelHour": "Stunde",
|
||||||
"LabelHours": "Stunden",
|
"LabelHours": "Stunden",
|
||||||
"LabelIcon": "Symbol",
|
"LabelIcon": "Symbol",
|
||||||
@@ -355,6 +371,7 @@
|
|||||||
"LabelLastSeen": "Zuletzt gesehen",
|
"LabelLastSeen": "Zuletzt gesehen",
|
||||||
"LabelLastTime": "Letztes Mal",
|
"LabelLastTime": "Letztes Mal",
|
||||||
"LabelLastUpdate": "Letzte Aktualisierung",
|
"LabelLastUpdate": "Letzte Aktualisierung",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
"LabelLayoutSinglePage": "Eine Seite",
|
"LabelLayoutSinglePage": "Eine Seite",
|
||||||
"LabelLayoutSplitPage": "Geteilte Seite",
|
"LabelLayoutSplitPage": "Geteilte Seite",
|
||||||
"LabelLess": "Weniger",
|
"LabelLess": "Weniger",
|
||||||
@@ -376,8 +393,10 @@
|
|||||||
"LabelMediaPlayer": "Mediaplayer",
|
"LabelMediaPlayer": "Mediaplayer",
|
||||||
"LabelMediaType": "Medientyp",
|
"LabelMediaType": "Medientyp",
|
||||||
"LabelMetaTag": "Meta Schlagwort",
|
"LabelMetaTag": "Meta Schlagwort",
|
||||||
|
"LabelMetaTags": "Meta Tags",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "Höher priorisierte Quellen für Metadaten überschreiben Metadaten aus Quellen mit niedrigerer Priorität",
|
"LabelMetadataOrderOfPrecedenceDescription": "Höher priorisierte Quellen für Metadaten überschreiben Metadaten aus Quellen mit niedrigerer Priorität",
|
||||||
"LabelMetadataProvider": "Metadatenanbieter",
|
"LabelMetadataProvider": "Metadatenanbieter",
|
||||||
|
"LabelMinute": "Minute",
|
||||||
"LabelMinutes": "Minuten",
|
"LabelMinutes": "Minuten",
|
||||||
"LabelMissing": "Fehlend",
|
"LabelMissing": "Fehlend",
|
||||||
"LabelMissingEbook": "E-Book fehlt",
|
"LabelMissingEbook": "E-Book fehlt",
|
||||||
@@ -386,6 +405,7 @@
|
|||||||
"LabelMobileRedirectURIsDescription": "Dies ist eine Whitelist gültiger Umleitungs-URIs für mobile Apps. Der Standardwert ist <code>audiobookshelf://oauth</code>, den du entfernen oder durch zusätzliche URIs für die Integration von Drittanbieter-Apps ergänzen kannst. Die Verwendung eines Sternchens (<code>*</code>) als alleiniger Eintrag erlaubt jede URI.",
|
"LabelMobileRedirectURIsDescription": "Dies ist eine Whitelist gültiger Umleitungs-URIs für mobile Apps. Der Standardwert ist <code>audiobookshelf://oauth</code>, den du entfernen oder durch zusätzliche URIs für die Integration von Drittanbieter-Apps ergänzen kannst. Die Verwendung eines Sternchens (<code>*</code>) als alleiniger Eintrag erlaubt jede URI.",
|
||||||
"LabelMore": "Mehr",
|
"LabelMore": "Mehr",
|
||||||
"LabelMoreInfo": "Mehr Infos",
|
"LabelMoreInfo": "Mehr Infos",
|
||||||
|
"LabelName": "Name",
|
||||||
"LabelNarrator": "Erzähler",
|
"LabelNarrator": "Erzähler",
|
||||||
"LabelNarrators": "Erzähler",
|
"LabelNarrators": "Erzähler",
|
||||||
"LabelNew": "Neu",
|
"LabelNew": "Neu",
|
||||||
@@ -399,6 +419,7 @@
|
|||||||
"LabelNotFinished": "Nicht beendet",
|
"LabelNotFinished": "Nicht beendet",
|
||||||
"LabelNotStarted": "Nicht begonnen",
|
"LabelNotStarted": "Nicht begonnen",
|
||||||
"LabelNotes": "Notizen",
|
"LabelNotes": "Notizen",
|
||||||
|
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
||||||
"LabelNotificationAvailableVariables": "Verfügbare Variablen",
|
"LabelNotificationAvailableVariables": "Verfügbare Variablen",
|
||||||
"LabelNotificationBodyTemplate": "Textvorlage",
|
"LabelNotificationBodyTemplate": "Textvorlage",
|
||||||
"LabelNotificationEvent": "Benachrichtigungs Event",
|
"LabelNotificationEvent": "Benachrichtigungs Event",
|
||||||
@@ -429,8 +450,11 @@
|
|||||||
"LabelPlayMethod": "Abspielmethode",
|
"LabelPlayMethod": "Abspielmethode",
|
||||||
"LabelPlayerChapterNumberMarker": "{0} von {1}",
|
"LabelPlayerChapterNumberMarker": "{0} von {1}",
|
||||||
"LabelPlaylists": "Wiedergabelisten",
|
"LabelPlaylists": "Wiedergabelisten",
|
||||||
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcastSearchRegion": "Podcast-Suchregion",
|
"LabelPodcastSearchRegion": "Podcast-Suchregion",
|
||||||
"LabelPodcastType": "Podcast Typ",
|
"LabelPodcastType": "Podcast Typ",
|
||||||
|
"LabelPodcasts": "Podcasts",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Zu ignorierende(s) Vorwort(e) (Groß- und Kleinschreibung wird nicht berücksichtigt)",
|
"LabelPrefixesToIgnore": "Zu ignorierende(s) Vorwort(e) (Groß- und Kleinschreibung wird nicht berücksichtigt)",
|
||||||
"LabelPreventIndexing": "Verhindere, dass dein Feed von iTunes- und Google-Podcast-Verzeichnissen indiziert wird",
|
"LabelPreventIndexing": "Verhindere, dass dein Feed von iTunes- und Google-Podcast-Verzeichnissen indiziert wird",
|
||||||
"LabelPrimaryEbook": "Primäres E-Book",
|
"LabelPrimaryEbook": "Primäres E-Book",
|
||||||
@@ -447,6 +471,7 @@
|
|||||||
"LabelRSSFeedOpen": "RSS Feed Offen",
|
"LabelRSSFeedOpen": "RSS Feed Offen",
|
||||||
"LabelRSSFeedPreventIndexing": "Indizierung verhindern",
|
"LabelRSSFeedPreventIndexing": "Indizierung verhindern",
|
||||||
"LabelRSSFeedSlug": "RSS-Feed-Schlagwort",
|
"LabelRSSFeedSlug": "RSS-Feed-Schlagwort",
|
||||||
|
"LabelRSSFeedURL": "RSS Feed URL",
|
||||||
"LabelRandomly": "Zufällig",
|
"LabelRandomly": "Zufällig",
|
||||||
"LabelReAddSeriesToContinueListening": "Serien erneut zur Fortsetzungsliste hinzufügen",
|
"LabelReAddSeriesToContinueListening": "Serien erneut zur Fortsetzungsliste hinzufügen",
|
||||||
"LabelRead": "Lesen",
|
"LabelRead": "Lesen",
|
||||||
@@ -456,6 +481,7 @@
|
|||||||
"LabelRecentlyAdded": "Kürzlich hinzugefügt",
|
"LabelRecentlyAdded": "Kürzlich hinzugefügt",
|
||||||
"LabelRecommended": "Empfohlen",
|
"LabelRecommended": "Empfohlen",
|
||||||
"LabelRedo": "Wiederholen",
|
"LabelRedo": "Wiederholen",
|
||||||
|
"LabelRegion": "Region",
|
||||||
"LabelReleaseDate": "Veröffentlichungsdatum",
|
"LabelReleaseDate": "Veröffentlichungsdatum",
|
||||||
"LabelRemoveCover": "Entferne Titelbild",
|
"LabelRemoveCover": "Entferne Titelbild",
|
||||||
"LabelRowsPerPage": "Zeilen pro Seite",
|
"LabelRowsPerPage": "Zeilen pro Seite",
|
||||||
@@ -522,6 +548,7 @@
|
|||||||
"LabelSize": "Größe",
|
"LabelSize": "Größe",
|
||||||
"LabelSleepTimer": "Schlummerfunktion",
|
"LabelSleepTimer": "Schlummerfunktion",
|
||||||
"LabelSlug": "URL Teil",
|
"LabelSlug": "URL Teil",
|
||||||
|
"LabelStart": "Start",
|
||||||
"LabelStartTime": "Startzeit",
|
"LabelStartTime": "Startzeit",
|
||||||
"LabelStarted": "Gestartet",
|
"LabelStarted": "Gestartet",
|
||||||
"LabelStartedAt": "Gestartet am",
|
"LabelStartedAt": "Gestartet am",
|
||||||
@@ -548,6 +575,7 @@
|
|||||||
"LabelTagsNotAccessibleToUser": "Für Benutzer nicht zugängliche Schlagwörter",
|
"LabelTagsNotAccessibleToUser": "Für Benutzer nicht zugängliche Schlagwörter",
|
||||||
"LabelTasks": "Laufende Aufgaben",
|
"LabelTasks": "Laufende Aufgaben",
|
||||||
"LabelTextEditorBulletedList": "Aufzählungsliste",
|
"LabelTextEditorBulletedList": "Aufzählungsliste",
|
||||||
|
"LabelTextEditorLink": "Link",
|
||||||
"LabelTextEditorNumberedList": "nummerierte Liste",
|
"LabelTextEditorNumberedList": "nummerierte Liste",
|
||||||
"LabelTextEditorUnlink": "entkoppeln",
|
"LabelTextEditorUnlink": "entkoppeln",
|
||||||
"LabelTheme": "Farbschema",
|
"LabelTheme": "Farbschema",
|
||||||
@@ -595,6 +623,7 @@
|
|||||||
"LabelUser": "Benutzer",
|
"LabelUser": "Benutzer",
|
||||||
"LabelUsername": "Benutzername",
|
"LabelUsername": "Benutzername",
|
||||||
"LabelValue": "Wert",
|
"LabelValue": "Wert",
|
||||||
|
"LabelVersion": "Version",
|
||||||
"LabelViewBookmarks": "Lesezeichen anzeigen",
|
"LabelViewBookmarks": "Lesezeichen anzeigen",
|
||||||
"LabelViewChapters": "Kapitel anzeigen",
|
"LabelViewChapters": "Kapitel anzeigen",
|
||||||
"LabelViewPlayerSettings": "Zeige player Einstellungen",
|
"LabelViewPlayerSettings": "Zeige player Einstellungen",
|
||||||
@@ -780,6 +809,8 @@
|
|||||||
"StatsSpentListening": "zugehört",
|
"StatsSpentListening": "zugehört",
|
||||||
"StatsTopAuthor": "TOP AUTOR",
|
"StatsTopAuthor": "TOP AUTOR",
|
||||||
"StatsTopAuthors": "TOP AUTOREN",
|
"StatsTopAuthors": "TOP AUTOREN",
|
||||||
|
"StatsTopGenre": "TOP GENRE",
|
||||||
|
"StatsTopGenres": "TOP GENRES",
|
||||||
"StatsTopMonth": "TOP MONAT",
|
"StatsTopMonth": "TOP MONAT",
|
||||||
"StatsTopNarrator": "TOP SPRECHER",
|
"StatsTopNarrator": "TOP SPRECHER",
|
||||||
"StatsTopNarrators": "TOP SPRECHER",
|
"StatsTopNarrators": "TOP SPRECHER",
|
||||||
@@ -843,14 +874,23 @@
|
|||||||
"ToastEpisodeDownloadQueueClearSuccess": "Warteschlange für Episoden-Downloads gelöscht",
|
"ToastEpisodeDownloadQueueClearSuccess": "Warteschlange für Episoden-Downloads gelöscht",
|
||||||
"ToastErrorCannotShare": "Das kann nicht nativ auf diesem Gerät freigegeben werden",
|
"ToastErrorCannotShare": "Das kann nicht nativ auf diesem Gerät freigegeben werden",
|
||||||
"ToastFailedToLoadData": "Daten laden fehlgeschlagen",
|
"ToastFailedToLoadData": "Daten laden fehlgeschlagen",
|
||||||
|
"ToastFailedToShare": "Fehler beim Teilen",
|
||||||
|
"ToastFailedToUpdateAccount": "Fehler beim ändern des Accounts",
|
||||||
|
"ToastFailedToUpdateUser": "Fehler beim ändern des Benutzers",
|
||||||
|
"ToastInvalidImageUrl": "Ungültiger Bild URL",
|
||||||
|
"ToastInvalidUrl": "Ungültiger URL",
|
||||||
"ToastItemCoverUpdateFailed": "Fehler bei der Aktualisierung des Titelbildes",
|
"ToastItemCoverUpdateFailed": "Fehler bei der Aktualisierung des Titelbildes",
|
||||||
"ToastItemCoverUpdateSuccess": "Titelbild aktualisiert",
|
"ToastItemCoverUpdateSuccess": "Titelbild aktualisiert",
|
||||||
|
"ToastItemDeletedFailed": "Fehler beim löschen des Artikels",
|
||||||
|
"ToastItemDeletedSuccess": "Artikel gelöscht",
|
||||||
"ToastItemDetailsUpdateFailed": "Fehler bei der Aktualisierung der Artikeldetails",
|
"ToastItemDetailsUpdateFailed": "Fehler bei der Aktualisierung der Artikeldetails",
|
||||||
"ToastItemDetailsUpdateSuccess": "Artikeldetails aktualisiert",
|
"ToastItemDetailsUpdateSuccess": "Artikeldetails aktualisiert",
|
||||||
"ToastItemMarkedAsFinishedFailed": "Fehler bei der Markierung des Mediums als \"Beendet\"",
|
"ToastItemMarkedAsFinishedFailed": "Fehler bei der Markierung des Artikels als \"Beendet\"",
|
||||||
"ToastItemMarkedAsFinishedSuccess": "Medium als \"Beendet\" markiert",
|
"ToastItemMarkedAsFinishedSuccess": "Artikel als \"Beendet\" markiert",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "Fehler bei der Markierung des Mediums als \"Nicht Beendet\"",
|
"ToastItemMarkedAsNotFinishedFailed": "Fehler bei der Markierung des Artikels als \"Nicht Beendet\"",
|
||||||
"ToastItemMarkedAsNotFinishedSuccess": "Medium als \"Nicht Beendet\" markiert",
|
"ToastItemMarkedAsNotFinishedSuccess": "Artikel als \"Nicht Beendet\" markiert",
|
||||||
|
"ToastItemUpdateFailed": "Fehler beim ändern des Artikels",
|
||||||
|
"ToastItemUpdateSuccess": "Artikel wurde verändert",
|
||||||
"ToastLibraryCreateFailed": "Bibliothek konnte nicht erstellt werden",
|
"ToastLibraryCreateFailed": "Bibliothek konnte nicht erstellt werden",
|
||||||
"ToastLibraryCreateSuccess": "Bibliothek \"{0}\" erstellt",
|
"ToastLibraryCreateSuccess": "Bibliothek \"{0}\" erstellt",
|
||||||
"ToastLibraryDeleteFailed": "Bibliothek konnte nicht gelöscht werden",
|
"ToastLibraryDeleteFailed": "Bibliothek konnte nicht gelöscht werden",
|
||||||
@@ -859,6 +899,25 @@
|
|||||||
"ToastLibraryScanStarted": "Bibliotheksscan gestartet",
|
"ToastLibraryScanStarted": "Bibliotheksscan gestartet",
|
||||||
"ToastLibraryUpdateFailed": "Aktualisierung der Bibliothek fehlgeschlagen",
|
"ToastLibraryUpdateFailed": "Aktualisierung der Bibliothek fehlgeschlagen",
|
||||||
"ToastLibraryUpdateSuccess": "Bibliothek \"{0}\" aktualisiert",
|
"ToastLibraryUpdateSuccess": "Bibliothek \"{0}\" aktualisiert",
|
||||||
|
"ToastNameEmailRequired": "Name und Email sind erforderlich",
|
||||||
|
"ToastNameRequired": "Name ist erforderlich",
|
||||||
|
"ToastNewUserCreatedFailed": "Fehler beim erstellen des Accounts: \"{ 0}\"",
|
||||||
|
"ToastNewUserCreatedSuccess": "Neuer Account erstellt",
|
||||||
|
"ToastNewUserLibraryError": "Mindestens eine Bibliothek muss ausgewählt werden",
|
||||||
|
"ToastNewUserPasswordError": "Passwort erforderlich, nur der root Benutzer darf ein leeres Passwort haben",
|
||||||
|
"ToastNewUserTagError": "Mindestens ein Tag muss ausgewählt sein",
|
||||||
|
"ToastNewUserUsernameError": "Nutzername eingeben",
|
||||||
|
"ToastNoUpdatesNecessary": "Keine Änderungen nötig",
|
||||||
|
"ToastNotificationCreateFailed": "Fehler beim erstellen der Benachrichtig",
|
||||||
|
"ToastNotificationDeleteFailed": "Fehler beim löschen der Benachrichtigung",
|
||||||
|
"ToastNotificationFailedMaximum": "Maximale Fehlversuche muss >= 0 sein",
|
||||||
|
"ToastNotificationQueueMaximum": "Maximale Benachrichtigungswarteschlange muss >= 0 sein",
|
||||||
|
"ToastNotificationSettingsUpdateFailed": "Fehler beim ändern der Benachrichtigungseinstellungen",
|
||||||
|
"ToastNotificationSettingsUpdateSuccess": "Benachrichtigungseinstellungen geändert",
|
||||||
|
"ToastNotificationTestTriggerFailed": "Fehler beim Auslösen der Testbenachrichtigung",
|
||||||
|
"ToastNotificationTestTriggerSuccess": "Testbenachrichtigung ausgelöst",
|
||||||
|
"ToastNotificationUpdateFailed": "Fehler bein ändern der Benachrichtigung",
|
||||||
|
"ToastNotificationUpdateSuccess": "Benachrichtigung geändert",
|
||||||
"ToastPlaylistCreateFailed": "Erstellen der Wiedergabeliste fehlgeschlagen",
|
"ToastPlaylistCreateFailed": "Erstellen der Wiedergabeliste fehlgeschlagen",
|
||||||
"ToastPlaylistCreateSuccess": "Wiedergabeliste erstellt",
|
"ToastPlaylistCreateSuccess": "Wiedergabeliste erstellt",
|
||||||
"ToastPlaylistRemoveSuccess": "Wiedergabeliste gelöscht",
|
"ToastPlaylistRemoveSuccess": "Wiedergabeliste gelöscht",
|
||||||
@@ -866,27 +925,52 @@
|
|||||||
"ToastPlaylistUpdateSuccess": "Wiedergabeliste aktualisiert",
|
"ToastPlaylistUpdateSuccess": "Wiedergabeliste aktualisiert",
|
||||||
"ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden",
|
"ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden",
|
||||||
"ToastPodcastCreateSuccess": "Podcast erstellt",
|
"ToastPodcastCreateSuccess": "Podcast erstellt",
|
||||||
|
"ToastPodcastGetFeedFailed": "Fehler beim abrufen des Podcast Feeds",
|
||||||
|
"ToastPodcastNoEpisodesInFeed": "Keine Episoden in RSS Feed gefunden",
|
||||||
|
"ToastPodcastNoRssFeed": "Podcast enthält keinen RSS Feed",
|
||||||
|
"ToastProviderCreatedFailed": "Fehler beim hinzufügen des Anbieters",
|
||||||
"ToastProviderCreatedSuccess": "Neuer Anbieter hinzugefügt",
|
"ToastProviderCreatedSuccess": "Neuer Anbieter hinzugefügt",
|
||||||
"ToastProviderNameAndUrlRequired": "Name und URL notwendig",
|
"ToastProviderNameAndUrlRequired": "Name und URL notwendig",
|
||||||
"ToastProviderRemoveSuccess": "Anbieter entfernt",
|
"ToastProviderRemoveSuccess": "Anbieter entfernt",
|
||||||
"ToastRSSFeedCloseFailed": "RSS-Feed konnte nicht geschlossen werden",
|
"ToastRSSFeedCloseFailed": "RSS-Feed konnte nicht geschlossen werden",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS-Feed geschlossen",
|
"ToastRSSFeedCloseSuccess": "RSS-Feed geschlossen",
|
||||||
|
"ToastRemoveFailed": "Fehler beim entfernen",
|
||||||
"ToastRemoveItemFromCollectionFailed": "Löschen des Mediums aus der Sammlung fehlgeschlagen",
|
"ToastRemoveItemFromCollectionFailed": "Löschen des Mediums aus der Sammlung fehlgeschlagen",
|
||||||
"ToastRemoveItemFromCollectionSuccess": "Medium aus der Sammlung gelöscht",
|
"ToastRemoveItemFromCollectionSuccess": "Medium aus der Sammlung gelöscht",
|
||||||
|
"ToastRemoveItemsWithIssuesFailed": "Entfernen von fehlerhaften Bibliotheksartikeln fehlgeschlagenen",
|
||||||
|
"ToastRemoveItemsWithIssuesSuccess": "Fehlerhafte Bibliotheksartikel entfernt",
|
||||||
|
"ToastRenameFailed": "Umbenennen fehlgeschlagen",
|
||||||
|
"ToastRescanFailed": "Erneut scannen fehlgeschlagen für {0}",
|
||||||
|
"ToastRescanRemoved": "Erneut scannen erledigt, Artikel wurde entfernt",
|
||||||
|
"ToastRescanUpToDate": "Erneut scannen erledigt, Artikel wahr auf dem neusten Stand",
|
||||||
|
"ToastRescanUpdated": "Erneut scannen erledigt, Artikel wurde verändert",
|
||||||
|
"ToastScanFailed": "Fehler beim scannen des Artikels der Bibliothek",
|
||||||
|
"ToastSelectAtLeastOneUser": "Wähle mindestens einen Benutzer aus",
|
||||||
"ToastSendEbookToDeviceFailed": "E-Book konnte nicht auf Gerät übertragen werden",
|
"ToastSendEbookToDeviceFailed": "E-Book konnte nicht auf Gerät übertragen werden",
|
||||||
"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",
|
"ToastServerSettingsUpdateFailed": "Die Server-Einstellungen wurden nicht gespeichert",
|
||||||
"ToastServerSettingsUpdateSuccess": "Die Server-Einstellungen wurden geupdated",
|
"ToastServerSettingsUpdateSuccess": "Die Server-Einstellungen wurden geupdated",
|
||||||
|
"ToastSessionCloseFailed": "Fehler beim schließen der Sitzung",
|
||||||
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
|
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
|
||||||
"ToastSessionDeleteSuccess": "Sitzung gelöscht",
|
"ToastSessionDeleteSuccess": "Sitzung gelöscht",
|
||||||
|
"ToastSlugMustChange": "URL-Schlüssel enthält ungültige Zeichen",
|
||||||
|
"ToastSlugRequired": "URL-Schlüssel erforderlich",
|
||||||
"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",
|
"ToastSortingPrefixesEmptyError": "Es muss mindestens ein Sortier-Prefix vorhanden sein",
|
||||||
"ToastSortingPrefixesUpdateFailed": "Update der Sortier-Prefixe ist fehlgeschlagen",
|
"ToastSortingPrefixesUpdateFailed": "Update der Sortier-Prefixe ist fehlgeschlagen",
|
||||||
"ToastSortingPrefixesUpdateSuccess": "Die Sortier-Prefixe wirden geupdated ({0} Einträge)",
|
"ToastSortingPrefixesUpdateSuccess": "Die Sortier-Prefixe wirden geupdated ({0} Einträge)",
|
||||||
|
"ToastTitleRequired": "Titel erforderlich",
|
||||||
|
"ToastUnknownError": "Unbekannter Fehler",
|
||||||
|
"ToastUnlinkOpenIdFailed": "Fehler beim entkoppeln des Benutzers von OpenID",
|
||||||
|
"ToastUnlinkOpenIdSuccess": "Benutzer entkoppelt von OpenID",
|
||||||
"ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
|
"ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
|
||||||
"ToastUserDeleteSuccess": "Benutzer gelöscht"
|
"ToastUserDeleteSuccess": "Benutzer gelöscht",
|
||||||
|
"ToastUserPasswordChangeSuccess": "Passwort erfolgreich verändert",
|
||||||
|
"ToastUserPasswordMismatch": "Passwörter stimmen nicht überein",
|
||||||
|
"ToastUserPasswordMustChange": "Neues Passwort muss sich von altem Passwort unterscheiden",
|
||||||
|
"ToastUserRootRequireName": "Root Benutzername muss angegeben werden"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
"ButtonStats": "Stats",
|
"ButtonStats": "Stats",
|
||||||
"ButtonSubmit": "Submit",
|
"ButtonSubmit": "Submit",
|
||||||
"ButtonTest": "Test",
|
"ButtonTest": "Test",
|
||||||
"ButtonUnlinkOpedId": "Unlink OpenID",
|
"ButtonUnlinkOpenId": "Unlink OpenID",
|
||||||
"ButtonUpload": "Upload",
|
"ButtonUpload": "Upload",
|
||||||
"ButtonUploadBackup": "Upload Backup",
|
"ButtonUploadBackup": "Upload Backup",
|
||||||
"ButtonUploadCover": "Upload Cover",
|
"ButtonUploadCover": "Upload Cover",
|
||||||
|
|||||||
+113
-10
@@ -30,6 +30,7 @@
|
|||||||
"ButtonEditChapters": "Editar Capítulo",
|
"ButtonEditChapters": "Editar Capítulo",
|
||||||
"ButtonEditPodcast": "Editar Podcast",
|
"ButtonEditPodcast": "Editar Podcast",
|
||||||
"ButtonEnable": "Permitir",
|
"ButtonEnable": "Permitir",
|
||||||
|
"ButtonFireOnTest": "Activar evento de prueba",
|
||||||
"ButtonForceReScan": "Forzar Re-Escaneo",
|
"ButtonForceReScan": "Forzar Re-Escaneo",
|
||||||
"ButtonFullPath": "Ruta de Acceso Completa",
|
"ButtonFullPath": "Ruta de Acceso Completa",
|
||||||
"ButtonHide": "Esconder",
|
"ButtonHide": "Esconder",
|
||||||
@@ -46,8 +47,10 @@
|
|||||||
"ButtonMatchAllAuthors": "Encontrar Todos los Autores",
|
"ButtonMatchAllAuthors": "Encontrar Todos los Autores",
|
||||||
"ButtonMatchBooks": "Encontrar Libros",
|
"ButtonMatchBooks": "Encontrar Libros",
|
||||||
"ButtonNevermind": "Olvidar",
|
"ButtonNevermind": "Olvidar",
|
||||||
|
"ButtonNext": "Siguiente",
|
||||||
"ButtonNextChapter": "Siguiente Capítulo",
|
"ButtonNextChapter": "Siguiente Capítulo",
|
||||||
"ButtonNextItemInQueue": "El siguiente elemento en cola",
|
"ButtonNextItemInQueue": "El siguiente elemento en cola",
|
||||||
|
"ButtonOk": "De acuerdo",
|
||||||
"ButtonOpenFeed": "Abrir fuente",
|
"ButtonOpenFeed": "Abrir fuente",
|
||||||
"ButtonOpenManager": "Abrir Editor",
|
"ButtonOpenManager": "Abrir Editor",
|
||||||
"ButtonPause": "Pausar",
|
"ButtonPause": "Pausar",
|
||||||
@@ -84,6 +87,7 @@
|
|||||||
"ButtonScanLibrary": "Escanear Biblioteca",
|
"ButtonScanLibrary": "Escanear Biblioteca",
|
||||||
"ButtonSearch": "Buscar",
|
"ButtonSearch": "Buscar",
|
||||||
"ButtonSelectFolderPath": "Seleccionar Ruta de Carpeta",
|
"ButtonSelectFolderPath": "Seleccionar Ruta de Carpeta",
|
||||||
|
"ButtonSeries": "Series",
|
||||||
"ButtonSetChaptersFromTracks": "Seleccionar Capítulos Según las Pistas",
|
"ButtonSetChaptersFromTracks": "Seleccionar Capítulos Según las Pistas",
|
||||||
"ButtonShare": "Compartir",
|
"ButtonShare": "Compartir",
|
||||||
"ButtonShiftTimes": "Desplazar Tiempos",
|
"ButtonShiftTimes": "Desplazar Tiempos",
|
||||||
@@ -93,7 +97,7 @@
|
|||||||
"ButtonStats": "Estadísticas",
|
"ButtonStats": "Estadísticas",
|
||||||
"ButtonSubmit": "Enviar",
|
"ButtonSubmit": "Enviar",
|
||||||
"ButtonTest": "Prueba",
|
"ButtonTest": "Prueba",
|
||||||
"ButtonUnlinkOpedId": "Desvincular OpenID",
|
"ButtonUnlinkOpenId": "Desvincular OpenID",
|
||||||
"ButtonUpload": "Subir",
|
"ButtonUpload": "Subir",
|
||||||
"ButtonUploadBackup": "Subir Respaldo",
|
"ButtonUploadBackup": "Subir Respaldo",
|
||||||
"ButtonUploadCover": "Subir Portada",
|
"ButtonUploadCover": "Subir Portada",
|
||||||
@@ -125,6 +129,7 @@
|
|||||||
"HeaderDetails": "Detalles",
|
"HeaderDetails": "Detalles",
|
||||||
"HeaderDownloadQueue": "Lista de Descarga",
|
"HeaderDownloadQueue": "Lista de Descarga",
|
||||||
"HeaderEbookFiles": "Archivos de libros digitales",
|
"HeaderEbookFiles": "Archivos de libros digitales",
|
||||||
|
"HeaderEmail": "Correo electrónico",
|
||||||
"HeaderEmailSettings": "Opciones de Email",
|
"HeaderEmailSettings": "Opciones de Email",
|
||||||
"HeaderEpisodes": "Episodios",
|
"HeaderEpisodes": "Episodios",
|
||||||
"HeaderEreaderDevices": "Dispositivos Ereader",
|
"HeaderEreaderDevices": "Dispositivos Ereader",
|
||||||
@@ -152,6 +157,7 @@
|
|||||||
"HeaderNewAccount": "Nueva Cuenta",
|
"HeaderNewAccount": "Nueva Cuenta",
|
||||||
"HeaderNewLibrary": "Nueva Biblioteca",
|
"HeaderNewLibrary": "Nueva Biblioteca",
|
||||||
"HeaderNotificationCreate": "Crear notificación",
|
"HeaderNotificationCreate": "Crear notificación",
|
||||||
|
"HeaderNotificationUpdate": "Notificación de actualización",
|
||||||
"HeaderNotifications": "Notificaciones",
|
"HeaderNotifications": "Notificaciones",
|
||||||
"HeaderOpenIDConnectAuthentication": "Autenticación OpenID Connect",
|
"HeaderOpenIDConnectAuthentication": "Autenticación OpenID Connect",
|
||||||
"HeaderOpenRSSFeed": "Abrir fuente RSS",
|
"HeaderOpenRSSFeed": "Abrir fuente RSS",
|
||||||
@@ -177,6 +183,7 @@
|
|||||||
"HeaderSettings": "Configuraciones",
|
"HeaderSettings": "Configuraciones",
|
||||||
"HeaderSettingsDisplay": "Interfaz",
|
"HeaderSettingsDisplay": "Interfaz",
|
||||||
"HeaderSettingsExperimental": "Funciones Experimentales",
|
"HeaderSettingsExperimental": "Funciones Experimentales",
|
||||||
|
"HeaderSettingsGeneral": "General",
|
||||||
"HeaderSettingsScanner": "Escáner",
|
"HeaderSettingsScanner": "Escáner",
|
||||||
"HeaderSleepTimer": "Temporizador de apagado",
|
"HeaderSleepTimer": "Temporizador de apagado",
|
||||||
"HeaderStatsLargestItems": "Artículos mas Grandes",
|
"HeaderStatsLargestItems": "Artículos mas Grandes",
|
||||||
@@ -235,8 +242,10 @@
|
|||||||
"LabelBackupsMaxBackupSizeHelp": "Como protección contra una configuración errónea, los respaldos fallarán si se excede el tamaño configurado.",
|
"LabelBackupsMaxBackupSizeHelp": "Como protección contra una configuración errónea, los respaldos fallarán si se excede el tamaño configurado.",
|
||||||
"LabelBackupsNumberToKeep": "Numero de respaldos para conservar",
|
"LabelBackupsNumberToKeep": "Numero de respaldos para conservar",
|
||||||
"LabelBackupsNumberToKeepHelp": "Solamente 1 respaldo se removerá a la vez. Si tiene mas respaldos guardados, debe removerlos manualmente.",
|
"LabelBackupsNumberToKeepHelp": "Solamente 1 respaldo se removerá a la vez. Si tiene mas respaldos guardados, debe removerlos manualmente.",
|
||||||
|
"LabelBitrate": "Tasa de bits",
|
||||||
"LabelBooks": "Libros",
|
"LabelBooks": "Libros",
|
||||||
"LabelButtonText": "Texto del botón",
|
"LabelButtonText": "Texto del botón",
|
||||||
|
"LabelByAuthor": "por {0}",
|
||||||
"LabelChangePassword": "Cambiar Contraseña",
|
"LabelChangePassword": "Cambiar Contraseña",
|
||||||
"LabelChannels": "Canales",
|
"LabelChannels": "Canales",
|
||||||
"LabelChapterTitle": "Titulo del Capítulo",
|
"LabelChapterTitle": "Titulo del Capítulo",
|
||||||
@@ -244,6 +253,7 @@
|
|||||||
"LabelChaptersFound": "Capítulo Encontrado",
|
"LabelChaptersFound": "Capítulo Encontrado",
|
||||||
"LabelClickForMoreInfo": "Click para más información",
|
"LabelClickForMoreInfo": "Click para más información",
|
||||||
"LabelClosePlayer": "Cerrar reproductor",
|
"LabelClosePlayer": "Cerrar reproductor",
|
||||||
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Colapsar serie",
|
"LabelCollapseSeries": "Colapsar serie",
|
||||||
"LabelCollapseSubSeries": "Contraer la subserie",
|
"LabelCollapseSubSeries": "Contraer la subserie",
|
||||||
"LabelCollection": "Colección",
|
"LabelCollection": "Colección",
|
||||||
@@ -282,6 +292,7 @@
|
|||||||
"LabelEbook": "Libro electrónico",
|
"LabelEbook": "Libro electrónico",
|
||||||
"LabelEbooks": "Libros electrónicos",
|
"LabelEbooks": "Libros electrónicos",
|
||||||
"LabelEdit": "Editar",
|
"LabelEdit": "Editar",
|
||||||
|
"LabelEmail": "Correo electrónico",
|
||||||
"LabelEmailSettingsFromAddress": "Remitente",
|
"LabelEmailSettingsFromAddress": "Remitente",
|
||||||
"LabelEmailSettingsRejectUnauthorized": "Rechazar certificados no autorizados",
|
"LabelEmailSettingsRejectUnauthorized": "Rechazar certificados no autorizados",
|
||||||
"LabelEmailSettingsRejectUnauthorizedHelp": "Deshabilitar la validación de certificados SSL puede exponer tu conexión a riesgos de seguridad, como ataques man-in-the-middle. Desactiva esta opción sólo si conoces las implicaciones y confias en el servidor de correo al que te conectas.",
|
"LabelEmailSettingsRejectUnauthorizedHelp": "Deshabilitar la validación de certificados SSL puede exponer tu conexión a riesgos de seguridad, como ataques man-in-the-middle. Desactiva esta opción sólo si conoces las implicaciones y confias en el servidor de correo al que te conectas.",
|
||||||
@@ -330,6 +341,7 @@
|
|||||||
"LabelHasSupplementaryEbook": "Tiene un libro complementario",
|
"LabelHasSupplementaryEbook": "Tiene un libro complementario",
|
||||||
"LabelHideSubtitles": "Ocultar subtítulos",
|
"LabelHideSubtitles": "Ocultar subtítulos",
|
||||||
"LabelHighestPriority": "Mayor prioridad",
|
"LabelHighestPriority": "Mayor prioridad",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hora",
|
"LabelHour": "Hora",
|
||||||
"LabelHours": "Horas",
|
"LabelHours": "Horas",
|
||||||
"LabelIcon": "Icono",
|
"LabelIcon": "Icono",
|
||||||
@@ -359,16 +371,18 @@
|
|||||||
"LabelLastTime": "Última Vez",
|
"LabelLastTime": "Última Vez",
|
||||||
"LabelLastUpdate": "Última Actualización",
|
"LabelLastUpdate": "Última Actualización",
|
||||||
"LabelLayout": "Distribución",
|
"LabelLayout": "Distribución",
|
||||||
"LabelLayoutSinglePage": "Una Página",
|
"LabelLayoutSinglePage": "Página única",
|
||||||
"LabelLayoutSplitPage": "Dos Páginas",
|
"LabelLayoutSplitPage": "Dos Páginas",
|
||||||
"LabelLess": "Menos",
|
"LabelLess": "Menos",
|
||||||
"LabelLibrariesAccessibleToUser": "Bibliotecas Disponibles para el Usuario",
|
"LabelLibrariesAccessibleToUser": "Bibliotecas Disponibles para el Usuario",
|
||||||
"LabelLibrary": "Biblioteca",
|
"LabelLibrary": "Biblioteca",
|
||||||
|
"LabelLibraryFilterSublistEmpty": "Sin {0}",
|
||||||
"LabelLibraryItem": "Elemento de Biblioteca",
|
"LabelLibraryItem": "Elemento de Biblioteca",
|
||||||
"LabelLibraryName": "Nombre de Biblioteca",
|
"LabelLibraryName": "Nombre de Biblioteca",
|
||||||
"LabelLimit": "Limites",
|
"LabelLimit": "Limites",
|
||||||
"LabelLineSpacing": "Interlineado",
|
"LabelLineSpacing": "Interlineado",
|
||||||
"LabelListenAgain": "Volver a escuchar",
|
"LabelListenAgain": "Volver a escuchar",
|
||||||
|
"LabelLogLevelDebug": "Depurar",
|
||||||
"LabelLogLevelInfo": "Información",
|
"LabelLogLevelInfo": "Información",
|
||||||
"LabelLogLevelWarn": "Advertencia",
|
"LabelLogLevelWarn": "Advertencia",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Buscar Nuevos Episodios a partir de esta Fecha",
|
"LabelLookForNewEpisodesAfterDate": "Buscar Nuevos Episodios a partir de esta Fecha",
|
||||||
@@ -416,7 +430,7 @@
|
|||||||
"LabelNumberOfBooks": "Numero de Libros",
|
"LabelNumberOfBooks": "Numero de Libros",
|
||||||
"LabelNumberOfEpisodes": "# de Episodios",
|
"LabelNumberOfEpisodes": "# de Episodios",
|
||||||
"LabelOpenIDAdvancedPermsClaimDescription": "Nombre de la notificación de OpenID que contiene permisos avanzados para acciones de usuario dentro de la aplicación que se aplicarán a roles que no sean de administrador (<b>si están configurados</b>). Si el reclamo no aparece en la respuesta, se denegará el acceso a ABS. Si falta una sola opción, se tratará como <code>falsa</code>. Asegúrese de que la notificación del proveedor de identidades coincida con la estructura esperada:",
|
"LabelOpenIDAdvancedPermsClaimDescription": "Nombre de la notificación de OpenID que contiene permisos avanzados para acciones de usuario dentro de la aplicación que se aplicarán a roles que no sean de administrador (<b>si están configurados</b>). Si el reclamo no aparece en la respuesta, se denegará el acceso a ABS. Si falta una sola opción, se tratará como <code>falsa</code>. Asegúrese de que la notificación del proveedor de identidades coincida con la estructura esperada:",
|
||||||
"LabelOpenIDClaims": "Deje las siguientes opciones vacías para deshabilitar la asignación avanzada de grupos y permisos, lo que asignaría de manera automática al grupo 'Usuario'",
|
"LabelOpenIDClaims": "Deje las siguientes opciones vacías para deshabilitar la asignación avanzada de grupos y permisos, lo que asignaría de manera automática al grupo 'Usuario'.",
|
||||||
"LabelOpenIDGroupClaimDescription": "Nombre de la declaración OpenID que contiene una lista de grupos del usuario. Comúnmente conocidos como <code>grupos</code>. <b>Si se configura</b>, la aplicación asignará automáticamente roles en función de la pertenencia a grupos del usuario, siempre que estos grupos se denominen \"admin\", \"user\" o \"guest\" en la notificación. La solicitud debe contener una lista, y si un usuario pertenece a varios grupos, la aplicación asignará el rol correspondiente al mayor nivel de acceso. Si ningún grupo coincide, se denegará el acceso.",
|
"LabelOpenIDGroupClaimDescription": "Nombre de la declaración OpenID que contiene una lista de grupos del usuario. Comúnmente conocidos como <code>grupos</code>. <b>Si se configura</b>, la aplicación asignará automáticamente roles en función de la pertenencia a grupos del usuario, siempre que estos grupos se denominen \"admin\", \"user\" o \"guest\" en la notificación. La solicitud debe contener una lista, y si un usuario pertenece a varios grupos, la aplicación asignará el rol correspondiente al mayor nivel de acceso. Si ningún grupo coincide, se denegará el acceso.",
|
||||||
"LabelOpenRSSFeed": "Abrir Fuente RSS",
|
"LabelOpenRSSFeed": "Abrir Fuente RSS",
|
||||||
"LabelOverwrite": "Sobrescribir",
|
"LabelOverwrite": "Sobrescribir",
|
||||||
@@ -433,9 +447,12 @@
|
|||||||
"LabelPersonalYearReview": "Revisión de tu año ({0})",
|
"LabelPersonalYearReview": "Revisión de tu año ({0})",
|
||||||
"LabelPhotoPathURL": "Ruta de Acceso/URL de Foto",
|
"LabelPhotoPathURL": "Ruta de Acceso/URL de Foto",
|
||||||
"LabelPlayMethod": "Método de Reproducción",
|
"LabelPlayMethod": "Método de Reproducción",
|
||||||
|
"LabelPlayerChapterNumberMarker": "{0} de {1}",
|
||||||
"LabelPlaylists": "Lista de Reproducción",
|
"LabelPlaylists": "Lista de Reproducción",
|
||||||
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcastSearchRegion": "Región de búsqueda de podcasts",
|
"LabelPodcastSearchRegion": "Región de búsqueda de podcasts",
|
||||||
"LabelPodcastType": "Tipo Podcast",
|
"LabelPodcastType": "Tipo Podcast",
|
||||||
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPort": "Puerto",
|
"LabelPort": "Puerto",
|
||||||
"LabelPrefixesToIgnore": "Prefijos para Ignorar (no distingue entre mayúsculas y minúsculas.)",
|
"LabelPrefixesToIgnore": "Prefijos para Ignorar (no distingue entre mayúsculas y minúsculas.)",
|
||||||
"LabelPreventIndexing": "Evite que su fuente sea indexada por los directorios de podcasts de iTunes y Google",
|
"LabelPreventIndexing": "Evite que su fuente sea indexada por los directorios de podcasts de iTunes y Google",
|
||||||
@@ -476,6 +493,7 @@
|
|||||||
"LabelSelectUsers": "Seleccionar usuarios",
|
"LabelSelectUsers": "Seleccionar usuarios",
|
||||||
"LabelSendEbookToDevice": "Enviar Ebook a...",
|
"LabelSendEbookToDevice": "Enviar Ebook a...",
|
||||||
"LabelSequence": "Secuencia",
|
"LabelSequence": "Secuencia",
|
||||||
|
"LabelSeries": "Series",
|
||||||
"LabelSeriesName": "Nombre de la Serie",
|
"LabelSeriesName": "Nombre de la Serie",
|
||||||
"LabelSeriesProgress": "Progreso de la Serie",
|
"LabelSeriesProgress": "Progreso de la Serie",
|
||||||
"LabelServerYearReview": "Resumen del año del servidor ({0})",
|
"LabelServerYearReview": "Resumen del año del servidor ({0})",
|
||||||
@@ -527,6 +545,7 @@
|
|||||||
"LabelShowSubtitles": "Mostrar subtítulos",
|
"LabelShowSubtitles": "Mostrar subtítulos",
|
||||||
"LabelSize": "Tamaño",
|
"LabelSize": "Tamaño",
|
||||||
"LabelSleepTimer": "Temporizador de apagado",
|
"LabelSleepTimer": "Temporizador de apagado",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Iniciar",
|
"LabelStart": "Iniciar",
|
||||||
"LabelStartTime": "Tiempo de Inicio",
|
"LabelStartTime": "Tiempo de Inicio",
|
||||||
"LabelStarted": "Iniciado",
|
"LabelStarted": "Iniciado",
|
||||||
@@ -624,14 +643,14 @@
|
|||||||
"MessageBackupsLocationNoEditNote": "Nota: La ubicación de la copia de seguridad se establece a través de una variable de entorno y no se puede cambiar aquí.",
|
"MessageBackupsLocationNoEditNote": "Nota: La ubicación de la copia de seguridad se establece a través de una variable de entorno y no se puede cambiar aquí.",
|
||||||
"MessageBackupsLocationPathEmpty": "La ruta de la copia de seguridad no puede estar vacía",
|
"MessageBackupsLocationPathEmpty": "La ruta de la copia de seguridad no puede estar vacía",
|
||||||
"MessageBatchQuickMatchDescription": "\"Encontrar Rápido\" tratará de agregar portadas y metadatos faltantes de los elementos seleccionados. Habilite la opción de abajo para que \"Encontrar Rápido\" pueda sobrescribir portadas y/o metadatos existentes.",
|
"MessageBatchQuickMatchDescription": "\"Encontrar Rápido\" tratará de agregar portadas y metadatos faltantes de los elementos seleccionados. Habilite la opción de abajo para que \"Encontrar Rápido\" pueda sobrescribir portadas y/o metadatos existentes.",
|
||||||
"MessageBookshelfNoCollections": "No tienes ninguna colección.",
|
"MessageBookshelfNoCollections": "No tienes ninguna colección",
|
||||||
"MessageBookshelfNoRSSFeeds": "Ninguna Fuente RSS esta abierta",
|
"MessageBookshelfNoRSSFeeds": "Ninguna Fuente RSS esta abierta",
|
||||||
"MessageBookshelfNoResultsForFilter": "Ningún Resultado para el filtro \"{0}: {1}\"",
|
"MessageBookshelfNoResultsForFilter": "Ningún Resultado para el filtro \"{0}: {1}\"",
|
||||||
"MessageBookshelfNoResultsForQuery": "No hay resultados para la consulta",
|
"MessageBookshelfNoResultsForQuery": "No hay resultados para la consulta",
|
||||||
"MessageBookshelfNoSeries": "No tienes ninguna serie",
|
"MessageBookshelfNoSeries": "No tienes ninguna serie",
|
||||||
"MessageChapterEndIsAfter": "El final del capítulo es después del final de tu audiolibro.",
|
"MessageChapterEndIsAfter": "El final del capítulo es después del final de tu audiolibro",
|
||||||
"MessageChapterErrorFirstNotZero": "El primer capitulo debe iniciar en 0",
|
"MessageChapterErrorFirstNotZero": "El primer capitulo debe iniciar en 0",
|
||||||
"MessageChapterErrorStartGteDuration": "El tiempo de inicio no es válido: debe ser inferior a la duración del audiolibro.",
|
"MessageChapterErrorStartGteDuration": "El tiempo de inicio no es válido: debe ser inferior a la duración del audiolibro",
|
||||||
"MessageChapterErrorStartLtPrev": "El tiempo de inicio no es válido: debe ser mayor o igual que el tiempo de inicio del capítulo anterior",
|
"MessageChapterErrorStartLtPrev": "El tiempo de inicio no es válido: debe ser mayor o igual que el tiempo de inicio del capítulo anterior",
|
||||||
"MessageChapterStartIsAfter": "El comienzo del capítulo es después del final de su audiolibro",
|
"MessageChapterStartIsAfter": "El comienzo del capítulo es después del final de su audiolibro",
|
||||||
"MessageCheckingCron": "Revisando cron...",
|
"MessageCheckingCron": "Revisando cron...",
|
||||||
@@ -673,8 +692,9 @@
|
|||||||
"MessageConfirmRenameTagWarning": "Advertencia! Una etiqueta similar ya existe \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Advertencia! Una etiqueta similar ya existe \"{0}\".",
|
||||||
"MessageConfirmResetProgress": "¿Estás seguro de que quieres reiniciar tu progreso?",
|
"MessageConfirmResetProgress": "¿Estás seguro de que quieres reiniciar tu progreso?",
|
||||||
"MessageConfirmSendEbookToDevice": "¿Está seguro de que enviar {0} ebook(s) \"{1}\" al dispositivo \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "¿Está seguro de que enviar {0} ebook(s) \"{1}\" al dispositivo \"{2}\"?",
|
||||||
|
"MessageConfirmUnlinkOpenId": "¿Estás seguro de que deseas desvincular este usuario de OpenID?",
|
||||||
"MessageDownloadingEpisode": "Descargando Capitulo",
|
"MessageDownloadingEpisode": "Descargando Capitulo",
|
||||||
"MessageDragFilesIntoTrackOrder": "Arrastra los archivos al orden correcto de las pistas.",
|
"MessageDragFilesIntoTrackOrder": "Arrastra los archivos al orden correcto de las pistas",
|
||||||
"MessageEmbedFailed": "¡Error al insertar!",
|
"MessageEmbedFailed": "¡Error al insertar!",
|
||||||
"MessageEmbedFinished": "Incrustación Terminada!",
|
"MessageEmbedFinished": "Incrustación Terminada!",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} Episodio(s) en cola para descargar",
|
"MessageEpisodesQueuedForDownload": "{0} Episodio(s) en cola para descargar",
|
||||||
@@ -707,6 +727,7 @@
|
|||||||
"MessageNoCollections": "Sin Colecciones",
|
"MessageNoCollections": "Sin Colecciones",
|
||||||
"MessageNoCoversFound": "Ninguna Portada Encontrada",
|
"MessageNoCoversFound": "Ninguna Portada Encontrada",
|
||||||
"MessageNoDescription": "Sin Descripción",
|
"MessageNoDescription": "Sin Descripción",
|
||||||
|
"MessageNoDevices": "Sin dispositivos",
|
||||||
"MessageNoDownloadsInProgress": "No hay descargas actualmente en curso",
|
"MessageNoDownloadsInProgress": "No hay descargas actualmente en curso",
|
||||||
"MessageNoDownloadsQueued": "Sin Lista de Descarga",
|
"MessageNoDownloadsQueued": "Sin Lista de Descarga",
|
||||||
"MessageNoEpisodeMatchesFound": "No se encontraron episodios que coinciden",
|
"MessageNoEpisodeMatchesFound": "No se encontraron episodios que coinciden",
|
||||||
@@ -734,6 +755,7 @@
|
|||||||
"MessagePauseChapter": "Pausar la reproducción del capítulo",
|
"MessagePauseChapter": "Pausar la reproducción del capítulo",
|
||||||
"MessagePlayChapter": "Escuchar el comienzo del capítulo",
|
"MessagePlayChapter": "Escuchar el comienzo del capítulo",
|
||||||
"MessagePlaylistCreateFromCollection": "Crear una lista de reproducción a partir de una colección",
|
"MessagePlaylistCreateFromCollection": "Crear una lista de reproducción a partir de una colección",
|
||||||
|
"MessagePleaseWait": "Por favor, espera...",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "El podcast no tiene una URL de fuente RSS que pueda usar",
|
"MessagePodcastHasNoRSSFeedForMatching": "El podcast no tiene una URL de fuente RSS que pueda usar",
|
||||||
"MessageQuickMatchDescription": "Rellenar detalles de elementos vacíos y portada con los primeros resultados de '{0}'. No sobrescribe los detalles a menos que la opción \"Preferir Metadatos Encontrados\" del servidor esté habilitada.",
|
"MessageQuickMatchDescription": "Rellenar detalles de elementos vacíos y portada con los primeros resultados de '{0}'. No sobrescribe los detalles a menos que la opción \"Preferir Metadatos Encontrados\" del servidor esté habilitada.",
|
||||||
"MessageRemoveChapter": "Remover capítulos",
|
"MessageRemoveChapter": "Remover capítulos",
|
||||||
@@ -794,18 +816,28 @@
|
|||||||
"StatsYearInReview": "RESEÑA DEL AÑO",
|
"StatsYearInReview": "RESEÑA DEL AÑO",
|
||||||
"ToastAccountUpdateFailed": "Error al actualizar cuenta",
|
"ToastAccountUpdateFailed": "Error al actualizar cuenta",
|
||||||
"ToastAccountUpdateSuccess": "Cuenta actualizada",
|
"ToastAccountUpdateSuccess": "Cuenta actualizada",
|
||||||
|
"ToastAppriseUrlRequired": "Debes ingresar una URL de Apprise",
|
||||||
"ToastAuthorImageRemoveSuccess": "Se eliminó la imagen del autor",
|
"ToastAuthorImageRemoveSuccess": "Se eliminó la imagen del autor",
|
||||||
|
"ToastAuthorNotFound": "No se encontró el autor \"{0}\"",
|
||||||
|
"ToastAuthorRemoveSuccess": "Autor eliminado",
|
||||||
|
"ToastAuthorSearchNotFound": "No se encontró al autor",
|
||||||
"ToastAuthorUpdateFailed": "Error al actualizar el autor",
|
"ToastAuthorUpdateFailed": "Error al actualizar el autor",
|
||||||
"ToastAuthorUpdateMerged": "Autor combinado",
|
"ToastAuthorUpdateMerged": "Autor combinado",
|
||||||
"ToastAuthorUpdateSuccess": "Autor actualizado",
|
"ToastAuthorUpdateSuccess": "Autor actualizado",
|
||||||
"ToastAuthorUpdateSuccessNoImageFound": "Autor actualizado (Imagen no encontrada)",
|
"ToastAuthorUpdateSuccessNoImageFound": "Autor actualizado (Imagen no encontrada)",
|
||||||
|
"ToastBackupAppliedSuccess": "Copia de seguridad aplicada",
|
||||||
"ToastBackupCreateFailed": "Error al crear respaldo",
|
"ToastBackupCreateFailed": "Error al crear respaldo",
|
||||||
"ToastBackupCreateSuccess": "Respaldo creado",
|
"ToastBackupCreateSuccess": "Respaldo creado",
|
||||||
"ToastBackupDeleteFailed": "Error al eliminar respaldo",
|
"ToastBackupDeleteFailed": "Error al eliminar respaldo",
|
||||||
"ToastBackupDeleteSuccess": "Respaldo eliminado",
|
"ToastBackupDeleteSuccess": "Respaldo eliminado",
|
||||||
|
"ToastBackupInvalidMaxKeep": "Número no válido de copias de seguridad a conservar",
|
||||||
|
"ToastBackupInvalidMaxSize": "Tamaño máximo de copia de seguridad no válido",
|
||||||
|
"ToastBackupPathUpdateFailed": "Error al actualizar la ruta de la copia de seguridad",
|
||||||
"ToastBackupRestoreFailed": "Error al restaurar el respaldo",
|
"ToastBackupRestoreFailed": "Error al restaurar el respaldo",
|
||||||
"ToastBackupUploadFailed": "Error al subir el respaldo",
|
"ToastBackupUploadFailed": "Error al subir el respaldo",
|
||||||
"ToastBackupUploadSuccess": "Respaldo cargado",
|
"ToastBackupUploadSuccess": "Respaldo cargado",
|
||||||
|
"ToastBatchDeleteFailed": "Error al eliminar por lotes",
|
||||||
|
"ToastBatchDeleteSuccess": "Borrado por lotes correcto",
|
||||||
"ToastBatchUpdateFailed": "Subida masiva fallida",
|
"ToastBatchUpdateFailed": "Subida masiva fallida",
|
||||||
"ToastBatchUpdateSuccess": "Subida masiva exitosa",
|
"ToastBatchUpdateSuccess": "Subida masiva exitosa",
|
||||||
"ToastBookmarkCreateFailed": "Error al crear marcador",
|
"ToastBookmarkCreateFailed": "Error al crear marcador",
|
||||||
@@ -817,22 +849,46 @@
|
|||||||
"ToastCachePurgeSuccess": "Caché purgado de manera exitosa",
|
"ToastCachePurgeSuccess": "Caché purgado de manera exitosa",
|
||||||
"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",
|
||||||
|
"ToastChaptersRemoved": "Capítulos eliminados",
|
||||||
|
"ToastCollectionItemsAddFailed": "Artículo(s) añadido(s) a la colección fallido(s)",
|
||||||
|
"ToastCollectionItemsAddSuccess": "Artículo(s) añadido(s) a la colección correctamente",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Elementos(s) removidos de la colección",
|
"ToastCollectionItemsRemoveSuccess": "Elementos(s) removidos de la colección",
|
||||||
"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",
|
||||||
|
"ToastCoverUpdateFailed": "Error al actualizar la cubierta",
|
||||||
"ToastDeleteFileFailed": "Error el eliminar archivo",
|
"ToastDeleteFileFailed": "Error el eliminar archivo",
|
||||||
"ToastDeleteFileSuccess": "Archivo eliminado",
|
"ToastDeleteFileSuccess": "Archivo eliminado",
|
||||||
|
"ToastDeviceAddFailed": "Error al añadir dispositivo",
|
||||||
|
"ToastDeviceNameAlreadyExists": "Un libro electrónico ya existe con ese nombre",
|
||||||
|
"ToastDeviceTestEmailFailed": "Error al enviar correo de prueba",
|
||||||
|
"ToastDeviceTestEmailSuccess": "Correo electrónico de prueba enviado",
|
||||||
|
"ToastDeviceUpdateFailed": "Error al actualizar el dispositivo",
|
||||||
|
"ToastEmailSettingsUpdateFailed": "Error al actualizar la configuración del correo electrónico",
|
||||||
|
"ToastEmailSettingsUpdateSuccess": "Configuración del correo electrónico actualizada",
|
||||||
|
"ToastEncodeCancelFailed": "No se pudo cancelar la codificación",
|
||||||
|
"ToastEncodeCancelSucces": "Codificación cancelada",
|
||||||
|
"ToastEpisodeDownloadQueueClearFailed": "No se pudo borrar la cola",
|
||||||
|
"ToastEpisodeDownloadQueueClearSuccess": "Se borró la cola de descargas de los episodios",
|
||||||
"ToastErrorCannotShare": "No se puede compartir de forma nativa en este dispositivo",
|
"ToastErrorCannotShare": "No se puede compartir de forma nativa en este dispositivo",
|
||||||
"ToastFailedToLoadData": "Error al cargar data",
|
"ToastFailedToLoadData": "Error al cargar data",
|
||||||
|
"ToastFailedToShare": "Error al compartir",
|
||||||
|
"ToastFailedToUpdateAccount": "Error al actualizar la cuenta",
|
||||||
|
"ToastFailedToUpdateUser": "Error al actualizar el usuario",
|
||||||
|
"ToastInvalidImageUrl": "URL de la imagen no válida",
|
||||||
|
"ToastInvalidUrl": "URL no válida",
|
||||||
"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",
|
||||||
|
"ToastItemDeletedFailed": "Error al eliminar el elemento",
|
||||||
|
"ToastItemDeletedSuccess": "Elemento borrado",
|
||||||
"ToastItemDetailsUpdateFailed": "Error al actualizar los detalles del elemento",
|
"ToastItemDetailsUpdateFailed": "Error al actualizar los detalles del elemento",
|
||||||
"ToastItemDetailsUpdateSuccess": "Detalles del Elemento Actualizados",
|
"ToastItemDetailsUpdateSuccess": "Detalles del Elemento Actualizados",
|
||||||
"ToastItemMarkedAsFinishedFailed": "Error al marcar como terminado",
|
"ToastItemMarkedAsFinishedFailed": "Error al marcar como terminado",
|
||||||
"ToastItemMarkedAsFinishedSuccess": "Elemento marcado como terminado",
|
"ToastItemMarkedAsFinishedSuccess": "Elemento marcado como terminado",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "No se ha podido marcar como no finalizado",
|
"ToastItemMarkedAsNotFinishedFailed": "No se ha podido marcar como no finalizado",
|
||||||
"ToastItemMarkedAsNotFinishedSuccess": "Elemento marcado como No Terminado",
|
"ToastItemMarkedAsNotFinishedSuccess": "Elemento marcado como No Terminado",
|
||||||
|
"ToastItemUpdateFailed": "Error al actualizar el elemento",
|
||||||
|
"ToastItemUpdateSuccess": "Elemento actualizado",
|
||||||
"ToastLibraryCreateFailed": "Error al crear biblioteca",
|
"ToastLibraryCreateFailed": "Error al crear biblioteca",
|
||||||
"ToastLibraryCreateSuccess": "Biblioteca \"{0}\" creada",
|
"ToastLibraryCreateSuccess": "Biblioteca \"{0}\" creada",
|
||||||
"ToastLibraryDeleteFailed": "Error al eliminar biblioteca",
|
"ToastLibraryDeleteFailed": "Error al eliminar biblioteca",
|
||||||
@@ -841,31 +897,78 @@
|
|||||||
"ToastLibraryScanStarted": "Se inició el escaneo de la biblioteca",
|
"ToastLibraryScanStarted": "Se inició el escaneo de la biblioteca",
|
||||||
"ToastLibraryUpdateFailed": "Error al actualizar la biblioteca",
|
"ToastLibraryUpdateFailed": "Error al actualizar la biblioteca",
|
||||||
"ToastLibraryUpdateSuccess": "Biblioteca \"{0}\" actualizada",
|
"ToastLibraryUpdateSuccess": "Biblioteca \"{0}\" actualizada",
|
||||||
|
"ToastNameEmailRequired": "Nombre y correo electrónico obligatorios",
|
||||||
|
"ToastNameRequired": "Nombre obligatorio",
|
||||||
|
"ToastNewUserCreatedFailed": "Error al crear la cuenta: \"{0}\"",
|
||||||
|
"ToastNewUserCreatedSuccess": "Nueva cuenta creada",
|
||||||
|
"ToastNewUserLibraryError": "Debes seleccionar al menos una biblioteca",
|
||||||
|
"ToastNewUserPasswordError": "Debes tener una contraseña, solo el usuario root puede estar sin contraseña",
|
||||||
|
"ToastNewUserTagError": "Debes seleccionar al menos una etiqueta",
|
||||||
|
"ToastNewUserUsernameError": "Introduce un nombre de usuario",
|
||||||
|
"ToastNoUpdatesNecessary": "No es necesario actualizar",
|
||||||
|
"ToastNotificationCreateFailed": "Error al crear notificación",
|
||||||
|
"ToastNotificationDeleteFailed": "Error al borrar la notificación",
|
||||||
|
"ToastNotificationFailedMaximum": "El número máximo de intentos fallidos debe ser ≥ 0",
|
||||||
|
"ToastNotificationQueueMaximum": "La cola de notificación máxima debe ser ≥ 0",
|
||||||
|
"ToastNotificationSettingsUpdateFailed": "Error al actualizar los ajustes de la notificación",
|
||||||
|
"ToastNotificationSettingsUpdateSuccess": "Ajustes de la notificación actualizados",
|
||||||
|
"ToastNotificationTestTriggerFailed": "No se ha podido activar la notificación de prueba",
|
||||||
|
"ToastNotificationTestTriggerSuccess": "Notificación de prueba activada",
|
||||||
|
"ToastNotificationUpdateFailed": "No se ha podido actualizar la notificación",
|
||||||
|
"ToastNotificationUpdateSuccess": "Notificación actualizada",
|
||||||
"ToastPlaylistCreateFailed": "Error al crear la lista de reproducción",
|
"ToastPlaylistCreateFailed": "Error al crear la lista de reproducción",
|
||||||
"ToastPlaylistCreateSuccess": "Lista de reproducción creada",
|
"ToastPlaylistCreateSuccess": "Lista de reproducción creada",
|
||||||
"ToastPlaylistRemoveSuccess": "Lista de reproducción eliminada",
|
"ToastPlaylistRemoveSuccess": "Lista de reproducción eliminada",
|
||||||
"ToastPlaylistUpdateFailed": "Error al actualizar la lista de reproducción.",
|
"ToastPlaylistUpdateFailed": "Error al actualizar la lista de reproducción",
|
||||||
"ToastPlaylistUpdateSuccess": "Lista de reproducción actualizada",
|
"ToastPlaylistUpdateSuccess": "Lista de reproducción actualizada",
|
||||||
"ToastPodcastCreateFailed": "Error al crear podcast",
|
"ToastPodcastCreateFailed": "Error al crear podcast",
|
||||||
"ToastPodcastCreateSuccess": "Podcast creado",
|
"ToastPodcastCreateSuccess": "Podcast creado",
|
||||||
|
"ToastPodcastGetFeedFailed": "No se puede obtener el podcast",
|
||||||
|
"ToastPodcastNoEpisodesInFeed": "No se han encontrado episodios en el feed del RSS",
|
||||||
|
"ToastPodcastNoRssFeed": "El podcast no tiene feed RSS",
|
||||||
|
"ToastProviderCreatedFailed": "Error al añadir el proveedor",
|
||||||
|
"ToastProviderCreatedSuccess": "Nuevo proveedor añadido",
|
||||||
|
"ToastProviderNameAndUrlRequired": "Nombre y Url obligatorios",
|
||||||
|
"ToastProviderRemoveSuccess": "Proveedor eliminado",
|
||||||
"ToastRSSFeedCloseFailed": "Error al cerrar fuente RSS",
|
"ToastRSSFeedCloseFailed": "Error al cerrar fuente RSS",
|
||||||
"ToastRSSFeedCloseSuccess": "Fuente RSS cerrada",
|
"ToastRSSFeedCloseSuccess": "Fuente RSS cerrada",
|
||||||
|
"ToastRemoveFailed": "Error al eliminar",
|
||||||
"ToastRemoveItemFromCollectionFailed": "Error al eliminar el elemento de la colección",
|
"ToastRemoveItemFromCollectionFailed": "Error al eliminar el elemento de la colección",
|
||||||
"ToastRemoveItemFromCollectionSuccess": "Elemento eliminado de la colección.",
|
"ToastRemoveItemFromCollectionSuccess": "Elemento eliminado de la colección",
|
||||||
|
"ToastRemoveItemsWithIssuesFailed": "Error en la eliminación de artículos de biblioteca incorrectos",
|
||||||
|
"ToastRemoveItemsWithIssuesSuccess": "Se eliminaron artículos de biblioteca incorrectos",
|
||||||
|
"ToastRenameFailed": "Error al cambiar el nombre",
|
||||||
|
"ToastRescanFailed": "Error al volver a escanear para {0}",
|
||||||
|
"ToastRescanRemoved": "Se eliminó el elemento reescaneado",
|
||||||
|
"ToastRescanUpToDate": "Reescaneado del artículo completo, estaba actualizado",
|
||||||
|
"ToastRescanUpdated": "Reescaneado completado, el artículo ha sido actualizado",
|
||||||
|
"ToastScanFailed": "No se pudo escanear el elemento de la biblioteca",
|
||||||
|
"ToastSelectAtLeastOneUser": "Selecciona al menos un usuario",
|
||||||
"ToastSendEbookToDeviceFailed": "Error al enviar el ebook al dispositivo",
|
"ToastSendEbookToDeviceFailed": "Error al enviar el ebook al dispositivo",
|
||||||
"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": "Error al actualizar configuración del servidor",
|
"ToastServerSettingsUpdateFailed": "Error al actualizar configuración del servidor",
|
||||||
"ToastServerSettingsUpdateSuccess": "Configuración del servidor actualizada",
|
"ToastServerSettingsUpdateSuccess": "Configuración del servidor actualizada",
|
||||||
|
"ToastSessionCloseFailed": "Error al cerrar la sesión",
|
||||||
"ToastSessionDeleteFailed": "Error al eliminar sesión",
|
"ToastSessionDeleteFailed": "Error al eliminar sesión",
|
||||||
"ToastSessionDeleteSuccess": "Sesión eliminada",
|
"ToastSessionDeleteSuccess": "Sesión eliminada",
|
||||||
|
"ToastSlugMustChange": "El slug contiene caracteres no válidos",
|
||||||
|
"ToastSlugRequired": "Slug obligatorio",
|
||||||
"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": "Debe tener por lo menos 1 prefijo para ordenar",
|
"ToastSortingPrefixesEmptyError": "Debe tener por lo menos 1 prefijo para ordenar",
|
||||||
"ToastSortingPrefixesUpdateFailed": "Error al actualizar los prefijos de ordenar",
|
"ToastSortingPrefixesUpdateFailed": "Error al actualizar los prefijos de ordenar",
|
||||||
"ToastSortingPrefixesUpdateSuccess": "Prefijos de ordenar actualizaron ({0} items)",
|
"ToastSortingPrefixesUpdateSuccess": "Prefijos de ordenar actualizaron ({0} items)",
|
||||||
|
"ToastTitleRequired": "Título obligatorio",
|
||||||
|
"ToastUnknownError": "Error desconocido",
|
||||||
|
"ToastUnlinkOpenIdFailed": "Error al desvincular el usuario de OpenID",
|
||||||
|
"ToastUnlinkOpenIdSuccess": "Usuario desvinculado de OpenID",
|
||||||
"ToastUserDeleteFailed": "Error al eliminar el usuario",
|
"ToastUserDeleteFailed": "Error al eliminar el usuario",
|
||||||
"ToastUserDeleteSuccess": "Usuario eliminado"
|
"ToastUserDeleteSuccess": "Usuario eliminado",
|
||||||
|
"ToastUserPasswordChangeSuccess": "Contraseña modificada correctamente",
|
||||||
|
"ToastUserPasswordMismatch": "No coinciden las contraseñas",
|
||||||
|
"ToastUserPasswordMustChange": "La nueva contraseña no puede ser igual que la anterior",
|
||||||
|
"ToastUserRootRequireName": "Debes introducir un nombre de usuario root"
|
||||||
}
|
}
|
||||||
|
|||||||
+108
-9
@@ -20,6 +20,7 @@
|
|||||||
"ButtonClearFilter": "Effacer le filtre",
|
"ButtonClearFilter": "Effacer le filtre",
|
||||||
"ButtonCloseFeed": "Fermer le flux",
|
"ButtonCloseFeed": "Fermer le flux",
|
||||||
"ButtonCloseSession": "Fermer la session",
|
"ButtonCloseSession": "Fermer la session",
|
||||||
|
"ButtonCollections": "Collections",
|
||||||
"ButtonConfigureScanner": "Configurer l’analyse",
|
"ButtonConfigureScanner": "Configurer l’analyse",
|
||||||
"ButtonCreate": "Créer",
|
"ButtonCreate": "Créer",
|
||||||
"ButtonCreateBackup": "Créer une sauvegarde",
|
"ButtonCreateBackup": "Créer une sauvegarde",
|
||||||
@@ -49,9 +50,11 @@
|
|||||||
"ButtonNevermind": "Non merci",
|
"ButtonNevermind": "Non merci",
|
||||||
"ButtonNext": "Suivant",
|
"ButtonNext": "Suivant",
|
||||||
"ButtonNextChapter": "Chapitre suivant",
|
"ButtonNextChapter": "Chapitre suivant",
|
||||||
"ButtonNextItemInQueue": "Elément suivant de la file d'attente",
|
"ButtonNextItemInQueue": "Élément suivant dans la file d’attente",
|
||||||
|
"ButtonOk": "Ok",
|
||||||
"ButtonOpenFeed": "Ouvrir le flux",
|
"ButtonOpenFeed": "Ouvrir le flux",
|
||||||
"ButtonOpenManager": "Ouvrir le gestionnaire",
|
"ButtonOpenManager": "Ouvrir le gestionnaire",
|
||||||
|
"ButtonPause": "Pause",
|
||||||
"ButtonPlay": "Lire",
|
"ButtonPlay": "Lire",
|
||||||
"ButtonPlaying": "En lecture",
|
"ButtonPlaying": "En lecture",
|
||||||
"ButtonPlaylists": "Listes de lecture",
|
"ButtonPlaylists": "Listes de lecture",
|
||||||
@@ -94,7 +97,7 @@
|
|||||||
"ButtonStartMetadataEmbed": "Démarrer les Métadonnées intégrées",
|
"ButtonStartMetadataEmbed": "Démarrer les Métadonnées intégrées",
|
||||||
"ButtonStats": "Statistiques",
|
"ButtonStats": "Statistiques",
|
||||||
"ButtonSubmit": "Soumettre",
|
"ButtonSubmit": "Soumettre",
|
||||||
"ButtonUnlinkOpedId": "Dissocier OpenID",
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Téléverser",
|
"ButtonUpload": "Téléverser",
|
||||||
"ButtonUploadBackup": "Téléverser une sauvegarde",
|
"ButtonUploadBackup": "Téléverser une sauvegarde",
|
||||||
"ButtonUploadCover": "Téléverser une couverture",
|
"ButtonUploadCover": "Téléverser une couverture",
|
||||||
@@ -112,10 +115,12 @@
|
|||||||
"HeaderAppriseNotificationSettings": "Configuration des notifications Apprise",
|
"HeaderAppriseNotificationSettings": "Configuration des notifications Apprise",
|
||||||
"HeaderAudioTracks": "Pistes audio",
|
"HeaderAudioTracks": "Pistes audio",
|
||||||
"HeaderAudiobookTools": "Outils de gestion de fichiers de livres audio",
|
"HeaderAudiobookTools": "Outils de gestion de fichiers de livres audio",
|
||||||
|
"HeaderAuthentication": "Authentification",
|
||||||
"HeaderBackups": "Sauvegardes",
|
"HeaderBackups": "Sauvegardes",
|
||||||
"HeaderChangePassword": "Modifier le mot de passe",
|
"HeaderChangePassword": "Modifier le mot de passe",
|
||||||
"HeaderChapters": "Chapitres",
|
"HeaderChapters": "Chapitres",
|
||||||
"HeaderChooseAFolder": "Choisir un dossier",
|
"HeaderChooseAFolder": "Choisir un dossier",
|
||||||
|
"HeaderCollection": "Collection",
|
||||||
"HeaderCollectionItems": "Entrées de la collection",
|
"HeaderCollectionItems": "Entrées de la collection",
|
||||||
"HeaderCover": "Couverture",
|
"HeaderCover": "Couverture",
|
||||||
"HeaderCurrentDownloads": "Téléchargements en cours",
|
"HeaderCurrentDownloads": "Téléchargements en cours",
|
||||||
@@ -153,10 +158,12 @@
|
|||||||
"HeaderNewLibrary": "Nouvelle bibliothèque",
|
"HeaderNewLibrary": "Nouvelle bibliothèque",
|
||||||
"HeaderNotificationCreate": "Créer une notification",
|
"HeaderNotificationCreate": "Créer une notification",
|
||||||
"HeaderNotificationUpdate": "Mise à jour de la notification",
|
"HeaderNotificationUpdate": "Mise à jour de la notification",
|
||||||
|
"HeaderNotifications": "Notifications",
|
||||||
"HeaderOpenIDConnectAuthentication": "Authentification via OpenID Connect",
|
"HeaderOpenIDConnectAuthentication": "Authentification via OpenID Connect",
|
||||||
"HeaderOpenRSSFeed": "Ouvrir le flux RSS",
|
"HeaderOpenRSSFeed": "Ouvrir le flux RSS",
|
||||||
"HeaderOtherFiles": "Autres fichiers",
|
"HeaderOtherFiles": "Autres fichiers",
|
||||||
"HeaderPasswordAuthentication": "Authentification par mot de passe",
|
"HeaderPasswordAuthentication": "Authentification par mot de passe",
|
||||||
|
"HeaderPermissions": "Permissions",
|
||||||
"HeaderPlayerQueue": "Liste d’écoute",
|
"HeaderPlayerQueue": "Liste d’écoute",
|
||||||
"HeaderPlayerSettings": "Paramètres du lecteur",
|
"HeaderPlayerSettings": "Paramètres du lecteur",
|
||||||
"HeaderPlaylist": "Liste de lecture",
|
"HeaderPlaylist": "Liste de lecture",
|
||||||
@@ -171,6 +178,7 @@
|
|||||||
"HeaderSavedMediaProgress": "Progression de la sauvegarde des médias",
|
"HeaderSavedMediaProgress": "Progression de la sauvegarde des médias",
|
||||||
"HeaderSchedule": "Programmation",
|
"HeaderSchedule": "Programmation",
|
||||||
"HeaderScheduleLibraryScans": "Analyse automatique de la bibliothèque",
|
"HeaderScheduleLibraryScans": "Analyse automatique de la bibliothèque",
|
||||||
|
"HeaderSession": "Session",
|
||||||
"HeaderSetBackupSchedule": "Activer la sauvegarde automatique",
|
"HeaderSetBackupSchedule": "Activer la sauvegarde automatique",
|
||||||
"HeaderSettings": "Paramètres",
|
"HeaderSettings": "Paramètres",
|
||||||
"HeaderSettingsDisplay": "Affichage",
|
"HeaderSettingsDisplay": "Affichage",
|
||||||
@@ -183,6 +191,7 @@
|
|||||||
"HeaderStatsMinutesListeningChart": "Minutes d’écoute (7 derniers jours)",
|
"HeaderStatsMinutesListeningChart": "Minutes d’écoute (7 derniers jours)",
|
||||||
"HeaderStatsRecentSessions": "Sessions récentes",
|
"HeaderStatsRecentSessions": "Sessions récentes",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Auteurs",
|
"HeaderStatsTop10Authors": "Top 10 Auteurs",
|
||||||
|
"HeaderStatsTop5Genres": "Top 5 Genres",
|
||||||
"HeaderTableOfContents": "Table des matières",
|
"HeaderTableOfContents": "Table des matières",
|
||||||
"HeaderTools": "Outils",
|
"HeaderTools": "Outils",
|
||||||
"HeaderUpdateAccount": "Mettre à jour le compte",
|
"HeaderUpdateAccount": "Mettre à jour le compte",
|
||||||
@@ -197,6 +206,7 @@
|
|||||||
"LabelAbridgedUnchecked": "Intégral (non vérifié)",
|
"LabelAbridgedUnchecked": "Intégral (non vérifié)",
|
||||||
"LabelAccessibleBy": "Accessible par",
|
"LabelAccessibleBy": "Accessible par",
|
||||||
"LabelAccountType": "Type de compte",
|
"LabelAccountType": "Type de compte",
|
||||||
|
"LabelAccountTypeAdmin": "Admin",
|
||||||
"LabelAccountTypeGuest": "Invité",
|
"LabelAccountTypeGuest": "Invité",
|
||||||
"LabelAccountTypeUser": "Utilisateur",
|
"LabelAccountTypeUser": "Utilisateur",
|
||||||
"LabelActivity": "Activité",
|
"LabelActivity": "Activité",
|
||||||
@@ -232,6 +242,7 @@
|
|||||||
"LabelBackupsMaxBackupSizeHelp": "Afin de prévenir les mauvaises configuration, la sauvegarde échouera si elle excède la taille limite.",
|
"LabelBackupsMaxBackupSizeHelp": "Afin de prévenir les mauvaises configuration, la sauvegarde échouera si elle excède la taille limite.",
|
||||||
"LabelBackupsNumberToKeep": "Nombre de sauvegardes à conserver",
|
"LabelBackupsNumberToKeep": "Nombre de sauvegardes à conserver",
|
||||||
"LabelBackupsNumberToKeepHelp": "Seule une sauvegarde sera supprimée à la fois. Si vous avez déjà plus de sauvegardes à effacer, vous devez les supprimer manuellement.",
|
"LabelBackupsNumberToKeepHelp": "Seule une sauvegarde sera supprimée à la fois. Si vous avez déjà plus de sauvegardes à effacer, vous devez les supprimer manuellement.",
|
||||||
|
"LabelBitrate": "Bitrate",
|
||||||
"LabelBooks": "Livres",
|
"LabelBooks": "Livres",
|
||||||
"LabelButtonText": "Texte du bouton",
|
"LabelButtonText": "Texte du bouton",
|
||||||
"LabelByAuthor": "par {0}",
|
"LabelByAuthor": "par {0}",
|
||||||
@@ -242,8 +253,11 @@
|
|||||||
"LabelChaptersFound": "chapitres trouvés",
|
"LabelChaptersFound": "chapitres trouvés",
|
||||||
"LabelClickForMoreInfo": "Cliquez ici pour plus d’informations",
|
"LabelClickForMoreInfo": "Cliquez ici pour plus d’informations",
|
||||||
"LabelClosePlayer": "Fermer le lecteur",
|
"LabelClosePlayer": "Fermer le lecteur",
|
||||||
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Réduire les séries",
|
"LabelCollapseSeries": "Réduire les séries",
|
||||||
"LabelCollapseSubSeries": "Replier les sous-séries",
|
"LabelCollapseSubSeries": "Replier les sous-séries",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
|
"LabelCollections": "Collections",
|
||||||
"LabelComplete": "Complet",
|
"LabelComplete": "Complet",
|
||||||
"LabelConfirmPassword": "Confirmer le mot de passe",
|
"LabelConfirmPassword": "Confirmer le mot de passe",
|
||||||
"LabelContinueListening": "Continuer la lecture",
|
"LabelContinueListening": "Continuer la lecture",
|
||||||
@@ -259,6 +273,7 @@
|
|||||||
"LabelDatetime": "Date",
|
"LabelDatetime": "Date",
|
||||||
"LabelDays": "Jours",
|
"LabelDays": "Jours",
|
||||||
"LabelDeleteFromFileSystemCheckbox": "Supprimer du système de fichiers (décocher pour ne supprimer que de la base de données)",
|
"LabelDeleteFromFileSystemCheckbox": "Supprimer du système de fichiers (décocher pour ne supprimer que de la base de données)",
|
||||||
|
"LabelDescription": "Description",
|
||||||
"LabelDeselectAll": "Tout déselectionner",
|
"LabelDeselectAll": "Tout déselectionner",
|
||||||
"LabelDevice": "Appareil",
|
"LabelDevice": "Appareil",
|
||||||
"LabelDeviceInfo": "Détail de l’appareil",
|
"LabelDeviceInfo": "Détail de l’appareil",
|
||||||
@@ -303,8 +318,9 @@
|
|||||||
"LabelFetchingMetadata": "Récupération des métadonnées",
|
"LabelFetchingMetadata": "Récupération des métadonnées",
|
||||||
"LabelFile": "Fichier",
|
"LabelFile": "Fichier",
|
||||||
"LabelFileBirthtime": "Création du fichier",
|
"LabelFileBirthtime": "Création du fichier",
|
||||||
|
"LabelFileBornDate": "Créé {0}",
|
||||||
"LabelFileModified": "Modification du fichier",
|
"LabelFileModified": "Modification du fichier",
|
||||||
"LabelFileModifiedDate": "{0} modifiés",
|
"LabelFileModifiedDate": "Modifié le {0}",
|
||||||
"LabelFilename": "Nom de fichier",
|
"LabelFilename": "Nom de fichier",
|
||||||
"LabelFilterByUser": "Filtrer par utilisateur",
|
"LabelFilterByUser": "Filtrer par utilisateur",
|
||||||
"LabelFindEpisodes": "Trouver des épisodes",
|
"LabelFindEpisodes": "Trouver des épisodes",
|
||||||
@@ -317,6 +333,9 @@
|
|||||||
"LabelFontItalic": "Italique",
|
"LabelFontItalic": "Italique",
|
||||||
"LabelFontScale": "Taille de la police de caractère",
|
"LabelFontScale": "Taille de la police de caractère",
|
||||||
"LabelFontStrikethrough": "Barrer",
|
"LabelFontStrikethrough": "Barrer",
|
||||||
|
"LabelFormat": "Format",
|
||||||
|
"LabelGenre": "Genre",
|
||||||
|
"LabelGenres": "Genres",
|
||||||
"LabelHardDeleteFile": "Suppression du fichier",
|
"LabelHardDeleteFile": "Suppression du fichier",
|
||||||
"LabelHasEbook": "A un livre numérique",
|
"LabelHasEbook": "A un livre numérique",
|
||||||
"LabelHasSupplementaryEbook": "A un livre numérique supplémentaire",
|
"LabelHasSupplementaryEbook": "A un livre numérique supplémentaire",
|
||||||
@@ -363,6 +382,9 @@
|
|||||||
"LabelLimit": "Limite",
|
"LabelLimit": "Limite",
|
||||||
"LabelLineSpacing": "Espacement des lignes",
|
"LabelLineSpacing": "Espacement des lignes",
|
||||||
"LabelListenAgain": "Écouter à nouveau",
|
"LabelListenAgain": "Écouter à nouveau",
|
||||||
|
"LabelLogLevelDebug": "Débogage",
|
||||||
|
"LabelLogLevelInfo": "Info",
|
||||||
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Rechercher les nouveaux épisodes après cette date",
|
"LabelLookForNewEpisodesAfterDate": "Rechercher les nouveaux épisodes après cette date",
|
||||||
"LabelLowestPriority": "Priorité la plus basse",
|
"LabelLowestPriority": "Priorité la plus basse",
|
||||||
"LabelMatchExistingUsersBy": "Correspondance avec les utilisateurs existants",
|
"LabelMatchExistingUsersBy": "Correspondance avec les utilisateurs existants",
|
||||||
@@ -373,6 +395,8 @@
|
|||||||
"LabelMetaTags": "Balises de métadonnée",
|
"LabelMetaTags": "Balises de métadonnée",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "Les sources de métadonnées ayant une priorité plus élevée auront la priorité sur celles ayant une priorité moins élevée",
|
"LabelMetadataOrderOfPrecedenceDescription": "Les sources de métadonnées ayant une priorité plus élevée auront la priorité sur celles ayant une priorité moins élevée",
|
||||||
"LabelMetadataProvider": "Fournisseur de métadonnées",
|
"LabelMetadataProvider": "Fournisseur de métadonnées",
|
||||||
|
"LabelMinute": "Minute",
|
||||||
|
"LabelMinutes": "Minutes",
|
||||||
"LabelMissing": "Manquant",
|
"LabelMissing": "Manquant",
|
||||||
"LabelMissingEbook": "Ne possède aucun livre numérique",
|
"LabelMissingEbook": "Ne possède aucun livre numérique",
|
||||||
"LabelMissingSupplementaryEbook": "Ne possède aucun livre numérique supplémentaire",
|
"LabelMissingSupplementaryEbook": "Ne possède aucun livre numérique supplémentaire",
|
||||||
@@ -393,12 +417,13 @@
|
|||||||
"LabelNoEpisodesSelected": "Aucun épisode sélectionné",
|
"LabelNoEpisodesSelected": "Aucun épisode sélectionné",
|
||||||
"LabelNotFinished": "Non terminé",
|
"LabelNotFinished": "Non terminé",
|
||||||
"LabelNotStarted": "Pas commencé",
|
"LabelNotStarted": "Pas commencé",
|
||||||
|
"LabelNotes": "Notes",
|
||||||
"LabelNotificationAppriseURL": "URL(s) d’Apprise",
|
"LabelNotificationAppriseURL": "URL(s) d’Apprise",
|
||||||
"LabelNotificationAvailableVariables": "Variables disponibles",
|
"LabelNotificationAvailableVariables": "Variables disponibles",
|
||||||
"LabelNotificationBodyTemplate": "Modèle de message",
|
"LabelNotificationBodyTemplate": "Modèle de message",
|
||||||
"LabelNotificationEvent": "Evènement de Notification",
|
"LabelNotificationEvent": "Evènement de Notification",
|
||||||
"LabelNotificationTitleTemplate": "Modèle de titre",
|
"LabelNotificationTitleTemplate": "Modèle de titre",
|
||||||
"LabelNotificationsMaxFailedAttempts": "Nombres de tentatives d’envoi",
|
"LabelNotificationsMaxFailedAttempts": "Nombre maximal de tentatives échouées atteint",
|
||||||
"LabelNotificationsMaxFailedAttemptsHelp": "La notification est abandonnée une fois ce seuil atteint",
|
"LabelNotificationsMaxFailedAttemptsHelp": "La notification est abandonnée une fois ce seuil atteint",
|
||||||
"LabelNotificationsMaxQueueSize": "Nombres de notifications maximum à mettre en attente",
|
"LabelNotificationsMaxQueueSize": "Nombres de notifications maximum à mettre en attente",
|
||||||
"LabelNotificationsMaxQueueSizeHelp": "La limite de notification est de un évènement par seconde. Les notifications seront ignorées si la file d’attente est à son maximum. Cela empêche un flot trop important.",
|
"LabelNotificationsMaxQueueSizeHelp": "La limite de notification est de un évènement par seconde. Les notifications seront ignorées si la file d’attente est à son maximum. Cela empêche un flot trop important.",
|
||||||
@@ -411,6 +436,7 @@
|
|||||||
"LabelOverwrite": "Écraser",
|
"LabelOverwrite": "Écraser",
|
||||||
"LabelPassword": "Mot de passe",
|
"LabelPassword": "Mot de passe",
|
||||||
"LabelPath": "Chemin",
|
"LabelPath": "Chemin",
|
||||||
|
"LabelPermanent": "Permanent",
|
||||||
"LabelPermissionsAccessAllLibraries": "Peut accéder à toutes les bibliothèque",
|
"LabelPermissionsAccessAllLibraries": "Peut accéder à toutes les bibliothèque",
|
||||||
"LabelPermissionsAccessAllTags": "Peut accéder à toutes les étiquettes",
|
"LabelPermissionsAccessAllTags": "Peut accéder à toutes les étiquettes",
|
||||||
"LabelPermissionsAccessExplicitContent": "Peut accéder au contenu restreint",
|
"LabelPermissionsAccessExplicitContent": "Peut accéder au contenu restreint",
|
||||||
@@ -423,8 +449,11 @@
|
|||||||
"LabelPlayMethod": "Méthode d’écoute",
|
"LabelPlayMethod": "Méthode d’écoute",
|
||||||
"LabelPlayerChapterNumberMarker": "{0} sur {1}",
|
"LabelPlayerChapterNumberMarker": "{0} sur {1}",
|
||||||
"LabelPlaylists": "Listes de lecture",
|
"LabelPlaylists": "Listes de lecture",
|
||||||
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcastSearchRegion": "Région de recherche de podcasts",
|
"LabelPodcastSearchRegion": "Région de recherche de podcasts",
|
||||||
"LabelPodcastType": "Type de Podcast",
|
"LabelPodcastType": "Type de Podcast",
|
||||||
|
"LabelPodcasts": "Podcasts",
|
||||||
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Préfixes à Ignorer (Insensible à la Casse)",
|
"LabelPrefixesToIgnore": "Préfixes à Ignorer (Insensible à la Casse)",
|
||||||
"LabelPreventIndexing": "Empêcher l’indexation de votre flux par les bases de données iTunes et Google podcast",
|
"LabelPreventIndexing": "Empêcher l’indexation de votre flux par les bases de données iTunes et Google podcast",
|
||||||
"LabelPrimaryEbook": "Premier livre numérique",
|
"LabelPrimaryEbook": "Premier livre numérique",
|
||||||
@@ -440,7 +469,7 @@
|
|||||||
"LabelRSSFeedCustomOwnerName": "Nom propriétaire personnalisé",
|
"LabelRSSFeedCustomOwnerName": "Nom propriétaire personnalisé",
|
||||||
"LabelRSSFeedOpen": "Flux RSS ouvert",
|
"LabelRSSFeedOpen": "Flux RSS ouvert",
|
||||||
"LabelRSSFeedPreventIndexing": "Empêcher l’indexation",
|
"LabelRSSFeedPreventIndexing": "Empêcher l’indexation",
|
||||||
"LabelRSSFeedSlug": "Balise URL du flux RSS",
|
"LabelRSSFeedSlug": "Identifiant d’URL du flux RSS",
|
||||||
"LabelRSSFeedURL": "Adresse du flux RSS",
|
"LabelRSSFeedURL": "Adresse du flux RSS",
|
||||||
"LabelRandomly": "Au hasard",
|
"LabelRandomly": "Au hasard",
|
||||||
"LabelReAddSeriesToContinueListening": "Ajouter à nouveau la série pour continuer à l’écouter",
|
"LabelReAddSeriesToContinueListening": "Ajouter à nouveau la série pour continuer à l’écouter",
|
||||||
@@ -517,7 +546,7 @@
|
|||||||
"LabelShowSubtitles": "Afficher les sous-titres",
|
"LabelShowSubtitles": "Afficher les sous-titres",
|
||||||
"LabelSize": "Taille",
|
"LabelSize": "Taille",
|
||||||
"LabelSleepTimer": "Minuterie de mise en veille",
|
"LabelSleepTimer": "Minuterie de mise en veille",
|
||||||
"LabelSlug": "Balise",
|
"LabelSlug": "Identifiant d’URL",
|
||||||
"LabelStart": "Démarrer",
|
"LabelStart": "Démarrer",
|
||||||
"LabelStartTime": "Heure de démarrage",
|
"LabelStartTime": "Heure de démarrage",
|
||||||
"LabelStarted": "Démarré",
|
"LabelStarted": "Démarré",
|
||||||
@@ -532,6 +561,7 @@
|
|||||||
"LabelStatsInARow": "d’affilée(s)",
|
"LabelStatsInARow": "d’affilée(s)",
|
||||||
"LabelStatsItemsFinished": "Éléments terminés",
|
"LabelStatsItemsFinished": "Éléments terminés",
|
||||||
"LabelStatsItemsInLibrary": "Éléments dans la bibliothèque",
|
"LabelStatsItemsInLibrary": "Éléments dans la bibliothèque",
|
||||||
|
"LabelStatsMinutes": "minutes",
|
||||||
"LabelStatsMinutesListening": "Minutes d’écoute",
|
"LabelStatsMinutesListening": "Minutes d’écoute",
|
||||||
"LabelStatsOverallDays": "Nombre total de jours",
|
"LabelStatsOverallDays": "Nombre total de jours",
|
||||||
"LabelStatsOverallHours": "Nombre total d’heures",
|
"LabelStatsOverallHours": "Nombre total d’heures",
|
||||||
@@ -552,6 +582,7 @@
|
|||||||
"LabelThemeLight": "Clair",
|
"LabelThemeLight": "Clair",
|
||||||
"LabelTimeBase": "Base de temps",
|
"LabelTimeBase": "Base de temps",
|
||||||
"LabelTimeDurationXHours": "{0} heures",
|
"LabelTimeDurationXHours": "{0} heures",
|
||||||
|
"LabelTimeDurationXMinutes": "{0} minutes",
|
||||||
"LabelTimeDurationXSeconds": "{0} secondes",
|
"LabelTimeDurationXSeconds": "{0} secondes",
|
||||||
"LabelTimeInMinutes": "Temps en minutes",
|
"LabelTimeInMinutes": "Temps en minutes",
|
||||||
"LabelTimeListened": "Temps d’écoute",
|
"LabelTimeListened": "Temps d’écoute",
|
||||||
@@ -573,6 +604,7 @@
|
|||||||
"LabelTracksMultiTrack": "Piste multiple",
|
"LabelTracksMultiTrack": "Piste multiple",
|
||||||
"LabelTracksNone": "Aucune piste",
|
"LabelTracksNone": "Aucune piste",
|
||||||
"LabelTracksSingleTrack": "Piste simple",
|
"LabelTracksSingleTrack": "Piste simple",
|
||||||
|
"LabelType": "Type",
|
||||||
"LabelUnabridged": "Version intégrale",
|
"LabelUnabridged": "Version intégrale",
|
||||||
"LabelUndo": "Annuler",
|
"LabelUndo": "Annuler",
|
||||||
"LabelUnknown": "Inconnu",
|
"LabelUnknown": "Inconnu",
|
||||||
@@ -590,10 +622,12 @@
|
|||||||
"LabelUser": "Utilisateur",
|
"LabelUser": "Utilisateur",
|
||||||
"LabelUsername": "Nom d’utilisateur",
|
"LabelUsername": "Nom d’utilisateur",
|
||||||
"LabelValue": "Valeur",
|
"LabelValue": "Valeur",
|
||||||
|
"LabelVersion": "Version",
|
||||||
"LabelViewBookmarks": "Afficher les favoris",
|
"LabelViewBookmarks": "Afficher les favoris",
|
||||||
"LabelViewChapters": "Afficher les chapitres",
|
"LabelViewChapters": "Afficher les chapitres",
|
||||||
"LabelViewPlayerSettings": "Afficher les paramètres du lecteur",
|
"LabelViewPlayerSettings": "Afficher les paramètres du lecteur",
|
||||||
"LabelViewQueue": "Afficher la liste de lecture",
|
"LabelViewQueue": "Afficher la liste de lecture",
|
||||||
|
"LabelVolume": "Volume",
|
||||||
"LabelWeekdaysToRun": "Jours de la semaine à exécuter",
|
"LabelWeekdaysToRun": "Jours de la semaine à exécuter",
|
||||||
"LabelXBooks": "{0} livres",
|
"LabelXBooks": "{0} livres",
|
||||||
"LabelXItems": "{0} éléments",
|
"LabelXItems": "{0} éléments",
|
||||||
@@ -665,7 +699,7 @@
|
|||||||
"MessageEmbedFailed": "Échec de l’intégration !",
|
"MessageEmbedFailed": "Échec de l’intégration !",
|
||||||
"MessageEmbedFinished": "Intégration terminée !",
|
"MessageEmbedFinished": "Intégration terminée !",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
|
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
|
||||||
"MessageEreaderDevices": "Pour garantir la livraison des livres électroniques, vous devrez peut-être ajouter le courriel ci-dessus comme expéditeur valide pour chaque appareil répertorié ci-dessous.",
|
"MessageEreaderDevices": "Pour garantir l’envoi des livres électroniques, vous devrez peut-être ajouter le courriel ci-dessus comme expéditeur valide pour chaque appareil répertorié ci-dessous.",
|
||||||
"MessageFeedURLWillBe": "L’URL du flux sera {0}",
|
"MessageFeedURLWillBe": "L’URL du flux sera {0}",
|
||||||
"MessageFetching": "Récupération…",
|
"MessageFetching": "Récupération…",
|
||||||
"MessageForceReScanDescription": "analysera de nouveau tous les fichiers. Les étiquettes ID3 des fichiers audio, les fichiers OPF et les fichiers texte seront analysés comme s’ils étaient nouveaux.",
|
"MessageForceReScanDescription": "analysera de nouveau tous les fichiers. Les étiquettes ID3 des fichiers audio, les fichiers OPF et les fichiers texte seront analysés comme s’ils étaient nouveaux.",
|
||||||
@@ -678,7 +712,7 @@
|
|||||||
"MessageLoading": "Chargement…",
|
"MessageLoading": "Chargement…",
|
||||||
"MessageLoadingFolders": "Chargement des dossiers…",
|
"MessageLoadingFolders": "Chargement des dossiers…",
|
||||||
"MessageLogsDescription": "Les journaux sont stockés dans <code>/metadata/logs</code> sous forme de fichiers JSON. Les journaux d’incidents sont stockés dans <code>/metadata/logs/crash_logs.txt</code>.",
|
"MessageLogsDescription": "Les journaux sont stockés dans <code>/metadata/logs</code> sous forme de fichiers JSON. Les journaux d’incidents sont stockés dans <code>/metadata/logs/crash_logs.txt</code>.",
|
||||||
"MessageM4BFailed": "M4B a échoué !",
|
"MessageM4BFailed": "Échec de la conversion en M4B !",
|
||||||
"MessageM4BFinished": "M4B terminé !",
|
"MessageM4BFinished": "M4B terminé !",
|
||||||
"MessageMapChapterTitles": "Faire correspondre les titres de chapitres avec ceux de vos livres audio existants sans ajuster les horodatages",
|
"MessageMapChapterTitles": "Faire correspondre les titres de chapitres avec ceux de vos livres audio existants sans ajuster les horodatages",
|
||||||
"MessageMarkAllEpisodesFinished": "Marquer tous les épisodes terminés",
|
"MessageMarkAllEpisodesFinished": "Marquer tous les épisodes terminés",
|
||||||
@@ -770,9 +804,12 @@
|
|||||||
"StatsBooksFinishedThisYear": "Quelques livres terminés cette année…",
|
"StatsBooksFinishedThisYear": "Quelques livres terminés cette année…",
|
||||||
"StatsBooksListenedTo": "livres écoutés",
|
"StatsBooksListenedTo": "livres écoutés",
|
||||||
"StatsCollectionGrewTo": "Votre collection de livres a atteint…",
|
"StatsCollectionGrewTo": "Votre collection de livres a atteint…",
|
||||||
|
"StatsSessions": "sessions",
|
||||||
"StatsSpentListening": "temps passé à écouter",
|
"StatsSpentListening": "temps passé à écouter",
|
||||||
"StatsTopAuthor": "TOP AUTEUR",
|
"StatsTopAuthor": "TOP AUTEUR",
|
||||||
"StatsTopAuthors": "TOP AUTEURS",
|
"StatsTopAuthors": "TOP AUTEURS",
|
||||||
|
"StatsTopGenre": "TOP GENRE",
|
||||||
|
"StatsTopGenres": "TOP GENRES",
|
||||||
"StatsTopMonth": "TOP MOIS",
|
"StatsTopMonth": "TOP MOIS",
|
||||||
"StatsTopNarrator": "TOP NARRATEUR",
|
"StatsTopNarrator": "TOP NARRATEUR",
|
||||||
"StatsTopNarrators": "TOP NARRATEURS",
|
"StatsTopNarrators": "TOP NARRATEURS",
|
||||||
@@ -827,17 +864,32 @@
|
|||||||
"ToastDeviceNameAlreadyExists": "Un appareil de lecture avec ce nom existe déjà",
|
"ToastDeviceNameAlreadyExists": "Un appareil de lecture avec ce nom existe déjà",
|
||||||
"ToastDeviceTestEmailFailed": "Échec de l’envoi du courriel de test",
|
"ToastDeviceTestEmailFailed": "Échec de l’envoi du courriel de test",
|
||||||
"ToastDeviceTestEmailSuccess": "Courriel de test envoyé",
|
"ToastDeviceTestEmailSuccess": "Courriel de test envoyé",
|
||||||
|
"ToastDeviceUpdateFailed": "Échec de la mise à jour",
|
||||||
"ToastEmailSettingsUpdateFailed": "Échec de la mise à jour des paramètres de messagerie",
|
"ToastEmailSettingsUpdateFailed": "Échec de la mise à jour des paramètres de messagerie",
|
||||||
|
"ToastEmailSettingsUpdateSuccess": "Paramètres de messagerie mis à jour",
|
||||||
|
"ToastEncodeCancelFailed": "Échec de l’annulation de l’encodage",
|
||||||
|
"ToastEncodeCancelSucces": "Encodage annulé",
|
||||||
|
"ToastEpisodeDownloadQueueClearFailed": "Échec de la suppression de la file d'attente",
|
||||||
|
"ToastEpisodeDownloadQueueClearSuccess": "File d’attente de téléchargement des épisodes effacée",
|
||||||
"ToastErrorCannotShare": "Impossible de partager nativement sur cet appareil",
|
"ToastErrorCannotShare": "Impossible de partager nativement sur cet appareil",
|
||||||
"ToastFailedToLoadData": "Échec du chargement des données",
|
"ToastFailedToLoadData": "Échec du chargement des données",
|
||||||
|
"ToastFailedToShare": "Échec du partage",
|
||||||
|
"ToastFailedToUpdateAccount": "Échec de la mise à jour du compte",
|
||||||
|
"ToastFailedToUpdateUser": "La mise a jour de l'utilisateur à échouée",
|
||||||
|
"ToastInvalidImageUrl": "URL de l'image invalide",
|
||||||
|
"ToastInvalidUrl": "URL invalide",
|
||||||
"ToastItemCoverUpdateFailed": "Échec de la mise à jour de la couverture de l’élément",
|
"ToastItemCoverUpdateFailed": "Échec de la mise à jour de la couverture de l’élément",
|
||||||
"ToastItemCoverUpdateSuccess": "Couverture mise à jour",
|
"ToastItemCoverUpdateSuccess": "Couverture mise à jour",
|
||||||
|
"ToastItemDeletedFailed": "La suppression de l'élément à échouée",
|
||||||
|
"ToastItemDeletedSuccess": "Élément supprimé",
|
||||||
"ToastItemDetailsUpdateFailed": "Échec de la mise à jour des détails de l’élément",
|
"ToastItemDetailsUpdateFailed": "Échec de la mise à jour des détails de l’élément",
|
||||||
"ToastItemDetailsUpdateSuccess": "Détails de l’élément mis à jour",
|
"ToastItemDetailsUpdateSuccess": "Détails de l’élément mis à jour",
|
||||||
"ToastItemMarkedAsFinishedFailed": "Échec de l’annotation terminée",
|
"ToastItemMarkedAsFinishedFailed": "Échec de l’annotation terminée",
|
||||||
"ToastItemMarkedAsFinishedSuccess": "Article marqué comme terminé",
|
"ToastItemMarkedAsFinishedSuccess": "Article marqué comme terminé",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "Échec de l’annotation non-terminée",
|
"ToastItemMarkedAsNotFinishedFailed": "Échec de l’annotation non-terminée",
|
||||||
"ToastItemMarkedAsNotFinishedSuccess": "Article marqué comme non-terminé",
|
"ToastItemMarkedAsNotFinishedSuccess": "Article marqué comme non-terminé",
|
||||||
|
"ToastItemUpdateFailed": "La mise a jour de l’élément à échoué",
|
||||||
|
"ToastItemUpdateSuccess": "Élément mis a jour",
|
||||||
"ToastLibraryCreateFailed": "Échec de la création de bibliothèque",
|
"ToastLibraryCreateFailed": "Échec de la création de bibliothèque",
|
||||||
"ToastLibraryCreateSuccess": "Bibliothèque « {0} » créée",
|
"ToastLibraryCreateSuccess": "Bibliothèque « {0} » créée",
|
||||||
"ToastLibraryDeleteFailed": "Échec de la suppression de la bibliothèque",
|
"ToastLibraryDeleteFailed": "Échec de la suppression de la bibliothèque",
|
||||||
@@ -846,6 +898,25 @@
|
|||||||
"ToastLibraryScanStarted": "Analyse de la bibliothèque démarrée",
|
"ToastLibraryScanStarted": "Analyse de la bibliothèque démarrée",
|
||||||
"ToastLibraryUpdateFailed": "Échec de la mise à jour de la bibliothèque",
|
"ToastLibraryUpdateFailed": "Échec de la mise à jour de la bibliothèque",
|
||||||
"ToastLibraryUpdateSuccess": "Bibliothèque « {0} » mise à jour",
|
"ToastLibraryUpdateSuccess": "Bibliothèque « {0} » mise à jour",
|
||||||
|
"ToastNameEmailRequired": "Le nom et le courriel sont requis",
|
||||||
|
"ToastNameRequired": "Le nom est requis",
|
||||||
|
"ToastNewUserCreatedFailed": "La création du compte à échouée : « {0} »",
|
||||||
|
"ToastNewUserCreatedSuccess": "Nouveau compte créé",
|
||||||
|
"ToastNewUserLibraryError": "Au moins une bibliothèque est requise",
|
||||||
|
"ToastNewUserPasswordError": "Un mot de passe est requis, seul l’utilisateur root peut avoir un mot de passe vide",
|
||||||
|
"ToastNewUserTagError": "Au moins un tag est requis",
|
||||||
|
"ToastNewUserUsernameError": "Entrez un nom d’utilisateur",
|
||||||
|
"ToastNoUpdatesNecessary": "Aucune mise à jour nécessaire",
|
||||||
|
"ToastNotificationCreateFailed": "La création de la notification à échouée",
|
||||||
|
"ToastNotificationDeleteFailed": "La suppression de la notification à échouée",
|
||||||
|
"ToastNotificationFailedMaximum": "Le nombre maximum de tentatives échouées doit être >= 0",
|
||||||
|
"ToastNotificationQueueMaximum": "Le nombre de notification maximum doit être >= 0",
|
||||||
|
"ToastNotificationSettingsUpdateFailed": "La mise a jour des paramètres de notification a échouée",
|
||||||
|
"ToastNotificationSettingsUpdateSuccess": "Paramètres de notification mis à jour",
|
||||||
|
"ToastNotificationTestTriggerFailed": "L'envoi de la notification de test à échoué",
|
||||||
|
"ToastNotificationTestTriggerSuccess": "Notification de test déclenchée",
|
||||||
|
"ToastNotificationUpdateFailed": "Échec de la mise à jour de la notification",
|
||||||
|
"ToastNotificationUpdateSuccess": "Notification mise à jour",
|
||||||
"ToastPlaylistCreateFailed": "Échec de la création de la liste de lecture",
|
"ToastPlaylistCreateFailed": "Échec de la création de la liste de lecture",
|
||||||
"ToastPlaylistCreateSuccess": "Liste de lecture créée",
|
"ToastPlaylistCreateSuccess": "Liste de lecture créée",
|
||||||
"ToastPlaylistRemoveSuccess": "Liste de lecture supprimée",
|
"ToastPlaylistRemoveSuccess": "Liste de lecture supprimée",
|
||||||
@@ -853,24 +924,52 @@
|
|||||||
"ToastPlaylistUpdateSuccess": "Liste de lecture mise à jour",
|
"ToastPlaylistUpdateSuccess": "Liste de lecture mise à jour",
|
||||||
"ToastPodcastCreateFailed": "Échec de la création du podcast",
|
"ToastPodcastCreateFailed": "Échec de la création du podcast",
|
||||||
"ToastPodcastCreateSuccess": "Podcast créé avec succès",
|
"ToastPodcastCreateSuccess": "Podcast créé avec succès",
|
||||||
|
"ToastPodcastGetFeedFailed": "Échec de la récupération du flux du podcast",
|
||||||
|
"ToastPodcastNoEpisodesInFeed": "Aucun épisode trouvé dans le flux RSS",
|
||||||
|
"ToastPodcastNoRssFeed": "Le podcast n’a pas de flux RSS",
|
||||||
|
"ToastProviderCreatedFailed": "Échec de l’ajout du fournisseur",
|
||||||
|
"ToastProviderCreatedSuccess": "Nouveau fournisseur ajouté",
|
||||||
|
"ToastProviderNameAndUrlRequired": "Nom et URL requis",
|
||||||
|
"ToastProviderRemoveSuccess": "Fournisseur supprimé",
|
||||||
"ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS",
|
"ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS",
|
||||||
"ToastRSSFeedCloseSuccess": "Flux RSS fermé",
|
"ToastRSSFeedCloseSuccess": "Flux RSS fermé",
|
||||||
|
"ToastRemoveFailed": "Échec de la suppression",
|
||||||
"ToastRemoveItemFromCollectionFailed": "Échec de la suppression d’un élément de la collection",
|
"ToastRemoveItemFromCollectionFailed": "Échec de la suppression d’un élément de la collection",
|
||||||
"ToastRemoveItemFromCollectionSuccess": "Élément supprimé de la collection",
|
"ToastRemoveItemFromCollectionSuccess": "Élément supprimé de la collection",
|
||||||
|
"ToastRemoveItemsWithIssuesFailed": "Échec de la suppression des éléments de bibliothèque présentant des problèmes",
|
||||||
|
"ToastRemoveItemsWithIssuesSuccess": "Éléments de bibliothèque supprimés avec des problèmes",
|
||||||
|
"ToastRenameFailed": "Échec du renommage",
|
||||||
|
"ToastRescanFailed": "Échec de la nouvelle analyse pour {0}",
|
||||||
|
"ToastRescanRemoved": "Nouvelle analyse terminée, l’élément a été supprimé",
|
||||||
|
"ToastRescanUpToDate": "Nouvelle analyse terminée, l’élément était déjà à jour",
|
||||||
|
"ToastRescanUpdated": "Nouvelle analyse terminée, l’élément a été mis à jour",
|
||||||
|
"ToastScanFailed": "Échec de l’analyse de l’élément de la bibliothèque",
|
||||||
|
"ToastSelectAtLeastOneUser": "Sélectionnez au moins un utilisateur",
|
||||||
"ToastSendEbookToDeviceFailed": "Échec de l’envoi du livre numérique à l’appareil",
|
"ToastSendEbookToDeviceFailed": "Échec de l’envoi du livre numérique à l’appareil",
|
||||||
"ToastSendEbookToDeviceSuccess": "Livre numérique envoyé à l’appareil : {0}",
|
"ToastSendEbookToDeviceSuccess": "Livre numérique envoyé à l’appareil : {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": "Échec de la mise à jour des paramètres du serveur",
|
"ToastServerSettingsUpdateFailed": "Échec de la mise à jour des paramètres du serveur",
|
||||||
"ToastServerSettingsUpdateSuccess": "Mise à jour des paramètres du serveur",
|
"ToastServerSettingsUpdateSuccess": "Mise à jour des paramètres du serveur",
|
||||||
|
"ToastSessionCloseFailed": "Échec de la fermeture de la session",
|
||||||
"ToastSessionDeleteFailed": "Échec de la suppression de session",
|
"ToastSessionDeleteFailed": "Échec de la suppression de session",
|
||||||
"ToastSessionDeleteSuccess": "Session supprimée",
|
"ToastSessionDeleteSuccess": "Session supprimée",
|
||||||
|
"ToastSlugMustChange": "L’identifiant d’URL contient des caractères invalides",
|
||||||
|
"ToastSlugRequired": "L’identifiant d’URL est requis",
|
||||||
"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": "Doit avoir au moins 1 préfixe de tri",
|
"ToastSortingPrefixesEmptyError": "Doit avoir au moins 1 préfixe de tri",
|
||||||
"ToastSortingPrefixesUpdateFailed": "Échec de la mise à jour des préfixes de tri",
|
"ToastSortingPrefixesUpdateFailed": "Échec de la mise à jour des préfixes de tri",
|
||||||
"ToastSortingPrefixesUpdateSuccess": "Mise à jour des préfixes de tri ({0} élément)",
|
"ToastSortingPrefixesUpdateSuccess": "Mise à jour des préfixes de tri ({0} élément)",
|
||||||
|
"ToastTitleRequired": "Le titre est requis",
|
||||||
|
"ToastUnknownError": "Erreur inconnue",
|
||||||
|
"ToastUnlinkOpenIdFailed": "Échec de la dissociation de l’utilisateur l’OpenID",
|
||||||
|
"ToastUnlinkOpenIdSuccess": "Utilisateur dissocié de OpenID",
|
||||||
"ToastUserDeleteFailed": "Échec de la suppression de l’utilisateur",
|
"ToastUserDeleteFailed": "Échec de la suppression de l’utilisateur",
|
||||||
"ToastUserDeleteSuccess": "Utilisateur supprimé"
|
"ToastUserDeleteSuccess": "Utilisateur supprimé",
|
||||||
|
"ToastUserPasswordChangeSuccess": "Mot de passe modifié avec succès",
|
||||||
|
"ToastUserPasswordMismatch": "Les mots de passe ne correspondent pas",
|
||||||
|
"ToastUserPasswordMustChange": "Le nouveau mot de passe ne peut pas être identique à l’ancien",
|
||||||
|
"ToastUserRootRequireName": "Vous devez entrer un nom d’utilisateur root"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"ButtonCheckAndDownloadNewEpisodes": "નવા એપિસોડ્સ ચેક કરો અને ડાઉનલોડ કરો",
|
"ButtonCheckAndDownloadNewEpisodes": "નવા એપિસોડ્સ ચેક કરો અને ડાઉનલોડ કરો",
|
||||||
"ButtonChooseAFolder": "ફોલ્ડર પસંદ કરો",
|
"ButtonChooseAFolder": "ફોલ્ડર પસંદ કરો",
|
||||||
"ButtonChooseFiles": "ફાઇલો પસંદ કરો",
|
"ButtonChooseFiles": "ફાઇલો પસંદ કરો",
|
||||||
"ButtonClearFilter": "ફિલ્ટર જતુ કરો ",
|
"ButtonClearFilter": "ફિલ્ટર જતુ કરો",
|
||||||
"ButtonCloseFeed": "ફીડ બંધ કરો",
|
"ButtonCloseFeed": "ફીડ બંધ કરો",
|
||||||
"ButtonCollections": "સંગ્રહ",
|
"ButtonCollections": "સંગ્રહ",
|
||||||
"ButtonConfigureScanner": "સ્કેનર સેટિંગ બદલો",
|
"ButtonConfigureScanner": "સ્કેનર સેટિંગ બદલો",
|
||||||
|
|||||||
+34
-26
@@ -67,7 +67,7 @@
|
|||||||
"ButtonQueueRemoveItem": "Ukloni iz reda",
|
"ButtonQueueRemoveItem": "Ukloni iz reda",
|
||||||
"ButtonQuickEmbedMetadata": "Brzo ugrađivanje meta-podataka",
|
"ButtonQuickEmbedMetadata": "Brzo ugrađivanje meta-podataka",
|
||||||
"ButtonQuickMatch": "Brzo prepoznavanje",
|
"ButtonQuickMatch": "Brzo prepoznavanje",
|
||||||
"ButtonReScan": "Skeniraj ponovno",
|
"ButtonReScan": "Ponovno skeniraj",
|
||||||
"ButtonRead": "Pročitaj",
|
"ButtonRead": "Pročitaj",
|
||||||
"ButtonReadLess": "Pročitaj manje",
|
"ButtonReadLess": "Pročitaj manje",
|
||||||
"ButtonReadMore": "Pročitaj više",
|
"ButtonReadMore": "Pročitaj više",
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
"ButtonStartMetadataEmbed": "Pokreni ugradnju meta-podataka",
|
"ButtonStartMetadataEmbed": "Pokreni ugradnju meta-podataka",
|
||||||
"ButtonStats": "Statistika",
|
"ButtonStats": "Statistika",
|
||||||
"ButtonSubmit": "Podnesi",
|
"ButtonSubmit": "Podnesi",
|
||||||
"ButtonUnlinkOpedId": "Odspoji OpenID",
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Učitaj",
|
"ButtonUpload": "Učitaj",
|
||||||
"ButtonUploadBackup": "Učitaj sigurnosnu kopiju",
|
"ButtonUploadBackup": "Učitaj sigurnosnu kopiju",
|
||||||
"ButtonUploadCover": "Učitaj naslovnicu",
|
"ButtonUploadCover": "Učitaj naslovnicu",
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
"HeaderAudiobookTools": "Alati za upravljanje datotekama zvučnih knjiga",
|
"HeaderAudiobookTools": "Alati za upravljanje datotekama zvučnih knjiga",
|
||||||
"HeaderAuthentication": "Provjera autentičnosti",
|
"HeaderAuthentication": "Provjera autentičnosti",
|
||||||
"HeaderBackups": "Sigurnosne kopije",
|
"HeaderBackups": "Sigurnosne kopije",
|
||||||
"HeaderChangePassword": "Promijeni zaporku",
|
"HeaderChangePassword": "Promjena zaporke",
|
||||||
"HeaderChapters": "Poglavlja",
|
"HeaderChapters": "Poglavlja",
|
||||||
"HeaderChooseAFolder": "Odaberi mapu",
|
"HeaderChooseAFolder": "Odaberi mapu",
|
||||||
"HeaderCollection": "Zbirka",
|
"HeaderCollection": "Zbirka",
|
||||||
@@ -201,9 +201,9 @@
|
|||||||
"HeaderUsers": "Korisnici",
|
"HeaderUsers": "Korisnici",
|
||||||
"HeaderYearReview": "Pregled godine {0}",
|
"HeaderYearReview": "Pregled godine {0}",
|
||||||
"HeaderYourStats": "Vaša statistika",
|
"HeaderYourStats": "Vaša statistika",
|
||||||
"LabelAbridged": "Skraćena",
|
"LabelAbridged": "Skraćeno",
|
||||||
"LabelAbridgedChecked": "Skraćena (označeno)",
|
"LabelAbridgedChecked": "Skraćeno (označeno)",
|
||||||
"LabelAbridgedUnchecked": "Neskraćena (neoznačeno)",
|
"LabelAbridgedUnchecked": "Neskraćeno (neoznačeno)",
|
||||||
"LabelAccessibleBy": "Dostupno",
|
"LabelAccessibleBy": "Dostupno",
|
||||||
"LabelAccountType": "Vrsta korisničkog računa",
|
"LabelAccountType": "Vrsta korisničkog računa",
|
||||||
"LabelAccountTypeAdmin": "Administrator",
|
"LabelAccountTypeAdmin": "Administrator",
|
||||||
@@ -213,7 +213,8 @@
|
|||||||
"LabelAddToCollection": "Dodaj u zbirku",
|
"LabelAddToCollection": "Dodaj u zbirku",
|
||||||
"LabelAddToCollectionBatch": "Dodaj {0} knjiga u zbirku",
|
"LabelAddToCollectionBatch": "Dodaj {0} knjiga u zbirku",
|
||||||
"LabelAddToPlaylist": "Dodaj na popis za izvođenje",
|
"LabelAddToPlaylist": "Dodaj na popis za izvođenje",
|
||||||
"LabelAddedAt": "Dodano u",
|
"LabelAddToPlaylistBatch": "Dodaj {0} stavki u popis za izvođenje",
|
||||||
|
"LabelAddedAt": "Dodano",
|
||||||
"LabelAddedDate": "Dodano {0}",
|
"LabelAddedDate": "Dodano {0}",
|
||||||
"LabelAdminUsersOnly": "Samo korisnici administratori",
|
"LabelAdminUsersOnly": "Samo korisnici administratori",
|
||||||
"LabelAll": "Sve",
|
"LabelAll": "Sve",
|
||||||
@@ -242,7 +243,7 @@
|
|||||||
"LabelBackupsNumberToKeep": "Broj sigurnosnih kopija za čuvanje",
|
"LabelBackupsNumberToKeep": "Broj sigurnosnih kopija za čuvanje",
|
||||||
"LabelBackupsNumberToKeepHelp": "Moguće je izbrisati samo jednu po jednu sigurnosnu kopiju, ako ih već imate više trebat ćete ih ručno ukloniti.",
|
"LabelBackupsNumberToKeepHelp": "Moguće je izbrisati samo jednu po jednu sigurnosnu kopiju, ako ih već imate više trebat ćete ih ručno ukloniti.",
|
||||||
"LabelBitrate": "Protok",
|
"LabelBitrate": "Protok",
|
||||||
"LabelBooks": "Knjige",
|
"LabelBooks": "knjiga/e",
|
||||||
"LabelButtonText": "Tekst gumba",
|
"LabelButtonText": "Tekst gumba",
|
||||||
"LabelByAuthor": "po {0}",
|
"LabelByAuthor": "po {0}",
|
||||||
"LabelChangePassword": "Promijeni lozinku",
|
"LabelChangePassword": "Promijeni lozinku",
|
||||||
@@ -253,10 +254,10 @@
|
|||||||
"LabelClickForMoreInfo": "Kliknite za više informacija",
|
"LabelClickForMoreInfo": "Kliknite za više informacija",
|
||||||
"LabelClosePlayer": "Zatvori reproduktor",
|
"LabelClosePlayer": "Zatvori reproduktor",
|
||||||
"LabelCodec": "Kodek",
|
"LabelCodec": "Kodek",
|
||||||
"LabelCollapseSeries": "Serijal prikaži sažeto",
|
"LabelCollapseSeries": "Serijale prikaži sažeto",
|
||||||
"LabelCollapseSubSeries": "Podserijal prikaži sažeto",
|
"LabelCollapseSubSeries": "Podserijale prikaži sažeto",
|
||||||
"LabelCollection": "Zbirka",
|
"LabelCollection": "Zbirka",
|
||||||
"LabelCollections": "Zbirke",
|
"LabelCollections": "Zbirka/i",
|
||||||
"LabelComplete": "Dovršeno",
|
"LabelComplete": "Dovršeno",
|
||||||
"LabelConfirmPassword": "Potvrdi lozinku",
|
"LabelConfirmPassword": "Potvrdi lozinku",
|
||||||
"LabelContinueListening": "Nastavi slušati",
|
"LabelContinueListening": "Nastavi slušati",
|
||||||
@@ -316,7 +317,7 @@
|
|||||||
"LabelFeedURL": "URL izvora",
|
"LabelFeedURL": "URL izvora",
|
||||||
"LabelFetchingMetadata": "Dohvaćanje meta-podataka",
|
"LabelFetchingMetadata": "Dohvaćanje meta-podataka",
|
||||||
"LabelFile": "Datoteka",
|
"LabelFile": "Datoteka",
|
||||||
"LabelFileBirthtime": "Vrijeme stvaranja datoteke",
|
"LabelFileBirthtime": "Datoteka stvorena",
|
||||||
"LabelFileBornDate": "Stvoreno {0}",
|
"LabelFileBornDate": "Stvoreno {0}",
|
||||||
"LabelFileModified": "Datoteka izmijenjena",
|
"LabelFileModified": "Datoteka izmijenjena",
|
||||||
"LabelFileModifiedDate": "Izmijenjeno {0}",
|
"LabelFileModifiedDate": "Izmijenjeno {0}",
|
||||||
@@ -332,6 +333,7 @@
|
|||||||
"LabelFontItalic": "Kurziv",
|
"LabelFontItalic": "Kurziv",
|
||||||
"LabelFontScale": "Veličina slova",
|
"LabelFontScale": "Veličina slova",
|
||||||
"LabelFontStrikethrough": "Precrtano",
|
"LabelFontStrikethrough": "Precrtano",
|
||||||
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Žanr",
|
"LabelGenre": "Žanr",
|
||||||
"LabelGenres": "Žanrovi",
|
"LabelGenres": "Žanrovi",
|
||||||
"LabelHardDeleteFile": "Obriši datoteku zauvijek",
|
"LabelHardDeleteFile": "Obriši datoteku zauvijek",
|
||||||
@@ -347,6 +349,7 @@
|
|||||||
"LabelInProgress": "U tijeku",
|
"LabelInProgress": "U tijeku",
|
||||||
"LabelIncludeInTracklist": "Uključi u popisu zvučnih zapisa",
|
"LabelIncludeInTracklist": "Uključi u popisu zvučnih zapisa",
|
||||||
"LabelIncomplete": "Nepotpuno",
|
"LabelIncomplete": "Nepotpuno",
|
||||||
|
"LabelInterval": "Interval",
|
||||||
"LabelIntervalCustomDailyWeekly": "Prilagođeno dnevno/tjedno",
|
"LabelIntervalCustomDailyWeekly": "Prilagođeno dnevno/tjedno",
|
||||||
"LabelIntervalEvery12Hours": "Svakih 12 sati",
|
"LabelIntervalEvery12Hours": "Svakih 12 sati",
|
||||||
"LabelIntervalEvery15Minutes": "Svakih 15 minuta",
|
"LabelIntervalEvery15Minutes": "Svakih 15 minuta",
|
||||||
@@ -365,8 +368,8 @@
|
|||||||
"LabelLastBookAdded": "Zadnja dodana knjiga",
|
"LabelLastBookAdded": "Zadnja dodana knjiga",
|
||||||
"LabelLastBookUpdated": "Zadnja ažurirana knjiga",
|
"LabelLastBookUpdated": "Zadnja ažurirana knjiga",
|
||||||
"LabelLastSeen": "Zadnje pogledano",
|
"LabelLastSeen": "Zadnje pogledano",
|
||||||
"LabelLastTime": "Prošli put",
|
"LabelLastTime": "Zadnji puta",
|
||||||
"LabelLastUpdate": "Zadnja aktualizacija",
|
"LabelLastUpdate": "Zadnje ažuriranje",
|
||||||
"LabelLayout": "Prikaz",
|
"LabelLayout": "Prikaz",
|
||||||
"LabelLayoutSinglePage": "Jedna stranica",
|
"LabelLayoutSinglePage": "Jedna stranica",
|
||||||
"LabelLayoutSplitPage": "Podijeli stranicu",
|
"LabelLayoutSplitPage": "Podijeli stranicu",
|
||||||
@@ -379,12 +382,15 @@
|
|||||||
"LabelLimit": "Ograničenje",
|
"LabelLimit": "Ograničenje",
|
||||||
"LabelLineSpacing": "Razmak između redaka",
|
"LabelLineSpacing": "Razmak između redaka",
|
||||||
"LabelListenAgain": "Ponovno poslušaj",
|
"LabelListenAgain": "Ponovno poslušaj",
|
||||||
|
"LabelLogLevelDebug": "Debug",
|
||||||
|
"LabelLogLevelInfo": "Info",
|
||||||
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Traži nove epizode nakon ovog datuma",
|
"LabelLookForNewEpisodesAfterDate": "Traži nove epizode nakon ovog datuma",
|
||||||
"LabelLowestPriority": "Najniži prioritet",
|
"LabelLowestPriority": "Najniži prioritet",
|
||||||
"LabelMatchExistingUsersBy": "Prepoznaj postojeće korisnike pomoću",
|
"LabelMatchExistingUsersBy": "Prepoznaj postojeće korisnike pomoću",
|
||||||
"LabelMatchExistingUsersByDescription": "Rabi se za povezivanje postojećih korisnika. Nakon što se spoje, korisnike se prepoznaje temeljem jedinstvene oznake vašeg pružatelja SSO usluga",
|
"LabelMatchExistingUsersByDescription": "Rabi se za povezivanje postojećih korisnika. Nakon što se spoje, korisnike se prepoznaje temeljem jedinstvene oznake vašeg pružatelja SSO usluga",
|
||||||
"LabelMediaPlayer": "Reproduktor medijskih sadržaja",
|
"LabelMediaPlayer": "Reproduktor medijskih sadržaja",
|
||||||
"LabelMediaType": "Vrsta medijskog zapisa",
|
"LabelMediaType": "Vrsta medija",
|
||||||
"LabelMetaTag": "Meta oznaka",
|
"LabelMetaTag": "Meta oznaka",
|
||||||
"LabelMetaTags": "Meta oznake",
|
"LabelMetaTags": "Meta oznake",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "Izvori meta-podataka višeg prioriteta nadjačat će izvore nižeg prioriteta",
|
"LabelMetadataOrderOfPrecedenceDescription": "Izvori meta-podataka višeg prioriteta nadjačat će izvore nižeg prioriteta",
|
||||||
@@ -405,7 +411,7 @@
|
|||||||
"LabelNewPassword": "Nova lozinka",
|
"LabelNewPassword": "Nova lozinka",
|
||||||
"LabelNewestAuthors": "Najnoviji autori",
|
"LabelNewestAuthors": "Najnoviji autori",
|
||||||
"LabelNewestEpisodes": "Najnoviji nastavci",
|
"LabelNewestEpisodes": "Najnoviji nastavci",
|
||||||
"LabelNextBackupDate": "Datum sljedeće izrade sigurnosne kopije",
|
"LabelNextBackupDate": "Sljedeće izrada sigurnosne kopije",
|
||||||
"LabelNextScheduledRun": "Sljedeće zakazano izvođenje",
|
"LabelNextScheduledRun": "Sljedeće zakazano izvođenje",
|
||||||
"LabelNoCustomMetadataProviders": "Nema prilagođenih pružatelja meta-podataka",
|
"LabelNoCustomMetadataProviders": "Nema prilagođenih pružatelja meta-podataka",
|
||||||
"LabelNoEpisodesSelected": "Nema odabranih nastavaka",
|
"LabelNoEpisodesSelected": "Nema odabranih nastavaka",
|
||||||
@@ -443,6 +449,7 @@
|
|||||||
"LabelPlayMethod": "Način reprodukcije",
|
"LabelPlayMethod": "Način reprodukcije",
|
||||||
"LabelPlayerChapterNumberMarker": "{0} od {1}",
|
"LabelPlayerChapterNumberMarker": "{0} od {1}",
|
||||||
"LabelPlaylists": "Popisi za izvođenje",
|
"LabelPlaylists": "Popisi za izvođenje",
|
||||||
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcastSearchRegion": "Zemljopisno područje kod pretraživanja podcasta",
|
"LabelPodcastSearchRegion": "Zemljopisno područje kod pretraživanja podcasta",
|
||||||
"LabelPodcastType": "Vrsta podcasta",
|
"LabelPodcastType": "Vrsta podcasta",
|
||||||
"LabelPodcasts": "Podcasti",
|
"LabelPodcasts": "Podcasti",
|
||||||
@@ -469,7 +476,7 @@
|
|||||||
"LabelRead": "Čitaj",
|
"LabelRead": "Čitaj",
|
||||||
"LabelReadAgain": "Ponovno čitaj",
|
"LabelReadAgain": "Ponovno čitaj",
|
||||||
"LabelReadEbookWithoutProgress": "Čitaj e-knjige bez praćenja napretka",
|
"LabelReadEbookWithoutProgress": "Čitaj e-knjige bez praćenja napretka",
|
||||||
"LabelRecentSeries": "Nedavni serijal",
|
"LabelRecentSeries": "Najnoviji serijali",
|
||||||
"LabelRecentlyAdded": "Nedavno dodano",
|
"LabelRecentlyAdded": "Nedavno dodano",
|
||||||
"LabelRecommended": "Preporučeno",
|
"LabelRecommended": "Preporučeno",
|
||||||
"LabelRedo": "Ponovi",
|
"LabelRedo": "Ponovi",
|
||||||
@@ -487,7 +494,7 @@
|
|||||||
"LabelSelectUsers": "Označi korisnike",
|
"LabelSelectUsers": "Označi korisnike",
|
||||||
"LabelSendEbookToDevice": "Pošalji e-knjigu",
|
"LabelSendEbookToDevice": "Pošalji e-knjigu",
|
||||||
"LabelSequence": "Slijed",
|
"LabelSequence": "Slijed",
|
||||||
"LabelSeries": "Serijali",
|
"LabelSeries": "Serijal/a",
|
||||||
"LabelSeriesName": "Ime serijala",
|
"LabelSeriesName": "Ime serijala",
|
||||||
"LabelSeriesProgress": "Napredak u serijalu",
|
"LabelSeriesProgress": "Napredak u serijalu",
|
||||||
"LabelServerYearReview": "Godišnji pregled poslužitelja ({0})",
|
"LabelServerYearReview": "Godišnji pregled poslužitelja ({0})",
|
||||||
@@ -501,7 +508,7 @@
|
|||||||
"LabelSettingsDisableWatcher": "Isključi praćenje datotečnog sustava",
|
"LabelSettingsDisableWatcher": "Isključi praćenje datotečnog sustava",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Onemogući praćenje datotečnog sustava za ovu knjižnicu",
|
"LabelSettingsDisableWatcherForLibrary": "Onemogući praćenje datotečnog sustava za ovu knjižnicu",
|
||||||
"LabelSettingsDisableWatcherHelp": "Onemogućuje automatsko dodavanje ili ažuriranje stavki kod uočenih promjena datoteka. *Potrebno je ponovno pokrenuti poslužitelj",
|
"LabelSettingsDisableWatcherHelp": "Onemogućuje automatsko dodavanje ili ažuriranje stavki kod uočenih promjena datoteka. *Potrebno je ponovno pokrenuti poslužitelj",
|
||||||
"LabelSettingsEnableWatcher": "Omogući praćenje",
|
"LabelSettingsEnableWatcher": "Omogući praćenje promjena",
|
||||||
"LabelSettingsEnableWatcherForLibrary": "Omogući praćenje promjena u mapi knjižnice",
|
"LabelSettingsEnableWatcherForLibrary": "Omogući praćenje promjena u mapi knjižnice",
|
||||||
"LabelSettingsEnableWatcherHelp": "Omogućuje automatsko dodavanje/ažuriranje stavki kada se uoče izmjene datoteka. *Potrebno je ponovno pokretanje poslužitelja",
|
"LabelSettingsEnableWatcherHelp": "Omogućuje automatsko dodavanje/ažuriranje stavki kada se uoče izmjene datoteka. *Potrebno je ponovno pokretanje poslužitelja",
|
||||||
"LabelSettingsEpubsAllowScriptedContent": "Omogući skripte u epub datotekama",
|
"LabelSettingsEpubsAllowScriptedContent": "Omogući skripte u epub datotekama",
|
||||||
@@ -514,7 +521,7 @@
|
|||||||
"LabelSettingsHideSingleBookSeriesHelp": "Serijali koji se sastoje od samo jedne knjige neće se prikazivati na stranici serijala i na policama početne stranice.",
|
"LabelSettingsHideSingleBookSeriesHelp": "Serijali koji se sastoje od samo jedne knjige neće se prikazivati na stranici serijala i na policama početne stranice.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Prikaži početnu stranicu kao policu s knjigama",
|
"LabelSettingsHomePageBookshelfView": "Prikaži početnu stranicu kao policu s knjigama",
|
||||||
"LabelSettingsLibraryBookshelfView": "Prikaži knjižnicu kao policu s knjigama",
|
"LabelSettingsLibraryBookshelfView": "Prikaži knjižnicu kao policu s knjigama",
|
||||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Preskoči ranije knjige u Nastavi serijal",
|
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Preskoči ranije knjige u funkciji Nastavi serijal",
|
||||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Na polici početne stranice Nastavi serijal prikazuje se prva nezapočeta knjiga serijala koji imaju barem jednu dovršenu knjigu i nijednu započetu knjigu. Ako uključite ovu opciju, serijal će vam se nastaviti od zadnje dovršene knjige umjesto od prve nezapočete knjige.",
|
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Na polici početne stranice Nastavi serijal prikazuje se prva nezapočeta knjiga serijala koji imaju barem jednu dovršenu knjigu i nijednu započetu knjigu. Ako uključite ovu opciju, serijal će vam se nastaviti od zadnje dovršene knjige umjesto od prve nezapočete knjige.",
|
||||||
"LabelSettingsParseSubtitles": "Raščlani podnaslove",
|
"LabelSettingsParseSubtitles": "Raščlani podnaslove",
|
||||||
"LabelSettingsParseSubtitlesHelp": "Iz naziva mape zvučne knjige raščlanjuje podnaslov.<br>Podnaslov mora biti odvojen s \" - \"<br>npr. \"Naslov knjige - Ovo je podnaslov\" imat će podnaslov \"Ovo je podnaslov\"",
|
"LabelSettingsParseSubtitlesHelp": "Iz naziva mape zvučne knjige raščlanjuje podnaslov.<br>Podnaslov mora biti odvojen s \" - \"<br>npr. \"Naslov knjige - Ovo je podnaslov\" imat će podnaslov \"Ovo je podnaslov\"",
|
||||||
@@ -532,14 +539,15 @@
|
|||||||
"LabelSettingsStoreMetadataWithItemHelp": "Meta-podatci se obično spremaju u /metadata/items; ako uključite ovu postavku meta-podatci će se čuvati u mapama knjižničkih stavki",
|
"LabelSettingsStoreMetadataWithItemHelp": "Meta-podatci se obično spremaju u /metadata/items; ako uključite ovu postavku meta-podatci će se čuvati u mapama knjižničkih stavki",
|
||||||
"LabelSettingsTimeFormat": "Format vremena",
|
"LabelSettingsTimeFormat": "Format vremena",
|
||||||
"LabelShare": "Podijeli",
|
"LabelShare": "Podijeli",
|
||||||
"LabelShareOpen": "Podijeli otvoreno",
|
"LabelShareOpen": "Dijeljenje otvoreno",
|
||||||
"LabelShareURL": "URL za dijeljenje",
|
"LabelShareURL": "URL za dijeljenje",
|
||||||
"LabelShowAll": "Prikaži sve",
|
"LabelShowAll": "Prikaži sve",
|
||||||
"LabelShowSeconds": "Prikaži sekunde",
|
"LabelShowSeconds": "Prikaži sekunde",
|
||||||
"LabelShowSubtitles": "Pokaži podnaslove",
|
"LabelShowSubtitles": "Prikaži podnaslove",
|
||||||
"LabelSize": "Veličina",
|
"LabelSize": "Veličina",
|
||||||
"LabelSleepTimer": "Timer za spavanje",
|
"LabelSleepTimer": "Timer za spavanje",
|
||||||
"LabelStart": "Pokreni",
|
"LabelSlug": "Slug",
|
||||||
|
"LabelStart": "Početak",
|
||||||
"LabelStartTime": "Vrijeme početka",
|
"LabelStartTime": "Vrijeme početka",
|
||||||
"LabelStarted": "Započeto",
|
"LabelStarted": "Započeto",
|
||||||
"LabelStartedAt": "Započeto",
|
"LabelStartedAt": "Započeto",
|
||||||
@@ -552,7 +560,7 @@
|
|||||||
"LabelStatsHours": "Sati",
|
"LabelStatsHours": "Sati",
|
||||||
"LabelStatsInARow": "uzastopno",
|
"LabelStatsInARow": "uzastopno",
|
||||||
"LabelStatsItemsFinished": "Dovršenih stavki",
|
"LabelStatsItemsFinished": "Dovršenih stavki",
|
||||||
"LabelStatsItemsInLibrary": "Stavke u biblioteki",
|
"LabelStatsItemsInLibrary": "Stavke u knjižnici",
|
||||||
"LabelStatsMinutes": "minute",
|
"LabelStatsMinutes": "minute",
|
||||||
"LabelStatsMinutesListening": "Minuta odslušano",
|
"LabelStatsMinutesListening": "Minuta odslušano",
|
||||||
"LabelStatsOverallDays": "Ukupno dana",
|
"LabelStatsOverallDays": "Ukupno dana",
|
||||||
@@ -597,7 +605,7 @@
|
|||||||
"LabelTracksNone": "Nema zapisa",
|
"LabelTracksNone": "Nema zapisa",
|
||||||
"LabelTracksSingleTrack": "Jedan zvučni zapis",
|
"LabelTracksSingleTrack": "Jedan zvučni zapis",
|
||||||
"LabelType": "Vrsta",
|
"LabelType": "Vrsta",
|
||||||
"LabelUnabridged": "Neskraćena",
|
"LabelUnabridged": "Neskraćeno",
|
||||||
"LabelUndo": "Vrati",
|
"LabelUndo": "Vrati",
|
||||||
"LabelUnknown": "Nepoznato",
|
"LabelUnknown": "Nepoznato",
|
||||||
"LabelUnknownPublishDate": "Nepoznat datum objavljivanja",
|
"LabelUnknownPublishDate": "Nepoznat datum objavljivanja",
|
||||||
@@ -711,7 +719,7 @@
|
|||||||
"MessageMarkAllEpisodesNotFinished": "Označi sve nastavke nedovršenima",
|
"MessageMarkAllEpisodesNotFinished": "Označi sve nastavke nedovršenima",
|
||||||
"MessageMarkAsFinished": "Označi kao dovršeno",
|
"MessageMarkAsFinished": "Označi kao dovršeno",
|
||||||
"MessageMarkAsNotFinished": "Označi kao nedovršeno",
|
"MessageMarkAsNotFinished": "Označi kao nedovršeno",
|
||||||
"MessageMatchBooksDescription": "će probati prepoznati knjige iz knjižnice u katalogu odabranog pružatelja podatka te nadopuniti podatke koji nedostaju i naslovnice. Ne prepisuje preko postojećih podataka.",
|
"MessageMatchBooksDescription": "će pokušati prepoznati knjige iz knjižnice u katalogu odabranog pružatelja podatka te nadopuniti podatke koji nedostaju i naslovnice. Ne prepisuje preko postojećih podataka.",
|
||||||
"MessageNoAudioTracks": "Nema zvučnih zapisa",
|
"MessageNoAudioTracks": "Nema zvučnih zapisa",
|
||||||
"MessageNoAuthors": "Nema autora",
|
"MessageNoAuthors": "Nema autora",
|
||||||
"MessageNoBackups": "Nema sigurnosnih kopija",
|
"MessageNoBackups": "Nema sigurnosnih kopija",
|
||||||
|
|||||||
@@ -600,7 +600,7 @@
|
|||||||
"MessageInsertChapterBelow": "Fejezet beszúrása alulra",
|
"MessageInsertChapterBelow": "Fejezet beszúrása alulra",
|
||||||
"MessageItemsSelected": "{0} kiválasztott elem",
|
"MessageItemsSelected": "{0} kiválasztott elem",
|
||||||
"MessageItemsUpdated": "{0} frissített elem",
|
"MessageItemsUpdated": "{0} frissített elem",
|
||||||
"MessageJoinUsOn": "Csatlakozzon hozzánk: ",
|
"MessageJoinUsOn": "Csatlakozzon hozzánk",
|
||||||
"MessageListeningSessionsInTheLastYear": "{0} hallgatási munkamenet az elmúlt évben",
|
"MessageListeningSessionsInTheLastYear": "{0} hallgatási munkamenet az elmúlt évben",
|
||||||
"MessageLoading": "Betöltés...",
|
"MessageLoading": "Betöltés...",
|
||||||
"MessageLoadingFolders": "Mappák betöltése...",
|
"MessageLoadingFolders": "Mappák betöltése...",
|
||||||
@@ -652,9 +652,9 @@
|
|||||||
"MessageRemoveEpisodes": "Epizód(ok) eltávolítása: {0}",
|
"MessageRemoveEpisodes": "Epizód(ok) eltávolítása: {0}",
|
||||||
"MessageRemoveFromPlayerQueue": "Eltávolítás a lejátszási sorból",
|
"MessageRemoveFromPlayerQueue": "Eltávolítás a lejátszási sorból",
|
||||||
"MessageRemoveUserWarning": "Biztosan véglegesen törölni szeretné a(z) \"{0}\" felhasználót?",
|
"MessageRemoveUserWarning": "Biztosan véglegesen törölni szeretné a(z) \"{0}\" felhasználót?",
|
||||||
"MessageReportBugsAndContribute": "Hibák jelentése, funkciók kérése és hozzájárulás itt:",
|
"MessageReportBugsAndContribute": "Hibák jelentése, funkciók kérése és hozzájárulás itt",
|
||||||
"MessageResetChaptersConfirm": "Biztosan alaphelyzetbe szeretné állítani a fejezeteket és visszavonni a módosításokat?",
|
"MessageResetChaptersConfirm": "Biztosan alaphelyzetbe szeretné állítani a fejezeteket és visszavonni a módosításokat?",
|
||||||
"MessageRestoreBackupConfirm": "Biztosan vissza szeretné állítani a biztonsági másolatot, amely ekkor készült:",
|
"MessageRestoreBackupConfirm": "Biztosan vissza szeretné állítani a biztonsági másolatot, amely ekkor készült",
|
||||||
"MessageRestoreBackupWarning": "A biztonsági mentés visszaállítása felülírja az egész adatbázist, amely a /config mappában található, valamint a borítóképeket a /metadata/items és /metadata/authors mappákban.<br /><br />A biztonsági mentések nem módosítják a könyvtár mappáiban található fájlokat. Ha engedélyezte a szerverbeállításokat a borítóképek és a metaadatok könyvtármappákban való tárolására, akkor ezek nem kerülnek biztonsági mentésre vagy felülírásra.<br /><br />A szerver használó összes kliens automatikusan frissül.",
|
"MessageRestoreBackupWarning": "A biztonsági mentés visszaállítása felülírja az egész adatbázist, amely a /config mappában található, valamint a borítóképeket a /metadata/items és /metadata/authors mappákban.<br /><br />A biztonsági mentések nem módosítják a könyvtár mappáiban található fájlokat. Ha engedélyezte a szerverbeállításokat a borítóképek és a metaadatok könyvtármappákban való tárolására, akkor ezek nem kerülnek biztonsági mentésre vagy felülírásra.<br /><br />A szerver használó összes kliens automatikusan frissül.",
|
||||||
"MessageSearchResultsFor": "Keresési eredmények",
|
"MessageSearchResultsFor": "Keresési eredmények",
|
||||||
"MessageSelected": "{0} kiválasztva",
|
"MessageSelected": "{0} kiválasztva",
|
||||||
|
|||||||
+38
-1
@@ -19,6 +19,7 @@
|
|||||||
"ButtonChooseFiles": "Wybierz pliki",
|
"ButtonChooseFiles": "Wybierz pliki",
|
||||||
"ButtonClearFilter": "Wyczyść filtr",
|
"ButtonClearFilter": "Wyczyść filtr",
|
||||||
"ButtonCloseFeed": "Zamknij kanał",
|
"ButtonCloseFeed": "Zamknij kanał",
|
||||||
|
"ButtonCloseSession": "Zamknij otwartą sesję",
|
||||||
"ButtonCollections": "Kolekcje",
|
"ButtonCollections": "Kolekcje",
|
||||||
"ButtonConfigureScanner": "Skonfiguruj skaner",
|
"ButtonConfigureScanner": "Skonfiguruj skaner",
|
||||||
"ButtonCreate": "Utwórz",
|
"ButtonCreate": "Utwórz",
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
"ButtonEdit": "Edycja",
|
"ButtonEdit": "Edycja",
|
||||||
"ButtonEditChapters": "Edytuj rozdziały",
|
"ButtonEditChapters": "Edytuj rozdziały",
|
||||||
"ButtonEditPodcast": "Edytuj podcast",
|
"ButtonEditPodcast": "Edytuj podcast",
|
||||||
|
"ButtonEnable": "Włącz",
|
||||||
"ButtonForceReScan": "Wymuś ponowne skanowanie",
|
"ButtonForceReScan": "Wymuś ponowne skanowanie",
|
||||||
"ButtonFullPath": "Pełna ścieżka",
|
"ButtonFullPath": "Pełna ścieżka",
|
||||||
"ButtonHide": "Ukryj",
|
"ButtonHide": "Ukryj",
|
||||||
@@ -47,8 +49,10 @@
|
|||||||
"ButtonNext": "Następny",
|
"ButtonNext": "Następny",
|
||||||
"ButtonNextChapter": "Następny rozdział",
|
"ButtonNextChapter": "Następny rozdział",
|
||||||
"ButtonNextItemInQueue": "Następny element w kolejce",
|
"ButtonNextItemInQueue": "Następny element w kolejce",
|
||||||
|
"ButtonOk": "Ok",
|
||||||
"ButtonOpenFeed": "Otwórz feed",
|
"ButtonOpenFeed": "Otwórz feed",
|
||||||
"ButtonOpenManager": "Otwórz menadżera",
|
"ButtonOpenManager": "Otwórz menadżera",
|
||||||
|
"ButtonPause": "Wstrzymaj",
|
||||||
"ButtonPlay": "Odtwarzaj",
|
"ButtonPlay": "Odtwarzaj",
|
||||||
"ButtonPlaying": "Odtwarzane",
|
"ButtonPlaying": "Odtwarzane",
|
||||||
"ButtonPlaylists": "Listy odtwarzania",
|
"ButtonPlaylists": "Listy odtwarzania",
|
||||||
@@ -90,6 +94,7 @@
|
|||||||
"ButtonStartMetadataEmbed": "Osadź metadane",
|
"ButtonStartMetadataEmbed": "Osadź metadane",
|
||||||
"ButtonStats": "Statystyki",
|
"ButtonStats": "Statystyki",
|
||||||
"ButtonSubmit": "Zaloguj",
|
"ButtonSubmit": "Zaloguj",
|
||||||
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Wgraj",
|
"ButtonUpload": "Wgraj",
|
||||||
"ButtonUploadBackup": "Wgraj kopię zapasową",
|
"ButtonUploadBackup": "Wgraj kopię zapasową",
|
||||||
"ButtonUploadCover": "Wgraj okładkę",
|
"ButtonUploadCover": "Wgraj okładkę",
|
||||||
@@ -102,6 +107,7 @@
|
|||||||
"ErrorUploadFetchMetadataNoResults": "Nie można pobrać metadanych — spróbuj zaktualizować tytuł i/lub autora",
|
"ErrorUploadFetchMetadataNoResults": "Nie można pobrać metadanych — spróbuj zaktualizować tytuł i/lub autora",
|
||||||
"ErrorUploadLacksTitle": "Musi mieć tytuł",
|
"ErrorUploadLacksTitle": "Musi mieć tytuł",
|
||||||
"HeaderAccount": "Konto",
|
"HeaderAccount": "Konto",
|
||||||
|
"HeaderAddCustomMetadataProvider": "Dodaj niestandardowego dostawcę metadanych",
|
||||||
"HeaderAdvanced": "Zaawansowane",
|
"HeaderAdvanced": "Zaawansowane",
|
||||||
"HeaderAppriseNotificationSettings": "Ustawienia powiadomień Apprise",
|
"HeaderAppriseNotificationSettings": "Ustawienia powiadomień Apprise",
|
||||||
"HeaderAudioTracks": "Ścieżki audio",
|
"HeaderAudioTracks": "Ścieżki audio",
|
||||||
@@ -120,6 +126,7 @@
|
|||||||
"HeaderDetails": "Szczegóły",
|
"HeaderDetails": "Szczegóły",
|
||||||
"HeaderDownloadQueue": "Kolejka do ściągania",
|
"HeaderDownloadQueue": "Kolejka do ściągania",
|
||||||
"HeaderEbookFiles": "Pliki Ebook",
|
"HeaderEbookFiles": "Pliki Ebook",
|
||||||
|
"HeaderEmail": "E-mail",
|
||||||
"HeaderEmailSettings": "Ustawienia e-mail",
|
"HeaderEmailSettings": "Ustawienia e-mail",
|
||||||
"HeaderEpisodes": "Rozdziały",
|
"HeaderEpisodes": "Rozdziały",
|
||||||
"HeaderEreaderDevices": "Czytniki",
|
"HeaderEreaderDevices": "Czytniki",
|
||||||
@@ -145,6 +152,8 @@
|
|||||||
"HeaderMetadataToEmbed": "Osadź metadane",
|
"HeaderMetadataToEmbed": "Osadź metadane",
|
||||||
"HeaderNewAccount": "Nowe konto",
|
"HeaderNewAccount": "Nowe konto",
|
||||||
"HeaderNewLibrary": "Nowa biblioteka",
|
"HeaderNewLibrary": "Nowa biblioteka",
|
||||||
|
"HeaderNotificationCreate": "Utwórz powiadomienie",
|
||||||
|
"HeaderNotificationUpdate": "Zaktualizuj powiadomienie",
|
||||||
"HeaderNotifications": "Powiadomienia",
|
"HeaderNotifications": "Powiadomienia",
|
||||||
"HeaderOpenIDConnectAuthentication": "Uwierzytelnianie OpenID Connect",
|
"HeaderOpenIDConnectAuthentication": "Uwierzytelnianie OpenID Connect",
|
||||||
"HeaderOpenRSSFeed": "Utwórz kanał RSS",
|
"HeaderOpenRSSFeed": "Utwórz kanał RSS",
|
||||||
@@ -157,7 +166,9 @@
|
|||||||
"HeaderPlaylistItems": "Pozycje listy odtwarzania",
|
"HeaderPlaylistItems": "Pozycje listy odtwarzania",
|
||||||
"HeaderPodcastsToAdd": "Podcasty do dodania",
|
"HeaderPodcastsToAdd": "Podcasty do dodania",
|
||||||
"HeaderPreviewCover": "Podgląd okładki",
|
"HeaderPreviewCover": "Podgląd okładki",
|
||||||
|
"HeaderRSSFeedGeneral": "Szczegóły RSS",
|
||||||
"HeaderRSSFeedIsOpen": "Kanał RSS jest otwarty",
|
"HeaderRSSFeedIsOpen": "Kanał RSS jest otwarty",
|
||||||
|
"HeaderRSSFeeds": "Kanały RSS",
|
||||||
"HeaderRemoveEpisode": "Usuń odcinek",
|
"HeaderRemoveEpisode": "Usuń odcinek",
|
||||||
"HeaderRemoveEpisodes": "Usuń {0} odcinków",
|
"HeaderRemoveEpisodes": "Usuń {0} odcinków",
|
||||||
"HeaderSavedMediaProgress": "Zapisany postęp",
|
"HeaderSavedMediaProgress": "Zapisany postęp",
|
||||||
@@ -200,6 +211,7 @@
|
|||||||
"LabelAddToPlaylist": "Dodaj do playlisty",
|
"LabelAddToPlaylist": "Dodaj do playlisty",
|
||||||
"LabelAddToPlaylistBatch": "Dodaj {0} pozycji do playlisty",
|
"LabelAddToPlaylistBatch": "Dodaj {0} pozycji do playlisty",
|
||||||
"LabelAddedAt": "Dodano",
|
"LabelAddedAt": "Dodano",
|
||||||
|
"LabelAddedDate": "Dodano {0}",
|
||||||
"LabelAdminUsersOnly": "Tylko użytkownicy administracyjni",
|
"LabelAdminUsersOnly": "Tylko użytkownicy administracyjni",
|
||||||
"LabelAll": "Wszystkie",
|
"LabelAll": "Wszystkie",
|
||||||
"LabelAllUsers": "Wszyscy użytkownicy",
|
"LabelAllUsers": "Wszyscy użytkownicy",
|
||||||
@@ -215,15 +227,19 @@
|
|||||||
"LabelAutoFetchMetadata": "Automatycznie pobierz metadane",
|
"LabelAutoFetchMetadata": "Automatycznie pobierz metadane",
|
||||||
"LabelAutoFetchMetadataHelp": "Pobiera metadane dotyczące tytułu, autora i serii, aby usprawnić przesyłanie. Po przesłaniu może być konieczne dopasowanie dodatkowych metadanych.",
|
"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": "Uruchom automatycznie",
|
"LabelAutoLaunch": "Uruchom automatycznie",
|
||||||
|
"LabelAutoRegister": "Automatyczna rejestracja",
|
||||||
|
"LabelAutoRegisterDescription": "Automatycznie utwórz nowych użytkowników po zalogowaniu",
|
||||||
"LabelBackToUser": "Powrót",
|
"LabelBackToUser": "Powrót",
|
||||||
"LabelBackupLocation": "Lokalizacja kopii zapasowej",
|
"LabelBackupLocation": "Lokalizacja kopii zapasowej",
|
||||||
"LabelBackupsEnableAutomaticBackups": "Włącz automatyczne kopie zapasowe",
|
"LabelBackupsEnableAutomaticBackups": "Włącz automatyczne kopie zapasowe",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "Kopie zapasowe są zapisywane w folderze /metadata/backups",
|
"LabelBackupsEnableAutomaticBackupsHelp": "Kopie zapasowe są zapisywane w folderze /metadata/backups",
|
||||||
"LabelBackupsMaxBackupSize": "Maksymalny rozmiar kopii zapasowej (w GB)",
|
"LabelBackupsMaxBackupSize": "Maksymalny rozmiar kopii zapasowej (w GB) (0 oznacza nieograniczony)",
|
||||||
"LabelBackupsMaxBackupSizeHelp": "Jako zabezpieczenie przed błędną konfiguracją, kopie zapasowe nie będą wykonywane, jeśli przekroczą skonfigurowany rozmiar.",
|
"LabelBackupsMaxBackupSizeHelp": "Jako zabezpieczenie przed błędną konfiguracją, kopie zapasowe nie będą wykonywane, jeśli przekroczą skonfigurowany rozmiar.",
|
||||||
"LabelBackupsNumberToKeep": "Liczba kopii zapasowych do przechowywania",
|
"LabelBackupsNumberToKeep": "Liczba kopii zapasowych do przechowywania",
|
||||||
"LabelBackupsNumberToKeepHelp": "Tylko 1 kopia zapasowa zostanie usunięta, więc jeśli masz już więcej kopii zapasowych, powinieneś je ręcznie usunąć.",
|
"LabelBackupsNumberToKeepHelp": "Tylko 1 kopia zapasowa zostanie usunięta, więc jeśli masz już więcej kopii zapasowych, powinieneś je ręcznie usunąć.",
|
||||||
|
"LabelBitrate": "Bitrate",
|
||||||
"LabelBooks": "Książki",
|
"LabelBooks": "Książki",
|
||||||
|
"LabelButtonText": "Tekst przycisku",
|
||||||
"LabelByAuthor": "autorstwa {0}",
|
"LabelByAuthor": "autorstwa {0}",
|
||||||
"LabelChangePassword": "Zmień hasło",
|
"LabelChangePassword": "Zmień hasło",
|
||||||
"LabelChannels": "Kanały",
|
"LabelChannels": "Kanały",
|
||||||
@@ -232,6 +248,7 @@
|
|||||||
"LabelChaptersFound": "Znalezione rozdziały",
|
"LabelChaptersFound": "Znalezione rozdziały",
|
||||||
"LabelClickForMoreInfo": "Kliknij po więcej szczegółów",
|
"LabelClickForMoreInfo": "Kliknij po więcej szczegółów",
|
||||||
"LabelClosePlayer": "Zamknij odtwarzacz",
|
"LabelClosePlayer": "Zamknij odtwarzacz",
|
||||||
|
"LabelCodec": "Kodek",
|
||||||
"LabelCollapseSeries": "Podsumuj serię",
|
"LabelCollapseSeries": "Podsumuj serię",
|
||||||
"LabelCollapseSubSeries": "Zwiń podserie",
|
"LabelCollapseSubSeries": "Zwiń podserie",
|
||||||
"LabelCollection": "Kolekcja",
|
"LabelCollection": "Kolekcja",
|
||||||
@@ -247,6 +264,7 @@
|
|||||||
"LabelCronExpression": "Wyrażenie CRON",
|
"LabelCronExpression": "Wyrażenie CRON",
|
||||||
"LabelCurrent": "Aktualny",
|
"LabelCurrent": "Aktualny",
|
||||||
"LabelCurrently": "Obecnie:",
|
"LabelCurrently": "Obecnie:",
|
||||||
|
"LabelCustomCronExpression": "Niestandardowe wyrażenie Cron:",
|
||||||
"LabelDatetime": "Data i godzina",
|
"LabelDatetime": "Data i godzina",
|
||||||
"LabelDays": "Dni",
|
"LabelDays": "Dni",
|
||||||
"LabelDeleteFromFileSystemCheckbox": "Usuń z systemu plików (odznacz, aby usunąć tylko z bazy danych)",
|
"LabelDeleteFromFileSystemCheckbox": "Usuń z systemu plików (odznacz, aby usunąć tylko z bazy danych)",
|
||||||
@@ -254,6 +272,7 @@
|
|||||||
"LabelDeselectAll": "Odznacz wszystko",
|
"LabelDeselectAll": "Odznacz wszystko",
|
||||||
"LabelDevice": "Urządzenie",
|
"LabelDevice": "Urządzenie",
|
||||||
"LabelDeviceInfo": "Informacja o urządzeniu",
|
"LabelDeviceInfo": "Informacja o urządzeniu",
|
||||||
|
"LabelDeviceIsAvailableTo": "Urządzenie jest dostępne do...",
|
||||||
"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",
|
||||||
@@ -261,16 +280,20 @@
|
|||||||
"LabelDownload": "Pobierz",
|
"LabelDownload": "Pobierz",
|
||||||
"LabelDownloadNEpisodes": "Ściąganie {0} odcinków",
|
"LabelDownloadNEpisodes": "Ściąganie {0} odcinków",
|
||||||
"LabelDuration": "Czas trwania",
|
"LabelDuration": "Czas trwania",
|
||||||
|
"LabelDurationComparisonExactMatch": "(dokładne dopasowanie)",
|
||||||
"LabelDurationComparisonLonger": "({0} dłużej)",
|
"LabelDurationComparisonLonger": "({0} dłużej)",
|
||||||
"LabelDurationComparisonShorter": "({0} krócej)",
|
"LabelDurationComparisonShorter": "({0} krócej)",
|
||||||
"LabelDurationFound": "Znaleziona długość:",
|
"LabelDurationFound": "Znaleziona długość:",
|
||||||
|
"LabelEbook": "Ebook",
|
||||||
"LabelEbooks": "Ebooki",
|
"LabelEbooks": "Ebooki",
|
||||||
"LabelEdit": "Edytuj",
|
"LabelEdit": "Edytuj",
|
||||||
|
"LabelEmail": "E-mail",
|
||||||
"LabelEmailSettingsFromAddress": "Z adresu",
|
"LabelEmailSettingsFromAddress": "Z adresu",
|
||||||
"LabelEmailSettingsRejectUnauthorized": "Odrzuć nieautoryzowane certyfikaty",
|
"LabelEmailSettingsRejectUnauthorized": "Odrzuć nieautoryzowane certyfikaty",
|
||||||
"LabelEmailSettingsRejectUnauthorizedHelp": "Wyłączenie walidacji certyfikatów SSL może narazić cię na ryzyka bezpieczeństwa, takie jak ataki man-in-the-middle. Wyłącz tą opcję wyłącznie jeśli rozumiesz tego skutki i ufasz serwerowi pocztowemu, do którego się podłączasz.",
|
"LabelEmailSettingsRejectUnauthorizedHelp": "Wyłączenie walidacji certyfikatów SSL może narazić cię na ryzyka bezpieczeństwa, takie jak ataki man-in-the-middle. Wyłącz tą opcję wyłącznie jeśli rozumiesz tego skutki i ufasz serwerowi pocztowemu, do którego się podłączasz.",
|
||||||
"LabelEmailSettingsSecure": "Bezpieczeństwo",
|
"LabelEmailSettingsSecure": "Bezpieczeństwo",
|
||||||
"LabelEmailSettingsSecureHelp": "Jeśli włączysz, połączenie będzie korzystać z TLS podczas łączenia do serwera. Jeśli wyłączysz, TLS będzie wykorzystane jeśli serwer wspiera rozszerzenie STARTTLS. W większości przypadków włącz to ustawienie jeśli łączysz się do portu 465. Dla portów 587 lub 25 pozostaw to ustawienie wyłączone. (na podstawie nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "Jeśli włączysz, połączenie będzie korzystać z TLS podczas łączenia do serwera. Jeśli wyłączysz, TLS będzie wykorzystane jeśli serwer wspiera rozszerzenie STARTTLS. W większości przypadków włącz to ustawienie jeśli łączysz się do portu 465. Dla portów 587 lub 25 pozostaw to ustawienie wyłączone. (na podstawie nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Adres testowy",
|
||||||
"LabelEmbeddedCover": "Wbudowana okładka",
|
"LabelEmbeddedCover": "Wbudowana okładka",
|
||||||
"LabelEnable": "Włącz",
|
"LabelEnable": "Włącz",
|
||||||
"LabelEnd": "Zakończ",
|
"LabelEnd": "Zakończ",
|
||||||
@@ -278,16 +301,21 @@
|
|||||||
"LabelEpisode": "Odcinek",
|
"LabelEpisode": "Odcinek",
|
||||||
"LabelEpisodeTitle": "Tytuł odcinka",
|
"LabelEpisodeTitle": "Tytuł odcinka",
|
||||||
"LabelEpisodeType": "Typ odcinka",
|
"LabelEpisodeType": "Typ odcinka",
|
||||||
|
"LabelEpisodes": "Epizody",
|
||||||
"LabelExample": "Przykład",
|
"LabelExample": "Przykład",
|
||||||
"LabelExpandSeries": "Rozwiń serie",
|
"LabelExpandSeries": "Rozwiń serie",
|
||||||
"LabelExpandSubSeries": "Rozwiń podserie",
|
"LabelExpandSubSeries": "Rozwiń podserie",
|
||||||
"LabelExplicit": "Nieprzyzwoite",
|
"LabelExplicit": "Nieprzyzwoite",
|
||||||
|
"LabelExplicitChecked": "Nieprzyzwoite (sprawdzone)",
|
||||||
|
"LabelExplicitUnchecked": "Przyzwoite (niesprawdzone)",
|
||||||
"LabelExportOPML": "Wyeksportuj OPML",
|
"LabelExportOPML": "Wyeksportuj OPML",
|
||||||
"LabelFeedURL": "URL kanału",
|
"LabelFeedURL": "URL kanału",
|
||||||
"LabelFetchingMetadata": "Pobieranie metadanych",
|
"LabelFetchingMetadata": "Pobieranie metadanych",
|
||||||
"LabelFile": "Plik",
|
"LabelFile": "Plik",
|
||||||
"LabelFileBirthtime": "Data utworzenia pliku",
|
"LabelFileBirthtime": "Data utworzenia pliku",
|
||||||
|
"LabelFileBornDate": "Utworzony {0}",
|
||||||
"LabelFileModified": "Data modyfikacji pliku",
|
"LabelFileModified": "Data modyfikacji pliku",
|
||||||
|
"LabelFileModifiedDate": "Modyfikowany {0}",
|
||||||
"LabelFilename": "Nazwa pliku",
|
"LabelFilename": "Nazwa pliku",
|
||||||
"LabelFilterByUser": "Filtruj według danego użytkownika",
|
"LabelFilterByUser": "Filtruj według danego użytkownika",
|
||||||
"LabelFindEpisodes": "Znajdź odcinki",
|
"LabelFindEpisodes": "Znajdź odcinki",
|
||||||
@@ -297,8 +325,10 @@
|
|||||||
"LabelFontBold": "Pogrubiony",
|
"LabelFontBold": "Pogrubiony",
|
||||||
"LabelFontBoldness": "Grubość czcionki",
|
"LabelFontBoldness": "Grubość czcionki",
|
||||||
"LabelFontFamily": "Rodzina czcionek",
|
"LabelFontFamily": "Rodzina czcionek",
|
||||||
|
"LabelFontItalic": "Kursywa",
|
||||||
"LabelFontScale": "Rozmiar czcionki",
|
"LabelFontScale": "Rozmiar czcionki",
|
||||||
"LabelFontStrikethrough": "Przekreślony",
|
"LabelFontStrikethrough": "Przekreślony",
|
||||||
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Gatunek",
|
"LabelGenre": "Gatunek",
|
||||||
"LabelGenres": "Gatunki",
|
"LabelGenres": "Gatunki",
|
||||||
"LabelHardDeleteFile": "Usuń trwale plik",
|
"LabelHardDeleteFile": "Usuń trwale plik",
|
||||||
@@ -306,6 +336,7 @@
|
|||||||
"LabelHasSupplementaryEbook": "Posiada dodatkowy ebook",
|
"LabelHasSupplementaryEbook": "Posiada dodatkowy ebook",
|
||||||
"LabelHideSubtitles": "Ukryj napisy",
|
"LabelHideSubtitles": "Ukryj napisy",
|
||||||
"LabelHighestPriority": "Najwyższy priorytet",
|
"LabelHighestPriority": "Najwyższy priorytet",
|
||||||
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Godzina",
|
"LabelHour": "Godzina",
|
||||||
"LabelHours": "Godziny",
|
"LabelHours": "Godziny",
|
||||||
"LabelIcon": "Ikona",
|
"LabelIcon": "Ikona",
|
||||||
@@ -324,6 +355,7 @@
|
|||||||
"LabelIntervalEveryHour": "Każdej godziny",
|
"LabelIntervalEveryHour": "Każdej godziny",
|
||||||
"LabelInvert": "Inversja",
|
"LabelInvert": "Inversja",
|
||||||
"LabelItem": "Pozycja",
|
"LabelItem": "Pozycja",
|
||||||
|
"LabelJumpBackwardAmount": "Rozmiar skoku do przodu",
|
||||||
"LabelLanguage": "Język",
|
"LabelLanguage": "Język",
|
||||||
"LabelLanguageDefaultServer": "Domyślny język serwera",
|
"LabelLanguageDefaultServer": "Domyślny język serwera",
|
||||||
"LabelLanguages": "Języki",
|
"LabelLanguages": "Języki",
|
||||||
@@ -338,10 +370,13 @@
|
|||||||
"LabelLess": "Mniej",
|
"LabelLess": "Mniej",
|
||||||
"LabelLibrariesAccessibleToUser": "Biblioteki dostępne dla użytkownika",
|
"LabelLibrariesAccessibleToUser": "Biblioteki dostępne dla użytkownika",
|
||||||
"LabelLibrary": "Biblioteka",
|
"LabelLibrary": "Biblioteka",
|
||||||
|
"LabelLibraryFilterSublistEmpty": "Brak {0}",
|
||||||
"LabelLibraryItem": "Element biblioteki",
|
"LabelLibraryItem": "Element biblioteki",
|
||||||
"LabelLibraryName": "Nazwa biblioteki",
|
"LabelLibraryName": "Nazwa biblioteki",
|
||||||
|
"LabelLimit": "Limit",
|
||||||
"LabelLineSpacing": "Odstęp między wierszami",
|
"LabelLineSpacing": "Odstęp między wierszami",
|
||||||
"LabelListenAgain": "Słuchaj ponownie",
|
"LabelListenAgain": "Słuchaj ponownie",
|
||||||
|
"LabelLogLevelDebug": "Debugowanie",
|
||||||
"LabelLogLevelInfo": "Informacja",
|
"LabelLogLevelInfo": "Informacja",
|
||||||
"LabelLogLevelWarn": "Ostrzeżenie",
|
"LabelLogLevelWarn": "Ostrzeżenie",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Szukaj nowych odcinków po dacie",
|
"LabelLookForNewEpisodesAfterDate": "Szukaj nowych odcinków po dacie",
|
||||||
@@ -351,6 +386,7 @@
|
|||||||
"LabelMediaPlayer": "Odtwarzacz",
|
"LabelMediaPlayer": "Odtwarzacz",
|
||||||
"LabelMediaType": "Typ mediów",
|
"LabelMediaType": "Typ mediów",
|
||||||
"LabelMetaTag": "Tag",
|
"LabelMetaTag": "Tag",
|
||||||
|
"LabelMetaTags": "Meta Tagi",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "Źródła metadanych o wyższym priorytecie będą zastępują źródła o niższym priorytecie",
|
"LabelMetadataOrderOfPrecedenceDescription": "Źródła metadanych o wyższym priorytecie będą zastępują źródła o niższym priorytecie",
|
||||||
"LabelMetadataProvider": "Dostawca metadanych",
|
"LabelMetadataProvider": "Dostawca metadanych",
|
||||||
"LabelMinute": "Minuta",
|
"LabelMinute": "Minuta",
|
||||||
@@ -358,6 +394,7 @@
|
|||||||
"LabelMissing": "Brakujący",
|
"LabelMissing": "Brakujący",
|
||||||
"LabelMissingEbook": "Nie posiada ebooka",
|
"LabelMissingEbook": "Nie posiada ebooka",
|
||||||
"LabelMissingSupplementaryEbook": "Nie posiada dodatkowego ebooka",
|
"LabelMissingSupplementaryEbook": "Nie posiada dodatkowego ebooka",
|
||||||
|
"LabelMobileRedirectURIs": "Dozwolone URI przekierowań mobilnych",
|
||||||
"LabelMore": "Więcej",
|
"LabelMore": "Więcej",
|
||||||
"LabelMoreInfo": "Więcej informacji",
|
"LabelMoreInfo": "Więcej informacji",
|
||||||
"LabelName": "Nazwa",
|
"LabelName": "Nazwa",
|
||||||
|
|||||||
+179
-4
@@ -19,6 +19,7 @@
|
|||||||
"ButtonChooseFiles": "Выбор файлов",
|
"ButtonChooseFiles": "Выбор файлов",
|
||||||
"ButtonClearFilter": "Очистить фильтр",
|
"ButtonClearFilter": "Очистить фильтр",
|
||||||
"ButtonCloseFeed": "Закрыть канал",
|
"ButtonCloseFeed": "Закрыть канал",
|
||||||
|
"ButtonCloseSession": "Закрыть открытый сеанс",
|
||||||
"ButtonCollections": "Коллекции",
|
"ButtonCollections": "Коллекции",
|
||||||
"ButtonConfigureScanner": "Конфигурация сканера",
|
"ButtonConfigureScanner": "Конфигурация сканера",
|
||||||
"ButtonCreate": "Создать",
|
"ButtonCreate": "Создать",
|
||||||
@@ -28,6 +29,9 @@
|
|||||||
"ButtonEdit": "Редактировать",
|
"ButtonEdit": "Редактировать",
|
||||||
"ButtonEditChapters": "Редактировать главы",
|
"ButtonEditChapters": "Редактировать главы",
|
||||||
"ButtonEditPodcast": "Редактировать подкаст",
|
"ButtonEditPodcast": "Редактировать подкаст",
|
||||||
|
"ButtonEnable": "Включить",
|
||||||
|
"ButtonFireAndFail": "Пожар и неудача",
|
||||||
|
"ButtonFireOnTest": "Испытание на огнестойкость",
|
||||||
"ButtonForceReScan": "Принудительно пересканировать",
|
"ButtonForceReScan": "Принудительно пересканировать",
|
||||||
"ButtonFullPath": "Полный путь",
|
"ButtonFullPath": "Полный путь",
|
||||||
"ButtonHide": "Скрыть",
|
"ButtonHide": "Скрыть",
|
||||||
@@ -44,18 +48,24 @@
|
|||||||
"ButtonMatchAllAuthors": "Найти всех авторов",
|
"ButtonMatchAllAuthors": "Найти всех авторов",
|
||||||
"ButtonMatchBooks": "Найти книги",
|
"ButtonMatchBooks": "Найти книги",
|
||||||
"ButtonNevermind": "Не важно",
|
"ButtonNevermind": "Не важно",
|
||||||
|
"ButtonNext": "Следующий",
|
||||||
"ButtonNextChapter": "Следующая глава",
|
"ButtonNextChapter": "Следующая глава",
|
||||||
|
"ButtonNextItemInQueue": "Следующий элемент в очереди",
|
||||||
|
"ButtonOk": "Ok",
|
||||||
"ButtonOpenFeed": "Открыть канал",
|
"ButtonOpenFeed": "Открыть канал",
|
||||||
"ButtonOpenManager": "Открыть менеджер",
|
"ButtonOpenManager": "Открыть менеджер",
|
||||||
|
"ButtonPause": "Пауза",
|
||||||
"ButtonPlay": "Слушать",
|
"ButtonPlay": "Слушать",
|
||||||
"ButtonPlaying": "Проигрывается",
|
"ButtonPlaying": "Проигрывается",
|
||||||
"ButtonPlaylists": "Плейлисты",
|
"ButtonPlaylists": "Плейлисты",
|
||||||
"ButtonPrevious": "Предыдущий",
|
"ButtonPrevious": "Предыдущий",
|
||||||
"ButtonPreviousChapter": "Предыдущая глава",
|
"ButtonPreviousChapter": "Предыдущая глава",
|
||||||
|
"ButtonProbeAudioFile": "Сканирование аудиофайла",
|
||||||
"ButtonPurgeAllCache": "Очистить весь кэш",
|
"ButtonPurgeAllCache": "Очистить весь кэш",
|
||||||
"ButtonPurgeItemsCache": "Очистить кэш элементов",
|
"ButtonPurgeItemsCache": "Очистить кэш элементов",
|
||||||
"ButtonQueueAddItem": "Добавить в очередь",
|
"ButtonQueueAddItem": "Добавить в очередь",
|
||||||
"ButtonQueueRemoveItem": "Удалить из очереди",
|
"ButtonQueueRemoveItem": "Удалить из очереди",
|
||||||
|
"ButtonQuickEmbedMetadata": "Быстрое встраивание метаданных",
|
||||||
"ButtonQuickMatch": "Быстрый поиск",
|
"ButtonQuickMatch": "Быстрый поиск",
|
||||||
"ButtonReScan": "Пересканировать",
|
"ButtonReScan": "Пересканировать",
|
||||||
"ButtonRead": "Читать",
|
"ButtonRead": "Читать",
|
||||||
@@ -85,6 +95,7 @@
|
|||||||
"ButtonShow": "Показать",
|
"ButtonShow": "Показать",
|
||||||
"ButtonStartM4BEncode": "Начать кодирование M4B",
|
"ButtonStartM4BEncode": "Начать кодирование M4B",
|
||||||
"ButtonStartMetadataEmbed": "Начать встраивание метаданных",
|
"ButtonStartMetadataEmbed": "Начать встраивание метаданных",
|
||||||
|
"ButtonStats": "Статистика",
|
||||||
"ButtonSubmit": "Применить",
|
"ButtonSubmit": "Применить",
|
||||||
"ButtonTest": "Тест",
|
"ButtonTest": "Тест",
|
||||||
"ButtonUpload": "Загрузить",
|
"ButtonUpload": "Загрузить",
|
||||||
@@ -99,6 +110,7 @@
|
|||||||
"ErrorUploadFetchMetadataNoResults": "Не удалось получить метаданные - попробуйте обновить название и/или автора",
|
"ErrorUploadFetchMetadataNoResults": "Не удалось получить метаданные - попробуйте обновить название и/или автора",
|
||||||
"ErrorUploadLacksTitle": "Название должно быть заполнено",
|
"ErrorUploadLacksTitle": "Название должно быть заполнено",
|
||||||
"HeaderAccount": "Учетная запись",
|
"HeaderAccount": "Учетная запись",
|
||||||
|
"HeaderAddCustomMetadataProvider": "Добавление пользовательского поставщика метаданных",
|
||||||
"HeaderAdvanced": "Дополнительно",
|
"HeaderAdvanced": "Дополнительно",
|
||||||
"HeaderAppriseNotificationSettings": "Настройки оповещений",
|
"HeaderAppriseNotificationSettings": "Настройки оповещений",
|
||||||
"HeaderAudioTracks": "Аудио треки",
|
"HeaderAudioTracks": "Аудио треки",
|
||||||
@@ -117,6 +129,7 @@
|
|||||||
"HeaderDetails": "Подробности",
|
"HeaderDetails": "Подробности",
|
||||||
"HeaderDownloadQueue": "Очередь скачивания",
|
"HeaderDownloadQueue": "Очередь скачивания",
|
||||||
"HeaderEbookFiles": "Файлы e-книг",
|
"HeaderEbookFiles": "Файлы e-книг",
|
||||||
|
"HeaderEmail": "E-mail",
|
||||||
"HeaderEmailSettings": "Настройки Email",
|
"HeaderEmailSettings": "Настройки Email",
|
||||||
"HeaderEpisodes": "Эпизоды",
|
"HeaderEpisodes": "Эпизоды",
|
||||||
"HeaderEreaderDevices": "Устройства E-книга",
|
"HeaderEreaderDevices": "Устройства E-книга",
|
||||||
@@ -143,6 +156,8 @@
|
|||||||
"HeaderMetadataToEmbed": "Метаинформация для встраивания",
|
"HeaderMetadataToEmbed": "Метаинформация для встраивания",
|
||||||
"HeaderNewAccount": "Новая учетная запись",
|
"HeaderNewAccount": "Новая учетная запись",
|
||||||
"HeaderNewLibrary": "Новая библиотека",
|
"HeaderNewLibrary": "Новая библиотека",
|
||||||
|
"HeaderNotificationCreate": "Создать уведомление",
|
||||||
|
"HeaderNotificationUpdate": "Уведомление об обновлении",
|
||||||
"HeaderNotifications": "Уведомления",
|
"HeaderNotifications": "Уведомления",
|
||||||
"HeaderOpenIDConnectAuthentication": "Аутентификация OpenID Connect",
|
"HeaderOpenIDConnectAuthentication": "Аутентификация OpenID Connect",
|
||||||
"HeaderOpenRSSFeed": "Открыть RSS-канал",
|
"HeaderOpenRSSFeed": "Открыть RSS-канал",
|
||||||
@@ -150,6 +165,7 @@
|
|||||||
"HeaderPasswordAuthentication": "Аутентификация по паролю",
|
"HeaderPasswordAuthentication": "Аутентификация по паролю",
|
||||||
"HeaderPermissions": "Разрешения",
|
"HeaderPermissions": "Разрешения",
|
||||||
"HeaderPlayerQueue": "Очередь воспроизведения",
|
"HeaderPlayerQueue": "Очередь воспроизведения",
|
||||||
|
"HeaderPlayerSettings": "Настройки плеера",
|
||||||
"HeaderPlaylist": "Плейлист",
|
"HeaderPlaylist": "Плейлист",
|
||||||
"HeaderPlaylistItems": "Элементы списка воспроизведения",
|
"HeaderPlaylistItems": "Элементы списка воспроизведения",
|
||||||
"HeaderPodcastsToAdd": "Подкасты для добавления",
|
"HeaderPodcastsToAdd": "Подкасты для добавления",
|
||||||
@@ -199,6 +215,7 @@
|
|||||||
"LabelAddToPlaylist": "Добавить в плейлист",
|
"LabelAddToPlaylist": "Добавить в плейлист",
|
||||||
"LabelAddToPlaylistBatch": "Добавить {0} элементов в плейлист",
|
"LabelAddToPlaylistBatch": "Добавить {0} элементов в плейлист",
|
||||||
"LabelAddedAt": "Дата добавления",
|
"LabelAddedAt": "Дата добавления",
|
||||||
|
"LabelAddedDate": "Добавлено {0}",
|
||||||
"LabelAdminUsersOnly": "Только для пользователей с правами администратора",
|
"LabelAdminUsersOnly": "Только для пользователей с правами администратора",
|
||||||
"LabelAll": "Все",
|
"LabelAll": "Все",
|
||||||
"LabelAllUsers": "Все пользователи",
|
"LabelAllUsers": "Все пользователи",
|
||||||
@@ -221,13 +238,14 @@
|
|||||||
"LabelBackupLocation": "Путь для бэкапов",
|
"LabelBackupLocation": "Путь для бэкапов",
|
||||||
"LabelBackupsEnableAutomaticBackups": "Включить автоматическое бэкапирование",
|
"LabelBackupsEnableAutomaticBackups": "Включить автоматическое бэкапирование",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "Бэкапы сохраняются в /metadata/backups",
|
"LabelBackupsEnableAutomaticBackupsHelp": "Бэкапы сохраняются в /metadata/backups",
|
||||||
"LabelBackupsMaxBackupSize": "Максимальный размер бэкапа (в GB)",
|
"LabelBackupsMaxBackupSize": "Максимальный размер бэкапа (в GB) (0 для неограниченного лимита)",
|
||||||
"LabelBackupsMaxBackupSizeHelp": "В качестве защиты процесс бэкапирования будет завершаться ошибкой, если будет превышен настроенный размер.",
|
"LabelBackupsMaxBackupSizeHelp": "В качестве защиты процесс бэкапирования будет завершаться ошибкой, если будет превышен настроенный размер.",
|
||||||
"LabelBackupsNumberToKeep": "Сохранять бэкапов",
|
"LabelBackupsNumberToKeep": "Сохранять бэкапов",
|
||||||
"LabelBackupsNumberToKeepHelp": "За один раз только 1 бэкап будет удален, так что если у вас будет больше бэкапов, то их нужно удалить вручную.",
|
"LabelBackupsNumberToKeepHelp": "За один раз только 1 бэкап будет удален, так что если у вас будет больше бэкапов, то их нужно удалить вручную.",
|
||||||
"LabelBitrate": "Битрейт",
|
"LabelBitrate": "Битрейт",
|
||||||
"LabelBooks": "Книги",
|
"LabelBooks": "Книги",
|
||||||
"LabelButtonText": "Текст кнопки",
|
"LabelButtonText": "Текст кнопки",
|
||||||
|
"LabelByAuthor": "{0}",
|
||||||
"LabelChangePassword": "Изменить пароль",
|
"LabelChangePassword": "Изменить пароль",
|
||||||
"LabelChannels": "Каналы",
|
"LabelChannels": "Каналы",
|
||||||
"LabelChapterTitle": "Название главы",
|
"LabelChapterTitle": "Название главы",
|
||||||
@@ -237,6 +255,7 @@
|
|||||||
"LabelClosePlayer": "Закрыть проигрыватель",
|
"LabelClosePlayer": "Закрыть проигрыватель",
|
||||||
"LabelCodec": "Кодек",
|
"LabelCodec": "Кодек",
|
||||||
"LabelCollapseSeries": "Свернуть серии",
|
"LabelCollapseSeries": "Свернуть серии",
|
||||||
|
"LabelCollapseSubSeries": "Свернуть подсерию",
|
||||||
"LabelCollection": "Коллекция",
|
"LabelCollection": "Коллекция",
|
||||||
"LabelCollections": "Коллекции",
|
"LabelCollections": "Коллекции",
|
||||||
"LabelComplete": "Завершить",
|
"LabelComplete": "Завершить",
|
||||||
@@ -252,6 +271,7 @@
|
|||||||
"LabelCurrently": "Текущее:",
|
"LabelCurrently": "Текущее:",
|
||||||
"LabelCustomCronExpression": "Пользовательское выражение Cron:",
|
"LabelCustomCronExpression": "Пользовательское выражение Cron:",
|
||||||
"LabelDatetime": "Дата и время",
|
"LabelDatetime": "Дата и время",
|
||||||
|
"LabelDays": "Дней",
|
||||||
"LabelDeleteFromFileSystemCheckbox": "Удалить из файловой системы (снимите флажок, чтобы удалить только из базы данных)",
|
"LabelDeleteFromFileSystemCheckbox": "Удалить из файловой системы (снимите флажок, чтобы удалить только из базы данных)",
|
||||||
"LabelDescription": "Описание",
|
"LabelDescription": "Описание",
|
||||||
"LabelDeselectAll": "Снять выделение",
|
"LabelDeselectAll": "Снять выделение",
|
||||||
@@ -272,6 +292,7 @@
|
|||||||
"LabelEbook": "E-книга",
|
"LabelEbook": "E-книга",
|
||||||
"LabelEbooks": "E-книги",
|
"LabelEbooks": "E-книги",
|
||||||
"LabelEdit": "Редактировать",
|
"LabelEdit": "Редактировать",
|
||||||
|
"LabelEmail": "E-mail",
|
||||||
"LabelEmailSettingsFromAddress": "Адрес От",
|
"LabelEmailSettingsFromAddress": "Адрес От",
|
||||||
"LabelEmailSettingsRejectUnauthorized": "Отклонение неавторизованных сертификатов",
|
"LabelEmailSettingsRejectUnauthorized": "Отклонение неавторизованных сертификатов",
|
||||||
"LabelEmailSettingsRejectUnauthorizedHelp": "Отключение проверки SSL-сертификата может подвергнуть ваше подключение рискам безопасности, таким как атаки типа \"man-in-the-middle\". Отключайте эту опцию только в том случае, если вы понимаете последствия и доверяете почтовому серверу, к которому подключаетесь.",
|
"LabelEmailSettingsRejectUnauthorizedHelp": "Отключение проверки SSL-сертификата может подвергнуть ваше подключение рискам безопасности, таким как атаки типа \"man-in-the-middle\". Отключайте эту опцию только в том случае, если вы понимаете последствия и доверяете почтовому серверу, к которому подключаетесь.",
|
||||||
@@ -281,18 +302,25 @@
|
|||||||
"LabelEmbeddedCover": "Встроенная обложка",
|
"LabelEmbeddedCover": "Встроенная обложка",
|
||||||
"LabelEnable": "Включить",
|
"LabelEnable": "Включить",
|
||||||
"LabelEnd": "Конец",
|
"LabelEnd": "Конец",
|
||||||
|
"LabelEndOfChapter": "Конец главы",
|
||||||
"LabelEpisode": "Эпизод",
|
"LabelEpisode": "Эпизод",
|
||||||
"LabelEpisodeTitle": "Имя эпизода",
|
"LabelEpisodeTitle": "Имя эпизода",
|
||||||
"LabelEpisodeType": "Тип эпизода",
|
"LabelEpisodeType": "Тип эпизода",
|
||||||
|
"LabelEpisodes": "Эпизодов",
|
||||||
"LabelExample": "Пример",
|
"LabelExample": "Пример",
|
||||||
|
"LabelExpandSeries": "Развернуть серию",
|
||||||
|
"LabelExpandSubSeries": "Развернуть подсерию",
|
||||||
"LabelExplicit": "Явный",
|
"LabelExplicit": "Явный",
|
||||||
"LabelExplicitChecked": "Явный (отмечено)",
|
"LabelExplicitChecked": "Явный (отмечено)",
|
||||||
"LabelExplicitUnchecked": "Не явно (не отмечено)",
|
"LabelExplicitUnchecked": "Не явно (не отмечено)",
|
||||||
|
"LabelExportOPML": "Экспорт OPML",
|
||||||
"LabelFeedURL": "URL канала",
|
"LabelFeedURL": "URL канала",
|
||||||
"LabelFetchingMetadata": "Извлечение метаданных",
|
"LabelFetchingMetadata": "Извлечение метаданных",
|
||||||
"LabelFile": "Файл",
|
"LabelFile": "Файл",
|
||||||
"LabelFileBirthtime": "Дата создания",
|
"LabelFileBirthtime": "Дата создания",
|
||||||
|
"LabelFileBornDate": "Родился {0}",
|
||||||
"LabelFileModified": "Дата модификации",
|
"LabelFileModified": "Дата модификации",
|
||||||
|
"LabelFileModifiedDate": "Изменено {0}",
|
||||||
"LabelFilename": "Имя файла",
|
"LabelFilename": "Имя файла",
|
||||||
"LabelFilterByUser": "Фильтр по пользователю",
|
"LabelFilterByUser": "Фильтр по пользователю",
|
||||||
"LabelFindEpisodes": "Найти эпизоды",
|
"LabelFindEpisodes": "Найти эпизоды",
|
||||||
@@ -302,6 +330,7 @@
|
|||||||
"LabelFontBold": "Жирный",
|
"LabelFontBold": "Жирный",
|
||||||
"LabelFontBoldness": "Жирность шрифта",
|
"LabelFontBoldness": "Жирность шрифта",
|
||||||
"LabelFontFamily": "Семейство шрифтов",
|
"LabelFontFamily": "Семейство шрифтов",
|
||||||
|
"LabelFontItalic": "Курсив",
|
||||||
"LabelFontScale": "Масштаб шрифта",
|
"LabelFontScale": "Масштаб шрифта",
|
||||||
"LabelFontStrikethrough": "Зачеркнутый",
|
"LabelFontStrikethrough": "Зачеркнутый",
|
||||||
"LabelFormat": "Формат",
|
"LabelFormat": "Формат",
|
||||||
@@ -310,9 +339,11 @@
|
|||||||
"LabelHardDeleteFile": "Жесткое удаление файла",
|
"LabelHardDeleteFile": "Жесткое удаление файла",
|
||||||
"LabelHasEbook": "Есть e-книга",
|
"LabelHasEbook": "Есть e-книга",
|
||||||
"LabelHasSupplementaryEbook": "Есть дополнительная e-книга",
|
"LabelHasSupplementaryEbook": "Есть дополнительная e-книга",
|
||||||
|
"LabelHideSubtitles": "Скрыть субтитры",
|
||||||
"LabelHighestPriority": "Наивысший приоритет",
|
"LabelHighestPriority": "Наивысший приоритет",
|
||||||
"LabelHost": "Хост",
|
"LabelHost": "Хост",
|
||||||
"LabelHour": "Часы",
|
"LabelHour": "Часы",
|
||||||
|
"LabelHours": "Часов",
|
||||||
"LabelIcon": "Иконка",
|
"LabelIcon": "Иконка",
|
||||||
"LabelImageURLFromTheWeb": "URL-адрес изображения из Интернета",
|
"LabelImageURLFromTheWeb": "URL-адрес изображения из Интернета",
|
||||||
"LabelInProgress": "В процессе",
|
"LabelInProgress": "В процессе",
|
||||||
@@ -329,6 +360,8 @@
|
|||||||
"LabelIntervalEveryHour": "Каждый час",
|
"LabelIntervalEveryHour": "Каждый час",
|
||||||
"LabelInvert": "Инвертировать",
|
"LabelInvert": "Инвертировать",
|
||||||
"LabelItem": "Элемент",
|
"LabelItem": "Элемент",
|
||||||
|
"LabelJumpBackwardAmount": "Прыжок назад на величину",
|
||||||
|
"LabelJumpForwardAmount": "Прыжок вперед на величину",
|
||||||
"LabelLanguage": "Язык",
|
"LabelLanguage": "Язык",
|
||||||
"LabelLanguageDefaultServer": "Язык сервера по умолчанию",
|
"LabelLanguageDefaultServer": "Язык сервера по умолчанию",
|
||||||
"LabelLanguages": "Языки",
|
"LabelLanguages": "Языки",
|
||||||
@@ -343,11 +376,15 @@
|
|||||||
"LabelLess": "Менее",
|
"LabelLess": "Менее",
|
||||||
"LabelLibrariesAccessibleToUser": "Библиотеки доступные для пользователя",
|
"LabelLibrariesAccessibleToUser": "Библиотеки доступные для пользователя",
|
||||||
"LabelLibrary": "Библиотека",
|
"LabelLibrary": "Библиотека",
|
||||||
|
"LabelLibraryFilterSublistEmpty": "Нет {0}",
|
||||||
"LabelLibraryItem": "Элемент библиотеки",
|
"LabelLibraryItem": "Элемент библиотеки",
|
||||||
"LabelLibraryName": "Имя библиотеки",
|
"LabelLibraryName": "Имя библиотеки",
|
||||||
"LabelLimit": "Лимит",
|
"LabelLimit": "Лимит",
|
||||||
"LabelLineSpacing": "Межстрочный интервал",
|
"LabelLineSpacing": "Межстрочный интервал",
|
||||||
"LabelListenAgain": "Послушать снова",
|
"LabelListenAgain": "Послушать снова",
|
||||||
|
"LabelLogLevelDebug": "Debug",
|
||||||
|
"LabelLogLevelInfo": "Info",
|
||||||
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
||||||
"LabelLowestPriority": "Самый низкий приоритет",
|
"LabelLowestPriority": "Самый низкий приоритет",
|
||||||
"LabelMatchExistingUsersBy": "Сопоставление существующих пользователей по",
|
"LabelMatchExistingUsersBy": "Сопоставление существующих пользователей по",
|
||||||
@@ -359,6 +396,7 @@
|
|||||||
"LabelMetadataOrderOfPrecedenceDescription": "Источники метаданных с более высоким приоритетом будут переопределять источники метаданных с более низким приоритетом",
|
"LabelMetadataOrderOfPrecedenceDescription": "Источники метаданных с более высоким приоритетом будут переопределять источники метаданных с более низким приоритетом",
|
||||||
"LabelMetadataProvider": "Провайдер",
|
"LabelMetadataProvider": "Провайдер",
|
||||||
"LabelMinute": "Минуты",
|
"LabelMinute": "Минуты",
|
||||||
|
"LabelMinutes": "Минуты",
|
||||||
"LabelMissing": "Потеряно",
|
"LabelMissing": "Потеряно",
|
||||||
"LabelMissingEbook": "Нет e-книги",
|
"LabelMissingEbook": "Нет e-книги",
|
||||||
"LabelMissingSupplementaryEbook": "Нет дополнительной e-книги",
|
"LabelMissingSupplementaryEbook": "Нет дополнительной e-книги",
|
||||||
@@ -398,6 +436,7 @@
|
|||||||
"LabelOverwrite": "Перезаписать",
|
"LabelOverwrite": "Перезаписать",
|
||||||
"LabelPassword": "Пароль",
|
"LabelPassword": "Пароль",
|
||||||
"LabelPath": "Путь",
|
"LabelPath": "Путь",
|
||||||
|
"LabelPermanent": "Постоянный",
|
||||||
"LabelPermissionsAccessAllLibraries": "Есть доступ ко всем библиотекам",
|
"LabelPermissionsAccessAllLibraries": "Есть доступ ко всем библиотекам",
|
||||||
"LabelPermissionsAccessAllTags": "Есть доступ ко всем тегам",
|
"LabelPermissionsAccessAllTags": "Есть доступ ко всем тегам",
|
||||||
"LabelPermissionsAccessExplicitContent": "Есть доступ к явному содержимому",
|
"LabelPermissionsAccessExplicitContent": "Есть доступ к явному содержимому",
|
||||||
@@ -408,6 +447,7 @@
|
|||||||
"LabelPersonalYearReview": "Итоги прошедшего года ({0})",
|
"LabelPersonalYearReview": "Итоги прошедшего года ({0})",
|
||||||
"LabelPhotoPathURL": "Путь к фото/URL",
|
"LabelPhotoPathURL": "Путь к фото/URL",
|
||||||
"LabelPlayMethod": "Метод воспроизведения",
|
"LabelPlayMethod": "Метод воспроизведения",
|
||||||
|
"LabelPlayerChapterNumberMarker": "{0} из {1}",
|
||||||
"LabelPlaylists": "Плейлисты",
|
"LabelPlaylists": "Плейлисты",
|
||||||
"LabelPodcast": "Подкаст",
|
"LabelPodcast": "Подкаст",
|
||||||
"LabelPodcastSearchRegion": "Регион поиска подкастов",
|
"LabelPodcastSearchRegion": "Регион поиска подкастов",
|
||||||
@@ -419,8 +459,10 @@
|
|||||||
"LabelPrimaryEbook": "Основная e-книга",
|
"LabelPrimaryEbook": "Основная e-книга",
|
||||||
"LabelProgress": "Прогресс",
|
"LabelProgress": "Прогресс",
|
||||||
"LabelProvider": "Провайдер",
|
"LabelProvider": "Провайдер",
|
||||||
|
"LabelProviderAuthorizationValue": "Значение заголовка авторизации",
|
||||||
"LabelPubDate": "Дата публикации",
|
"LabelPubDate": "Дата публикации",
|
||||||
"LabelPublishYear": "Год публикации",
|
"LabelPublishYear": "Год публикации",
|
||||||
|
"LabelPublishedDate": "Опубликовано {0}",
|
||||||
"LabelPublisher": "Издатель",
|
"LabelPublisher": "Издатель",
|
||||||
"LabelPublishers": "Издатели",
|
"LabelPublishers": "Издатели",
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Пользовательский Email владельца",
|
"LabelRSSFeedCustomOwnerEmail": "Пользовательский Email владельца",
|
||||||
@@ -429,6 +471,8 @@
|
|||||||
"LabelRSSFeedPreventIndexing": "Запретить индексирование",
|
"LabelRSSFeedPreventIndexing": "Запретить индексирование",
|
||||||
"LabelRSSFeedSlug": "Встроить RSS-канал",
|
"LabelRSSFeedSlug": "Встроить RSS-канал",
|
||||||
"LabelRSSFeedURL": "URL RSS-канала",
|
"LabelRSSFeedURL": "URL RSS-канала",
|
||||||
|
"LabelRandomly": "Случайно",
|
||||||
|
"LabelReAddSeriesToContinueListening": "Повторно добавить серию в «Продолжить слушать»",
|
||||||
"LabelRead": "Читать",
|
"LabelRead": "Читать",
|
||||||
"LabelReadAgain": "Читать снова",
|
"LabelReadAgain": "Читать снова",
|
||||||
"LabelReadEbookWithoutProgress": "Читать e-книгу без сохранения прогресса",
|
"LabelReadEbookWithoutProgress": "Читать e-книгу без сохранения прогресса",
|
||||||
@@ -457,7 +501,7 @@
|
|||||||
"LabelSetEbookAsPrimary": "Установить как основную",
|
"LabelSetEbookAsPrimary": "Установить как основную",
|
||||||
"LabelSetEbookAsSupplementary": "Установить как дополнительную",
|
"LabelSetEbookAsSupplementary": "Установить как дополнительную",
|
||||||
"LabelSettingsAudiobooksOnly": "Только аудиокниги",
|
"LabelSettingsAudiobooksOnly": "Только аудиокниги",
|
||||||
"LabelSettingsAudiobooksOnlyHelp": "Если включить эту настройку, файлы электронных книг будут игнорироваться, за исключением случаев, когда они находятся в папке с аудиокнигами, в этом случае они будут рассматриваться как дополнительные электронные книги.",
|
"LabelSettingsAudiobooksOnlyHelp": "Если включить эту настройку, файлы электронных книг будут игнорироваться, за исключением случаев, когда они находятся в папке с аудиокнигами, в этом случае они будут рассматриваться как дополнительные электронные книги",
|
||||||
"LabelSettingsBookshelfViewHelp": "Конструкция с деревянными полками",
|
"LabelSettingsBookshelfViewHelp": "Конструкция с деревянными полками",
|
||||||
"LabelSettingsChromecastSupport": "Поддержка Chromecast",
|
"LabelSettingsChromecastSupport": "Поддержка Chromecast",
|
||||||
"LabelSettingsDateFormat": "Формат даты",
|
"LabelSettingsDateFormat": "Формат даты",
|
||||||
@@ -482,7 +526,7 @@
|
|||||||
"LabelSettingsParseSubtitles": "Разбор подзаголовков",
|
"LabelSettingsParseSubtitles": "Разбор подзаголовков",
|
||||||
"LabelSettingsParseSubtitlesHelp": "Извлечение подзаголовков из имен папок аудиокниг.<br>Подзаголовок должны быть отделен \" - \"<br>например \"Название Книги - Тут Подзаголовок\" подзаголовок будет \"Тут Подзаголовок\"",
|
"LabelSettingsParseSubtitlesHelp": "Извлечение подзаголовков из имен папок аудиокниг.<br>Подзаголовок должны быть отделен \" - \"<br>например \"Название Книги - Тут Подзаголовок\" подзаголовок будет \"Тут Подзаголовок\"",
|
||||||
"LabelSettingsPreferMatchedMetadata": "Предпочитать метаданные поиска",
|
"LabelSettingsPreferMatchedMetadata": "Предпочитать метаданные поиска",
|
||||||
"LabelSettingsPreferMatchedMetadataHelp": "Данные поиска будут перезаписывать данные книг при использовании Быстрого Поиска. По умолчанию Быстрый Поиск будет использоваться только при отсутствии данных",
|
"LabelSettingsPreferMatchedMetadataHelp": "Данные поиска будут перезаписывать данные книг при использовании Быстрого Поиска. По умолчанию Быстрый Поиск будет использоваться только при отсутствии данных.",
|
||||||
"LabelSettingsSkipMatchingBooksWithASIN": "Пропускать Поиск книг у которых уже заполнен ASIN",
|
"LabelSettingsSkipMatchingBooksWithASIN": "Пропускать Поиск книг у которых уже заполнен ASIN",
|
||||||
"LabelSettingsSkipMatchingBooksWithISBN": "Пропускать Поиск книг у которых уже заполнен ISBN",
|
"LabelSettingsSkipMatchingBooksWithISBN": "Пропускать Поиск книг у которых уже заполнен ISBN",
|
||||||
"LabelSettingsSortingIgnorePrefixes": "Игнорировать префиксы при сортировке",
|
"LabelSettingsSortingIgnorePrefixes": "Игнорировать префиксы при сортировке",
|
||||||
@@ -494,8 +538,12 @@
|
|||||||
"LabelSettingsStoreMetadataWithItem": "Хранить метаинформацию с элементом",
|
"LabelSettingsStoreMetadataWithItem": "Хранить метаинформацию с элементом",
|
||||||
"LabelSettingsStoreMetadataWithItemHelp": "По умолчанию метаинформация сохраняется в папке /metadata/items, при включении этой настройки метаинформация будет храниться в папке элемента",
|
"LabelSettingsStoreMetadataWithItemHelp": "По умолчанию метаинформация сохраняется в папке /metadata/items, при включении этой настройки метаинформация будет храниться в папке элемента",
|
||||||
"LabelSettingsTimeFormat": "Формат времени",
|
"LabelSettingsTimeFormat": "Формат времени",
|
||||||
|
"LabelShare": "Поделиться",
|
||||||
|
"LabelShareOpen": "Общедоступно",
|
||||||
|
"LabelShareURL": "Общедоступный URL",
|
||||||
"LabelShowAll": "Показать все",
|
"LabelShowAll": "Показать все",
|
||||||
"LabelShowSeconds": "Отображать секунды",
|
"LabelShowSeconds": "Отображать секунды",
|
||||||
|
"LabelShowSubtitles": "Показать субтитры",
|
||||||
"LabelSize": "Размер",
|
"LabelSize": "Размер",
|
||||||
"LabelSleepTimer": "Таймер сна",
|
"LabelSleepTimer": "Таймер сна",
|
||||||
"LabelSlug": "Слизень",
|
"LabelSlug": "Слизень",
|
||||||
@@ -526,12 +574,17 @@
|
|||||||
"LabelTagsNotAccessibleToUser": "Теги не доступные для пользователя",
|
"LabelTagsNotAccessibleToUser": "Теги не доступные для пользователя",
|
||||||
"LabelTasks": "Запущенные задачи",
|
"LabelTasks": "Запущенные задачи",
|
||||||
"LabelTextEditorBulletedList": "Маркированный список",
|
"LabelTextEditorBulletedList": "Маркированный список",
|
||||||
|
"LabelTextEditorLink": "Связь",
|
||||||
"LabelTextEditorNumberedList": "Нумерованный список",
|
"LabelTextEditorNumberedList": "Нумерованный список",
|
||||||
"LabelTextEditorUnlink": "Отсоединить",
|
"LabelTextEditorUnlink": "Отсоединить",
|
||||||
"LabelTheme": "Тема",
|
"LabelTheme": "Тема",
|
||||||
"LabelThemeDark": "Темная",
|
"LabelThemeDark": "Темная",
|
||||||
"LabelThemeLight": "Светлая",
|
"LabelThemeLight": "Светлая",
|
||||||
"LabelTimeBase": "Временная база",
|
"LabelTimeBase": "Временная база",
|
||||||
|
"LabelTimeDurationXHours": "{0} часов",
|
||||||
|
"LabelTimeDurationXMinutes": "{0} минут",
|
||||||
|
"LabelTimeDurationXSeconds": "{0} секунд",
|
||||||
|
"LabelTimeInMinutes": "Время в минутах",
|
||||||
"LabelTimeListened": "Время прослушивания",
|
"LabelTimeListened": "Время прослушивания",
|
||||||
"LabelTimeListenedToday": "Время прослушивания сегодня",
|
"LabelTimeListenedToday": "Время прослушивания сегодня",
|
||||||
"LabelTimeRemaining": "{0} осталось",
|
"LabelTimeRemaining": "{0} осталось",
|
||||||
@@ -555,6 +608,7 @@
|
|||||||
"LabelUnabridged": "Полное издание",
|
"LabelUnabridged": "Полное издание",
|
||||||
"LabelUndo": "Отменить",
|
"LabelUndo": "Отменить",
|
||||||
"LabelUnknown": "Неизвестно",
|
"LabelUnknown": "Неизвестно",
|
||||||
|
"LabelUnknownPublishDate": "Дата публикации неизвестна",
|
||||||
"LabelUpdateCover": "Обновить обложку",
|
"LabelUpdateCover": "Обновить обложку",
|
||||||
"LabelUpdateCoverHelp": "Позволяет перезаписывать существующие обложки для выбранных книг если будут найдены",
|
"LabelUpdateCoverHelp": "Позволяет перезаписывать существующие обложки для выбранных книг если будут найдены",
|
||||||
"LabelUpdateDetails": "Обновить подробности",
|
"LabelUpdateDetails": "Обновить подробности",
|
||||||
@@ -571,9 +625,12 @@
|
|||||||
"LabelVersion": "Версия",
|
"LabelVersion": "Версия",
|
||||||
"LabelViewBookmarks": "Закладки",
|
"LabelViewBookmarks": "Закладки",
|
||||||
"LabelViewChapters": "Главы",
|
"LabelViewChapters": "Главы",
|
||||||
|
"LabelViewPlayerSettings": "Просмотр настроек плеера",
|
||||||
"LabelViewQueue": "Очередь воспроизведения",
|
"LabelViewQueue": "Очередь воспроизведения",
|
||||||
"LabelVolume": "Громкость",
|
"LabelVolume": "Громкость",
|
||||||
"LabelWeekdaysToRun": "Дни недели для запуска",
|
"LabelWeekdaysToRun": "Дни недели для запуска",
|
||||||
|
"LabelXBooks": "{0} книг",
|
||||||
|
"LabelXItems": "{0} элементов",
|
||||||
"LabelYearReviewHide": "Скрыть итоги года",
|
"LabelYearReviewHide": "Скрыть итоги года",
|
||||||
"LabelYearReviewShow": "Итоги года",
|
"LabelYearReviewShow": "Итоги года",
|
||||||
"LabelYourAudiobookDuration": "Продолжительность Вашей книги",
|
"LabelYourAudiobookDuration": "Продолжительность Вашей книги",
|
||||||
@@ -583,6 +640,9 @@
|
|||||||
"MessageAddToPlayerQueue": "Добавить в очередь проигрывателя",
|
"MessageAddToPlayerQueue": "Добавить в очередь проигрывателя",
|
||||||
"MessageAppriseDescription": "Для использования этой функции необходимо иметь запущенный экземпляр <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или api которое обрабатывает те же самые запросы. <br />URL-адрес API Apprise должен быть полным URL-адресом для отправки уведомления, т.е., если API запущено по адресу <code>http://192.168.1.1:8337</code> тогда нужно указать <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Для использования этой функции необходимо иметь запущенный экземпляр <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или api которое обрабатывает те же самые запросы. <br />URL-адрес API Apprise должен быть полным URL-адресом для отправки уведомления, т.е., если API запущено по адресу <code>http://192.168.1.1:8337</code> тогда нужно указать <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageBackupsDescription": "Бэкап включает пользователей, прогресс пользователей, данные элементов библиотеки, настройки сервера и изображения хранящиеся в <code>/metadata/items</code> и <code>/metadata/authors</code>. Бэкапы <strong>НЕ</strong> сохраняют файлы из папок библиотек.",
|
"MessageBackupsDescription": "Бэкап включает пользователей, прогресс пользователей, данные элементов библиотеки, настройки сервера и изображения хранящиеся в <code>/metadata/items</code> и <code>/metadata/authors</code>. Бэкапы <strong>НЕ</strong> сохраняют файлы из папок библиотек.",
|
||||||
|
"MessageBackupsLocationEditNote": "Примечание: Обновление местоположения резервной копии не приведет к перемещению или изменению существующих резервных копий",
|
||||||
|
"MessageBackupsLocationNoEditNote": "Примечание: Местоположение резервного копирования задается с помощью переменной среды и не может быть изменено здесь.",
|
||||||
|
"MessageBackupsLocationPathEmpty": "Путь к расположению резервной копии не может быть пустым",
|
||||||
"MessageBatchQuickMatchDescription": "Быстрый Поиск попытается добавить отсутствующие обложки и метаданные для выбранных элементов. Включите параметры ниже, чтобы разрешить Быстрому Поиску перезаписывать существующие обложки и/или метаданные.",
|
"MessageBatchQuickMatchDescription": "Быстрый Поиск попытается добавить отсутствующие обложки и метаданные для выбранных элементов. Включите параметры ниже, чтобы разрешить Быстрому Поиску перезаписывать существующие обложки и/или метаданные.",
|
||||||
"MessageBookshelfNoCollections": "Вы еще не создали ни одной коллекции",
|
"MessageBookshelfNoCollections": "Вы еще не создали ни одной коллекции",
|
||||||
"MessageBookshelfNoRSSFeeds": "Нет открытых RSS-каналов",
|
"MessageBookshelfNoRSSFeeds": "Нет открытых RSS-каналов",
|
||||||
@@ -597,16 +657,22 @@
|
|||||||
"MessageCheckingCron": "Проверка cron...",
|
"MessageCheckingCron": "Проверка cron...",
|
||||||
"MessageConfirmCloseFeed": "Вы уверены, что хотите закрыть этот канал?",
|
"MessageConfirmCloseFeed": "Вы уверены, что хотите закрыть этот канал?",
|
||||||
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
||||||
|
"MessageConfirmDeleteDevice": "Вы уверены, что хотите удалить устройство для чтения электронных книг \"{0}\"?",
|
||||||
"MessageConfirmDeleteFile": "Это удалит файл из Вашей файловой системы. Вы уверены?",
|
"MessageConfirmDeleteFile": "Это удалит файл из Вашей файловой системы. Вы уверены?",
|
||||||
"MessageConfirmDeleteLibrary": "Вы уверены, что хотите навсегда удалить библиотеку \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Вы уверены, что хотите навсегда удалить библиотеку \"{0}\"?",
|
||||||
"MessageConfirmDeleteLibraryItem": "Это приведет к удалению элемента библиотеки из базы данных и файловой системы. Вы уверены?",
|
"MessageConfirmDeleteLibraryItem": "Это приведет к удалению элемента библиотеки из базы данных и файловой системы. Вы уверены?",
|
||||||
"MessageConfirmDeleteLibraryItems": "Это приведет к удалению {0} элементов библиотеки из базы данных и файловой системы. Вы уверены?",
|
"MessageConfirmDeleteLibraryItems": "Это приведет к удалению {0} элементов библиотеки из базы данных и файловой системы. Вы уверены?",
|
||||||
|
"MessageConfirmDeleteMetadataProvider": "Вы уверены, что хотите удалить пользовательский поставщик метаданных \"{0}\"?",
|
||||||
|
"MessageConfirmDeleteNotification": "Вы уверены, что хотите удалить это уведомление?",
|
||||||
"MessageConfirmDeleteSession": "Вы уверены, что хотите удалить этот сеанс?",
|
"MessageConfirmDeleteSession": "Вы уверены, что хотите удалить этот сеанс?",
|
||||||
"MessageConfirmForceReScan": "Вы уверены, что хотите принудительно выполнить повторное сканирование?",
|
"MessageConfirmForceReScan": "Вы уверены, что хотите принудительно выполнить повторное сканирование?",
|
||||||
"MessageConfirmMarkAllEpisodesFinished": "Вы уверены, что хотите отметить все эпизоды как завершенные?",
|
"MessageConfirmMarkAllEpisodesFinished": "Вы уверены, что хотите отметить все эпизоды как завершенные?",
|
||||||
"MessageConfirmMarkAllEpisodesNotFinished": "Вы уверены, что хотите отметить все эпизоды как не завершенные?",
|
"MessageConfirmMarkAllEpisodesNotFinished": "Вы уверены, что хотите отметить все эпизоды как не завершенные?",
|
||||||
|
"MessageConfirmMarkItemFinished": "Вы уверены, что хотите отметить «{0}» как завершенную?",
|
||||||
|
"MessageConfirmMarkItemNotFinished": "Вы уверены, что хотите отметить «{0}» как не завершенную?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как завершенные?",
|
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как завершенные?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как не завершенные?",
|
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как не завершенные?",
|
||||||
|
"MessageConfirmNotificationTestTrigger": "Активировать это уведомление с тестовыми данными?",
|
||||||
"MessageConfirmPurgeCache": "Очистка кэша удалит весь каталог в <code>/metadata/cache</code>. <br /><br />Вы уверены, что хотите удалить каталог кэша?",
|
"MessageConfirmPurgeCache": "Очистка кэша удалит весь каталог в <code>/metadata/cache</code>. <br /><br />Вы уверены, что хотите удалить каталог кэша?",
|
||||||
"MessageConfirmPurgeItemsCache": "Очистка кэша элементов удалит весь каталог в <code>/metadata/cache/items</code>.<br />Вы уверены?",
|
"MessageConfirmPurgeItemsCache": "Очистка кэша элементов удалит весь каталог в <code>/metadata/cache/items</code>.<br />Вы уверены?",
|
||||||
"MessageConfirmQuickEmbed": "Предупреждение! Быстрое встраивание не позволяет создавать резервные копии аудиофайлов. Убедитесь, что у вас есть резервная копия аудиофайлов. <br><br>Хотите продолжить?",
|
"MessageConfirmQuickEmbed": "Предупреждение! Быстрое встраивание не позволяет создавать резервные копии аудиофайлов. Убедитесь, что у вас есть резервная копия аудиофайлов. <br><br>Хотите продолжить?",
|
||||||
@@ -625,9 +691,12 @@
|
|||||||
"MessageConfirmRenameTag": "Вы уверены, что хотите переименовать тег \"{0}\" в \"{1}\" для всех элементов?",
|
"MessageConfirmRenameTag": "Вы уверены, что хотите переименовать тег \"{0}\" в \"{1}\" для всех элементов?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Примечание: Этот тег уже существует, поэтому они будут объединены.",
|
"MessageConfirmRenameTagMergeNote": "Примечание: Этот тег уже существует, поэтому они будут объединены.",
|
||||||
"MessageConfirmRenameTagWarning": "Предупреждение! Похожий тег с другими начальными буквами уже существует \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Предупреждение! Похожий тег с другими начальными буквами уже существует \"{0}\".",
|
||||||
|
"MessageConfirmResetProgress": "Вы уверены, что хотите сбросить свой прогресс?",
|
||||||
"MessageConfirmSendEbookToDevice": "Вы уверены, что хотите отправить {0} e-книгу \"{1}\" на устройство \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "Вы уверены, что хотите отправить {0} e-книгу \"{1}\" на устройство \"{2}\"?",
|
||||||
|
"MessageConfirmUnlinkOpenId": "Вы уверены, что хотите отвязать этого пользователя от OpenID?",
|
||||||
"MessageDownloadingEpisode": "Эпизод скачивается",
|
"MessageDownloadingEpisode": "Эпизод скачивается",
|
||||||
"MessageDragFilesIntoTrackOrder": "Перетащите файлы для исправления порядка треков",
|
"MessageDragFilesIntoTrackOrder": "Перетащите файлы для исправления порядка треков",
|
||||||
|
"MessageEmbedFailed": "Вставка не удалась!",
|
||||||
"MessageEmbedFinished": "Встраивание завершено!",
|
"MessageEmbedFinished": "Встраивание завершено!",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} Эпизод(ов) запланировано для закачки",
|
"MessageEpisodesQueuedForDownload": "{0} Эпизод(ов) запланировано для закачки",
|
||||||
"MessageEreaderDevices": "Чтобы обеспечить доставку электронных книг, вам может потребоваться добавить указанный выше адрес электронной почты в качестве действительного отправителя для каждого устройства, перечисленного ниже.",
|
"MessageEreaderDevices": "Чтобы обеспечить доставку электронных книг, вам может потребоваться добавить указанный выше адрес электронной почты в качестве действительного отправителя для каждого устройства, перечисленного ниже.",
|
||||||
@@ -659,6 +728,7 @@
|
|||||||
"MessageNoCollections": "Нет коллекций",
|
"MessageNoCollections": "Нет коллекций",
|
||||||
"MessageNoCoversFound": "Обложек не найдено",
|
"MessageNoCoversFound": "Обложек не найдено",
|
||||||
"MessageNoDescription": "Нет описания",
|
"MessageNoDescription": "Нет описания",
|
||||||
|
"MessageNoDevices": "Нет устройств",
|
||||||
"MessageNoDownloadsInProgress": "В настоящее время загрузка не выполняется",
|
"MessageNoDownloadsInProgress": "В настоящее время загрузка не выполняется",
|
||||||
"MessageNoDownloadsQueued": "Нет загрузок в очереди",
|
"MessageNoDownloadsQueued": "Нет загрузок в очереди",
|
||||||
"MessageNoEpisodeMatchesFound": "Совпадения эпизодов не найдены",
|
"MessageNoEpisodeMatchesFound": "Совпадения эпизодов не найдены",
|
||||||
@@ -681,10 +751,12 @@
|
|||||||
"MessageNoUpdatesWereNecessary": "Обновления не требовались",
|
"MessageNoUpdatesWereNecessary": "Обновления не требовались",
|
||||||
"MessageNoUserPlaylists": "У вас нет плейлистов",
|
"MessageNoUserPlaylists": "У вас нет плейлистов",
|
||||||
"MessageNotYetImplemented": "Пока не реализовано",
|
"MessageNotYetImplemented": "Пока не реализовано",
|
||||||
|
"MessageOpmlPreviewNote": "Примечание: Это предварительный просмотр разобранного файла OPML. Фактическое название подкаста будет взято из RSS-канала.",
|
||||||
"MessageOr": "или",
|
"MessageOr": "или",
|
||||||
"MessagePauseChapter": "Пауза воспроизведения главы",
|
"MessagePauseChapter": "Пауза воспроизведения главы",
|
||||||
"MessagePlayChapter": "Прослушать начало главы",
|
"MessagePlayChapter": "Прослушать начало главы",
|
||||||
"MessagePlaylistCreateFromCollection": "Создать плейлист из коллекции",
|
"MessagePlaylistCreateFromCollection": "Создать плейлист из коллекции",
|
||||||
|
"MessagePleaseWait": "Пожалуйста подождите...",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Подкаст не имеет URL-адреса RSS-канала, который можно использовать для поиска",
|
"MessagePodcastHasNoRSSFeedForMatching": "Подкаст не имеет URL-адреса RSS-канала, который можно использовать для поиска",
|
||||||
"MessageQuickMatchDescription": "Заполняет пустые детали элемента и обложку первым результатом поиска из «{0}». Не перезаписывает сведения, если не включен параметр сервера 'Предпочитать метаданные поиска'.",
|
"MessageQuickMatchDescription": "Заполняет пустые детали элемента и обложку первым результатом поиска из «{0}». Не перезаписывает сведения, если не включен параметр сервера 'Предпочитать метаданные поиска'.",
|
||||||
"MessageRemoveChapter": "Удалить главу",
|
"MessageRemoveChapter": "Удалить главу",
|
||||||
@@ -699,6 +771,9 @@
|
|||||||
"MessageSelected": "{0} выбрано",
|
"MessageSelected": "{0} выбрано",
|
||||||
"MessageServerCouldNotBeReached": "Не удалось связаться с сервером",
|
"MessageServerCouldNotBeReached": "Не удалось связаться с сервером",
|
||||||
"MessageSetChaptersFromTracksDescription": "Установка глав с использованием каждого аудиофайла в качестве главы и заголовка главы в качестве имени аудиофайла",
|
"MessageSetChaptersFromTracksDescription": "Установка глав с использованием каждого аудиофайла в качестве главы и заголовка главы в качестве имени аудиофайла",
|
||||||
|
"MessageShareExpirationWillBe": "Срок действия истекает <strong>{0}</strong>",
|
||||||
|
"MessageShareExpiresIn": "Срок действия истекает через {0}",
|
||||||
|
"MessageShareURLWillBe": "URL-адрес общего доступа будет <strong>{0}</strong>",
|
||||||
"MessageStartPlaybackAtTime": "Начать воспроизведение для \"{0}\" с {1}?",
|
"MessageStartPlaybackAtTime": "Начать воспроизведение для \"{0}\" с {1}?",
|
||||||
"MessageThinking": "Думаю...",
|
"MessageThinking": "Думаю...",
|
||||||
"MessageUploaderItemFailed": "Не удалось загрузить",
|
"MessageUploaderItemFailed": "Не удалось загрузить",
|
||||||
@@ -722,20 +797,48 @@
|
|||||||
"PlaceholderNewPlaylist": "Новое название плейлиста",
|
"PlaceholderNewPlaylist": "Новое название плейлиста",
|
||||||
"PlaceholderSearch": "Поиск...",
|
"PlaceholderSearch": "Поиск...",
|
||||||
"PlaceholderSearchEpisode": "Поиск эпизода...",
|
"PlaceholderSearchEpisode": "Поиск эпизода...",
|
||||||
|
"StatsAuthorsAdded": "авторов добавлено",
|
||||||
|
"StatsBooksAdded": "книг добавлено",
|
||||||
|
"StatsBooksAdditional": "Некоторые дополнения включают в себя…",
|
||||||
|
"StatsBooksFinished": "книг завершено",
|
||||||
|
"StatsBooksFinishedThisYear": "Некоторые книги закончены в этом году…",
|
||||||
|
"StatsBooksListenedTo": "книг прослушано",
|
||||||
|
"StatsCollectionGrewTo": "Ваша коллекция книг пополнилась…",
|
||||||
|
"StatsSessions": "сессий",
|
||||||
|
"StatsSpentListening": "потрачено на прослушивание",
|
||||||
|
"StatsTopAuthor": "ТОП АВТОР",
|
||||||
|
"StatsTopAuthors": "ТОП АВТОРОВ",
|
||||||
|
"StatsTopGenre": "ТОП ЖАНР",
|
||||||
|
"StatsTopGenres": "ТОП ЖАНРЫ",
|
||||||
|
"StatsTopMonth": "ЛУЧШИЙ МЕСЯЦ",
|
||||||
|
"StatsTopNarrator": "ТОП ЧТЕЦ",
|
||||||
|
"StatsTopNarrators": "ТОП ЧТЕЦЫ",
|
||||||
|
"StatsTotalDuration": "С общей продолжительностью…",
|
||||||
|
"StatsYearInReview": "ИТОГИ ГОДА",
|
||||||
"ToastAccountUpdateFailed": "Не удалось обновить учетную запись",
|
"ToastAccountUpdateFailed": "Не удалось обновить учетную запись",
|
||||||
"ToastAccountUpdateSuccess": "Учетная запись обновлена",
|
"ToastAccountUpdateSuccess": "Учетная запись обновлена",
|
||||||
|
"ToastAppriseUrlRequired": "Необходимо ввести URL-адрес Apprise",
|
||||||
"ToastAuthorImageRemoveSuccess": "Изображение автора удалено",
|
"ToastAuthorImageRemoveSuccess": "Изображение автора удалено",
|
||||||
|
"ToastAuthorNotFound": "Автор \"{0}\" не найден",
|
||||||
|
"ToastAuthorRemoveSuccess": "Автор удален",
|
||||||
|
"ToastAuthorSearchNotFound": "Автор не найден",
|
||||||
"ToastAuthorUpdateFailed": "Не удалось обновить автора",
|
"ToastAuthorUpdateFailed": "Не удалось обновить автора",
|
||||||
"ToastAuthorUpdateMerged": "Автор объединен",
|
"ToastAuthorUpdateMerged": "Автор объединен",
|
||||||
"ToastAuthorUpdateSuccess": "Автор обновлен",
|
"ToastAuthorUpdateSuccess": "Автор обновлен",
|
||||||
"ToastAuthorUpdateSuccessNoImageFound": "Автор обновлен (изображение не найдено)",
|
"ToastAuthorUpdateSuccessNoImageFound": "Автор обновлен (изображение не найдено)",
|
||||||
|
"ToastBackupAppliedSuccess": "Применена резервная копия",
|
||||||
"ToastBackupCreateFailed": "Не удалось создать бэкап",
|
"ToastBackupCreateFailed": "Не удалось создать бэкап",
|
||||||
"ToastBackupCreateSuccess": "Бэкап создан",
|
"ToastBackupCreateSuccess": "Бэкап создан",
|
||||||
"ToastBackupDeleteFailed": "Не удалось удалить бэкап",
|
"ToastBackupDeleteFailed": "Не удалось удалить бэкап",
|
||||||
"ToastBackupDeleteSuccess": "Бэкап удален",
|
"ToastBackupDeleteSuccess": "Бэкап удален",
|
||||||
|
"ToastBackupInvalidMaxKeep": "Недопустимое количество резервных копий для хранения",
|
||||||
|
"ToastBackupInvalidMaxSize": "Недопустимый максимальный размер резервной копии",
|
||||||
|
"ToastBackupPathUpdateFailed": "Не удалось обновить путь к резервному копированию",
|
||||||
"ToastBackupRestoreFailed": "Не удалось восстановить из бэкапа",
|
"ToastBackupRestoreFailed": "Не удалось восстановить из бэкапа",
|
||||||
"ToastBackupUploadFailed": "Не удалось загрузить бэкап",
|
"ToastBackupUploadFailed": "Не удалось загрузить бэкап",
|
||||||
"ToastBackupUploadSuccess": "Бэкап загружен",
|
"ToastBackupUploadSuccess": "Бэкап загружен",
|
||||||
|
"ToastBatchDeleteFailed": "Не удалось выполнить пакетное удаление",
|
||||||
|
"ToastBatchDeleteSuccess": "Успешное пакетное удаление",
|
||||||
"ToastBatchUpdateFailed": "Сбой пакетного обновления",
|
"ToastBatchUpdateFailed": "Сбой пакетного обновления",
|
||||||
"ToastBatchUpdateSuccess": "Успешное пакетное обновление",
|
"ToastBatchUpdateSuccess": "Успешное пакетное обновление",
|
||||||
"ToastBookmarkCreateFailed": "Не удалось создать закладку",
|
"ToastBookmarkCreateFailed": "Не удалось создать закладку",
|
||||||
@@ -747,21 +850,46 @@
|
|||||||
"ToastCachePurgeSuccess": "Кэш успешно очищен",
|
"ToastCachePurgeSuccess": "Кэш успешно очищен",
|
||||||
"ToastChaptersHaveErrors": "Главы имеют ошибки",
|
"ToastChaptersHaveErrors": "Главы имеют ошибки",
|
||||||
"ToastChaptersMustHaveTitles": "Главы должны содержать названия",
|
"ToastChaptersMustHaveTitles": "Главы должны содержать названия",
|
||||||
|
"ToastChaptersRemoved": "Удалены главы",
|
||||||
|
"ToastCollectionItemsAddFailed": "Не удалось добавить элемент(ы) в коллекцию",
|
||||||
|
"ToastCollectionItemsAddSuccess": "Элемент(ы) добавлены в коллекцию",
|
||||||
"ToastCollectionItemsRemoveSuccess": "Элемент(ы), удалены из коллекции",
|
"ToastCollectionItemsRemoveSuccess": "Элемент(ы), удалены из коллекции",
|
||||||
"ToastCollectionRemoveSuccess": "Коллекция удалена",
|
"ToastCollectionRemoveSuccess": "Коллекция удалена",
|
||||||
"ToastCollectionUpdateFailed": "Не удалось обновить коллекцию",
|
"ToastCollectionUpdateFailed": "Не удалось обновить коллекцию",
|
||||||
"ToastCollectionUpdateSuccess": "Коллекция обновлена",
|
"ToastCollectionUpdateSuccess": "Коллекция обновлена",
|
||||||
|
"ToastCoverUpdateFailed": "Не удалось обновить обложку",
|
||||||
"ToastDeleteFileFailed": "Не удалось удалить файл",
|
"ToastDeleteFileFailed": "Не удалось удалить файл",
|
||||||
"ToastDeleteFileSuccess": "Файл удален",
|
"ToastDeleteFileSuccess": "Файл удален",
|
||||||
|
"ToastDeviceAddFailed": "Не удалось добавить устройство",
|
||||||
|
"ToastDeviceNameAlreadyExists": "Устройство для чтения электронных книг с таким именем уже существует",
|
||||||
|
"ToastDeviceTestEmailFailed": "Не удалось отправить тестовое электронное письмо",
|
||||||
|
"ToastDeviceTestEmailSuccess": "Тестовое письмо отправлено",
|
||||||
|
"ToastDeviceUpdateFailed": "Не удалось обновить устройство",
|
||||||
|
"ToastEmailSettingsUpdateFailed": "Не удалось обновить настройки электронной почты",
|
||||||
|
"ToastEmailSettingsUpdateSuccess": "Обновлены настройки электронной почты",
|
||||||
|
"ToastEncodeCancelFailed": "Не удалось отменить кодирование",
|
||||||
|
"ToastEncodeCancelSucces": "Кодирование отменено",
|
||||||
|
"ToastEpisodeDownloadQueueClearFailed": "Не удалось очистить очередь",
|
||||||
|
"ToastEpisodeDownloadQueueClearSuccess": "Очередь загрузки эпизода очищена",
|
||||||
|
"ToastErrorCannotShare": "Невозможно предоставить общий доступ на этом устройстве",
|
||||||
"ToastFailedToLoadData": "Не удалось загрузить данные",
|
"ToastFailedToLoadData": "Не удалось загрузить данные",
|
||||||
|
"ToastFailedToShare": "Не удалось поделиться",
|
||||||
|
"ToastFailedToUpdateAccount": "Не удалось обновить учетную запись",
|
||||||
|
"ToastFailedToUpdateUser": "Не удалось обновить пользователя",
|
||||||
|
"ToastInvalidImageUrl": "Неверный URL изображения",
|
||||||
|
"ToastInvalidUrl": "Неверный URL",
|
||||||
"ToastItemCoverUpdateFailed": "Не удалось обновить обложку элемента",
|
"ToastItemCoverUpdateFailed": "Не удалось обновить обложку элемента",
|
||||||
"ToastItemCoverUpdateSuccess": "Обложка элемента обновлена",
|
"ToastItemCoverUpdateSuccess": "Обложка элемента обновлена",
|
||||||
|
"ToastItemDeletedFailed": "Не удалось удалить элемент",
|
||||||
|
"ToastItemDeletedSuccess": "Удаленный элемент",
|
||||||
"ToastItemDetailsUpdateFailed": "Не удалось обновить сведения об элементе",
|
"ToastItemDetailsUpdateFailed": "Не удалось обновить сведения об элементе",
|
||||||
"ToastItemDetailsUpdateSuccess": "Обновлены сведения об элементе",
|
"ToastItemDetailsUpdateSuccess": "Обновлены сведения об элементе",
|
||||||
"ToastItemMarkedAsFinishedFailed": "Не удалось пометить как Завершенный",
|
"ToastItemMarkedAsFinishedFailed": "Не удалось пометить как Завершенный",
|
||||||
"ToastItemMarkedAsFinishedSuccess": "Элемент помечен как Завершенный",
|
"ToastItemMarkedAsFinishedSuccess": "Элемент помечен как Завершенный",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "Не удалось пометить как Незавершенный",
|
"ToastItemMarkedAsNotFinishedFailed": "Не удалось пометить как Незавершенный",
|
||||||
"ToastItemMarkedAsNotFinishedSuccess": "Элемент помечен как Незавершенный",
|
"ToastItemMarkedAsNotFinishedSuccess": "Элемент помечен как Незавершенный",
|
||||||
|
"ToastItemUpdateFailed": "Не удалось обновить элемент",
|
||||||
|
"ToastItemUpdateSuccess": "Элемент обновлен",
|
||||||
"ToastLibraryCreateFailed": "Не удалось создать библиотеку",
|
"ToastLibraryCreateFailed": "Не удалось создать библиотеку",
|
||||||
"ToastLibraryCreateSuccess": "Библиотека \"{0}\" создана",
|
"ToastLibraryCreateSuccess": "Библиотека \"{0}\" создана",
|
||||||
"ToastLibraryDeleteFailed": "Не удалось удалить библиотеку",
|
"ToastLibraryDeleteFailed": "Не удалось удалить библиотеку",
|
||||||
@@ -770,6 +898,25 @@
|
|||||||
"ToastLibraryScanStarted": "Запущено сканирование библиотеки",
|
"ToastLibraryScanStarted": "Запущено сканирование библиотеки",
|
||||||
"ToastLibraryUpdateFailed": "Не удалось обновить библиотеку",
|
"ToastLibraryUpdateFailed": "Не удалось обновить библиотеку",
|
||||||
"ToastLibraryUpdateSuccess": "Библиотека \"{0}\" обновлена",
|
"ToastLibraryUpdateSuccess": "Библиотека \"{0}\" обновлена",
|
||||||
|
"ToastNameEmailRequired": "Имя и адрес электронной почты обязательны",
|
||||||
|
"ToastNameRequired": "Имя обязательно для заполнения",
|
||||||
|
"ToastNewUserCreatedFailed": "Не удалось создать учетную запись: \"{0}\"",
|
||||||
|
"ToastNewUserCreatedSuccess": "Новая учетная запись создана",
|
||||||
|
"ToastNewUserLibraryError": "Необходимо выбрать хотя бы одну библиотеку",
|
||||||
|
"ToastNewUserPasswordError": "Должен иметь пароль, только пользователь root может иметь пустой пароль",
|
||||||
|
"ToastNewUserTagError": "Необходимо выбрать хотя бы один тег",
|
||||||
|
"ToastNewUserUsernameError": "Введите имя пользователя",
|
||||||
|
"ToastNoUpdatesNecessary": "Обновления не требуются",
|
||||||
|
"ToastNotificationCreateFailed": "Не удалось создать уведомление",
|
||||||
|
"ToastNotificationDeleteFailed": "Не удалось удалить уведомление",
|
||||||
|
"ToastNotificationFailedMaximum": "Максимальное количество неудачных попыток должно быть >= 0",
|
||||||
|
"ToastNotificationQueueMaximum": "Максимальная очередь уведомлений должна быть >= 0",
|
||||||
|
"ToastNotificationSettingsUpdateFailed": "Не удалось обновить настройки уведомлений",
|
||||||
|
"ToastNotificationSettingsUpdateSuccess": "Обновлены настройки уведомлений",
|
||||||
|
"ToastNotificationTestTriggerFailed": "Не удалось активировать тестовое уведомление",
|
||||||
|
"ToastNotificationTestTriggerSuccess": "Сработавшее уведомление о тестировании",
|
||||||
|
"ToastNotificationUpdateFailed": "Не удалось обновить уведомление",
|
||||||
|
"ToastNotificationUpdateSuccess": "Уведомление обновлено",
|
||||||
"ToastPlaylistCreateFailed": "Не удалось создать плейлист",
|
"ToastPlaylistCreateFailed": "Не удалось создать плейлист",
|
||||||
"ToastPlaylistCreateSuccess": "Плейлист создан",
|
"ToastPlaylistCreateSuccess": "Плейлист создан",
|
||||||
"ToastPlaylistRemoveSuccess": "Плейлист удален",
|
"ToastPlaylistRemoveSuccess": "Плейлист удален",
|
||||||
@@ -777,24 +924,52 @@
|
|||||||
"ToastPlaylistUpdateSuccess": "Плейлист обновлен",
|
"ToastPlaylistUpdateSuccess": "Плейлист обновлен",
|
||||||
"ToastPodcastCreateFailed": "Не удалось создать подкаст",
|
"ToastPodcastCreateFailed": "Не удалось создать подкаст",
|
||||||
"ToastPodcastCreateSuccess": "Подкаст успешно создан",
|
"ToastPodcastCreateSuccess": "Подкаст успешно создан",
|
||||||
|
"ToastPodcastGetFeedFailed": "Не удалось получить ленту подкастов",
|
||||||
|
"ToastPodcastNoEpisodesInFeed": "В RSS-ленте эпизодов не найдено",
|
||||||
|
"ToastPodcastNoRssFeed": "В подкасте нет RSS-канала",
|
||||||
|
"ToastProviderCreatedFailed": "Не удалось добавить провайдера",
|
||||||
|
"ToastProviderCreatedSuccess": "Добавлен новый провайдер",
|
||||||
|
"ToastProviderNameAndUrlRequired": "Имя и URL обязательные",
|
||||||
|
"ToastProviderRemoveSuccess": "Провайдер удален",
|
||||||
"ToastRSSFeedCloseFailed": "Не удалось закрыть RSS-канал",
|
"ToastRSSFeedCloseFailed": "Не удалось закрыть RSS-канал",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS-канал закрыт",
|
"ToastRSSFeedCloseSuccess": "RSS-канал закрыт",
|
||||||
|
"ToastRemoveFailed": "Не удалось удалить",
|
||||||
"ToastRemoveItemFromCollectionFailed": "Не удалось удалить элемент из коллекции",
|
"ToastRemoveItemFromCollectionFailed": "Не удалось удалить элемент из коллекции",
|
||||||
"ToastRemoveItemFromCollectionSuccess": "Элемент удален из коллекции",
|
"ToastRemoveItemFromCollectionSuccess": "Элемент удален из коллекции",
|
||||||
|
"ToastRemoveItemsWithIssuesFailed": "Не удалось удалить элементы библиотеки с проблемами",
|
||||||
|
"ToastRemoveItemsWithIssuesSuccess": "Удалены элементы библиотеки с проблемами",
|
||||||
|
"ToastRenameFailed": "Не удалось переименовать",
|
||||||
|
"ToastRescanFailed": "Ошибка повторного сканирования для {0}",
|
||||||
|
"ToastRescanRemoved": "Повторное сканирование завершено, элемент был удален",
|
||||||
|
"ToastRescanUpToDate": "Повторное сканирование завершено, элемент был актуализирован",
|
||||||
|
"ToastRescanUpdated": "Повторное сканирование завершено, элемент был обновлен",
|
||||||
|
"ToastScanFailed": "Не удалось просканировать элемент библиотеки",
|
||||||
|
"ToastSelectAtLeastOneUser": "Выберите хотя бы одного пользователя",
|
||||||
"ToastSendEbookToDeviceFailed": "Не удалось отправить e-книгу на устройство",
|
"ToastSendEbookToDeviceFailed": "Не удалось отправить e-книгу на устройство",
|
||||||
"ToastSendEbookToDeviceSuccess": "E-книга отправлена на устройство \"{0}\"",
|
"ToastSendEbookToDeviceSuccess": "E-книга отправлена на устройство \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
|
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
|
||||||
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
|
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
|
||||||
"ToastServerSettingsUpdateFailed": "Не удалось обновить настройки сервера",
|
"ToastServerSettingsUpdateFailed": "Не удалось обновить настройки сервера",
|
||||||
"ToastServerSettingsUpdateSuccess": "Обновлены настройки сервера",
|
"ToastServerSettingsUpdateSuccess": "Обновлены настройки сервера",
|
||||||
|
"ToastSessionCloseFailed": "Не удалось закрыть сеанс",
|
||||||
"ToastSessionDeleteFailed": "Не удалось удалить сеанс",
|
"ToastSessionDeleteFailed": "Не удалось удалить сеанс",
|
||||||
"ToastSessionDeleteSuccess": "Сеанс удален",
|
"ToastSessionDeleteSuccess": "Сеанс удален",
|
||||||
|
"ToastSlugMustChange": "Slug содержит недопустимые символы",
|
||||||
|
"ToastSlugRequired": "Требуется Slug",
|
||||||
"ToastSocketConnected": "Сокет подключен",
|
"ToastSocketConnected": "Сокет подключен",
|
||||||
"ToastSocketDisconnected": "Сокет отключен",
|
"ToastSocketDisconnected": "Сокет отключен",
|
||||||
"ToastSocketFailedToConnect": "Не удалось подключить сокет",
|
"ToastSocketFailedToConnect": "Не удалось подключить сокет",
|
||||||
"ToastSortingPrefixesEmptyError": "Должен быть хотя бы 1 префикс сортировки",
|
"ToastSortingPrefixesEmptyError": "Должен быть хотя бы 1 префикс сортировки",
|
||||||
"ToastSortingPrefixesUpdateFailed": "Не удалось обновить префиксы сортировки",
|
"ToastSortingPrefixesUpdateFailed": "Не удалось обновить префиксы сортировки",
|
||||||
"ToastSortingPrefixesUpdateSuccess": "Обновлены префиксы сортировки ({0} элементов)",
|
"ToastSortingPrefixesUpdateSuccess": "Обновлены префиксы сортировки ({0} элементов)",
|
||||||
|
"ToastTitleRequired": "Название обязательно",
|
||||||
|
"ToastUnknownError": "Неизвестная ошибка",
|
||||||
|
"ToastUnlinkOpenIdFailed": "Не удалось отвязать пользователя от OpenID",
|
||||||
|
"ToastUnlinkOpenIdSuccess": "Пользователь отвязан от OpenID",
|
||||||
"ToastUserDeleteFailed": "Не удалось удалить пользователя",
|
"ToastUserDeleteFailed": "Не удалось удалить пользователя",
|
||||||
"ToastUserDeleteSuccess": "Пользователь удален"
|
"ToastUserDeleteSuccess": "Пользователь удален",
|
||||||
|
"ToastUserPasswordChangeSuccess": "Пароль успешно изменен",
|
||||||
|
"ToastUserPasswordMismatch": "Пароли не совпадают",
|
||||||
|
"ToastUserPasswordMustChange": "Новый пароль не может совпадать со старым паролем",
|
||||||
|
"ToastUserRootRequireName": "Необходимо ввести имя пользователя root"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,976 @@
|
|||||||
|
{
|
||||||
|
"ButtonAdd": "Dodaj",
|
||||||
|
"ButtonAddChapters": "Dodaj poglavja",
|
||||||
|
"ButtonAddDevice": "Dodaj napravo",
|
||||||
|
"ButtonAddLibrary": "Dodaj knjižnico",
|
||||||
|
"ButtonAddPodcasts": "Dodaj podcast",
|
||||||
|
"ButtonAddUser": "Dodaj uporabnika",
|
||||||
|
"ButtonAddYourFirstLibrary": "Dodajte svojo prvo knjižnico",
|
||||||
|
"ButtonApply": "Uveljavi",
|
||||||
|
"ButtonApplyChapters": "Uveljavi poglavja",
|
||||||
|
"ButtonAuthors": "Avtorji",
|
||||||
|
"ButtonBack": "Nazaj",
|
||||||
|
"ButtonBrowseForFolder": "Prebrskaj pot do mape",
|
||||||
|
"ButtonCancel": "Prekliči",
|
||||||
|
"ButtonCancelEncode": "Prekliči prekodiranje",
|
||||||
|
"ButtonChangeRootPassword": "Zamenjaj korensko geslo",
|
||||||
|
"ButtonCheckAndDownloadNewEpisodes": "Preveri in prenesi nove epizode",
|
||||||
|
"ButtonChooseAFolder": "Izberite mapo",
|
||||||
|
"ButtonChooseFiles": "Izberite datoteke",
|
||||||
|
"ButtonClearFilter": "Počisti filter",
|
||||||
|
"ButtonCloseFeed": "Zapri vir",
|
||||||
|
"ButtonCloseSession": "Zapri odprto sejo",
|
||||||
|
"ButtonCollections": "Zbirke",
|
||||||
|
"ButtonConfigureScanner": "Nastavi pregledovalnik",
|
||||||
|
"ButtonCreate": "Ustvari",
|
||||||
|
"ButtonCreateBackup": "Ustvari varnostno kopijo",
|
||||||
|
"ButtonDelete": "Izbriši",
|
||||||
|
"ButtonDownloadQueue": "Čakalna vrsta",
|
||||||
|
"ButtonEdit": "Uredi",
|
||||||
|
"ButtonEditChapters": "Uredi poglavja",
|
||||||
|
"ButtonEditPodcast": "Uredi podcast",
|
||||||
|
"ButtonEnable": "Omogoči",
|
||||||
|
"ButtonFireAndFail": "Zaženi in je bilo neuspešno",
|
||||||
|
"ButtonFireOnTest": "Zaženi samo na dogodku onTest",
|
||||||
|
"ButtonForceReScan": "Prisilno ponovno pregledovanje",
|
||||||
|
"ButtonFullPath": "Polna pot",
|
||||||
|
"ButtonHide": "Skrij",
|
||||||
|
"ButtonHome": "Domov",
|
||||||
|
"ButtonIssues": "Težave",
|
||||||
|
"ButtonJumpBackward": "Skoči nazaj",
|
||||||
|
"ButtonJumpForward": "Skoči naprej",
|
||||||
|
"ButtonLatest": "Najnovejše",
|
||||||
|
"ButtonLibrary": "Knjižnica",
|
||||||
|
"ButtonLogout": "Odjava",
|
||||||
|
"ButtonLookup": "Iskanje",
|
||||||
|
"ButtonManageTracks": "Upravljaj s posnetki",
|
||||||
|
"ButtonMapChapterTitles": "Poveži naslove poglavij",
|
||||||
|
"ButtonMatchAllAuthors": "Ujemanje vseh avtorjev",
|
||||||
|
"ButtonMatchBooks": "Ujemanje knjig",
|
||||||
|
"ButtonNevermind": "Ni važno",
|
||||||
|
"ButtonNext": "Naslednje",
|
||||||
|
"ButtonNextChapter": "Naslednje poglavje",
|
||||||
|
"ButtonNextItemInQueue": "Naslednji element v čakalni vrsti",
|
||||||
|
"ButtonOk": "V redu",
|
||||||
|
"ButtonOpenFeed": "Odpri vir",
|
||||||
|
"ButtonOpenManager": "Odpri upravljanje",
|
||||||
|
"ButtonPause": "Premor",
|
||||||
|
"ButtonPlay": "Predvajaj",
|
||||||
|
"ButtonPlaying": "Predvajam",
|
||||||
|
"ButtonPlaylists": "Seznami predvajanj",
|
||||||
|
"ButtonPrevious": "Prejšnje",
|
||||||
|
"ButtonPreviousChapter": "Prejšnje poglavje",
|
||||||
|
"ButtonProbeAudioFile": "Analiziraj zvočno datoteko",
|
||||||
|
"ButtonPurgeAllCache": "Počisti ves predpomnilnik",
|
||||||
|
"ButtonPurgeItemsCache": "Počisti predpomnilnik elementov",
|
||||||
|
"ButtonQueueAddItem": "Dodaj v čakalno vrsto",
|
||||||
|
"ButtonQueueRemoveItem": "Odstrani iz čakalne vrste",
|
||||||
|
"ButtonQuickEmbedMetadata": "Hitra vdelava metapodatkov",
|
||||||
|
"ButtonQuickMatch": "Hitro ujemanje",
|
||||||
|
"ButtonReScan": "Ponovno pregledovanje",
|
||||||
|
"ButtonRead": "Preberi",
|
||||||
|
"ButtonReadLess": "Preberi manj",
|
||||||
|
"ButtonReadMore": "Preberi več",
|
||||||
|
"ButtonRefresh": "Osveži",
|
||||||
|
"ButtonRemove": "Odstrani",
|
||||||
|
"ButtonRemoveAll": "Odstrani vse",
|
||||||
|
"ButtonRemoveAllLibraryItems": "Odstrani vse elemente v knjižnici",
|
||||||
|
"ButtonRemoveFromContinueListening": "Odstrani iz nadaljuj poslušanje",
|
||||||
|
"ButtonRemoveFromContinueReading": "Odstrani iz nadaljuj branje",
|
||||||
|
"ButtonRemoveSeriesFromContinueSeries": "Odstrani serijo iz nadaljuj serijo",
|
||||||
|
"ButtonReset": "Ponastavi",
|
||||||
|
"ButtonResetToDefault": "Ponastavi na privzeto",
|
||||||
|
"ButtonRestore": "Obnovi",
|
||||||
|
"ButtonSave": "Shrani",
|
||||||
|
"ButtonSaveAndClose": "Shrani iz zapri",
|
||||||
|
"ButtonSaveTracklist": "Shrani seznam skladb",
|
||||||
|
"ButtonScan": "Pregledovanje",
|
||||||
|
"ButtonScanLibrary": "Preglej knjižnico",
|
||||||
|
"ButtonSearch": "Poišči",
|
||||||
|
"ButtonSelectFolderPath": "Izberite pot do mape",
|
||||||
|
"ButtonSeries": "Serije",
|
||||||
|
"ButtonSetChaptersFromTracks": "Nastavi poglavja za posnetke",
|
||||||
|
"ButtonShare": "Deli",
|
||||||
|
"ButtonShiftTimes": "Zamakni čase",
|
||||||
|
"ButtonShow": "Prikaži",
|
||||||
|
"ButtonStartM4BEncode": "Zaženi M4B prekodiranje",
|
||||||
|
"ButtonStartMetadataEmbed": "Začni vdelavo metapodatkov",
|
||||||
|
"ButtonStats": "Statistika",
|
||||||
|
"ButtonSubmit": "Posreduj",
|
||||||
|
"ButtonTest": "Test",
|
||||||
|
"ButtonUnlinkOpenId": "Prekini povezavo OpenID",
|
||||||
|
"ButtonUpload": "Naloži",
|
||||||
|
"ButtonUploadBackup": "Naloži varnostno kopijo",
|
||||||
|
"ButtonUploadCover": "Naloži naslovnico",
|
||||||
|
"ButtonUploadOPMLFile": "Naloži OPML datoteko",
|
||||||
|
"ButtonUserDelete": "Izbriši uporabnika {0}",
|
||||||
|
"ButtonUserEdit": "Uredi uporabnika {0}",
|
||||||
|
"ButtonViewAll": "Poglej vse",
|
||||||
|
"ButtonYes": "Da",
|
||||||
|
"ErrorUploadFetchMetadataAPI": "Napaka pri pridobivanju metapodatkov",
|
||||||
|
"ErrorUploadFetchMetadataNoResults": "Ni bilo mogoče pridobiti metapodatkov - poskusi posodobi naslov in/ali avtorja",
|
||||||
|
"ErrorUploadLacksTitle": "Imeti mora naslov",
|
||||||
|
"HeaderAccount": "Račun",
|
||||||
|
"HeaderAddCustomMetadataProvider": "Dodaj ponudnika metapodatkov po meri",
|
||||||
|
"HeaderAdvanced": "Napredno",
|
||||||
|
"HeaderAppriseNotificationSettings": "Nastavitve obvestil Apprise",
|
||||||
|
"HeaderAudioTracks": "Zvočni posnetki",
|
||||||
|
"HeaderAudiobookTools": "Orodja za upravljanje datotek zvočnih knjig",
|
||||||
|
"HeaderAuthentication": "Avtentikacija",
|
||||||
|
"HeaderBackups": "Varnostne kopije",
|
||||||
|
"HeaderChangePassword": "Zamenjaj geslo",
|
||||||
|
"HeaderChapters": "Poglavja",
|
||||||
|
"HeaderChooseAFolder": "Izberite mapo",
|
||||||
|
"HeaderCollection": "Zbirka",
|
||||||
|
"HeaderCollectionItems": "Elementi zbirke",
|
||||||
|
"HeaderCover": "Naslovnica",
|
||||||
|
"HeaderCurrentDownloads": "Trenutni prenosi",
|
||||||
|
"HeaderCustomMessageOnLogin": "Sporočilo po meri ob prijavi",
|
||||||
|
"HeaderCustomMetadataProviders": "Ponudniki metapodatkov po meri",
|
||||||
|
"HeaderDetails": "Podrobnosti",
|
||||||
|
"HeaderDownloadQueue": "Čakalna vrsta prenosa",
|
||||||
|
"HeaderEbookFiles": "Datoteke e-knjig",
|
||||||
|
"HeaderEmail": "E-pošta",
|
||||||
|
"HeaderEmailSettings": "Nastavitve e-pošte",
|
||||||
|
"HeaderEpisodes": "Epizode",
|
||||||
|
"HeaderEreaderDevices": "Ebralne naprave",
|
||||||
|
"HeaderEreaderSettings": "Nastavitve ebralnika",
|
||||||
|
"HeaderFiles": "Datoteke",
|
||||||
|
"HeaderFindChapters": "Najdi poglavja",
|
||||||
|
"HeaderIgnoredFiles": "Prezrte datoteke",
|
||||||
|
"HeaderItemFiles": "Datoteke elementa",
|
||||||
|
"HeaderItemMetadataUtils": "Pripomočki za metapodatke elementa",
|
||||||
|
"HeaderLastListeningSession": "Zadnja seja poslušanja",
|
||||||
|
"HeaderLatestEpisodes": "Zadnje epizode",
|
||||||
|
"HeaderLibraries": "Knjižnice",
|
||||||
|
"HeaderLibraryFiles": "Datoteke knjižnice",
|
||||||
|
"HeaderLibraryStats": "Statistika knjižnice",
|
||||||
|
"HeaderListeningSessions": "Seje poslušanja",
|
||||||
|
"HeaderListeningStats": "Statistika poslušanja",
|
||||||
|
"HeaderLogin": "Prijava",
|
||||||
|
"HeaderLogs": "Dnevniki",
|
||||||
|
"HeaderManageGenres": "Upravljajne žanrov",
|
||||||
|
"HeaderManageTags": "Upravljanje oznak",
|
||||||
|
"HeaderMapDetails": "Podrobnosti povezave",
|
||||||
|
"HeaderMatch": "Ujemanje",
|
||||||
|
"HeaderMetadataOrderOfPrecedence": "Vrstni red metapodatkov",
|
||||||
|
"HeaderMetadataToEmbed": "Metapodatki za vdelavo",
|
||||||
|
"HeaderNewAccount": "Nov račun",
|
||||||
|
"HeaderNewLibrary": "Nova knjižnica",
|
||||||
|
"HeaderNotificationCreate": "Ustvari obvestilo",
|
||||||
|
"HeaderNotificationUpdate": "Posodobi obvestilo",
|
||||||
|
"HeaderNotifications": "Obvestila",
|
||||||
|
"HeaderOpenIDConnectAuthentication": "Preverjanje pristnosti OpenID Connect",
|
||||||
|
"HeaderOpenRSSFeed": "Odpri vir RSS",
|
||||||
|
"HeaderOtherFiles": "Ostale datoteke",
|
||||||
|
"HeaderPasswordAuthentication": "Preverjanje pristnosti gesla",
|
||||||
|
"HeaderPermissions": "Dovoljenja",
|
||||||
|
"HeaderPlayerQueue": "Čakalna vrsta predvajalnika",
|
||||||
|
"HeaderPlayerSettings": "Nastavitve predvajalnika",
|
||||||
|
"HeaderPlaylist": "Seznam predvajanja",
|
||||||
|
"HeaderPlaylistItems": "Elementi seznama predvajanja",
|
||||||
|
"HeaderPodcastsToAdd": "Podcasti za dodajanje",
|
||||||
|
"HeaderPreviewCover": "Naslovnica za predogled",
|
||||||
|
"HeaderRSSFeedGeneral": "RSS podrobnosti",
|
||||||
|
"HeaderRSSFeedIsOpen": "Vir RSS je odprt",
|
||||||
|
"HeaderRSSFeeds": "RSS viri",
|
||||||
|
"HeaderRemoveEpisode": "Odstrani epizodo",
|
||||||
|
"HeaderRemoveEpisodes": "Odstrani {0} epizod",
|
||||||
|
"HeaderSavedMediaProgress": "Shranjen napredek predstavnosti",
|
||||||
|
"HeaderSchedule": "Načrtovanje",
|
||||||
|
"HeaderScheduleLibraryScans": "Načrtuj samodejno pregledovanje knjižnice",
|
||||||
|
"HeaderSession": "Seja",
|
||||||
|
"HeaderSetBackupSchedule": "Nastavite urnik varnostnega kopiranja",
|
||||||
|
"HeaderSettings": "Nastavitve",
|
||||||
|
"HeaderSettingsDisplay": "Zaslon",
|
||||||
|
"HeaderSettingsExperimental": "Eksperimentalne funkcije",
|
||||||
|
"HeaderSettingsGeneral": "Splošno",
|
||||||
|
"HeaderSettingsScanner": "Skener",
|
||||||
|
"HeaderSleepTimer": "Časovnik za izklop",
|
||||||
|
"HeaderStatsLargestItems": "Največji elementi",
|
||||||
|
"HeaderStatsLongestItems": "Najdaljši elementi (ure)",
|
||||||
|
"HeaderStatsMinutesListeningChart": "Minut poslušanja (zadnjih 7 dni)",
|
||||||
|
"HeaderStatsRecentSessions": "Nedavne seje",
|
||||||
|
"HeaderStatsTop10Authors": "Najboljših 10 avtorjev",
|
||||||
|
"HeaderStatsTop5Genres": "Najboljših 5 žanrov",
|
||||||
|
"HeaderTableOfContents": "Kazalo",
|
||||||
|
"HeaderTools": "Orodja",
|
||||||
|
"HeaderUpdateAccount": "Posodobi račun",
|
||||||
|
"HeaderUpdateAuthor": "Posodobi avtorja",
|
||||||
|
"HeaderUpdateDetails": "Posodobi podrobnosti",
|
||||||
|
"HeaderUpdateLibrary": "Posodobi knjižnico",
|
||||||
|
"HeaderUsers": "Uporabniki",
|
||||||
|
"HeaderYearReview": "Leto {0} v pregledu",
|
||||||
|
"HeaderYourStats": "Tvoja statistika",
|
||||||
|
"LabelAbridged": "Skrajšano",
|
||||||
|
"LabelAbridgedChecked": "Skrajšano (omogočeno)",
|
||||||
|
"LabelAbridgedUnchecked": "Neskrajšano (onemogočeno)",
|
||||||
|
"LabelAccessibleBy": "Dostopno iz",
|
||||||
|
"LabelAccountType": "Vrsta računa",
|
||||||
|
"LabelAccountTypeAdmin": "Administrator",
|
||||||
|
"LabelAccountTypeGuest": "Gost",
|
||||||
|
"LabelAccountTypeUser": "Uporabnik",
|
||||||
|
"LabelActivity": "Aktivnost",
|
||||||
|
"LabelAddToCollection": "Dodaj v zbirko",
|
||||||
|
"LabelAddToCollectionBatch": "Dodaj {0} knjig v zbirko",
|
||||||
|
"LabelAddToPlaylist": "Dodaj na seznam predvajanja",
|
||||||
|
"LabelAddToPlaylistBatch": "Dodaj {0} elementov v seznam predvajanja",
|
||||||
|
"LabelAddedAt": "Dodano ob",
|
||||||
|
"LabelAddedDate": "Dodano {0}",
|
||||||
|
"LabelAdminUsersOnly": "Samo administratorji",
|
||||||
|
"LabelAll": "Vsi",
|
||||||
|
"LabelAllUsers": "Vsi uporabniki",
|
||||||
|
"LabelAllUsersExcludingGuests": "Vsi uporabniki razen gosti",
|
||||||
|
"LabelAllUsersIncludingGuests": "Vsi uporabniki vključno z gosti",
|
||||||
|
"LabelAlreadyInYourLibrary": "Že v tvoji knjižnici",
|
||||||
|
"LabelAppend": "Priloži",
|
||||||
|
"LabelAuthor": "Avtor",
|
||||||
|
"LabelAuthorFirstLast": "Avtor (ime priimek)",
|
||||||
|
"LabelAuthorLastFirst": "Avtor (priimek, ime)",
|
||||||
|
"LabelAuthors": "Avtorji",
|
||||||
|
"LabelAutoDownloadEpisodes": "Samodejni prenos epizod",
|
||||||
|
"LabelAutoFetchMetadata": "Samodejno pridobivanje metapodatkov",
|
||||||
|
"LabelAutoFetchMetadataHelp": "Pridobi metapodatke za naslov, avtorja in serijo za poenostavitev nalaganja. Po nalaganju bo morda treba ujemanje dodatnih metapodatkov.",
|
||||||
|
"LabelAutoLaunch": "Samodejni zagon",
|
||||||
|
"LabelAutoLaunchDescription": "Samodejna preusmeritev na ponudnika avtentikacije ob navigaciji na prijavno stran (ročna preglasitev poti <code>/login?autoLaunch=0</code>)",
|
||||||
|
"LabelAutoRegister": "Samodejna registracija",
|
||||||
|
"LabelAutoRegisterDescription": "Po prijavi samodejno ustvari nove uporabnike",
|
||||||
|
"LabelBackToUser": "Nazaj na uporabnika",
|
||||||
|
"LabelBackupLocation": "Lokacija rezervne kopije",
|
||||||
|
"LabelBackupsEnableAutomaticBackups": "Omogoči samodejno varnostno kopiranje",
|
||||||
|
"LabelBackupsEnableAutomaticBackupsHelp": "Varnostne kopije shranjene v /metadata/backups",
|
||||||
|
"LabelBackupsMaxBackupSize": "Največja velikost varnostne kopije (v GB) (0 za neomejeno)",
|
||||||
|
"LabelBackupsMaxBackupSizeHelp": "Kot zaščita pred napačno konfiguracijo, varnostne kopije ne bodo uspele, če presežejo konfigurirano velikost.",
|
||||||
|
"LabelBackupsNumberToKeep": "Število varnostnih kopij, ki jih je treba hraniti",
|
||||||
|
"LabelBackupsNumberToKeepHelp": "Naenkrat bo odstranjena samo ena varnostna kopija, če že imate več varnostnih kopij, jih odstranite ročno.",
|
||||||
|
"LabelBitrate": "Bitna hitrost",
|
||||||
|
"LabelBooks": "Knjige",
|
||||||
|
"LabelButtonText": "Besedilo gumba",
|
||||||
|
"LabelByAuthor": "od {0}",
|
||||||
|
"LabelChangePassword": "Spremeni geslo",
|
||||||
|
"LabelChannels": "Kanali",
|
||||||
|
"LabelChapterTitle": "Naslov poglavja",
|
||||||
|
"LabelChapters": "Poglavja",
|
||||||
|
"LabelChaptersFound": "najdenih poglavij",
|
||||||
|
"LabelClickForMoreInfo": "Klikni za več informacij",
|
||||||
|
"LabelClosePlayer": "Zapri predvajalnik",
|
||||||
|
"LabelCodec": "Kodek",
|
||||||
|
"LabelCollapseSeries": "Strni serije",
|
||||||
|
"LabelCollapseSubSeries": "Strni podserije",
|
||||||
|
"LabelCollection": "Zbirka",
|
||||||
|
"LabelCollections": "Zbirke",
|
||||||
|
"LabelComplete": "Končano",
|
||||||
|
"LabelConfirmPassword": "Potrdi geslo",
|
||||||
|
"LabelContinueListening": "Nadaljuj poslušanje",
|
||||||
|
"LabelContinueReading": "Nadaljuj branje",
|
||||||
|
"LabelContinueSeries": "Nadaljuj s serijo",
|
||||||
|
"LabelCover": "Naslovnica",
|
||||||
|
"LabelCoverImageURL": "URL naslovne slike",
|
||||||
|
"LabelCreatedAt": "Ustvarjeno ob",
|
||||||
|
"LabelCronExpression": "Cron izraz",
|
||||||
|
"LabelCurrent": "Trenutno",
|
||||||
|
"LabelCurrently": "Trenutno:",
|
||||||
|
"LabelCustomCronExpression": "Cron izraz po meri:",
|
||||||
|
"LabelDatetime": "Datum in ura",
|
||||||
|
"LabelDays": "Dnevi",
|
||||||
|
"LabelDeleteFromFileSystemCheckbox": "Izbriši iz datotečnega sistema (počisti polje, če želiš odstraniti samo iz zbirke podatkov)",
|
||||||
|
"LabelDescription": "Opis",
|
||||||
|
"LabelDeselectAll": "Odznači vse",
|
||||||
|
"LabelDevice": "Naprava",
|
||||||
|
"LabelDeviceInfo": "Podatki o napravi",
|
||||||
|
"LabelDeviceIsAvailableTo": "Naprava je na voljo za...",
|
||||||
|
"LabelDirectory": "Imenik",
|
||||||
|
"LabelDiscFromFilename": "Disk iz imena datoteke",
|
||||||
|
"LabelDiscFromMetadata": "Disk iz metapodatkov",
|
||||||
|
"LabelDiscover": "Odkrij",
|
||||||
|
"LabelDownload": "Prenos",
|
||||||
|
"LabelDownloadNEpisodes": "Prenesi {0} epizod",
|
||||||
|
"LabelDuration": "Trajanje",
|
||||||
|
"LabelDurationComparisonExactMatch": "(natančno ujemanje)",
|
||||||
|
"LabelDurationComparisonLonger": "({0} dlje)",
|
||||||
|
"LabelDurationComparisonShorter": "({0} krajše)",
|
||||||
|
"LabelDurationFound": "Najdeno trajanje:",
|
||||||
|
"LabelEbook": "E-knjiga",
|
||||||
|
"LabelEbooks": "E-knjige",
|
||||||
|
"LabelEdit": "Uredi",
|
||||||
|
"LabelEmail": "E-pošta",
|
||||||
|
"LabelEmailSettingsFromAddress": "Iz naslova",
|
||||||
|
"LabelEmailSettingsRejectUnauthorized": "Zavrni nepooblaščena potrdila",
|
||||||
|
"LabelEmailSettingsRejectUnauthorizedHelp": "Če onemogočite preverjanje veljavnosti potrdila SSL, lahko izpostavite svojo povezavo varnostnim tveganjem, kot so napadi človek v sredini. To možnost onemogočite le, če razumete posledice in zaupate poštnemu strežniku, s katerim se povezujete.",
|
||||||
|
"LabelEmailSettingsSecure": "Varno",
|
||||||
|
"LabelEmailSettingsSecureHelp": "Če je omogočeno, bo povezava pri povezovanju s strežnikom uporabljala TLS. Če je onemogočeno, se TLS uporablja, če strežnik podpira razširitev STARTTLS. V večini primerov nastavite to vrednost na omogočeno, če se povezujete z vrati 465. Za vrata 587 ali 25 naj ostane onemogočeno. (iz nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Testiraj naslov",
|
||||||
|
"LabelEmbeddedCover": "Vdelana naslovnica",
|
||||||
|
"LabelEnable": "Omogoči",
|
||||||
|
"LabelEnd": "Konec",
|
||||||
|
"LabelEndOfChapter": "Konec poglavja",
|
||||||
|
"LabelEpisode": "Epizoda",
|
||||||
|
"LabelEpisodeTitle": "Naslov epizode",
|
||||||
|
"LabelEpisodeType": "Tip epizode",
|
||||||
|
"LabelEpisodes": "Epizode",
|
||||||
|
"LabelExample": "Primer",
|
||||||
|
"LabelExpandSeries": "Razširi serije",
|
||||||
|
"LabelExpandSubSeries": "Razširi podserije",
|
||||||
|
"LabelExplicit": "Eksplicitno",
|
||||||
|
"LabelExplicitChecked": "Eksplicitno (omogočeno)",
|
||||||
|
"LabelExplicitUnchecked": "Ne eksplicitno (onemogočeno)",
|
||||||
|
"LabelExportOPML": "Izvozi OPML",
|
||||||
|
"LabelFeedURL": "URL vir",
|
||||||
|
"LabelFetchingMetadata": "Pridobivam metapodatke",
|
||||||
|
"LabelFile": "Datoteka",
|
||||||
|
"LabelFileBirthtime": "Čas ustvarjanja datoteke",
|
||||||
|
"LabelFileBornDate": "Ustvarjena {0}",
|
||||||
|
"LabelFileModified": "Datoteke spremenjena",
|
||||||
|
"LabelFileModifiedDate": "Spremenjena {0}",
|
||||||
|
"LabelFilename": "Ime datoteke",
|
||||||
|
"LabelFilterByUser": "Filtriraj po uporabniku",
|
||||||
|
"LabelFindEpisodes": "Poišči epizode",
|
||||||
|
"LabelFinished": "Zaključeno",
|
||||||
|
"LabelFolder": "Mapa",
|
||||||
|
"LabelFolders": "Mape",
|
||||||
|
"LabelFontBold": "Krepko",
|
||||||
|
"LabelFontBoldness": "Krepkost pisave",
|
||||||
|
"LabelFontFamily": "Družina pisave",
|
||||||
|
"LabelFontItalic": "Ležeče",
|
||||||
|
"LabelFontScale": "Merilo pisave",
|
||||||
|
"LabelFontStrikethrough": "Prečrtano",
|
||||||
|
"LabelFormat": "Oblika",
|
||||||
|
"LabelGenre": "Žanr",
|
||||||
|
"LabelGenres": "Žanri",
|
||||||
|
"LabelHardDeleteFile": "Trdo brisanje datoteke",
|
||||||
|
"LabelHasEbook": "Ima e-knjigo",
|
||||||
|
"LabelHasSupplementaryEbook": "Ima dodatno e-knjigo",
|
||||||
|
"LabelHideSubtitles": "Skrij podnapise",
|
||||||
|
"LabelHighestPriority": "Najvišja prioriteta",
|
||||||
|
"LabelHost": "Gostitelj",
|
||||||
|
"LabelHour": "Ura",
|
||||||
|
"LabelHours": "Ure",
|
||||||
|
"LabelIcon": "Ikona",
|
||||||
|
"LabelImageURLFromTheWeb": "URL slike iz spleta",
|
||||||
|
"LabelInProgress": "V teku",
|
||||||
|
"LabelIncludeInTracklist": "Vključi v seznam skladb",
|
||||||
|
"LabelIncomplete": "Nepopolno",
|
||||||
|
"LabelInterval": "Interval",
|
||||||
|
"LabelIntervalCustomDailyWeekly": "Dnevno/tedensko po meri",
|
||||||
|
"LabelIntervalEvery12Hours": "Vsakih 12 ur",
|
||||||
|
"LabelIntervalEvery15Minutes": "Vsakih 15 minut",
|
||||||
|
"LabelIntervalEvery2Hours": "Vsake 2 uri",
|
||||||
|
"LabelIntervalEvery30Minutes": "Vsakih 30 minut",
|
||||||
|
"LabelIntervalEvery6Hours": "Vsakih 6 ur",
|
||||||
|
"LabelIntervalEveryDay": "Vsak dan",
|
||||||
|
"LabelIntervalEveryHour": "Vsako uro",
|
||||||
|
"LabelInvert": "Obrni izbor",
|
||||||
|
"LabelItem": "Element",
|
||||||
|
"LabelJumpBackwardAmount": "Količina skoka nazaj",
|
||||||
|
"LabelJumpForwardAmount": "Količina skoka naprej",
|
||||||
|
"LabelLanguage": "Jezik",
|
||||||
|
"LabelLanguageDefaultServer": "Privzeti jezik strežnika",
|
||||||
|
"LabelLanguages": "Jeziki",
|
||||||
|
"LabelLastBookAdded": "Zadnja dodana knjiga",
|
||||||
|
"LabelLastBookUpdated": "Zadnja posodobljena knjiga",
|
||||||
|
"LabelLastSeen": "Nazadnje viden",
|
||||||
|
"LabelLastTime": "Zadnji čas",
|
||||||
|
"LabelLastUpdate": "Zadnja posodobitev",
|
||||||
|
"LabelLayout": "Postavitev",
|
||||||
|
"LabelLayoutSinglePage": "Ena stran",
|
||||||
|
"LabelLayoutSplitPage": "Razdeli stran",
|
||||||
|
"LabelLess": "Manj",
|
||||||
|
"LabelLibrariesAccessibleToUser": "Knjižnice, dostopne uporabniku",
|
||||||
|
"LabelLibrary": "Knjižnica",
|
||||||
|
"LabelLibraryFilterSublistEmpty": "Ne {0}",
|
||||||
|
"LabelLibraryItem": "Element knjižnice",
|
||||||
|
"LabelLibraryName": "Ime knjižnice",
|
||||||
|
"LabelLimit": "Omejitev",
|
||||||
|
"LabelLineSpacing": "Razmik med vrsticami",
|
||||||
|
"LabelListenAgain": "Poslušaj znova",
|
||||||
|
"LabelLogLevelDebug": "Odpravljanje napak",
|
||||||
|
"LabelLogLevelInfo": "Info",
|
||||||
|
"LabelLogLevelWarn": "Opozoritve",
|
||||||
|
"LabelLookForNewEpisodesAfterDate": "Poiščite nove epizode po tem datumu",
|
||||||
|
"LabelLowestPriority": "Najnižja prioriteta",
|
||||||
|
"LabelMatchExistingUsersBy": "Poveži obstoječe uporabnike po",
|
||||||
|
"LabelMatchExistingUsersByDescription": "Uporablja se za povezovanje obstoječih uporabnikov. Ko se vzpostavi povezava, se bodo uporabniki ujemali z enoličnim ID-jem vašega ponudnika SSO",
|
||||||
|
"LabelMediaPlayer": "Medijski predvajalnik",
|
||||||
|
"LabelMediaType": "Vrsta medija",
|
||||||
|
"LabelMetaTag": "Meta oznaka",
|
||||||
|
"LabelMetaTags": "Meta oznake",
|
||||||
|
"LabelMetadataOrderOfPrecedenceDescription": "Viri metapodatkov višje prioritete bodo preglasili vire metapodatkov nižje prioritete",
|
||||||
|
"LabelMetadataProvider": "Ponudnik metapodatkov",
|
||||||
|
"LabelMinute": "Minuta",
|
||||||
|
"LabelMinutes": "Minute",
|
||||||
|
"LabelMissing": "Manjkajoče",
|
||||||
|
"LabelMissingEbook": "Nima nobene eknjige",
|
||||||
|
"LabelMissingSupplementaryEbook": "Nima nobene dodatne eknjige",
|
||||||
|
"LabelMobileRedirectURIs": "Dovoljeni mobilni preusmeritveni URI-ji",
|
||||||
|
"LabelMobileRedirectURIsDescription": "To je seznam dovoljenih veljavnih preusmeritvenih URI-jev za mobilne aplikacije. Privzeti je <code>audiobookshelf://oauth</code>, ki ga lahko odstranite ali dopolnite z dodatnimi URI-ji za integracijo aplikacij tretjih oseb. Uporaba zvezdice (<code>*</code>) kot edinega vnosa dovoljuje kateri koli URI.",
|
||||||
|
"LabelMore": "Več",
|
||||||
|
"LabelMoreInfo": "Več informacij",
|
||||||
|
"LabelName": "Naziv",
|
||||||
|
"LabelNarrator": "Bralec",
|
||||||
|
"LabelNarrators": "Bralci",
|
||||||
|
"LabelNew": "Novo",
|
||||||
|
"LabelNewPassword": "Novo geslo",
|
||||||
|
"LabelNewestAuthors": "Najnovejši avtorji",
|
||||||
|
"LabelNewestEpisodes": "Najnovejše epizode",
|
||||||
|
"LabelNextBackupDate": "Naslednji datum varnostnega kopiranja",
|
||||||
|
"LabelNextScheduledRun": "Naslednji načrtovani zagon",
|
||||||
|
"LabelNoCustomMetadataProviders": "Ni ponudnikov metapodatkov po meri",
|
||||||
|
"LabelNoEpisodesSelected": "Izbrana ni nobena epizoda",
|
||||||
|
"LabelNotFinished": "Ni dokončano",
|
||||||
|
"LabelNotStarted": "Ni zagnano",
|
||||||
|
"LabelNotes": "Opombe",
|
||||||
|
"LabelNotificationAppriseURL": "Apprise URL(ji)",
|
||||||
|
"LabelNotificationAvailableVariables": "Razpoložljive spremenljivke",
|
||||||
|
"LabelNotificationBodyTemplate": "Predloga telesa",
|
||||||
|
"LabelNotificationEvent": "Dogodek obvestila",
|
||||||
|
"LabelNotificationTitleTemplate": "Predloga naslova",
|
||||||
|
"LabelNotificationsMaxFailedAttempts": "Najvišje število neuspelih poskusov",
|
||||||
|
"LabelNotificationsMaxFailedAttemptsHelp": "Obvestila so onemogočena, ko se tolikokrat neuspelo pošljejo",
|
||||||
|
"LabelNotificationsMaxQueueSize": "Največja velikost čakalne vrste za dogodke obvestil",
|
||||||
|
"LabelNotificationsMaxQueueSizeHelp": "Dogodki so omejeni na sprožitev 1 na sekundo. Dogodki bodo prezrti, če je čakalna vrsta najvišja. To preprečuje neželeno pošiljanje obvestil.",
|
||||||
|
"LabelNumberOfBooks": "Število knjig",
|
||||||
|
"LabelNumberOfEpisodes": "# od epizod",
|
||||||
|
"LabelOpenIDAdvancedPermsClaimDescription": "Ime zahtevka OpenID, ki vsebuje napredna dovoljenja za uporabniška dejanja v aplikaciji, ki bodo veljala za neskrbniške vloge (<b>če je konfigurirano</b>). Če trditev manjka v odgovoru, bo dostop do ABS zavrnjen. Če ena možnost manjka, bo obravnavana kot <code>false</code>. Zagotovite, da se zahtevek ponudnika identitete ujema s pričakovano strukturo:",
|
||||||
|
"LabelOpenIDClaims": "Pustite naslednje možnosti prazne, da onemogočite napredno dodeljevanje skupin in dovoljenj, nato pa samodejno dodelite skupino 'Uporabnik'.",
|
||||||
|
"LabelOpenIDGroupClaimDescription": "Ime zahtevka OpenID, ki vsebuje seznam uporabnikovih skupin. Običajno imenovane <code>skupine</code>. <b>Če je konfigurirana</b>, bo aplikacija samodejno dodelila vloge na podlagi članstva v skupini uporabnika, pod pogojem, da so te skupine v zahtevku poimenovane 'admin', 'user' ali 'guest' brez razlikovanja med velikimi in malimi črkami. Zahtevek mora vsebovati seznam in če uporabnik pripada več skupinam, mu aplikacija dodeli vlogo, ki ustreza najvišjemu nivoju dostopa. Če se nobena skupina ne ujema, bo dostop zavrnjen.",
|
||||||
|
"LabelOpenRSSFeed": "Odpri vir RSS",
|
||||||
|
"LabelOverwrite": "Prepiši",
|
||||||
|
"LabelPassword": "Geslo",
|
||||||
|
"LabelPath": "Pot",
|
||||||
|
"LabelPermanent": "Trajno",
|
||||||
|
"LabelPermissionsAccessAllLibraries": "Lahko dostopa do vseh knjižnic",
|
||||||
|
"LabelPermissionsAccessAllTags": "Lahko dostopa do vseh oznak",
|
||||||
|
"LabelPermissionsAccessExplicitContent": "Lahko dostopa do eksplicitne vsebine",
|
||||||
|
"LabelPermissionsDelete": "Lahko briše",
|
||||||
|
"LabelPermissionsDownload": "Lahko prenaša",
|
||||||
|
"LabelPermissionsUpdate": "Lahko posodablja",
|
||||||
|
"LabelPermissionsUpload": "Lahko nalaga",
|
||||||
|
"LabelPersonalYearReview": "Pregled tvojega leta ({0})",
|
||||||
|
"LabelPhotoPathURL": "Slika pot/URL",
|
||||||
|
"LabelPlayMethod": "Metoda predvajanja",
|
||||||
|
"LabelPlayerChapterNumberMarker": "{0} od {1}",
|
||||||
|
"LabelPlaylists": "Seznami predvajanja",
|
||||||
|
"LabelPodcast": "Podcast",
|
||||||
|
"LabelPodcastSearchRegion": "Regija iskanja podcastov",
|
||||||
|
"LabelPodcastType": "Vrsta podcasta",
|
||||||
|
"LabelPodcasts": "Podcasti",
|
||||||
|
"LabelPort": "Vrata",
|
||||||
|
"LabelPrefixesToIgnore": "Predpone, ki jih je treba prezreti (neobčutljivo na velike in male črke)",
|
||||||
|
"LabelPreventIndexing": "Preprečite, da bi vaš vir indeksirali imeniki podcastov iTunes in Google",
|
||||||
|
"LabelPrimaryEbook": "Primarna e-knjiga",
|
||||||
|
"LabelProgress": "Napredek",
|
||||||
|
"LabelProvider": "Ponudnik",
|
||||||
|
"LabelProviderAuthorizationValue": "Vrednost glave avtorizacije",
|
||||||
|
"LabelPubDate": "Datum objave",
|
||||||
|
"LabelPublishYear": "Leto objave",
|
||||||
|
"LabelPublishedDate": "Objavljeno {0}",
|
||||||
|
"LabelPublisher": "Založnik",
|
||||||
|
"LabelPublishers": "Založniki",
|
||||||
|
"LabelRSSFeedCustomOwnerEmail": "E-pošta lastnika po meri",
|
||||||
|
"LabelRSSFeedCustomOwnerName": "Ime lastnika po meri",
|
||||||
|
"LabelRSSFeedOpen": "Odprt vir RSS",
|
||||||
|
"LabelRSSFeedPreventIndexing": "Prepreči indeksiranje",
|
||||||
|
"LabelRSSFeedSlug": "Slug RSS vira",
|
||||||
|
"LabelRSSFeedURL": "URL vira RSS",
|
||||||
|
"LabelRandomly": "Naključno",
|
||||||
|
"LabelReAddSeriesToContinueListening": "Znova dodaj serijo za nadaljevanje poslušanja",
|
||||||
|
"LabelRead": "Preberi",
|
||||||
|
"LabelReadAgain": "Ponovno preberi",
|
||||||
|
"LabelReadEbookWithoutProgress": "Preberi eknjigo brez ohranjanja napredka",
|
||||||
|
"LabelRecentSeries": "Nedavne serije",
|
||||||
|
"LabelRecentlyAdded": "Nedavno dodano",
|
||||||
|
"LabelRecommended": "Priporočeno",
|
||||||
|
"LabelRedo": "Ponovi",
|
||||||
|
"LabelRegion": "Regija",
|
||||||
|
"LabelReleaseDate": "Datum izdaje",
|
||||||
|
"LabelRemoveCover": "Odstrani naslovnico",
|
||||||
|
"LabelRowsPerPage": "Vrstic na stran",
|
||||||
|
"LabelSearchTerm": "Iskalni pojem",
|
||||||
|
"LabelSearchTitle": "Naslov iskanja",
|
||||||
|
"LabelSearchTitleOrASIN": "Naslov iskanja ali ASIN",
|
||||||
|
"LabelSeason": "Sezona",
|
||||||
|
"LabelSelectAll": "Izberite vse",
|
||||||
|
"LabelSelectAllEpisodes": "Izberite vse epizode",
|
||||||
|
"LabelSelectEpisodesShowing": "Izberi {0} prikazanih epizod",
|
||||||
|
"LabelSelectUsers": "Izberite uporabnike",
|
||||||
|
"LabelSendEbookToDevice": "Pošlji eknjigo k...",
|
||||||
|
"LabelSequence": "Zaporedje",
|
||||||
|
"LabelSeries": "Serije",
|
||||||
|
"LabelSeriesName": "Ime serije",
|
||||||
|
"LabelSeriesProgress": "Napredek serije",
|
||||||
|
"LabelServerYearReview": "Pregled leta strežnika ({0})",
|
||||||
|
"LabelSetEbookAsPrimary": "Nastavi kot primarno",
|
||||||
|
"LabelSetEbookAsSupplementary": "Nastavi kot dodatno",
|
||||||
|
"LabelSettingsAudiobooksOnly": "Samo zvočne knjige",
|
||||||
|
"LabelSettingsAudiobooksOnlyHelp": "Če omogočite to nastavitev, bodo datoteke eknjig prezrte, razen če so znotraj mape zvočnih knjig, v tem primeru bodo nastavljene kot dodatne e-knjige",
|
||||||
|
"LabelSettingsBookshelfViewHelp": "Skeuomorfna oblika z lesenimi policami",
|
||||||
|
"LabelSettingsChromecastSupport": "Podpora za Chromecast",
|
||||||
|
"LabelSettingsDateFormat": "Oblika datuma",
|
||||||
|
"LabelSettingsDisableWatcher": "Onemogoči pregledovalca",
|
||||||
|
"LabelSettingsDisableWatcherForLibrary": "Onemogoči pregledovalca map za knjižnico",
|
||||||
|
"LabelSettingsDisableWatcherHelp": "Onemogoči samodejno dodajanje/posodabljanje elementov, ko so zaznane spremembe datoteke. *Potreben je ponovni zagon strežnika",
|
||||||
|
"LabelSettingsEnableWatcher": "Omogoči pregledovalca",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Omogoči pregledovalca map za knjižnico",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Omogoča samodejno dodajanje/posodabljanje elementov, ko so zaznane spremembe datoteke. *Potreben je ponovni zagon strežnika",
|
||||||
|
"LabelSettingsEpubsAllowScriptedContent": "Dovoli skriptirano vsebino v epubih",
|
||||||
|
"LabelSettingsEpubsAllowScriptedContentHelp": "Dovoli datotekam epub izvajanje skript. Priporočljivo je, da to nastavitev pustite onemogočeno, razen če zaupate viru datotek epub.",
|
||||||
|
"LabelSettingsExperimentalFeatures": "Eksperimentalne funkcije",
|
||||||
|
"LabelSettingsExperimentalFeaturesHelp": "Funkcije v razvoju, ki bi lahko uporabile vaše povratne informacije in pomoč pri testiranju. Kliknite, da odprete razpravo na githubu.",
|
||||||
|
"LabelSettingsFindCovers": "Poišči naslovnice",
|
||||||
|
"LabelSettingsFindCoversHelp": "Če vaša zvočna knjiga nima vdelane naslovnice ali slike naslovnice v mapi, bo pregledovalnik poskušal najti naslovnico.<br>Opomba: To bo podaljšalo čas pregledovanja",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Skrij serije s samo eno knjigo",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Serije, ki imajo eno knjigo, bodo skrite na strani serije in policah domače strani.",
|
||||||
|
"LabelSettingsHomePageBookshelfView": "Domača stran bo imela pogled knjižne police",
|
||||||
|
"LabelSettingsLibraryBookshelfView": "Knjižnična uporaba pogleda knjižne police",
|
||||||
|
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Preskoči prejšnje knjige v nadaljevanju serije",
|
||||||
|
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Polica z domačo stranjo Nadaljuj serijo prikazuje prvo nezačeto knjigo v seriji, ki ima vsaj eno dokončano knjigo in ni nobene knjige v teku. Če omogočite to nastavitev, se bo serija nadaljevala od najbolj dokončane knjige namesto od prve nezačete knjige.",
|
||||||
|
"LabelSettingsParseSubtitles": "Uporabi podnapise",
|
||||||
|
"LabelSettingsParseSubtitlesHelp": "Izvleci podnapise iz imen map zvočnih knjig.<br>Podnaslov mora biti ločen z \" - \"<br>npr. »Naslov knjige – Tu podnapis« ima podnaslov »Tu podnapis«",
|
||||||
|
"LabelSettingsPreferMatchedMetadata": "Prednost imajo ujemajoči se metapodatki",
|
||||||
|
"LabelSettingsPreferMatchedMetadataHelp": "Pri uporabi hitrega ujemanja bodo ujemajoči se podatki preglasili podrobnosti artikla. Hitro ujemanje bo privzeto izpolnil samo manjkajoče podrobnosti.",
|
||||||
|
"LabelSettingsSkipMatchingBooksWithASIN": "Preskoči ujemajoče se knjige, ki že imajo ASIN",
|
||||||
|
"LabelSettingsSkipMatchingBooksWithISBN": "Preskoči ujemajoče se knjige, ki že imajo oznako ISBN",
|
||||||
|
"LabelSettingsSortingIgnorePrefixes": "Pri razvrščanju ne upoštevajte predpon",
|
||||||
|
"LabelSettingsSortingIgnorePrefixesHelp": "npr. za naslov knjige s predpono \"the\" bi se \"The Book Title\" razvrstil kot \"Book Title, The\"",
|
||||||
|
"LabelSettingsSquareBookCovers": "Uporabi kvadratne platnice knjig",
|
||||||
|
"LabelSettingsSquareBookCoversHelp": "Raje uporabi kvadratne platnice kot standardne knjižne platnice 1.6:1",
|
||||||
|
"LabelSettingsStoreCoversWithItem": "Shrani naslovnice skupaj z elementom",
|
||||||
|
"LabelSettingsStoreCoversWithItemHelp": "Naslovnice so privzeto shranjene v /metadata/items, če omogočite to nastavitev, bodo platnice shranjene v mapi elementov knjižnice. Shranjena bo samo ena datoteka z imenom \"cover\"",
|
||||||
|
"LabelSettingsStoreMetadataWithItem": "Shrani metapodatke skupaj z elementom",
|
||||||
|
"LabelSettingsStoreMetadataWithItemHelp": "Datoteke z metapodatki so privzeto shranjene v /metadata/items, če omogočite to nastavitev, boste datoteke z metapodatki shranili v mape elementov vaše knjižnice",
|
||||||
|
"LabelSettingsTimeFormat": "Oblika časa",
|
||||||
|
"LabelShare": "Deli",
|
||||||
|
"LabelShareOpen": "Deli odprto",
|
||||||
|
"LabelShareURL": "Deli URL",
|
||||||
|
"LabelShowAll": "Prikaži vse",
|
||||||
|
"LabelShowSeconds": "Prikaži sekunde",
|
||||||
|
"LabelShowSubtitles": "Prikaži podnapise",
|
||||||
|
"LabelSize": "Velikost",
|
||||||
|
"LabelSleepTimer": "Časovnik za spanje",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
|
"LabelStart": "Začetek",
|
||||||
|
"LabelStartTime": "Začetni čas",
|
||||||
|
"LabelStarted": "Začeto",
|
||||||
|
"LabelStartedAt": "Začeto ob",
|
||||||
|
"LabelStatsAudioTracks": "Zvočni posnetki",
|
||||||
|
"LabelStatsAuthors": "Avtorji",
|
||||||
|
"LabelStatsBestDay": "Najboljši dan",
|
||||||
|
"LabelStatsDailyAverage": "Dnevno povprečje",
|
||||||
|
"LabelStatsDays": "Dnevi",
|
||||||
|
"LabelStatsDaysListened": "Poslušani dnevi",
|
||||||
|
"LabelStatsHours": "Ure",
|
||||||
|
"LabelStatsInARow": "v vrsti",
|
||||||
|
"LabelStatsItemsFinished": "Končani elementi",
|
||||||
|
"LabelStatsItemsInLibrary": "Elementi v knjižnici",
|
||||||
|
"LabelStatsMinutes": "minute",
|
||||||
|
"LabelStatsMinutesListening": "Poslušane minute",
|
||||||
|
"LabelStatsOverallDays": "Skupaj dnevi",
|
||||||
|
"LabelStatsOverallHours": "Skupaj ure",
|
||||||
|
"LabelStatsWeekListening": "Tednov poslušanja",
|
||||||
|
"LabelSubtitle": "Podnapis",
|
||||||
|
"LabelSupportedFileTypes": "Podprte vrste datotek",
|
||||||
|
"LabelTag": "Oznaka",
|
||||||
|
"LabelTags": "Oznake",
|
||||||
|
"LabelTagsAccessibleToUser": "Oznake, dostopne uporabniku",
|
||||||
|
"LabelTagsNotAccessibleToUser": "Oznake, ki niso dostopne uporabniku",
|
||||||
|
"LabelTasks": "Tekoče naloge",
|
||||||
|
"LabelTextEditorBulletedList": "Seznam z oznakami",
|
||||||
|
"LabelTextEditorLink": "Povezava",
|
||||||
|
"LabelTextEditorNumberedList": "Številčni seznam",
|
||||||
|
"LabelTextEditorUnlink": "Odveži",
|
||||||
|
"LabelTheme": "Tema",
|
||||||
|
"LabelThemeDark": "Temna",
|
||||||
|
"LabelThemeLight": "Svetla",
|
||||||
|
"LabelTimeBase": "Odvisna od časa",
|
||||||
|
"LabelTimeDurationXHours": "{0} ur",
|
||||||
|
"LabelTimeDurationXMinutes": "{0} minut",
|
||||||
|
"LabelTimeDurationXSeconds": "{0} sekund",
|
||||||
|
"LabelTimeInMinutes": "Čas v minutah",
|
||||||
|
"LabelTimeListened": "Čas poslušanja",
|
||||||
|
"LabelTimeListenedToday": "Čas poslušanja danes",
|
||||||
|
"LabelTimeRemaining": "Še {0}",
|
||||||
|
"LabelTimeToShift": "Čas prestavljanja v sekundah",
|
||||||
|
"LabelTitle": "Naslov",
|
||||||
|
"LabelToolsEmbedMetadata": "Vdelaj metapodatke",
|
||||||
|
"LabelToolsEmbedMetadataDescription": "Vdelajte metapodatke v zvočne datoteke, vključno s sliko naslovnice in poglavji.",
|
||||||
|
"LabelToolsMakeM4b": "Ustvari datoteko zvočne knjige M4B",
|
||||||
|
"LabelToolsMakeM4bDescription": "Ustvarite datoteko zvočne knjige .M4B z vdelanimi metapodatki, sliko naslovnice in poglavji.",
|
||||||
|
"LabelToolsSplitM4b": "Razdeli M4B v MP3 datoteke",
|
||||||
|
"LabelToolsSplitM4bDescription": "Ustvarite MP3 datoteke iz datoteke M4B, razdeljene po poglavjih z vdelanimi metapodatki, naslovno sliko in poglavji.",
|
||||||
|
"LabelTotalDuration": "Skupno trajanje",
|
||||||
|
"LabelTotalTimeListened": "Skupni čas poslušanja",
|
||||||
|
"LabelTrackFromFilename": "Posnetek iz datoteke",
|
||||||
|
"LabelTrackFromMetadata": "Posnetek iz metapodatkov",
|
||||||
|
"LabelTracks": "Posnetki",
|
||||||
|
"LabelTracksMultiTrack": "Več posnetkov",
|
||||||
|
"LabelTracksNone": "Brez posnetka",
|
||||||
|
"LabelTracksSingleTrack": "Enojni posnetek",
|
||||||
|
"LabelType": "Vrsta",
|
||||||
|
"LabelUnabridged": "Neskrajšano",
|
||||||
|
"LabelUndo": "Razveljavi",
|
||||||
|
"LabelUnknown": "Neznano",
|
||||||
|
"LabelUnknownPublishDate": "Neznan datum objave",
|
||||||
|
"LabelUpdateCover": "Posodobi naslovnico",
|
||||||
|
"LabelUpdateCoverHelp": "Dovoli prepisovanje obstoječih naslovnic za izbrane knjige, ko se najde ujemanje",
|
||||||
|
"LabelUpdateDetails": "Posodobi podrobnosti",
|
||||||
|
"LabelUpdateDetailsHelp": "Dovoli prepisovanje obstoječih podrobnosti za izbrane knjige, ko se najde ujemanje",
|
||||||
|
"LabelUpdatedAt": "Posodobljeno ob",
|
||||||
|
"LabelUploaderDragAndDrop": "Povleci in spusti datoteke ali mape",
|
||||||
|
"LabelUploaderDropFiles": "Spusti datoteke",
|
||||||
|
"LabelUploaderItemFetchMetadataHelp": "Samodejno pridobi naslov, avtorja in serijo",
|
||||||
|
"LabelUseChapterTrack": "Uporabi posnetek poglavij",
|
||||||
|
"LabelUseFullTrack": "Uporabi celoten posnetek",
|
||||||
|
"LabelUser": "Uporabnik",
|
||||||
|
"LabelUsername": "Uporabniško ime",
|
||||||
|
"LabelValue": "Vrednost",
|
||||||
|
"LabelVersion": "Verzija",
|
||||||
|
"LabelViewBookmarks": "Ogled zaznamkov",
|
||||||
|
"LabelViewChapters": "Ogled poglavij",
|
||||||
|
"LabelViewPlayerSettings": "Ogled nastavitev predvajalnika",
|
||||||
|
"LabelViewQueue": "Ogled čakalno vrsto predvajalnika",
|
||||||
|
"LabelVolume": "Glasnost",
|
||||||
|
"LabelWeekdaysToRun": "Delovni dnevi predvajanja",
|
||||||
|
"LabelXBooks": "{0} knjig",
|
||||||
|
"LabelXItems": "{0} elementov",
|
||||||
|
"LabelYearReviewHide": "Skrij pregled leta",
|
||||||
|
"LabelYearReviewShow": "Poglej pregled leta",
|
||||||
|
"LabelYourAudiobookDuration": "Trajanje tvojih zvočnih knjig",
|
||||||
|
"LabelYourBookmarks": "Tvoji zaznamki",
|
||||||
|
"LabelYourPlaylists": "Tvoje seznami predvajanj",
|
||||||
|
"LabelYourProgress": "Tvoj napredek",
|
||||||
|
"MessageAddToPlayerQueue": "Dodaj v čakalno vrsto predvajalnika",
|
||||||
|
"MessageAppriseDescription": "Če želite uporabljati to funkcijo, morate imeti zagnan primerek <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> ali API, ki bo obravnaval te iste zahteve. <br />Url API-ja Apprise mora biti celotna pot URL-ja za pošiljanje obvestila, npr. če je vaš primerek API-ja postrežen na <code>http://192.168.1.1:8337</code>, bi morali vnesti <code >http://192.168.1.1:8337/notify</code>.",
|
||||||
|
"MessageBackupsDescription": "Varnostne kopije vključujejo uporabnike, napredek uporabnikov, podrobnosti elementov knjižnice, nastavitve strežnika in slike, shranjene v <code>/metadata/items</code> & <code>/metadata/authors</code>. Varnostne kopije <strong>ne</strong> vključujejo datotek, shranjenih v mapah vaše knjižnice.",
|
||||||
|
"MessageBackupsLocationEditNote": "Opomba: Posodabljanje lokacije varnostne kopije ne bo premaknilo ali spremenilo obstoječih varnostnih kopij",
|
||||||
|
"MessageBackupsLocationNoEditNote": "Opomba: Lokacija varnostne kopije je nastavljena s spremenljivko okolja in je tu ni mogoče spremeniti.",
|
||||||
|
"MessageBackupsLocationPathEmpty": "Pot do lokacije varnostne kopije ne sme biti prazna",
|
||||||
|
"MessageBatchQuickMatchDescription": "Hitro ujemanje bo poskušal dodati manjkajoče naslovnice in metapodatke za izbrane elemente. Omogočite spodnje možnosti, da omogočite hitremu ujemanju, da prepiše obstoječe naslovnice in/ali metapodatke.",
|
||||||
|
"MessageBookshelfNoCollections": "Ustvaril nisi še nobene zbirke",
|
||||||
|
"MessageBookshelfNoRSSFeeds": "Noben vir RSS ni odprt",
|
||||||
|
"MessageBookshelfNoResultsForFilter": "Ni rezultatov za filter \"{0}: {1}\"",
|
||||||
|
"MessageBookshelfNoResultsForQuery": "Ni rezultatov za poizvedbo",
|
||||||
|
"MessageBookshelfNoSeries": "Nimate serij",
|
||||||
|
"MessageChapterEndIsAfter": "Konec poglavja je za koncem vaše zvočne knjige",
|
||||||
|
"MessageChapterErrorFirstNotZero": "Prvo poglavje se mora začeti pri 0",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Neveljaven začetni čas mora biti krajši od trajanja zvočne knjige",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Neveljaven začetni čas mora biti večji od ali enak začetnemu času prejšnjega poglavja",
|
||||||
|
"MessageChapterStartIsAfter": "Začetek poglavja je po koncu vaše zvočne knjige",
|
||||||
|
"MessageCheckingCron": "Preverjam cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Ali ste prepričani, da želite zapreti ta vir?",
|
||||||
|
"MessageConfirmDeleteBackup": "Ali ste prepričani, da želite izbrisati varnostno kopijo za {0}?",
|
||||||
|
"MessageConfirmDeleteDevice": "Ali ste prepričani, da želite izbrisati e-bralnik \"{0}\"?",
|
||||||
|
"MessageConfirmDeleteFile": "To bo izbrisalo datoteko iz vašega datotečnega sistema. Ali ste prepričani?",
|
||||||
|
"MessageConfirmDeleteLibrary": "Ali ste prepričani, da želite trajno izbrisati knjižnico \"{0}\"?",
|
||||||
|
"MessageConfirmDeleteLibraryItem": "S tem boste element knjižnice izbrisali iz baze podatkov in vašega datotečnega sistema. Ste prepričani?",
|
||||||
|
"MessageConfirmDeleteLibraryItems": "To bo izbrisalo {0} elementov knjižnice iz baze podatkov in vašega datotečnega sistema. Ste prepričani?",
|
||||||
|
"MessageConfirmDeleteMetadataProvider": "Ali ste prepričani, da želite izbrisati ponudnika metapodatkov po meri \"{0}\"?",
|
||||||
|
"MessageConfirmDeleteNotification": "Ali ste prepričani, da želite izbrisati to obvestilo?",
|
||||||
|
"MessageConfirmDeleteSession": "Ali ste prepričani, da želite izbrisati to sejo?",
|
||||||
|
"MessageConfirmForceReScan": "Ali ste prepričani, da želite vsiliti ponovno iskanje?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Ali ste prepričani, da želite označiti vse epizode kot dokončane?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Ali ste prepričani, da želite vse epizode označiti kot nedokončane?",
|
||||||
|
"MessageConfirmMarkItemFinished": "Ali ste prepričani, da želite \"{0}\" označiti kot dokončanega?",
|
||||||
|
"MessageConfirmMarkItemNotFinished": "Ali ste prepričani, da želite \"{0}\" označiti kot nedokončanega?",
|
||||||
|
"MessageConfirmMarkSeriesFinished": "Ali ste prepričani, da želite vse knjige v tej seriji označiti kot dokončane?",
|
||||||
|
"MessageConfirmMarkSeriesNotFinished": "Ali ste prepričani, da želite vse knjige v tej seriji označiti kot nedokončane?",
|
||||||
|
"MessageConfirmNotificationTestTrigger": "Želite sprožiti to obvestilo s testnimi podatki?",
|
||||||
|
"MessageConfirmPurgeCache": "Čiščenje predpomnilnika bo izbrisalo celoten imenik v <code>/metadata/cache</code>. <br /><br />Ali ste prepričani, da želite odstraniti imenik predpomnilnika?",
|
||||||
|
"MessageConfirmPurgeItemsCache": "Čiščenje predpomnilnika elementov bo izbrisalo celoten imenik na <code>/metadata/cache/items</code>.<br />Ste prepričani?",
|
||||||
|
"MessageConfirmQuickEmbed": "Opozorilo! Hitra vdelava ne bo varnostno kopirala vaših zvočnih datotek. Prepričajte se, da imate varnostno kopijo zvočnih datotek. <br><br>Ali želite nadaljevati?",
|
||||||
|
"MessageConfirmReScanLibraryItems": "Ali ste prepričani, da želite ponovno poiskati {0} elementov?",
|
||||||
|
"MessageConfirmRemoveAllChapters": "Ali ste prepričani, da želite odstraniti vsa poglavja?",
|
||||||
|
"MessageConfirmRemoveAuthor": "Ali ste prepričani, da želite odstraniti avtorja \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveCollection": "Ali ste prepričani, da želite odstraniti zbirko \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveEpisode": "Ali ste prepričani, da želite odstraniti epizodo \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveEpisodes": "Ali ste prepričani, da želite odstraniti {0} epizod?",
|
||||||
|
"MessageConfirmRemoveListeningSessions": "Ali ste prepričani, da želite odstraniti {0} sej poslušanja?",
|
||||||
|
"MessageConfirmRemoveNarrator": "Ali ste prepričani, da želite odstraniti bralca \"{0}\"?",
|
||||||
|
"MessageConfirmRemovePlaylist": "Ali ste prepričani, da želite odstraniti svoj seznam predvajanja \"{0}\"?",
|
||||||
|
"MessageConfirmRenameGenre": "Ali ste prepričani, da želite preimenovati žanr \"{0}\" v \"{1}\" za vse elemente?",
|
||||||
|
"MessageConfirmRenameGenreMergeNote": "Opomba: Ta žanr že obstaja, zato bosta združeni.",
|
||||||
|
"MessageConfirmRenameGenreWarning": "Opozorilo! Podoben žanr z različnimi velikosti črk že obstaja \"{0}\".",
|
||||||
|
"MessageConfirmRenameTag": "Ali ste prepričani, da želite preimenovati oznako \"{0}\" v \"{1}\" za vse elemente?",
|
||||||
|
"MessageConfirmRenameTagMergeNote": "Opomba: Ta oznaka že obstaja, zato bosta združeni.",
|
||||||
|
"MessageConfirmRenameTagWarning": "Opozorilo! Podobna oznaka z različnimi velikosti črk že obstaja \"{0}\".",
|
||||||
|
"MessageConfirmResetProgress": "Ali ste prepričani, da želite ponastaviti svoj napredek?",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Ali ste prepričani, da želite poslati {0} e-knjigo \"{1}\" v napravo \"{2}\"?",
|
||||||
|
"MessageConfirmUnlinkOpenId": "Ali ste prepričani, da želite prekiniti povezavo tega uporabnika z OpenID?",
|
||||||
|
"MessageDownloadingEpisode": "Prenašam epizodo",
|
||||||
|
"MessageDragFilesIntoTrackOrder": "Povlecite datoteke v pravilen vrstni red posnetkov",
|
||||||
|
"MessageEmbedFailed": "Vdelava ni uspela!",
|
||||||
|
"MessageEmbedFinished": "Vdelava končana!",
|
||||||
|
"MessageEpisodesQueuedForDownload": "{0} epizod v čakalni vrsti za prenos",
|
||||||
|
"MessageEreaderDevices": "Da zagotovite dostavo e-knjig, boste morda morali dodati zgornji e-poštni naslov kot veljavnega pošiljatelja za vsako spodaj navedeno napravo.",
|
||||||
|
"MessageFeedURLWillBe": "URL vira bo {0}",
|
||||||
|
"MessageFetching": "Pridobivam...",
|
||||||
|
"MessageForceReScanDescription": "bo znova pregledal vse datoteke kot nov pregled. Oznake ID3 zvočnih datotek, datoteke OPF in besedilne datoteke bodo pregledane kot nove.",
|
||||||
|
"MessageImportantNotice": "Pomembno obvestilo!",
|
||||||
|
"MessageInsertChapterBelow": "Spodaj vstavite poglavje",
|
||||||
|
"MessageItemsSelected": "{0} izbranih elementov",
|
||||||
|
"MessageItemsUpdated": "Št. posodobljenih elementov: {0}",
|
||||||
|
"MessageJoinUsOn": "Pridružite se nam",
|
||||||
|
"MessageListeningSessionsInTheLastYear": "{0} sej poslušanja v zadnjem letu",
|
||||||
|
"MessageLoading": "Nalagam...",
|
||||||
|
"MessageLoadingFolders": "Nalagam mape...",
|
||||||
|
"MessageLogsDescription": "Dnevniki so shranjeni v <code>/metadata/logs</code> kot datoteke JSON. Dnevniki zrušitev so shranjeni v <code>/metadata/logs/crash_logs.txt</code>.",
|
||||||
|
"MessageM4BFailed": "M4B ni uspel!",
|
||||||
|
"MessageM4BFinished": "M4B končan!",
|
||||||
|
"MessageMapChapterTitles": "Preslikajte naslove poglavij v obstoječa poglavja zvočne knjige brez prilagajanja časovnih žigov",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Označi vse epizode kot končane",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Označi vse epizode kot nedokončane",
|
||||||
|
"MessageMarkAsFinished": "Označi kot dokončano",
|
||||||
|
"MessageMarkAsNotFinished": "Označi kot nedokončano",
|
||||||
|
"MessageMatchBooksDescription": "bo poskušal povezati knjige v knjižnici s knjigo izbranega ponudnika iskanja in izpolniti prazne podatke in naslovnico. Ne prepisujejo se pa podrobnosti.",
|
||||||
|
"MessageNoAudioTracks": "Ni zvočnih posnetkov",
|
||||||
|
"MessageNoAuthors": "Brez avtorjev",
|
||||||
|
"MessageNoBackups": "Brez varnostnih kopij",
|
||||||
|
"MessageNoBookmarks": "Brez zaznamkov",
|
||||||
|
"MessageNoChapters": "Brez poglavij",
|
||||||
|
"MessageNoCollections": "Brez zbirk",
|
||||||
|
"MessageNoCoversFound": "Ni naslovnic",
|
||||||
|
"MessageNoDescription": "Ni opisa",
|
||||||
|
"MessageNoDevices": "Ni naprav",
|
||||||
|
"MessageNoDownloadsInProgress": "Trenutno ni prenosov v teku",
|
||||||
|
"MessageNoDownloadsQueued": "Ni prenosov v čakalni vrsti",
|
||||||
|
"MessageNoEpisodeMatchesFound": "Ni zadetkov za epizodo",
|
||||||
|
"MessageNoEpisodes": "Ni epizod",
|
||||||
|
"MessageNoFoldersAvailable": "Ni na voljo nobene mape",
|
||||||
|
"MessageNoGenres": "Ni žanrov",
|
||||||
|
"MessageNoIssues": "Ni težav",
|
||||||
|
"MessageNoItems": "Ni elementov",
|
||||||
|
"MessageNoItemsFound": "Ni najdenih elementov",
|
||||||
|
"MessageNoListeningSessions": "Ni sej poslušanja",
|
||||||
|
"MessageNoLogs": "Ni dnevnikov",
|
||||||
|
"MessageNoMediaProgress": "Ni medijskega napredka",
|
||||||
|
"MessageNoNotifications": "Ni obvestil",
|
||||||
|
"MessageNoPodcastsFound": "Ni podcastov",
|
||||||
|
"MessageNoResults": "Ni rezultatov",
|
||||||
|
"MessageNoSearchResultsFor": "Ni rezultatov iskanja za \"{0}\"",
|
||||||
|
"MessageNoSeries": "Ni serij",
|
||||||
|
"MessageNoTags": "Ni oznak",
|
||||||
|
"MessageNoTasksRunning": "Nobeno opravili ne teče",
|
||||||
|
"MessageNoUpdatesWereNecessary": "Posodobitve niso bile potrebne",
|
||||||
|
"MessageNoUserPlaylists": "Nimate seznamov predvajanja",
|
||||||
|
"MessageNotYetImplemented": "Še ni implementirano",
|
||||||
|
"MessageOpmlPreviewNote": "Opomba: To je predogled razčlenjene datoteke OPML. Dejanski naslov podcasta bo vzet iz vira RSS.",
|
||||||
|
"MessageOr": "ali",
|
||||||
|
"MessagePauseChapter": "Začasno ustavite predvajanje poglavja",
|
||||||
|
"MessagePlayChapter": "Poslušajte začetek poglavja",
|
||||||
|
"MessagePlaylistCreateFromCollection": "Ustvari seznam predvajanja iz zbirke",
|
||||||
|
"MessagePleaseWait": "Prosim počakajte...",
|
||||||
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nima URL-ja vira RSS, ki bi ga lahko uporabil za ujemanje",
|
||||||
|
"MessageQuickMatchDescription": "Izpolni prazne podrobnosti elementa in naslovnico s prvim rezultatom ujemanja iz '{0}'. Ne prepiše podrobnosti, razen če je omogočena nastavitev strežnika 'Prednostno ujemajoči se metapodatki'.",
|
||||||
|
"MessageRemoveChapter": "Odstrani poglavje",
|
||||||
|
"MessageRemoveEpisodes": "Odstrani toliko epizod: {0}",
|
||||||
|
"MessageRemoveFromPlayerQueue": "Odstrani iz čakalne vrste predvajalnika",
|
||||||
|
"MessageRemoveUserWarning": "Ali ste prepričani, da želite trajno izbrisati uporabnika \"{0}\"?",
|
||||||
|
"MessageReportBugsAndContribute": "Prijavite hrošče, zahtevajte nove funkcije in prispevajte še naprej",
|
||||||
|
"MessageResetChaptersConfirm": "Ali ste prepričani, da želite ponastaviti poglavja in razveljaviti spremembe, ki ste jih naredili?",
|
||||||
|
"MessageRestoreBackupConfirm": "Ali ste prepričani, da želite obnoviti varnostno kopijo, ustvarjeno ob",
|
||||||
|
"MessageRestoreBackupWarning": "Obnovitev varnostne kopije bo prepisala celotno zbirko podatkov, ki se nahaja v /config, in zajema slike v /metadata/items in /metadata/authors.<br /><br />Varnostne kopije ne spreminjajo nobenih datotek v mapah vaše knjižnice. Če ste omogočili nastavitve strežnika za shranjevanje naslovnic in metapodatkov v mapah vaše knjižnice, potem ti niso varnostno kopirani ali prepisani.<br /><br />Vsi odjemalci, ki uporabljajo vaš strežnik, bodo samodejno osveženi.",
|
||||||
|
"MessageSearchResultsFor": "Rezultati iskanja za",
|
||||||
|
"MessageSelected": "{0} izbrano",
|
||||||
|
"MessageServerCouldNotBeReached": "Strežnika ni bilo mogoče doseči",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "Nastavite poglavja z uporabo vsake zvočne datoteke kot poglavja in naslova poglavja kot imena zvočne datoteke",
|
||||||
|
"MessageShareExpirationWillBe": "Potečeno bo <strong>{0}</strong>",
|
||||||
|
"MessageShareExpiresIn": "Poteče čez {0}",
|
||||||
|
"MessageShareURLWillBe": "URL za skupno rabo bo <strong>{0}</strong>",
|
||||||
|
"MessageStartPlaybackAtTime": "Začni predvajanje za \"{0}\" ob {1}?",
|
||||||
|
"MessageThinking": "Razmišljam...",
|
||||||
|
"MessageUploaderItemFailed": "Nalaganje ni uspelo",
|
||||||
|
"MessageUploaderItemSuccess": "Uspešno naloženo!",
|
||||||
|
"MessageUploading": "Nalaganje...",
|
||||||
|
"MessageValidCronExpression": "Veljaven cron izraz",
|
||||||
|
"MessageWatcherIsDisabledGlobally": "Pregledovalec je globalno onemogočen v nastavitvah strežnika",
|
||||||
|
"MessageXLibraryIsEmpty": "{0} Knjižnica je prazna!",
|
||||||
|
"MessageYourAudiobookDurationIsLonger": "Trajanje vaše zvočne knjige je daljše od ugotovljenega trajanja",
|
||||||
|
"MessageYourAudiobookDurationIsShorter": "Trajanje vaše zvočne knjige je krajše od ugotovljenega trajanja",
|
||||||
|
"NoteChangeRootPassword": "Korenski uporabnik je edini uporabnik, ki ima lahko prazno geslo",
|
||||||
|
"NoteChapterEditorTimes": "Opomba: Začetni čas prvega poglavja mora ostati pri 0:00 in zadnji čas začetka poglavja ne sme preseči tega trajanja zvočne knjige.",
|
||||||
|
"NoteFolderPicker": "Opomba: že preslikane mape ne bodo prikazane",
|
||||||
|
"NoteRSSFeedPodcastAppsHttps": "Opozorilo: večina aplikacij za podcaste bo zahtevala, da URL vira RSS uporablja HTTPS",
|
||||||
|
"NoteRSSFeedPodcastAppsPubDate": "Opozorilo: 1 ali več vaših epizod nima datuma objave. Nekatere aplikacije za podcaste to zahtevajo.",
|
||||||
|
"NoteUploaderFoldersWithMediaFiles": "Mape z predstavnostnimi datotekami bodo obravnavane kot ločene postavke knjižnice.",
|
||||||
|
"NoteUploaderOnlyAudioFiles": "Če nalagate samo zvočne datoteke, bo vsaka zvočna datoteka obravnavana kot ločena zvočna knjiga.",
|
||||||
|
"NoteUploaderUnsupportedFiles": "Nepodprte datoteke so prezrte. Ko izberete ali spustite mapo, se druge datoteke, ki niso v mapi elementov, prezrejo.",
|
||||||
|
"PlaceholderNewCollection": "Novo ime zbirke",
|
||||||
|
"PlaceholderNewFolderPath": "Pot nove mape",
|
||||||
|
"PlaceholderNewPlaylist": "Novo ime seznama predvajanja",
|
||||||
|
"PlaceholderSearch": "Poišči..",
|
||||||
|
"PlaceholderSearchEpisode": "Poišči epizodo...",
|
||||||
|
"StatsAuthorsAdded": "dodanih avtorjev",
|
||||||
|
"StatsBooksAdded": "dodanih knjig",
|
||||||
|
"StatsBooksAdditional": "Nekateri dodatki vključujejo…",
|
||||||
|
"StatsBooksFinished": "končane knjige",
|
||||||
|
"StatsBooksFinishedThisYear": "Nekaj knjig, ki so bile dokončane letos…",
|
||||||
|
"StatsBooksListenedTo": "poslušane knjige",
|
||||||
|
"StatsCollectionGrewTo": "Vaša zbirka knjig se je povečala na …",
|
||||||
|
"StatsSessions": "seje",
|
||||||
|
"StatsSpentListening": "porabil za poslušanje",
|
||||||
|
"StatsTopAuthor": "TOP AVTOR",
|
||||||
|
"StatsTopAuthors": "TOP AVTORJI",
|
||||||
|
"StatsTopGenre": "TOP ŽANR",
|
||||||
|
"StatsTopGenres": "TOP ŽANRI",
|
||||||
|
"StatsTopMonth": "TOP MESEC",
|
||||||
|
"StatsTopNarrator": "TOP BRALEC",
|
||||||
|
"StatsTopNarrators": "TOP BRALCI",
|
||||||
|
"StatsTotalDuration": "S skupnim trajanjem…",
|
||||||
|
"StatsYearInReview": "PREGLED LETA",
|
||||||
|
"ToastAccountUpdateFailed": "Računa ni bilo mogoče posodobiti",
|
||||||
|
"ToastAccountUpdateSuccess": "Račun posodobljen",
|
||||||
|
"ToastAppriseUrlRequired": "Vnesti morate Apprise URL",
|
||||||
|
"ToastAuthorImageRemoveSuccess": "Slika avtorja je odstranjena",
|
||||||
|
"ToastAuthorNotFound": "Avtor \"{0}\" ni bil najden",
|
||||||
|
"ToastAuthorRemoveSuccess": "Avtor odstranjen",
|
||||||
|
"ToastAuthorSearchNotFound": "Ne najdem avtorja",
|
||||||
|
"ToastAuthorUpdateFailed": "Avtorja ni bilo mogoče posodobiti",
|
||||||
|
"ToastAuthorUpdateMerged": "Avtor združen",
|
||||||
|
"ToastAuthorUpdateSuccess": "Avtor posodobljen",
|
||||||
|
"ToastAuthorUpdateSuccessNoImageFound": "Avtor posodobljen (ne najdem slike)",
|
||||||
|
"ToastBackupAppliedSuccess": "Uporabljena varnostna kopija",
|
||||||
|
"ToastBackupCreateFailed": "Varnostne kopije ni bilo mogoče ustvariti",
|
||||||
|
"ToastBackupCreateSuccess": "Varnostna kopija ustvarjena",
|
||||||
|
"ToastBackupDeleteFailed": "Varnostne kopije ni bilo mogoče izbrisati",
|
||||||
|
"ToastBackupDeleteSuccess": "Varnostna kopija izbrisana",
|
||||||
|
"ToastBackupInvalidMaxKeep": "Neveljavno število varnostnih kopij za ohranjanje",
|
||||||
|
"ToastBackupInvalidMaxSize": "Neveljavna največja velikost varnostne kopije",
|
||||||
|
"ToastBackupPathUpdateFailed": "Posodobitev poti varnostnih kopij ni uspela",
|
||||||
|
"ToastBackupRestoreFailed": "Varnostne kopije ni bilo mogoče obnoviti",
|
||||||
|
"ToastBackupUploadFailed": "Nalaganje varnostne kopije ni uspelo",
|
||||||
|
"ToastBackupUploadSuccess": "Varnostna kopija je naložena",
|
||||||
|
"ToastBatchDeleteFailed": "Paketno brisanje ni uspelo",
|
||||||
|
"ToastBatchDeleteSuccess": "Paketno brisanje je bilo uspešno",
|
||||||
|
"ToastBatchUpdateFailed": "Paketna posodobitev ni uspela",
|
||||||
|
"ToastBatchUpdateSuccess": "Paketna posodobitev je uspela",
|
||||||
|
"ToastBookmarkCreateFailed": "Zaznamka ni bilo mogoče ustvariti",
|
||||||
|
"ToastBookmarkCreateSuccess": "Zaznamek dodan",
|
||||||
|
"ToastBookmarkRemoveSuccess": "Zaznamek odstranjen",
|
||||||
|
"ToastBookmarkUpdateFailed": "Zaznamka ni bilo mogoče posodobiti",
|
||||||
|
"ToastBookmarkUpdateSuccess": "Zaznamek posodobljen",
|
||||||
|
"ToastCachePurgeFailed": "Čiščenje predpomnilnika ni uspelo",
|
||||||
|
"ToastCachePurgeSuccess": "Predpomnilnik je bil uspešno očiščen",
|
||||||
|
"ToastChaptersHaveErrors": "Poglavja imajo napake",
|
||||||
|
"ToastChaptersMustHaveTitles": "Poglavja morajo imeti naslove",
|
||||||
|
"ToastChaptersRemoved": "Poglavja so odstranjena",
|
||||||
|
"ToastCollectionItemsAddFailed": "Dodajanje elementov v zbirko ni uspelo",
|
||||||
|
"ToastCollectionItemsAddSuccess": "Dodajanje elementov v zbirko je bilo uspešno",
|
||||||
|
"ToastCollectionItemsRemoveSuccess": "Elementi so bili odstranjeni iz zbirke",
|
||||||
|
"ToastCollectionRemoveSuccess": "Zbirka je bila odstranjena",
|
||||||
|
"ToastCollectionUpdateFailed": "Zbirke ni bilo mogoče posodobiti",
|
||||||
|
"ToastCollectionUpdateSuccess": "Zbirka je bila posodobljena",
|
||||||
|
"ToastCoverUpdateFailed": "Posodobitev naslovnice ni uspela",
|
||||||
|
"ToastDeleteFileFailed": "Brisanje datoteke ni uspelo",
|
||||||
|
"ToastDeleteFileSuccess": "Datoteka je bila izbrisana",
|
||||||
|
"ToastDeviceAddFailed": "Naprave ni bilo mogoče dodati",
|
||||||
|
"ToastDeviceNameAlreadyExists": "Elektronska naprava s tem imenom že obstaja",
|
||||||
|
"ToastDeviceTestEmailFailed": "Pošiljanje testnega e-poštnega sporočila ni uspelo",
|
||||||
|
"ToastDeviceTestEmailSuccess": "Testno e-poštno sporočilo je poslano",
|
||||||
|
"ToastDeviceUpdateFailed": "Naprave ni bilo mogoče posodobiti",
|
||||||
|
"ToastEmailSettingsUpdateFailed": "E-poštnih nastavitev ni bilo mogoče posodobiti",
|
||||||
|
"ToastEmailSettingsUpdateSuccess": "E-poštne nastavitve so bile posodobljene",
|
||||||
|
"ToastEncodeCancelFailed": "Napaka pri preklicu prekodiranja",
|
||||||
|
"ToastEncodeCancelSucces": "Prekodiranje prekinjeno",
|
||||||
|
"ToastEpisodeDownloadQueueClearFailed": "Čiščenje čakalne vrste ni uspelo",
|
||||||
|
"ToastEpisodeDownloadQueueClearSuccess": "Čakalna vrsta za prenos epizod je počiščena",
|
||||||
|
"ToastErrorCannotShare": "V tej napravi ni mogoče dati v skupno rabo",
|
||||||
|
"ToastFailedToLoadData": "Podatkov ni bilo mogoče naložiti",
|
||||||
|
"ToastFailedToShare": "Skupna raba ni uspela",
|
||||||
|
"ToastFailedToUpdateAccount": "Računa ni bilo mogoče posodobiti",
|
||||||
|
"ToastFailedToUpdateUser": "Uporabnika ni bilo mogoče posodobiti",
|
||||||
|
"ToastInvalidImageUrl": "Neveljaven URL slike",
|
||||||
|
"ToastInvalidUrl": "Neveljaven URL",
|
||||||
|
"ToastItemCoverUpdateFailed": "Naslovnice elementa ni bilo mogoče posodobiti",
|
||||||
|
"ToastItemCoverUpdateSuccess": "Naslovnica elementa je bila posodobljena",
|
||||||
|
"ToastItemDeletedFailed": "Elementa ni bilo mogoče izbrisati",
|
||||||
|
"ToastItemDeletedSuccess": "Element je bil izbrisan",
|
||||||
|
"ToastItemDetailsUpdateFailed": "Posodobitev podrobnosti elementa ni uspela",
|
||||||
|
"ToastItemDetailsUpdateSuccess": "Podrobnosti elementa so bile posodobjene",
|
||||||
|
"ToastItemMarkedAsFinishedFailed": "Označevanje kot dokončano ni uspelo",
|
||||||
|
"ToastItemMarkedAsFinishedSuccess": "Element je označen kot dokončan",
|
||||||
|
"ToastItemMarkedAsNotFinishedFailed": "Ni bilo mogoče označiti kot nedokončano",
|
||||||
|
"ToastItemMarkedAsNotFinishedSuccess": "Element označen kot nedokončan",
|
||||||
|
"ToastItemUpdateFailed": "Elementa ni bilo mogoče posodobiti",
|
||||||
|
"ToastItemUpdateSuccess": "Element je bil posodobljen",
|
||||||
|
"ToastLibraryCreateFailed": "Knjižnice ni bilo mogoče ustvariti",
|
||||||
|
"ToastLibraryCreateSuccess": "Knjižnica \"{0}\" je bila ustvarjena",
|
||||||
|
"ToastLibraryDeleteFailed": "Knjižnice ni bilo mogoče izbrisati",
|
||||||
|
"ToastLibraryDeleteSuccess": "Knjižnica je bila izbrisana",
|
||||||
|
"ToastLibraryScanFailedToStart": "Pregleda ni bilo mogoče začeti",
|
||||||
|
"ToastLibraryScanStarted": "Pregled knjižnice se je začel",
|
||||||
|
"ToastLibraryUpdateFailed": "Knjižnice ni bilo mogoče posodobiti",
|
||||||
|
"ToastLibraryUpdateSuccess": "Knjižnica \"{0}\" je bila posodobljena",
|
||||||
|
"ToastNameEmailRequired": "Ime in e-pošta sta obvezna",
|
||||||
|
"ToastNameRequired": "Ime je obvezno",
|
||||||
|
"ToastNewUserCreatedFailed": "Računa ni bilo mogoče ustvariti: \"{0}\"",
|
||||||
|
"ToastNewUserCreatedSuccess": "Nov račun je bil ustvarjen",
|
||||||
|
"ToastNewUserLibraryError": "Izbrati morate vsaj eno knjižnico",
|
||||||
|
"ToastNewUserPasswordError": "Mora imeti geslo, samo korenski uporabnik ima lahko prazno geslo",
|
||||||
|
"ToastNewUserTagError": "Izbrati morate vsaj eno oznako",
|
||||||
|
"ToastNewUserUsernameError": "Vnesite uporabniško ime",
|
||||||
|
"ToastNoUpdatesNecessary": "Posodobitve niso potrebne",
|
||||||
|
"ToastNotificationCreateFailed": "Obvestila ni bilo mogoče ustvariti",
|
||||||
|
"ToastNotificationDeleteFailed": "Brisanje obvestila ni uspelo",
|
||||||
|
"ToastNotificationFailedMaximum": "Največje število neuspelih poskusov mora biti >= 0",
|
||||||
|
"ToastNotificationQueueMaximum": "Največja čakalna vrsta obvestil mora biti >= 0",
|
||||||
|
"ToastNotificationSettingsUpdateFailed": "Nastavitev obvestil ni bilo mogoče posodobiti",
|
||||||
|
"ToastNotificationSettingsUpdateSuccess": "Nastavitve obvestil so bile posodobljene",
|
||||||
|
"ToastNotificationTestTriggerFailed": "Sprožitev testnega obvestila ni uspela",
|
||||||
|
"ToastNotificationTestTriggerSuccess": "Sproženo testno obvestilo",
|
||||||
|
"ToastNotificationUpdateFailed": "Obvestila ni bilo mogoče posodobiti",
|
||||||
|
"ToastNotificationUpdateSuccess": "Obvestilo posodobljeno",
|
||||||
|
"ToastPlaylistCreateFailed": "Seznama predvajanja ni bilo mogoče ustvariti",
|
||||||
|
"ToastPlaylistCreateSuccess": "Seznam predvajanja je bil ustvarjen",
|
||||||
|
"ToastPlaylistRemoveSuccess": "Seznam predvajanja odstranjen",
|
||||||
|
"ToastPlaylistUpdateFailed": "Seznama predvajanja ni bilo mogoče posodobiti",
|
||||||
|
"ToastPlaylistUpdateSuccess": "Seznam predvajanja je bil posodobljen",
|
||||||
|
"ToastPodcastCreateFailed": "Podcasta ni bilo mogoče ustvariti",
|
||||||
|
"ToastPodcastCreateSuccess": "Podcast je bil uspešno ustvarjen",
|
||||||
|
"ToastPodcastGetFeedFailed": "Vira podcasta ni bilo mogoče pridobiti",
|
||||||
|
"ToastPodcastNoEpisodesInFeed": "V viru RSS ni bilo mogoče najti nobene epizode",
|
||||||
|
"ToastPodcastNoRssFeed": "Podcast nima vira RSS",
|
||||||
|
"ToastProviderCreatedFailed": "Ponudnika ni bilo mogoče dodati",
|
||||||
|
"ToastProviderCreatedSuccess": "Dodan je bil nov ponudnik",
|
||||||
|
"ToastProviderNameAndUrlRequired": "Obvezen podatek sta ime in URL",
|
||||||
|
"ToastProviderRemoveSuccess": "Ponudnik je bil odstranjen",
|
||||||
|
"ToastRSSFeedCloseFailed": "Vira RSS ni bilo mogoče zapreti",
|
||||||
|
"ToastRSSFeedCloseSuccess": "Vir RSS je bil zaprt",
|
||||||
|
"ToastRemoveFailed": "Odstranitev ni uspela",
|
||||||
|
"ToastRemoveItemFromCollectionFailed": "Elementa ni bilo mogoče odstraniti iz zbirke",
|
||||||
|
"ToastRemoveItemFromCollectionSuccess": "Element je bil odstranjen iz zbirke",
|
||||||
|
"ToastRemoveItemsWithIssuesFailed": "Elementov knjižnice s težavami ni bilo mogoče odstraniti",
|
||||||
|
"ToastRemoveItemsWithIssuesSuccess": "Odstranjeni so bili elementi knjižnice s težavami",
|
||||||
|
"ToastRenameFailed": "Preimenovanje ni uspelo",
|
||||||
|
"ToastRescanFailed": "Ponovni pregled ni uspel za {0}",
|
||||||
|
"ToastRescanRemoved": "Ponovni pregled celotnega elementa je bil odstranjen",
|
||||||
|
"ToastRescanUpToDate": "Ponovni pregled celotnega elementa je bil ažuren",
|
||||||
|
"ToastRescanUpdated": "Ponovni pregled celotnega elementa je bil posodobljen",
|
||||||
|
"ToastScanFailed": "Pregled elementa knjižnice ni uspel",
|
||||||
|
"ToastSelectAtLeastOneUser": "Izberite vsaj enega uporabnika",
|
||||||
|
"ToastSendEbookToDeviceFailed": "E-knjige ni bilo mogoče poslati v napravo",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-knjiga je bila poslana v napravo \"{0}\"",
|
||||||
|
"ToastSeriesUpdateFailed": "Posodobitev serije ni uspela",
|
||||||
|
"ToastSeriesUpdateSuccess": "Uspešna posodobitev serije",
|
||||||
|
"ToastServerSettingsUpdateFailed": "Nastavitev strežnika ni bilo mogoče posodobiti",
|
||||||
|
"ToastServerSettingsUpdateSuccess": "Nastavitve strežnika so bile posodobljene",
|
||||||
|
"ToastSessionCloseFailed": "Seje ni bilo mogoče zapreti",
|
||||||
|
"ToastSessionDeleteFailed": "Brisanje seje ni uspelo",
|
||||||
|
"ToastSessionDeleteSuccess": "Seja je bila izbrisana",
|
||||||
|
"ToastSlugMustChange": "Slug vsebuje neveljavne znake",
|
||||||
|
"ToastSlugRequired": "Slug je obvezen podatek",
|
||||||
|
"ToastSocketConnected": "Omrežna povezava je priklopljena",
|
||||||
|
"ToastSocketDisconnected": "Omrežna povezava je odklopljena",
|
||||||
|
"ToastSocketFailedToConnect": "Omrežna povezava ni uspela vzpostaviti priklopa",
|
||||||
|
"ToastSortingPrefixesEmptyError": "Imeti mora vsaj 1 predpono za razvrščanje",
|
||||||
|
"ToastSortingPrefixesUpdateFailed": "Posodobitev predpon za razvrščanje ni uspela",
|
||||||
|
"ToastSortingPrefixesUpdateSuccess": "Predpone za razvrščanje so bile posodobljene ({0} elementov)",
|
||||||
|
"ToastTitleRequired": "Naslov je obvezen",
|
||||||
|
"ToastUnknownError": "Neznana napaka",
|
||||||
|
"ToastUnlinkOpenIdFailed": "Prekinitev povezave uporabnika z OpenID ni uspela",
|
||||||
|
"ToastUnlinkOpenIdSuccess": "Uporabnik je prekinil povezavo z OpenID",
|
||||||
|
"ToastUserDeleteFailed": "Brisanje uporabnika ni uspelo",
|
||||||
|
"ToastUserDeleteSuccess": "Uporabnik je bil izbrisan",
|
||||||
|
"ToastUserPasswordChangeSuccess": "Geslo je bilo uspešno spremenjeno",
|
||||||
|
"ToastUserPasswordMismatch": "Gesli se ne ujemata",
|
||||||
|
"ToastUserPasswordMustChange": "Novo geslo se ne sme ujemati s starim geslom",
|
||||||
|
"ToastUserRootRequireName": "Vnesti morate korensko uporabniško ime"
|
||||||
|
}
|
||||||
+121
-7
@@ -19,6 +19,7 @@
|
|||||||
"ButtonChooseFiles": "选择文件",
|
"ButtonChooseFiles": "选择文件",
|
||||||
"ButtonClearFilter": "清除过滤器",
|
"ButtonClearFilter": "清除过滤器",
|
||||||
"ButtonCloseFeed": "关闭源",
|
"ButtonCloseFeed": "关闭源",
|
||||||
|
"ButtonCloseSession": "关闭开放会话",
|
||||||
"ButtonCollections": "收藏",
|
"ButtonCollections": "收藏",
|
||||||
"ButtonConfigureScanner": "配置扫描",
|
"ButtonConfigureScanner": "配置扫描",
|
||||||
"ButtonCreate": "创建",
|
"ButtonCreate": "创建",
|
||||||
@@ -28,6 +29,9 @@
|
|||||||
"ButtonEdit": "编辑",
|
"ButtonEdit": "编辑",
|
||||||
"ButtonEditChapters": "编辑章节",
|
"ButtonEditChapters": "编辑章节",
|
||||||
"ButtonEditPodcast": "编辑播客",
|
"ButtonEditPodcast": "编辑播客",
|
||||||
|
"ButtonEnable": "启用",
|
||||||
|
"ButtonFireAndFail": "故障和失败",
|
||||||
|
"ButtonFireOnTest": "测试事件触发",
|
||||||
"ButtonForceReScan": "强制重新扫描",
|
"ButtonForceReScan": "强制重新扫描",
|
||||||
"ButtonFullPath": "完整路径",
|
"ButtonFullPath": "完整路径",
|
||||||
"ButtonHide": "隐藏",
|
"ButtonHide": "隐藏",
|
||||||
@@ -46,6 +50,7 @@
|
|||||||
"ButtonNevermind": "没有关系",
|
"ButtonNevermind": "没有关系",
|
||||||
"ButtonNext": "下一个",
|
"ButtonNext": "下一个",
|
||||||
"ButtonNextChapter": "下一章节",
|
"ButtonNextChapter": "下一章节",
|
||||||
|
"ButtonNextItemInQueue": "队列中的下一个项目",
|
||||||
"ButtonOk": "确定",
|
"ButtonOk": "确定",
|
||||||
"ButtonOpenFeed": "打开源",
|
"ButtonOpenFeed": "打开源",
|
||||||
"ButtonOpenManager": "打开管理器",
|
"ButtonOpenManager": "打开管理器",
|
||||||
@@ -55,6 +60,7 @@
|
|||||||
"ButtonPlaylists": "播放列表",
|
"ButtonPlaylists": "播放列表",
|
||||||
"ButtonPrevious": "上一个",
|
"ButtonPrevious": "上一个",
|
||||||
"ButtonPreviousChapter": "上一章节",
|
"ButtonPreviousChapter": "上一章节",
|
||||||
|
"ButtonProbeAudioFile": "探测音频文件",
|
||||||
"ButtonPurgeAllCache": "清理所有缓存",
|
"ButtonPurgeAllCache": "清理所有缓存",
|
||||||
"ButtonPurgeItemsCache": "清理项目缓存",
|
"ButtonPurgeItemsCache": "清理项目缓存",
|
||||||
"ButtonQueueAddItem": "添加到队列",
|
"ButtonQueueAddItem": "添加到队列",
|
||||||
@@ -92,6 +98,7 @@
|
|||||||
"ButtonStats": "统计数据",
|
"ButtonStats": "统计数据",
|
||||||
"ButtonSubmit": "提交",
|
"ButtonSubmit": "提交",
|
||||||
"ButtonTest": "测试",
|
"ButtonTest": "测试",
|
||||||
|
"ButtonUnlinkOpenId": "取消 OpenID 链接",
|
||||||
"ButtonUpload": "上传",
|
"ButtonUpload": "上传",
|
||||||
"ButtonUploadBackup": "上传备份",
|
"ButtonUploadBackup": "上传备份",
|
||||||
"ButtonUploadCover": "上传封面",
|
"ButtonUploadCover": "上传封面",
|
||||||
@@ -104,6 +111,7 @@
|
|||||||
"ErrorUploadFetchMetadataNoResults": "无法获取元数据 - 尝试更新标题和/或作者",
|
"ErrorUploadFetchMetadataNoResults": "无法获取元数据 - 尝试更新标题和/或作者",
|
||||||
"ErrorUploadLacksTitle": "必须有标题",
|
"ErrorUploadLacksTitle": "必须有标题",
|
||||||
"HeaderAccount": "帐户",
|
"HeaderAccount": "帐户",
|
||||||
|
"HeaderAddCustomMetadataProvider": "添加自定义元数据提供商",
|
||||||
"HeaderAdvanced": "高级",
|
"HeaderAdvanced": "高级",
|
||||||
"HeaderAppriseNotificationSettings": "测试通知设置",
|
"HeaderAppriseNotificationSettings": "测试通知设置",
|
||||||
"HeaderAudioTracks": "音轨",
|
"HeaderAudioTracks": "音轨",
|
||||||
@@ -118,7 +126,7 @@
|
|||||||
"HeaderCover": "封面",
|
"HeaderCover": "封面",
|
||||||
"HeaderCurrentDownloads": "当前下载",
|
"HeaderCurrentDownloads": "当前下载",
|
||||||
"HeaderCustomMessageOnLogin": "登录时的自定义消息",
|
"HeaderCustomMessageOnLogin": "登录时的自定义消息",
|
||||||
"HeaderCustomMetadataProviders": "自定义元数据提供者",
|
"HeaderCustomMetadataProviders": "自定义元数据提供商",
|
||||||
"HeaderDetails": "详情",
|
"HeaderDetails": "详情",
|
||||||
"HeaderDownloadQueue": "下载队列",
|
"HeaderDownloadQueue": "下载队列",
|
||||||
"HeaderEbookFiles": "电子书文件",
|
"HeaderEbookFiles": "电子书文件",
|
||||||
@@ -149,6 +157,8 @@
|
|||||||
"HeaderMetadataToEmbed": "嵌入元数据",
|
"HeaderMetadataToEmbed": "嵌入元数据",
|
||||||
"HeaderNewAccount": "新建帐户",
|
"HeaderNewAccount": "新建帐户",
|
||||||
"HeaderNewLibrary": "新建媒体库",
|
"HeaderNewLibrary": "新建媒体库",
|
||||||
|
"HeaderNotificationCreate": "创建通知",
|
||||||
|
"HeaderNotificationUpdate": "更新通知",
|
||||||
"HeaderNotifications": "通知",
|
"HeaderNotifications": "通知",
|
||||||
"HeaderOpenIDConnectAuthentication": "OpenID 连接身份验证",
|
"HeaderOpenIDConnectAuthentication": "OpenID 连接身份验证",
|
||||||
"HeaderOpenRSSFeed": "打开 RSS 源",
|
"HeaderOpenRSSFeed": "打开 RSS 源",
|
||||||
@@ -206,6 +216,7 @@
|
|||||||
"LabelAddToPlaylist": "添加到播放列表",
|
"LabelAddToPlaylist": "添加到播放列表",
|
||||||
"LabelAddToPlaylistBatch": "添加 {0} 个项目到播放列表",
|
"LabelAddToPlaylistBatch": "添加 {0} 个项目到播放列表",
|
||||||
"LabelAddedAt": "添加于",
|
"LabelAddedAt": "添加于",
|
||||||
|
"LabelAddedDate": "添加 {0}",
|
||||||
"LabelAdminUsersOnly": "仅限管理员用户",
|
"LabelAdminUsersOnly": "仅限管理员用户",
|
||||||
"LabelAll": "全部",
|
"LabelAll": "全部",
|
||||||
"LabelAllUsers": "所有用户",
|
"LabelAllUsers": "所有用户",
|
||||||
@@ -235,6 +246,7 @@
|
|||||||
"LabelBitrate": "比特率",
|
"LabelBitrate": "比特率",
|
||||||
"LabelBooks": "图书",
|
"LabelBooks": "图书",
|
||||||
"LabelButtonText": "按钮文本",
|
"LabelButtonText": "按钮文本",
|
||||||
|
"LabelByAuthor": "由 {0}",
|
||||||
"LabelChangePassword": "修改密码",
|
"LabelChangePassword": "修改密码",
|
||||||
"LabelChannels": "声道",
|
"LabelChannels": "声道",
|
||||||
"LabelChapterTitle": "章节标题",
|
"LabelChapterTitle": "章节标题",
|
||||||
@@ -244,6 +256,7 @@
|
|||||||
"LabelClosePlayer": "关闭播放器",
|
"LabelClosePlayer": "关闭播放器",
|
||||||
"LabelCodec": "编解码",
|
"LabelCodec": "编解码",
|
||||||
"LabelCollapseSeries": "折叠系列",
|
"LabelCollapseSeries": "折叠系列",
|
||||||
|
"LabelCollapseSubSeries": "折叠子系列",
|
||||||
"LabelCollection": "收藏",
|
"LabelCollection": "收藏",
|
||||||
"LabelCollections": "收藏",
|
"LabelCollections": "收藏",
|
||||||
"LabelComplete": "已完成",
|
"LabelComplete": "已完成",
|
||||||
@@ -294,8 +307,10 @@
|
|||||||
"LabelEpisode": "剧集",
|
"LabelEpisode": "剧集",
|
||||||
"LabelEpisodeTitle": "剧集标题",
|
"LabelEpisodeTitle": "剧集标题",
|
||||||
"LabelEpisodeType": "剧集类型",
|
"LabelEpisodeType": "剧集类型",
|
||||||
|
"LabelEpisodes": "剧集",
|
||||||
"LabelExample": "示例",
|
"LabelExample": "示例",
|
||||||
"LabelExpandSeries": "展开系列",
|
"LabelExpandSeries": "展开系列",
|
||||||
|
"LabelExpandSubSeries": "展开子系列",
|
||||||
"LabelExplicit": "信息准确",
|
"LabelExplicit": "信息准确",
|
||||||
"LabelExplicitChecked": "明确(已选中)",
|
"LabelExplicitChecked": "明确(已选中)",
|
||||||
"LabelExplicitUnchecked": "不明确 (未选中)",
|
"LabelExplicitUnchecked": "不明确 (未选中)",
|
||||||
@@ -304,7 +319,9 @@
|
|||||||
"LabelFetchingMetadata": "正在获取元数据",
|
"LabelFetchingMetadata": "正在获取元数据",
|
||||||
"LabelFile": "文件",
|
"LabelFile": "文件",
|
||||||
"LabelFileBirthtime": "文件创建时间",
|
"LabelFileBirthtime": "文件创建时间",
|
||||||
|
"LabelFileBornDate": "生于 {0}",
|
||||||
"LabelFileModified": "文件修改时间",
|
"LabelFileModified": "文件修改时间",
|
||||||
|
"LabelFileModifiedDate": "已修改 {0}",
|
||||||
"LabelFilename": "文件名",
|
"LabelFilename": "文件名",
|
||||||
"LabelFilterByUser": "按用户筛选",
|
"LabelFilterByUser": "按用户筛选",
|
||||||
"LabelFindEpisodes": "查找剧集",
|
"LabelFindEpisodes": "查找剧集",
|
||||||
@@ -360,6 +377,7 @@
|
|||||||
"LabelLess": "较少",
|
"LabelLess": "较少",
|
||||||
"LabelLibrariesAccessibleToUser": "用户可访问的媒体库",
|
"LabelLibrariesAccessibleToUser": "用户可访问的媒体库",
|
||||||
"LabelLibrary": "媒体库",
|
"LabelLibrary": "媒体库",
|
||||||
|
"LabelLibraryFilterSublistEmpty": "没有 {0}",
|
||||||
"LabelLibraryItem": "媒体库项目",
|
"LabelLibraryItem": "媒体库项目",
|
||||||
"LabelLibraryName": "媒体库名称",
|
"LabelLibraryName": "媒体库名称",
|
||||||
"LabelLimit": "限制",
|
"LabelLimit": "限制",
|
||||||
@@ -371,13 +389,13 @@
|
|||||||
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
||||||
"LabelLowestPriority": "最低优先级",
|
"LabelLowestPriority": "最低优先级",
|
||||||
"LabelMatchExistingUsersBy": "匹配现有用户",
|
"LabelMatchExistingUsersBy": "匹配现有用户",
|
||||||
"LabelMatchExistingUsersByDescription": "用于连接现有用户. 连接后, 用户将通过SSO提供商提供的唯一 id 进行匹配",
|
"LabelMatchExistingUsersByDescription": "用于连接现有用户. 连接后, 用户将通过 SSO 提供商提供的唯一 id 进行匹配",
|
||||||
"LabelMediaPlayer": "媒体播放器",
|
"LabelMediaPlayer": "媒体播放器",
|
||||||
"LabelMediaType": "媒体类型",
|
"LabelMediaType": "媒体类型",
|
||||||
"LabelMetaTag": "元数据标签",
|
"LabelMetaTag": "元数据标签",
|
||||||
"LabelMetaTags": "元标签",
|
"LabelMetaTags": "元标签",
|
||||||
"LabelMetadataOrderOfPrecedenceDescription": "较高优先级的元数据源将覆盖较低优先级的元数据源",
|
"LabelMetadataOrderOfPrecedenceDescription": "较高优先级的元数据源将覆盖较低优先级的元数据源",
|
||||||
"LabelMetadataProvider": "元数据提供者",
|
"LabelMetadataProvider": "元数据提供商",
|
||||||
"LabelMinute": "分钟",
|
"LabelMinute": "分钟",
|
||||||
"LabelMinutes": "分钟",
|
"LabelMinutes": "分钟",
|
||||||
"LabelMissing": "丢失",
|
"LabelMissing": "丢失",
|
||||||
@@ -396,7 +414,7 @@
|
|||||||
"LabelNewestEpisodes": "最新剧集",
|
"LabelNewestEpisodes": "最新剧集",
|
||||||
"LabelNextBackupDate": "下次备份日期",
|
"LabelNextBackupDate": "下次备份日期",
|
||||||
"LabelNextScheduledRun": "下次任务运行",
|
"LabelNextScheduledRun": "下次任务运行",
|
||||||
"LabelNoCustomMetadataProviders": "没有自定义元数据提供程序",
|
"LabelNoCustomMetadataProviders": "没有自定义元数据提供商",
|
||||||
"LabelNoEpisodesSelected": "未选择任何剧集",
|
"LabelNoEpisodesSelected": "未选择任何剧集",
|
||||||
"LabelNotFinished": "未听完",
|
"LabelNotFinished": "未听完",
|
||||||
"LabelNotStarted": "未开始",
|
"LabelNotStarted": "未开始",
|
||||||
@@ -412,7 +430,7 @@
|
|||||||
"LabelNotificationsMaxQueueSizeHelp": "通知事件被限制为每秒触发 1 个. 如果队列处于最大大小, 则将忽略事件. 这可以防止通知垃圾邮件.",
|
"LabelNotificationsMaxQueueSizeHelp": "通知事件被限制为每秒触发 1 个. 如果队列处于最大大小, 则将忽略事件. 这可以防止通知垃圾邮件.",
|
||||||
"LabelNumberOfBooks": "图书数量",
|
"LabelNumberOfBooks": "图书数量",
|
||||||
"LabelNumberOfEpisodes": "# 集",
|
"LabelNumberOfEpisodes": "# 集",
|
||||||
"LabelOpenIDAdvancedPermsClaimDescription": "OpenID 声明的名称, 该声明包含应用程序内用户操作的高级权限, 该权限将应用于非管理员角色(<b>如果已配置</b>). 如果响应中缺少声明, 获取 ABS 的权限将被拒绝. 如果缺少单个选项, 它将被视为 <code>禁用</code>. 确保身份提供者的声明与预期结构匹配:",
|
"LabelOpenIDAdvancedPermsClaimDescription": "OpenID 声明的名称, 该声明包含应用程序内用户操作的高级权限, 该权限将应用于非管理员角色(<b>如果已配置</b>). 如果响应中缺少声明, 获取 ABS 的权限将被拒绝. 如果缺少单个选项, 它将被视为 <code>禁用</code>. 确保身份提供商的声明与预期结构匹配:",
|
||||||
"LabelOpenIDClaims": "将以下选项留空以禁用高级组和权限分配, 然后自动分配 'User' 组.",
|
"LabelOpenIDClaims": "将以下选项留空以禁用高级组和权限分配, 然后自动分配 'User' 组.",
|
||||||
"LabelOpenIDGroupClaimDescription": "OpenID 声明的名称, 该声明包含用户组的列表. 通常称为<code>组</code><b>如果已配置</b>, 应用程序将根据用户的组成员身份自动分配角色, 前提是这些组在声明中以不区分大小写的方式命名为 'Admin', 'User' 或 'Guest'. 声明应包含一个列表, 如果用户属于多个组, 则应用程序将分配与最高访问级别相对应的角色. 如果没有组匹配, 访问将被拒绝.",
|
"LabelOpenIDGroupClaimDescription": "OpenID 声明的名称, 该声明包含用户组的列表. 通常称为<code>组</code><b>如果已配置</b>, 应用程序将根据用户的组成员身份自动分配角色, 前提是这些组在声明中以不区分大小写的方式命名为 'Admin', 'User' 或 'Guest'. 声明应包含一个列表, 如果用户属于多个组, 则应用程序将分配与最高访问级别相对应的角色. 如果没有组匹配, 访问将被拒绝.",
|
||||||
"LabelOpenRSSFeed": "打开 RSS 源",
|
"LabelOpenRSSFeed": "打开 RSS 源",
|
||||||
@@ -430,6 +448,7 @@
|
|||||||
"LabelPersonalYearReview": "你的年度回顾 ({0})",
|
"LabelPersonalYearReview": "你的年度回顾 ({0})",
|
||||||
"LabelPhotoPathURL": "图片路径或 URL",
|
"LabelPhotoPathURL": "图片路径或 URL",
|
||||||
"LabelPlayMethod": "播放方法",
|
"LabelPlayMethod": "播放方法",
|
||||||
|
"LabelPlayerChapterNumberMarker": "{0} 于 {1}",
|
||||||
"LabelPlaylists": "播放列表",
|
"LabelPlaylists": "播放列表",
|
||||||
"LabelPodcast": "播客",
|
"LabelPodcast": "播客",
|
||||||
"LabelPodcastSearchRegion": "播客搜索地区",
|
"LabelPodcastSearchRegion": "播客搜索地区",
|
||||||
@@ -440,9 +459,11 @@
|
|||||||
"LabelPreventIndexing": "防止 iTunes 和 Google 播客目录对你的源进行索引",
|
"LabelPreventIndexing": "防止 iTunes 和 Google 播客目录对你的源进行索引",
|
||||||
"LabelPrimaryEbook": "主电子书",
|
"LabelPrimaryEbook": "主电子书",
|
||||||
"LabelProgress": "进度",
|
"LabelProgress": "进度",
|
||||||
"LabelProvider": "供应商",
|
"LabelProvider": "提供商",
|
||||||
|
"LabelProviderAuthorizationValue": "授权标头值",
|
||||||
"LabelPubDate": "出版日期",
|
"LabelPubDate": "出版日期",
|
||||||
"LabelPublishYear": "发布年份",
|
"LabelPublishYear": "发布年份",
|
||||||
|
"LabelPublishedDate": "已发布 {0}",
|
||||||
"LabelPublisher": "出版商",
|
"LabelPublisher": "出版商",
|
||||||
"LabelPublishers": "出版商",
|
"LabelPublishers": "出版商",
|
||||||
"LabelRSSFeedCustomOwnerEmail": "自定义所有者电子邮件",
|
"LabelRSSFeedCustomOwnerEmail": "自定义所有者电子邮件",
|
||||||
@@ -526,6 +547,7 @@
|
|||||||
"LabelShowSubtitles": "显示标题",
|
"LabelShowSubtitles": "显示标题",
|
||||||
"LabelSize": "文件大小",
|
"LabelSize": "文件大小",
|
||||||
"LabelSleepTimer": "睡眠定时",
|
"LabelSleepTimer": "睡眠定时",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "开始",
|
"LabelStart": "开始",
|
||||||
"LabelStartTime": "开始时间",
|
"LabelStartTime": "开始时间",
|
||||||
"LabelStarted": "开始于",
|
"LabelStarted": "开始于",
|
||||||
@@ -587,6 +609,7 @@
|
|||||||
"LabelUnabridged": "未删节",
|
"LabelUnabridged": "未删节",
|
||||||
"LabelUndo": "撤消",
|
"LabelUndo": "撤消",
|
||||||
"LabelUnknown": "未知",
|
"LabelUnknown": "未知",
|
||||||
|
"LabelUnknownPublishDate": "未知发布日期",
|
||||||
"LabelUpdateCover": "更新封面",
|
"LabelUpdateCover": "更新封面",
|
||||||
"LabelUpdateCoverHelp": "找到匹配项时允许覆盖所选书籍存在的封面",
|
"LabelUpdateCoverHelp": "找到匹配项时允许覆盖所选书籍存在的封面",
|
||||||
"LabelUpdateDetails": "更新详细信息",
|
"LabelUpdateDetails": "更新详细信息",
|
||||||
@@ -635,16 +658,22 @@
|
|||||||
"MessageCheckingCron": "检查计划任务...",
|
"MessageCheckingCron": "检查计划任务...",
|
||||||
"MessageConfirmCloseFeed": "你确定要关闭此订阅源吗?",
|
"MessageConfirmCloseFeed": "你确定要关闭此订阅源吗?",
|
||||||
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
||||||
|
"MessageConfirmDeleteDevice": "您确定要删除电子阅读器设备 \"{0}\" 吗?",
|
||||||
"MessageConfirmDeleteFile": "这将从文件系统中删除该文件. 你确定吗?",
|
"MessageConfirmDeleteFile": "这将从文件系统中删除该文件. 你确定吗?",
|
||||||
"MessageConfirmDeleteLibrary": "你确定要永久删除媒体库 \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "你确定要永久删除媒体库 \"{0}\"?",
|
||||||
"MessageConfirmDeleteLibraryItem": "这将从数据库和文件系统中删除库项目. 你确定吗?",
|
"MessageConfirmDeleteLibraryItem": "这将从数据库和文件系统中删除库项目. 你确定吗?",
|
||||||
"MessageConfirmDeleteLibraryItems": "这将从数据库和文件系统中删除 {0} 个库项目. 你确定吗?",
|
"MessageConfirmDeleteLibraryItems": "这将从数据库和文件系统中删除 {0} 个库项目. 你确定吗?",
|
||||||
|
"MessageConfirmDeleteMetadataProvider": "是否确实要删除自定义元数据提供商 \"{0}\" ?",
|
||||||
|
"MessageConfirmDeleteNotification": "您确定要删除此通知吗?",
|
||||||
"MessageConfirmDeleteSession": "你确定要删除此会话吗?",
|
"MessageConfirmDeleteSession": "你确定要删除此会话吗?",
|
||||||
"MessageConfirmForceReScan": "你确定要强制重新扫描吗?",
|
"MessageConfirmForceReScan": "你确定要强制重新扫描吗?",
|
||||||
"MessageConfirmMarkAllEpisodesFinished": "你确定要将所有剧集都标记为已完成吗?",
|
"MessageConfirmMarkAllEpisodesFinished": "你确定要将所有剧集都标记为已完成吗?",
|
||||||
"MessageConfirmMarkAllEpisodesNotFinished": "你确定要将所有剧集都标记为未完成吗?",
|
"MessageConfirmMarkAllEpisodesNotFinished": "你确定要将所有剧集都标记为未完成吗?",
|
||||||
|
"MessageConfirmMarkItemFinished": "您确定要将 \"{0}\" 标记为已完成吗?",
|
||||||
|
"MessageConfirmMarkItemNotFinished": "您确定要将 \"{0}\" 标记为未完成吗?",
|
||||||
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
|
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
|
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
|
||||||
|
"MessageConfirmNotificationTestTrigger": "使用测试数据触发此通知吗?",
|
||||||
"MessageConfirmPurgeCache": "清除缓存将删除 <code>/metadata/cache</code> 整个目录. <br /><br />你确定要删除缓存目录吗?",
|
"MessageConfirmPurgeCache": "清除缓存将删除 <code>/metadata/cache</code> 整个目录. <br /><br />你确定要删除缓存目录吗?",
|
||||||
"MessageConfirmPurgeItemsCache": "清除项目缓存将删除 <code>/metadata/cache/items</code> 整个目录.<br />你确定吗?",
|
"MessageConfirmPurgeItemsCache": "清除项目缓存将删除 <code>/metadata/cache/items</code> 整个目录.<br />你确定吗?",
|
||||||
"MessageConfirmQuickEmbed": "警告! 快速嵌入不会备份你的音频文件. 确保你有音频文件的备份. <br><br>你是否想继续吗?",
|
"MessageConfirmQuickEmbed": "警告! 快速嵌入不会备份你的音频文件. 确保你有音频文件的备份. <br><br>你是否想继续吗?",
|
||||||
@@ -663,7 +692,9 @@
|
|||||||
"MessageConfirmRenameTag": "你确定要将所有项目标签 \"{0}\" 重命名到 \"{1}\"?",
|
"MessageConfirmRenameTag": "你确定要将所有项目标签 \"{0}\" 重命名到 \"{1}\"?",
|
||||||
"MessageConfirmRenameTagMergeNote": "注意: 该标签已经存在, 因此它们将被合并.",
|
"MessageConfirmRenameTagMergeNote": "注意: 该标签已经存在, 因此它们将被合并.",
|
||||||
"MessageConfirmRenameTagWarning": "警告! 已经存在有大小写不同的类似标签 \"{0}\".",
|
"MessageConfirmRenameTagWarning": "警告! 已经存在有大小写不同的类似标签 \"{0}\".",
|
||||||
|
"MessageConfirmResetProgress": "你确定要重置进度吗?",
|
||||||
"MessageConfirmSendEbookToDevice": "你确定要发送 {0} 电子书 \"{1}\" 到设备 \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "你确定要发送 {0} 电子书 \"{1}\" 到设备 \"{2}\"?",
|
||||||
|
"MessageConfirmUnlinkOpenId": "您确定要取消该用户与 OpenID 的链接吗?",
|
||||||
"MessageDownloadingEpisode": "正在下载剧集",
|
"MessageDownloadingEpisode": "正在下载剧集",
|
||||||
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
||||||
"MessageEmbedFailed": "嵌入失败!",
|
"MessageEmbedFailed": "嵌入失败!",
|
||||||
@@ -698,6 +729,7 @@
|
|||||||
"MessageNoCollections": "没有收藏",
|
"MessageNoCollections": "没有收藏",
|
||||||
"MessageNoCoversFound": "没有找到封面",
|
"MessageNoCoversFound": "没有找到封面",
|
||||||
"MessageNoDescription": "没有描述",
|
"MessageNoDescription": "没有描述",
|
||||||
|
"MessageNoDevices": "没有设备",
|
||||||
"MessageNoDownloadsInProgress": "当前没有正在进行的下载",
|
"MessageNoDownloadsInProgress": "当前没有正在进行的下载",
|
||||||
"MessageNoDownloadsQueued": "下载队列无任务",
|
"MessageNoDownloadsQueued": "下载队列无任务",
|
||||||
"MessageNoEpisodeMatchesFound": "没有找到任何剧集匹配项",
|
"MessageNoEpisodeMatchesFound": "没有找到任何剧集匹配项",
|
||||||
@@ -725,6 +757,7 @@
|
|||||||
"MessagePauseChapter": "暂停章节播放",
|
"MessagePauseChapter": "暂停章节播放",
|
||||||
"MessagePlayChapter": "开始章节播放",
|
"MessagePlayChapter": "开始章节播放",
|
||||||
"MessagePlaylistCreateFromCollection": "从收藏中创建播放列表",
|
"MessagePlaylistCreateFromCollection": "从收藏中创建播放列表",
|
||||||
|
"MessagePleaseWait": "请稍等...",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "播客没有可用于匹配 RSS 源的 url",
|
"MessagePodcastHasNoRSSFeedForMatching": "播客没有可用于匹配 RSS 源的 url",
|
||||||
"MessageQuickMatchDescription": "使用来自 '{0}' 的第一个匹配结果填充空白详细信息和封面. 除非启用 '首选匹配元数据' 服务器设置, 否则不会覆盖详细信息.",
|
"MessageQuickMatchDescription": "使用来自 '{0}' 的第一个匹配结果填充空白详细信息和封面. 除非启用 '首选匹配元数据' 服务器设置, 否则不会覆盖详细信息.",
|
||||||
"MessageRemoveChapter": "移除章节",
|
"MessageRemoveChapter": "移除章节",
|
||||||
@@ -785,18 +818,28 @@
|
|||||||
"StatsYearInReview": "年度回顾",
|
"StatsYearInReview": "年度回顾",
|
||||||
"ToastAccountUpdateFailed": "账户更新失败",
|
"ToastAccountUpdateFailed": "账户更新失败",
|
||||||
"ToastAccountUpdateSuccess": "帐户已更新",
|
"ToastAccountUpdateSuccess": "帐户已更新",
|
||||||
|
"ToastAppriseUrlRequired": "必须输入 Apprise URL",
|
||||||
"ToastAuthorImageRemoveSuccess": "作者图像已删除",
|
"ToastAuthorImageRemoveSuccess": "作者图像已删除",
|
||||||
|
"ToastAuthorNotFound": "未找到作者 \"{0}\"",
|
||||||
|
"ToastAuthorRemoveSuccess": "作者已删除",
|
||||||
|
"ToastAuthorSearchNotFound": "未找到作者",
|
||||||
"ToastAuthorUpdateFailed": "作者更新失败",
|
"ToastAuthorUpdateFailed": "作者更新失败",
|
||||||
"ToastAuthorUpdateMerged": "作者已合并",
|
"ToastAuthorUpdateMerged": "作者已合并",
|
||||||
"ToastAuthorUpdateSuccess": "作者已更新",
|
"ToastAuthorUpdateSuccess": "作者已更新",
|
||||||
"ToastAuthorUpdateSuccessNoImageFound": "作者已更新 (未找到图像)",
|
"ToastAuthorUpdateSuccessNoImageFound": "作者已更新 (未找到图像)",
|
||||||
|
"ToastBackupAppliedSuccess": "已应用备份",
|
||||||
"ToastBackupCreateFailed": "备份创建失败",
|
"ToastBackupCreateFailed": "备份创建失败",
|
||||||
"ToastBackupCreateSuccess": "备份已创建",
|
"ToastBackupCreateSuccess": "备份已创建",
|
||||||
"ToastBackupDeleteFailed": "备份删除失败",
|
"ToastBackupDeleteFailed": "备份删除失败",
|
||||||
"ToastBackupDeleteSuccess": "备份已删除",
|
"ToastBackupDeleteSuccess": "备份已删除",
|
||||||
|
"ToastBackupInvalidMaxKeep": "要保留的备份数无效",
|
||||||
|
"ToastBackupInvalidMaxSize": "最大备份大小无效",
|
||||||
|
"ToastBackupPathUpdateFailed": "无法更新备份路径",
|
||||||
"ToastBackupRestoreFailed": "备份还原失败",
|
"ToastBackupRestoreFailed": "备份还原失败",
|
||||||
"ToastBackupUploadFailed": "上传备份失败",
|
"ToastBackupUploadFailed": "上传备份失败",
|
||||||
"ToastBackupUploadSuccess": "备份已上传",
|
"ToastBackupUploadSuccess": "备份已上传",
|
||||||
|
"ToastBatchDeleteFailed": "批量删除失败",
|
||||||
|
"ToastBatchDeleteSuccess": "批量删除成功",
|
||||||
"ToastBatchUpdateFailed": "批量更新失败",
|
"ToastBatchUpdateFailed": "批量更新失败",
|
||||||
"ToastBatchUpdateSuccess": "批量更新成功",
|
"ToastBatchUpdateSuccess": "批量更新成功",
|
||||||
"ToastBookmarkCreateFailed": "创建书签失败",
|
"ToastBookmarkCreateFailed": "创建书签失败",
|
||||||
@@ -808,22 +851,46 @@
|
|||||||
"ToastCachePurgeSuccess": "缓存清除成功",
|
"ToastCachePurgeSuccess": "缓存清除成功",
|
||||||
"ToastChaptersHaveErrors": "章节有错误",
|
"ToastChaptersHaveErrors": "章节有错误",
|
||||||
"ToastChaptersMustHaveTitles": "章节必须有标题",
|
"ToastChaptersMustHaveTitles": "章节必须有标题",
|
||||||
|
"ToastChaptersRemoved": "已删除章节",
|
||||||
|
"ToastCollectionItemsAddFailed": "项目添加到收藏夹失败",
|
||||||
|
"ToastCollectionItemsAddSuccess": "项目添加到收藏夹成功",
|
||||||
"ToastCollectionItemsRemoveSuccess": "项目从收藏夹移除",
|
"ToastCollectionItemsRemoveSuccess": "项目从收藏夹移除",
|
||||||
"ToastCollectionRemoveSuccess": "收藏夹已删除",
|
"ToastCollectionRemoveSuccess": "收藏夹已删除",
|
||||||
"ToastCollectionUpdateFailed": "更新收藏夹失败",
|
"ToastCollectionUpdateFailed": "更新收藏夹失败",
|
||||||
"ToastCollectionUpdateSuccess": "收藏夹已更新",
|
"ToastCollectionUpdateSuccess": "收藏夹已更新",
|
||||||
|
"ToastCoverUpdateFailed": "封面更新失败",
|
||||||
"ToastDeleteFileFailed": "删除文件失败",
|
"ToastDeleteFileFailed": "删除文件失败",
|
||||||
"ToastDeleteFileSuccess": "文件已删除",
|
"ToastDeleteFileSuccess": "文件已删除",
|
||||||
|
"ToastDeviceAddFailed": "添加设备失败",
|
||||||
|
"ToastDeviceNameAlreadyExists": "同名的电子阅读器设备已存在",
|
||||||
|
"ToastDeviceTestEmailFailed": "无法发送测试电子邮件",
|
||||||
|
"ToastDeviceTestEmailSuccess": "测试邮件已发送",
|
||||||
|
"ToastDeviceUpdateFailed": "无法更新设备",
|
||||||
|
"ToastEmailSettingsUpdateFailed": "无法更新电子邮件设置",
|
||||||
|
"ToastEmailSettingsUpdateSuccess": "电子邮件设置已更新",
|
||||||
|
"ToastEncodeCancelFailed": "取消编码失败",
|
||||||
|
"ToastEncodeCancelSucces": "编码已取消",
|
||||||
|
"ToastEpisodeDownloadQueueClearFailed": "无法清除队列",
|
||||||
|
"ToastEpisodeDownloadQueueClearSuccess": "剧集下载队列已清空",
|
||||||
"ToastErrorCannotShare": "无法在此设备上本地共享",
|
"ToastErrorCannotShare": "无法在此设备上本地共享",
|
||||||
"ToastFailedToLoadData": "加载数据失败",
|
"ToastFailedToLoadData": "加载数据失败",
|
||||||
|
"ToastFailedToShare": "分享失败",
|
||||||
|
"ToastFailedToUpdateAccount": "无法更新账户",
|
||||||
|
"ToastFailedToUpdateUser": "无法更新用户",
|
||||||
|
"ToastInvalidImageUrl": "图片网址无效",
|
||||||
|
"ToastInvalidUrl": "网址无效",
|
||||||
"ToastItemCoverUpdateFailed": "更新项目封面失败",
|
"ToastItemCoverUpdateFailed": "更新项目封面失败",
|
||||||
"ToastItemCoverUpdateSuccess": "项目封面已更新",
|
"ToastItemCoverUpdateSuccess": "项目封面已更新",
|
||||||
|
"ToastItemDeletedFailed": "删除项目失败",
|
||||||
|
"ToastItemDeletedSuccess": "已删除项目",
|
||||||
"ToastItemDetailsUpdateFailed": "更新项目详细信息失败",
|
"ToastItemDetailsUpdateFailed": "更新项目详细信息失败",
|
||||||
"ToastItemDetailsUpdateSuccess": "项目详细信息已更新",
|
"ToastItemDetailsUpdateSuccess": "项目详细信息已更新",
|
||||||
"ToastItemMarkedAsFinishedFailed": "无法标记为已听完",
|
"ToastItemMarkedAsFinishedFailed": "无法标记为已听完",
|
||||||
"ToastItemMarkedAsFinishedSuccess": "标记为已听完的项目",
|
"ToastItemMarkedAsFinishedSuccess": "标记为已听完的项目",
|
||||||
"ToastItemMarkedAsNotFinishedFailed": "无法标记为未听完",
|
"ToastItemMarkedAsNotFinishedFailed": "无法标记为未听完",
|
||||||
"ToastItemMarkedAsNotFinishedSuccess": "标记为未听完的项目",
|
"ToastItemMarkedAsNotFinishedSuccess": "标记为未听完的项目",
|
||||||
|
"ToastItemUpdateFailed": "更新项目失败",
|
||||||
|
"ToastItemUpdateSuccess": "项目已更新",
|
||||||
"ToastLibraryCreateFailed": "创建媒体库失败",
|
"ToastLibraryCreateFailed": "创建媒体库失败",
|
||||||
"ToastLibraryCreateSuccess": "媒体库 \"{0}\" 创建成功",
|
"ToastLibraryCreateSuccess": "媒体库 \"{0}\" 创建成功",
|
||||||
"ToastLibraryDeleteFailed": "删除媒体库失败",
|
"ToastLibraryDeleteFailed": "删除媒体库失败",
|
||||||
@@ -832,6 +899,25 @@
|
|||||||
"ToastLibraryScanStarted": "媒体库扫描已启动",
|
"ToastLibraryScanStarted": "媒体库扫描已启动",
|
||||||
"ToastLibraryUpdateFailed": "更新图书库失败",
|
"ToastLibraryUpdateFailed": "更新图书库失败",
|
||||||
"ToastLibraryUpdateSuccess": "媒体库 \"{0}\" 已更新",
|
"ToastLibraryUpdateSuccess": "媒体库 \"{0}\" 已更新",
|
||||||
|
"ToastNameEmailRequired": "姓名和电子邮件为必填项",
|
||||||
|
"ToastNameRequired": "姓名为必填项",
|
||||||
|
"ToastNewUserCreatedFailed": "无法创建帐户: \"{0}\"",
|
||||||
|
"ToastNewUserCreatedSuccess": "已创建新帐户",
|
||||||
|
"ToastNewUserLibraryError": "必须至少选择一个图书馆",
|
||||||
|
"ToastNewUserPasswordError": "必须有密码, 只有root用户可以有空密码",
|
||||||
|
"ToastNewUserTagError": "必须至少选择一个标签",
|
||||||
|
"ToastNewUserUsernameError": "输入用户名",
|
||||||
|
"ToastNoUpdatesNecessary": "无需更新",
|
||||||
|
"ToastNotificationCreateFailed": "无法创建通知",
|
||||||
|
"ToastNotificationDeleteFailed": "删除通知失败",
|
||||||
|
"ToastNotificationFailedMaximum": "最大失败尝试次数必须 >= 0",
|
||||||
|
"ToastNotificationQueueMaximum": "最大通知队列必须 >= 0",
|
||||||
|
"ToastNotificationSettingsUpdateFailed": "无法更新通知设置",
|
||||||
|
"ToastNotificationSettingsUpdateSuccess": "通知设置已更新",
|
||||||
|
"ToastNotificationTestTriggerFailed": "无法触发测试通知",
|
||||||
|
"ToastNotificationTestTriggerSuccess": "触发测试通知",
|
||||||
|
"ToastNotificationUpdateFailed": "更新通知失败",
|
||||||
|
"ToastNotificationUpdateSuccess": "通知已更新",
|
||||||
"ToastPlaylistCreateFailed": "创建播放列表失败",
|
"ToastPlaylistCreateFailed": "创建播放列表失败",
|
||||||
"ToastPlaylistCreateSuccess": "已成功创建播放列表",
|
"ToastPlaylistCreateSuccess": "已成功创建播放列表",
|
||||||
"ToastPlaylistRemoveSuccess": "播放列表已删除",
|
"ToastPlaylistRemoveSuccess": "播放列表已删除",
|
||||||
@@ -839,24 +925,52 @@
|
|||||||
"ToastPlaylistUpdateSuccess": "播放列表已更新",
|
"ToastPlaylistUpdateSuccess": "播放列表已更新",
|
||||||
"ToastPodcastCreateFailed": "创建播客失败",
|
"ToastPodcastCreateFailed": "创建播客失败",
|
||||||
"ToastPodcastCreateSuccess": "已成功创建播客",
|
"ToastPodcastCreateSuccess": "已成功创建播客",
|
||||||
|
"ToastPodcastGetFeedFailed": "无法获取播客信息",
|
||||||
|
"ToastPodcastNoEpisodesInFeed": "RSS 订阅中未找到任何剧集",
|
||||||
|
"ToastPodcastNoRssFeed": "播客没有 RSS 源",
|
||||||
|
"ToastProviderCreatedFailed": "无法添加提供商",
|
||||||
|
"ToastProviderCreatedSuccess": "已添加新提供商",
|
||||||
|
"ToastProviderNameAndUrlRequired": "名称和网址必需填写",
|
||||||
|
"ToastProviderRemoveSuccess": "提供商已移除",
|
||||||
"ToastRSSFeedCloseFailed": "关闭 RSS 源失败",
|
"ToastRSSFeedCloseFailed": "关闭 RSS 源失败",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS 源已关闭",
|
"ToastRSSFeedCloseSuccess": "RSS 源已关闭",
|
||||||
|
"ToastRemoveFailed": "删除失败",
|
||||||
"ToastRemoveItemFromCollectionFailed": "从收藏中删除项目失败",
|
"ToastRemoveItemFromCollectionFailed": "从收藏中删除项目失败",
|
||||||
"ToastRemoveItemFromCollectionSuccess": "项目已从收藏中删除",
|
"ToastRemoveItemFromCollectionSuccess": "项目已从收藏中删除",
|
||||||
|
"ToastRemoveItemsWithIssuesFailed": "无法删除有问题的库项目",
|
||||||
|
"ToastRemoveItemsWithIssuesSuccess": "已删除有问题的库项目",
|
||||||
|
"ToastRenameFailed": "重命名失败",
|
||||||
|
"ToastRescanFailed": "{0} 重新扫描失败",
|
||||||
|
"ToastRescanRemoved": "重新扫描完成项目已删除",
|
||||||
|
"ToastRescanUpToDate": "重新扫描完成项目已更新",
|
||||||
|
"ToastRescanUpdated": "重新扫描完成项目已更新",
|
||||||
|
"ToastScanFailed": "扫描库项目失败",
|
||||||
|
"ToastSelectAtLeastOneUser": "至少选择一位用户",
|
||||||
"ToastSendEbookToDeviceFailed": "发送电子书到设备失败",
|
"ToastSendEbookToDeviceFailed": "发送电子书到设备失败",
|
||||||
"ToastSendEbookToDeviceSuccess": "电子书已经发送到设备 \"{0}\"",
|
"ToastSendEbookToDeviceSuccess": "电子书已经发送到设备 \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "更新系列失败",
|
"ToastSeriesUpdateFailed": "更新系列失败",
|
||||||
"ToastSeriesUpdateSuccess": "系列已更新",
|
"ToastSeriesUpdateSuccess": "系列已更新",
|
||||||
"ToastServerSettingsUpdateFailed": "无法更新服务器设置",
|
"ToastServerSettingsUpdateFailed": "无法更新服务器设置",
|
||||||
"ToastServerSettingsUpdateSuccess": "服务器设置已更新",
|
"ToastServerSettingsUpdateSuccess": "服务器设置已更新",
|
||||||
|
"ToastSessionCloseFailed": "关闭会话失败",
|
||||||
"ToastSessionDeleteFailed": "删除会话失败",
|
"ToastSessionDeleteFailed": "删除会话失败",
|
||||||
"ToastSessionDeleteSuccess": "会话已删除",
|
"ToastSessionDeleteSuccess": "会话已删除",
|
||||||
|
"ToastSlugMustChange": "Slug 包含无效字符",
|
||||||
|
"ToastSlugRequired": "Slug 是必填项",
|
||||||
"ToastSocketConnected": "网络已连接",
|
"ToastSocketConnected": "网络已连接",
|
||||||
"ToastSocketDisconnected": "网络已断开",
|
"ToastSocketDisconnected": "网络已断开",
|
||||||
"ToastSocketFailedToConnect": "网络连接失败",
|
"ToastSocketFailedToConnect": "网络连接失败",
|
||||||
"ToastSortingPrefixesEmptyError": "必须至少有 1 个排序前缀",
|
"ToastSortingPrefixesEmptyError": "必须至少有 1 个排序前缀",
|
||||||
"ToastSortingPrefixesUpdateFailed": "无法更新排序前缀",
|
"ToastSortingPrefixesUpdateFailed": "无法更新排序前缀",
|
||||||
"ToastSortingPrefixesUpdateSuccess": "排序前缀已更新 ({0} 项)",
|
"ToastSortingPrefixesUpdateSuccess": "排序前缀已更新 ({0} 项)",
|
||||||
|
"ToastTitleRequired": "标题为必填项",
|
||||||
|
"ToastUnknownError": "未知错误",
|
||||||
|
"ToastUnlinkOpenIdFailed": "无法取消用户与 OpenID 的关联",
|
||||||
|
"ToastUnlinkOpenIdSuccess": "用户已取消与 OpenID 的关联",
|
||||||
"ToastUserDeleteFailed": "删除用户失败",
|
"ToastUserDeleteFailed": "删除用户失败",
|
||||||
"ToastUserDeleteSuccess": "用户已删除"
|
"ToastUserDeleteSuccess": "用户已删除",
|
||||||
|
"ToastUserPasswordChangeSuccess": "密码修改成功",
|
||||||
|
"ToastUserPasswordMismatch": "密码不匹配",
|
||||||
|
"ToastUserPasswordMustChange": "新密码不能与旧密码相同",
|
||||||
|
"ToastUserRootRequireName": "必须输入 root 用户名"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.13.0",
|
"version": "2.13.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.13.0",
|
"version": "2.13.4",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.13.0",
|
"version": "2.13.4",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
+1
-21
@@ -442,26 +442,6 @@ class Database {
|
|||||||
await this.models.feed.removeById(feedId)
|
await this.models.feed.removeById(feedId)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSeries(oldSeries) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
return this.models.series.updateFromOld(oldSeries)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createSeries(oldSeries) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
await this.models.series.createFromOld(oldSeries)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createBulkSeries(oldSeriesObjs) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
await this.models.series.createBulkFromOld(oldSeriesObjs)
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeSeries(seriesId) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
await this.models.series.removeById(seriesId)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createBulkBookAuthors(bookAuthors) {
|
async createBulkBookAuthors(bookAuthors) {
|
||||||
if (!this.sequelize) return false
|
if (!this.sequelize) return false
|
||||||
await this.models.bookAuthor.bulkCreate(bookAuthors)
|
await this.models.bookAuthor.bulkCreate(bookAuthors)
|
||||||
@@ -678,7 +658,7 @@ class Database {
|
|||||||
*/
|
*/
|
||||||
async getSeriesIdByName(libraryId, seriesName) {
|
async getSeriesIdByName(libraryId, seriesName) {
|
||||||
if (!this.libraryFilterData[libraryId]) {
|
if (!this.libraryFilterData[libraryId]) {
|
||||||
return (await this.seriesModel.getOldByNameAndLibrary(seriesName, libraryId))?.id || null
|
return (await this.seriesModel.getByNameAndLibrary(seriesName, libraryId))?.id || null
|
||||||
}
|
}
|
||||||
return this.libraryFilterData[libraryId].series.find((se) => se.name === seriesName)?.id || null
|
return this.libraryFilterData[libraryId].series.find((se) => se.name === seriesName)?.id || null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ class AuthorController {
|
|||||||
let hasUpdated = false
|
let hasUpdated = false
|
||||||
|
|
||||||
const authorNameUpdate = payload.name !== undefined && payload.name !== req.author.name
|
const authorNameUpdate = payload.name !== undefined && payload.name !== req.author.name
|
||||||
|
if (authorNameUpdate) {
|
||||||
|
payload.lastFirst = Database.authorModel.getLastFirst(payload.name)
|
||||||
|
}
|
||||||
|
|
||||||
// Check if author name matches another author and merge the authors
|
// Check if author name matches another author and merge the authors
|
||||||
let existingAuthor = null
|
let existingAuthor = null
|
||||||
@@ -169,6 +172,11 @@ class AuthorController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If lastFirst is not set, get it from the name
|
||||||
|
if (!authorNameUpdate && !req.author.lastFirst) {
|
||||||
|
payload.lastFirst = Database.authorModel.getLastFirst(req.author.name)
|
||||||
|
}
|
||||||
|
|
||||||
// Regular author update
|
// Regular author update
|
||||||
req.author.set(payload)
|
req.author.set(payload)
|
||||||
if (req.author.changed()) {
|
if (req.author.changed()) {
|
||||||
|
|||||||
@@ -629,11 +629,10 @@ class LibraryController {
|
|||||||
|
|
||||||
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)
|
||||||
const oldSeries = series.getOldSeries()
|
|
||||||
|
|
||||||
const libraryItemsInSeries = await libraryItemsBookFilters.getLibraryItemsForSeries(oldSeries, req.user)
|
const libraryItemsInSeries = await libraryItemsBookFilters.getLibraryItemsForSeries(series, req.user)
|
||||||
|
|
||||||
const seriesJson = oldSeries.toJSON()
|
const seriesJson = series.toOldJSON()
|
||||||
if (include.includes('progress')) {
|
if (include.includes('progress')) {
|
||||||
const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.media.id)?.isFinished)
|
const libraryItemsFinished = libraryItemsInSeries.filter((li) => !!req.user.getMediaProgress(li.media.id)?.isFinished)
|
||||||
seriesJson.progress = {
|
seriesJson.progress = {
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ class LibraryItemController {
|
|||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
startPlaybackSession(req, res) {
|
startPlaybackSession(req, res) {
|
||||||
if (!req.libraryItem.media.numTracks && req.libraryItem.mediaType !== 'video') {
|
if (!req.libraryItem.media.numTracks) {
|
||||||
Logger.error(`[LibraryItemController] startPlaybackSession cannot playback ${req.libraryItem.id}`)
|
Logger.error(`[LibraryItemController] startPlaybackSession cannot playback ${req.libraryItem.id}`)
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class MiscController {
|
|||||||
const files = Object.values(req.files)
|
const files = Object.values(req.files)
|
||||||
const { title, author, series, folder: folderId, library: libraryId } = req.body
|
const { title, author, series, folder: folderId, library: libraryId } = req.body
|
||||||
|
|
||||||
const library = await Database.libraryModel.findByPk(libraryId)
|
const library = await Database.libraryModel.findByIdWithFolders(libraryId)
|
||||||
if (!library) {
|
if (!library) {
|
||||||
return res.status(404).send(`Library not found with id ${libraryId}`)
|
return res.status(404).send(`Library not found with id ${libraryId}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class PodcastController {
|
|||||||
}
|
}
|
||||||
const payload = req.body
|
const payload = req.body
|
||||||
|
|
||||||
const library = await Database.libraryModel.findByPk(payload.libraryId)
|
const library = await Database.libraryModel.findByIdWithFolders(payload.libraryId)
|
||||||
if (!library) {
|
if (!library) {
|
||||||
Logger.error(`[PodcastController] Create: Library not found "${payload.libraryId}"`)
|
Logger.error(`[PodcastController] Create: Library not found "${payload.libraryId}"`)
|
||||||
return res.status(404).send('Library not found')
|
return res.status(404).send('Library not found')
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class RSSFeedController {
|
|||||||
async openRSSFeedForSeries(req, res) {
|
async openRSSFeedForSeries(req, res) {
|
||||||
const options = req.body || {}
|
const options = req.body || {}
|
||||||
|
|
||||||
const series = await Database.seriesModel.getOldById(req.params.seriesId)
|
const series = await Database.seriesModel.findByPk(req.params.seriesId)
|
||||||
if (!series) return res.sendStatus(404)
|
if (!series) return res.sendStatus(404)
|
||||||
|
|
||||||
// Check request body options exist
|
// Check request body options exist
|
||||||
@@ -140,7 +140,7 @@ class RSSFeedController {
|
|||||||
return res.status(400).send('Slug already in use')
|
return res.status(400).send('Slug already in use')
|
||||||
}
|
}
|
||||||
|
|
||||||
const seriesJson = series.toJSON()
|
const seriesJson = series.toOldJSON()
|
||||||
|
|
||||||
// Get books in series that have audio tracks
|
// Get books in series that have audio tracks
|
||||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ const Logger = require('../Logger')
|
|||||||
const BookFinder = require('../finders/BookFinder')
|
const BookFinder = require('../finders/BookFinder')
|
||||||
const PodcastFinder = require('../finders/PodcastFinder')
|
const PodcastFinder = require('../finders/PodcastFinder')
|
||||||
const AuthorFinder = require('../finders/AuthorFinder')
|
const AuthorFinder = require('../finders/AuthorFinder')
|
||||||
const MusicFinder = require('../finders/MusicFinder')
|
|
||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
const { isValidASIN } = require('../utils')
|
const { isValidASIN } = require('../utils')
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilter
|
|||||||
* @property {import('../models/User')} user
|
* @property {import('../models/User')} user
|
||||||
*
|
*
|
||||||
* @typedef {Request & RequestUserObject} RequestWithUser
|
* @typedef {Request & RequestUserObject} RequestWithUser
|
||||||
|
*
|
||||||
|
* @typedef RequestEntityObject
|
||||||
|
* @property {import('../models/Series')} series
|
||||||
|
*
|
||||||
|
* @typedef {RequestWithUser & RequestEntityObject} SeriesControllerRequest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class SeriesController {
|
class SeriesController {
|
||||||
@@ -21,7 +26,7 @@ class SeriesController {
|
|||||||
* TODO: Update mobile app to use /api/libraries/:id/series/:seriesId API route instead
|
* TODO: Update mobile app to use /api/libraries/:id/series/:seriesId API route instead
|
||||||
* Series are not library specific so we need to know what the library id is
|
* Series are not library specific so we need to know what the library id is
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {SeriesControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async findOne(req, res) {
|
async findOne(req, res) {
|
||||||
@@ -30,7 +35,7 @@ class SeriesController {
|
|||||||
.map((v) => v.trim())
|
.map((v) => v.trim())
|
||||||
.filter((v) => !!v)
|
.filter((v) => !!v)
|
||||||
|
|
||||||
const seriesJson = req.series.toJSON()
|
const seriesJson = req.series.toOldJSON()
|
||||||
|
|
||||||
// Add progress map with isFinished flag
|
// Add progress map with isFinished flag
|
||||||
if (include.includes('progress')) {
|
if (include.includes('progress')) {
|
||||||
@@ -54,17 +59,28 @@ class SeriesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO: Currently unused in the client, should check for duplicate name
|
||||||
*
|
*
|
||||||
* @param {RequestWithUser} req
|
* @param {SeriesControllerRequest} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async update(req, res) {
|
async update(req, res) {
|
||||||
const hasUpdated = req.series.update(req.body)
|
const keysToUpdate = ['name', 'description']
|
||||||
if (hasUpdated) {
|
const payload = {}
|
||||||
await Database.updateSeries(req.series)
|
for (const key of keysToUpdate) {
|
||||||
SocketAuthority.emitter('series_updated', req.series.toJSON())
|
if (req.body[key] !== undefined && typeof req.body[key] === 'string') {
|
||||||
|
payload[key] = req.body[key]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res.json(req.series.toJSON())
|
if (!Object.keys(payload).length) {
|
||||||
|
return res.status(400).send('No valid fields to update')
|
||||||
|
}
|
||||||
|
req.series.set(payload)
|
||||||
|
if (req.series.changed()) {
|
||||||
|
await req.series.save()
|
||||||
|
SocketAuthority.emitter('series_updated', req.series.toOldJSON())
|
||||||
|
}
|
||||||
|
res.json(req.series.toOldJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,7 +90,7 @@ class SeriesController {
|
|||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
*/
|
*/
|
||||||
async middleware(req, res, next) {
|
async middleware(req, res, next) {
|
||||||
const series = await Database.seriesModel.getOldById(req.params.id)
|
const series = await Database.seriesModel.findByPk(req.params.id)
|
||||||
if (!series) return res.sendStatus(404)
|
if (!series) return res.sendStatus(404)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -205,9 +205,12 @@ class UserController {
|
|||||||
async update(req, res) {
|
async update(req, res) {
|
||||||
const user = req.reqUser
|
const user = req.reqUser
|
||||||
|
|
||||||
if (user.type === 'root' && !req.user.isRoot) {
|
if (user.isRoot && !req.user.isRoot) {
|
||||||
Logger.error(`[UserController] Admin user "${req.user.username}" attempted to update root user`)
|
Logger.error(`[UserController] Admin user "${req.user.username}" attempted to update root user`)
|
||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
|
} else if (user.isRoot) {
|
||||||
|
// Root user cannot update type
|
||||||
|
delete req.body.type
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatePayload = req.body
|
const updatePayload = req.body
|
||||||
@@ -270,8 +273,10 @@ class UserController {
|
|||||||
const permissions = {
|
const permissions = {
|
||||||
...user.permissions
|
...user.permissions
|
||||||
}
|
}
|
||||||
|
const defaultPermissions = Database.userModel.getDefaultPermissionsForUserType(updatePayload.type || user.type || 'user')
|
||||||
for (const key in updatePayload.permissions) {
|
for (const key in updatePayload.permissions) {
|
||||||
if (permissions[key] !== undefined) {
|
// Check that the key is a valid permission key or is included in the default permissions
|
||||||
|
if (permissions[key] !== undefined || defaultPermissions[key] !== undefined) {
|
||||||
if (typeof updatePayload.permissions[key] !== 'boolean') {
|
if (typeof updatePayload.permissions[key] !== 'boolean') {
|
||||||
Logger.warn(`[UserController] update: Invalid permission value for key ${key}. Should be boolean`)
|
Logger.warn(`[UserController] update: Invalid permission value for key ${key}. Should be boolean`)
|
||||||
} else if (permissions[key] !== updatePayload.permissions[key]) {
|
} else if (permissions[key] !== updatePayload.permissions[key]) {
|
||||||
|
|||||||
@@ -202,10 +202,14 @@ class BookFinder {
|
|||||||
* @returns {Promise<Object[]>}
|
* @returns {Promise<Object[]>}
|
||||||
*/
|
*/
|
||||||
async getCustomProviderResults(title, author, isbn, providerSlug) {
|
async getCustomProviderResults(title, author, isbn, providerSlug) {
|
||||||
const books = await this.customProviderAdapter.search(title, author, isbn, providerSlug, 'book', this.#providerResponseTimeout)
|
try {
|
||||||
if (this.verbose) Logger.debug(`Custom provider '${providerSlug}' Search Results: ${books.length || 0}`)
|
const books = await this.customProviderAdapter.search(title, author, isbn, providerSlug, 'book', this.#providerResponseTimeout)
|
||||||
|
if (this.verbose) Logger.debug(`Custom provider '${providerSlug}' Search Results: ${books.length || 0}`)
|
||||||
return books
|
return books
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`Error searching Custom provider '${providerSlug}':`, error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static TitleCandidates = class {
|
static TitleCandidates = class {
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
const MusicBrainz = require('../providers/MusicBrainz')
|
|
||||||
|
|
||||||
class MusicFinder {
|
|
||||||
constructor() {
|
|
||||||
this.musicBrainz = new MusicBrainz()
|
|
||||||
}
|
|
||||||
|
|
||||||
searchTrack(options) {
|
|
||||||
return this.musicBrainz.searchTrack(options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = new MusicFinder()
|
|
||||||
@@ -293,37 +293,27 @@ class PlaybackSessionManager {
|
|||||||
const newPlaybackSession = new PlaybackSession()
|
const newPlaybackSession = new PlaybackSession()
|
||||||
newPlaybackSession.setData(libraryItem, user.id, mediaPlayer, deviceInfo, userStartTime, episodeId)
|
newPlaybackSession.setData(libraryItem, user.id, mediaPlayer, deviceInfo, userStartTime, episodeId)
|
||||||
|
|
||||||
if (libraryItem.mediaType === 'video') {
|
let audioTracks = []
|
||||||
if (shouldDirectPlay) {
|
if (shouldDirectPlay) {
|
||||||
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}" with id ${newPlaybackSession.id}`)
|
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}" with id ${newPlaybackSession.id} (Device: ${newPlaybackSession.deviceDescription})`)
|
||||||
newPlaybackSession.videoTrack = libraryItem.media.getVideoTrack()
|
audioTracks = libraryItem.getDirectPlayTracklist(episodeId)
|
||||||
newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY
|
newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY
|
||||||
} else {
|
|
||||||
// HLS not supported for video yet
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let audioTracks = []
|
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting stream session for item "${libraryItem.id}" (Device: ${newPlaybackSession.deviceDescription})`)
|
||||||
if (shouldDirectPlay) {
|
const stream = new Stream(newPlaybackSession.id, this.StreamsPath, user, libraryItem, episodeId, userStartTime)
|
||||||
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting direct play session for item "${libraryItem.id}" with id ${newPlaybackSession.id} (Device: ${newPlaybackSession.deviceDescription})`)
|
await stream.generatePlaylist()
|
||||||
audioTracks = libraryItem.getDirectPlayTracklist(episodeId)
|
stream.start() // Start transcode
|
||||||
newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY
|
|
||||||
} else {
|
|
||||||
Logger.debug(`[PlaybackSessionManager] "${user.username}" starting stream session for item "${libraryItem.id}" (Device: ${newPlaybackSession.deviceDescription})`)
|
|
||||||
const stream = new Stream(newPlaybackSession.id, this.StreamsPath, user, libraryItem, episodeId, userStartTime)
|
|
||||||
await stream.generatePlaylist()
|
|
||||||
stream.start() // Start transcode
|
|
||||||
|
|
||||||
audioTracks = [stream.getAudioTrack()]
|
audioTracks = [stream.getAudioTrack()]
|
||||||
newPlaybackSession.stream = stream
|
newPlaybackSession.stream = stream
|
||||||
newPlaybackSession.playMethod = PlayMethod.TRANSCODE
|
newPlaybackSession.playMethod = PlayMethod.TRANSCODE
|
||||||
|
|
||||||
stream.on('closed', () => {
|
stream.on('closed', () => {
|
||||||
Logger.debug(`[PlaybackSessionManager] Stream closed for session "${newPlaybackSession.id}" (Device: ${newPlaybackSession.deviceDescription})`)
|
Logger.debug(`[PlaybackSessionManager] Stream closed for session "${newPlaybackSession.id}" (Device: ${newPlaybackSession.deviceDescription})`)
|
||||||
newPlaybackSession.stream = null
|
newPlaybackSession.stream = null
|
||||||
})
|
})
|
||||||
}
|
|
||||||
newPlaybackSession.audioTracks = audioTracks
|
|
||||||
}
|
}
|
||||||
|
newPlaybackSession.audioTracks = audioTracks
|
||||||
|
|
||||||
this.sessions.push(newPlaybackSession)
|
this.sessions.push(newPlaybackSession)
|
||||||
SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions))
|
SocketAuthority.adminEmitter('user_stream_update', user.toJSONForPublic(this.sessions))
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class RssFeedManager {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if (feedObj.entityType === 'series') {
|
} else if (feedObj.entityType === 'series') {
|
||||||
const series = await Database.seriesModel.getOldById(feedObj.entityId)
|
const series = await Database.seriesModel.findByPk(feedObj.entityId)
|
||||||
if (!series) {
|
if (!series) {
|
||||||
Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Series "${feedObj.entityId}" not found`)
|
Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Series "${feedObj.entityId}" not found`)
|
||||||
return false
|
return false
|
||||||
@@ -133,9 +133,9 @@ class RssFeedManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (feed.entityType === 'series') {
|
} else if (feed.entityType === 'series') {
|
||||||
const series = await Database.seriesModel.getOldById(feed.entityId)
|
const series = await Database.seriesModel.findByPk(feed.entityId)
|
||||||
if (series) {
|
if (series) {
|
||||||
const seriesJson = series.toJSON()
|
const seriesJson = series.toOldJSON()
|
||||||
|
|
||||||
// Get books in series that have audio tracks
|
// Get books in series that have audio tracks
|
||||||
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
seriesJson.books = (await libraryItemsBookFilters.getLibraryItemsForSeries(series)).filter((li) => li.media.numTracks)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
||||||
|
const parseNameString = require('../utils/parsers/parseNameString')
|
||||||
|
|
||||||
class Author extends Model {
|
class Author extends Model {
|
||||||
constructor(values, options) {
|
constructor(values, options) {
|
||||||
@@ -24,6 +25,16 @@ class Author extends Model {
|
|||||||
this.createdAt
|
this.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static getLastFirst(name) {
|
||||||
|
if (!name) return null
|
||||||
|
return parseNameString.nameToLastFirst(name)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if author exists
|
* Check if author exists
|
||||||
* @param {string} authorId
|
* @param {string} authorId
|
||||||
|
|||||||
+104
-103
@@ -38,7 +38,7 @@ class Collection extends Model {
|
|||||||
|
|
||||||
// Optionally include rssfeed for collection
|
// Optionally include rssfeed for collection
|
||||||
const collectionIncludes = []
|
const collectionIncludes = []
|
||||||
if (include.includes('rssfeed')) {
|
if (include?.includes('rssfeed')) {
|
||||||
collectionIncludes.push({
|
collectionIncludes.push({
|
||||||
model: this.sequelize.models.feed
|
model: this.sequelize.models.feed
|
||||||
})
|
})
|
||||||
@@ -115,78 +115,6 @@ class Collection extends Model {
|
|||||||
.filter((c) => c)
|
.filter((c) => c)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get old collection toJSONExpanded, items filtered for user permissions
|
|
||||||
*
|
|
||||||
* @param {import('./User')|null} user
|
|
||||||
* @param {string[]} [include]
|
|
||||||
* @returns {Promise<oldCollection>} oldCollection.toJSONExpanded
|
|
||||||
*/
|
|
||||||
async getOldJsonExpanded(user, include) {
|
|
||||||
this.books =
|
|
||||||
(await this.getBooks({
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.author,
|
|
||||||
through: {
|
|
||||||
attributes: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.series,
|
|
||||||
through: {
|
|
||||||
attributes: ['sequence']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
order: [Sequelize.literal('`collectionBook.order` ASC')]
|
|
||||||
})) || []
|
|
||||||
|
|
||||||
const oldCollection = this.sequelize.models.collection.getOldCollection(this)
|
|
||||||
|
|
||||||
// Filter books using user permissions
|
|
||||||
// TODO: Handle user permission restrictions on initial query
|
|
||||||
const books =
|
|
||||||
this.books?.filter((b) => {
|
|
||||||
if (user) {
|
|
||||||
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (b.explicit === true && !user.canAccessExplicitContent) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}) || []
|
|
||||||
|
|
||||||
// Map to library items
|
|
||||||
const libraryItems = books.map((b) => {
|
|
||||||
const libraryItem = b.libraryItem
|
|
||||||
delete b.libraryItem
|
|
||||||
libraryItem.media = b
|
|
||||||
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Users with restricted permissions will not see this collection
|
|
||||||
if (!books.length && oldCollection.books.length) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
|
|
||||||
|
|
||||||
if (include?.includes('rssfeed')) {
|
|
||||||
const feeds = await this.getFeeds()
|
|
||||||
if (feeds?.length) {
|
|
||||||
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(feeds[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return collectionExpanded
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get old collection from Collection
|
* Get old collection from Collection
|
||||||
* @param {Collection} collectionExpanded
|
* @param {Collection} collectionExpanded
|
||||||
@@ -250,36 +178,6 @@ class Collection extends Model {
|
|||||||
return this.getOldCollection(collection)
|
return this.getOldCollection(collection)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get old collection from current
|
|
||||||
* @returns {Promise<oldCollection>}
|
|
||||||
*/
|
|
||||||
async getOld() {
|
|
||||||
this.books =
|
|
||||||
(await this.getBooks({
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.libraryItem
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.author,
|
|
||||||
through: {
|
|
||||||
attributes: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: this.sequelize.models.series,
|
|
||||||
through: {
|
|
||||||
attributes: ['sequence']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
order: [Sequelize.literal('`collectionBook.order` ASC')]
|
|
||||||
})) || []
|
|
||||||
|
|
||||||
return this.sequelize.models.collection.getOldCollection(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all collections belonging to library
|
* Remove all collections belonging to library
|
||||||
* @param {string} libraryId
|
* @param {string} libraryId
|
||||||
@@ -320,6 +218,109 @@ class Collection extends Model {
|
|||||||
library.hasMany(Collection)
|
library.hasMany(Collection)
|
||||||
Collection.belongsTo(library)
|
Collection.belongsTo(library)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get old collection toJSONExpanded, items filtered for user permissions
|
||||||
|
*
|
||||||
|
* @param {import('./User')|null} user
|
||||||
|
* @param {string[]} [include]
|
||||||
|
* @returns {Promise<oldCollection>} oldCollection.toJSONExpanded
|
||||||
|
*/
|
||||||
|
async getOldJsonExpanded(user, include) {
|
||||||
|
this.books =
|
||||||
|
(await this.getBooks({
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.libraryItem
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.author,
|
||||||
|
through: {
|
||||||
|
attributes: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.series,
|
||||||
|
through: {
|
||||||
|
attributes: ['sequence']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
order: [Sequelize.literal('`collectionBook.order` ASC')]
|
||||||
|
})) || []
|
||||||
|
|
||||||
|
// Filter books using user permissions
|
||||||
|
// TODO: Handle user permission restrictions on initial query
|
||||||
|
const books =
|
||||||
|
this.books?.filter((b) => {
|
||||||
|
if (user) {
|
||||||
|
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (b.explicit === true && !user.canAccessExplicitContent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}) || []
|
||||||
|
|
||||||
|
// Map to library items
|
||||||
|
const libraryItems = books.map((b) => {
|
||||||
|
const libraryItem = b.libraryItem
|
||||||
|
delete b.libraryItem
|
||||||
|
libraryItem.media = b
|
||||||
|
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Users with restricted permissions will not see this collection
|
||||||
|
if (!books.length && this.books.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const collectionExpanded = this.toOldJSONExpanded(libraryItems)
|
||||||
|
|
||||||
|
if (include?.includes('rssfeed')) {
|
||||||
|
const feeds = await this.getFeeds()
|
||||||
|
if (feeds?.length) {
|
||||||
|
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(feeds[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectionExpanded
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string[]} libraryItemIds
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
toOldJSON(libraryItemIds) {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
libraryId: this.libraryId,
|
||||||
|
name: this.name,
|
||||||
|
description: this.description,
|
||||||
|
books: [...libraryItemIds],
|
||||||
|
lastUpdate: this.updatedAt.valueOf(),
|
||||||
|
createdAt: this.createdAt.valueOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import('../objects/LibraryItem')} oldLibraryItems
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
toOldJSONExpanded(oldLibraryItems) {
|
||||||
|
const json = this.toOldJSON(oldLibraryItems.map((li) => li.id))
|
||||||
|
json.books = json.books
|
||||||
|
.map((libraryItemId) => {
|
||||||
|
const book = oldLibraryItems.find((li) => li.id === libraryItemId)
|
||||||
|
return book ? book.toJSONExpanded() : null
|
||||||
|
})
|
||||||
|
.filter((b) => !!b)
|
||||||
|
return json
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Collection
|
module.exports = Collection
|
||||||
|
|||||||
@@ -365,7 +365,23 @@ class LibraryItem extends Model {
|
|||||||
if (existingValue instanceof Date) existingValue = existingValue.valueOf()
|
if (existingValue instanceof Date) existingValue = existingValue.valueOf()
|
||||||
|
|
||||||
if (!areEquivalent(updatedMedia[key], existingValue, true)) {
|
if (!areEquivalent(updatedMedia[key], existingValue, true)) {
|
||||||
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" ${libraryItemExpanded.mediaType}.${key} updated from ${existingValue} to ${updatedMedia[key]}`)
|
if (key === 'chapters') {
|
||||||
|
// Handle logging of chapters separately because the object is large
|
||||||
|
const chaptersRemoved = libraryItemExpanded.media.chapters.filter((ch) => !updatedMedia.chapters.some((uch) => uch.id === ch.id))
|
||||||
|
if (chaptersRemoved.length) {
|
||||||
|
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" chapters removed: ${chaptersRemoved.map((ch) => ch.title).join(', ')}`)
|
||||||
|
}
|
||||||
|
const chaptersAdded = updatedMedia.chapters.filter((uch) => !libraryItemExpanded.media.chapters.some((ch) => ch.id === uch.id))
|
||||||
|
if (chaptersAdded.length) {
|
||||||
|
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" chapters added: ${chaptersAdded.map((ch) => ch.title).join(', ')}`)
|
||||||
|
}
|
||||||
|
if (!chaptersRemoved.length && !chaptersAdded.length) {
|
||||||
|
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" chapters updated`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.debug(util.format(`[LibraryItem] "${libraryItemExpanded.media.title}" ${libraryItemExpanded.mediaType}.${key} updated from %j to %j`, existingValue, updatedMedia[key]))
|
||||||
|
}
|
||||||
|
|
||||||
hasMediaUpdates = true
|
hasMediaUpdates = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-79
@@ -1,6 +1,6 @@
|
|||||||
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
const { DataTypes, Model, where, fn, col } = require('sequelize')
|
||||||
|
|
||||||
const oldSeries = require('../objects/entities/Series')
|
const { getTitlePrefixAtEnd } = require('../utils/index')
|
||||||
|
|
||||||
class Series extends Model {
|
class Series extends Model {
|
||||||
constructor(values, options) {
|
constructor(values, options) {
|
||||||
@@ -22,70 +22,6 @@ class Series extends Model {
|
|||||||
this.updatedAt
|
this.updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getAllOldSeries() {
|
|
||||||
const series = await this.findAll()
|
|
||||||
return series.map((se) => se.getOldSeries())
|
|
||||||
}
|
|
||||||
|
|
||||||
getOldSeries() {
|
|
||||||
return new oldSeries({
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
description: this.description,
|
|
||||||
libraryId: this.libraryId,
|
|
||||||
addedAt: this.createdAt.valueOf(),
|
|
||||||
updatedAt: this.updatedAt.valueOf()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static updateFromOld(oldSeries) {
|
|
||||||
const series = this.getFromOld(oldSeries)
|
|
||||||
return this.update(series, {
|
|
||||||
where: {
|
|
||||||
id: series.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static createFromOld(oldSeries) {
|
|
||||||
const series = this.getFromOld(oldSeries)
|
|
||||||
return this.create(series)
|
|
||||||
}
|
|
||||||
|
|
||||||
static createBulkFromOld(oldSeriesObjs) {
|
|
||||||
const series = oldSeriesObjs.map(this.getFromOld)
|
|
||||||
return this.bulkCreate(series)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFromOld(oldSeries) {
|
|
||||||
return {
|
|
||||||
id: oldSeries.id,
|
|
||||||
name: oldSeries.name,
|
|
||||||
nameIgnorePrefix: oldSeries.nameIgnorePrefix,
|
|
||||||
description: oldSeries.description,
|
|
||||||
libraryId: oldSeries.libraryId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeById(seriesId) {
|
|
||||||
return this.destroy({
|
|
||||||
where: {
|
|
||||||
id: seriesId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get oldSeries by id
|
|
||||||
* @param {string} seriesId
|
|
||||||
* @returns {Promise<oldSeries>}
|
|
||||||
*/
|
|
||||||
static async getOldById(seriesId) {
|
|
||||||
const series = await this.findByPk(seriesId)
|
|
||||||
if (!series) return null
|
|
||||||
return series.getOldSeries()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if series exists
|
* Check if series exists
|
||||||
* @param {string} seriesId
|
* @param {string} seriesId
|
||||||
@@ -96,24 +32,21 @@ class Series extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get old series by name and libraryId. name case insensitive
|
* Get series by name and libraryId. name case insensitive
|
||||||
*
|
*
|
||||||
* @param {string} seriesName
|
* @param {string} seriesName
|
||||||
* @param {string} libraryId
|
* @param {string} libraryId
|
||||||
* @returns {Promise<oldSeries>}
|
* @returns {Promise<Series>}
|
||||||
*/
|
*/
|
||||||
static async getOldByNameAndLibrary(seriesName, libraryId) {
|
static async getByNameAndLibrary(seriesName, libraryId) {
|
||||||
const series = (
|
return this.findOne({
|
||||||
await this.findOne({
|
where: [
|
||||||
where: [
|
where(fn('lower', col('name')), seriesName.toLowerCase()),
|
||||||
where(fn('lower', col('name')), seriesName.toLowerCase()),
|
{
|
||||||
{
|
libraryId
|
||||||
libraryId
|
}
|
||||||
}
|
]
|
||||||
]
|
})
|
||||||
})
|
|
||||||
)?.getOldSeries()
|
|
||||||
return series
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,6 +96,26 @@ class Series extends Model {
|
|||||||
})
|
})
|
||||||
Series.belongsTo(library)
|
Series.belongsTo(library)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toOldJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
nameIgnorePrefix: getTitlePrefixAtEnd(this.name),
|
||||||
|
description: this.description,
|
||||||
|
addedAt: this.createdAt.valueOf(),
|
||||||
|
updatedAt: this.updatedAt.valueOf(),
|
||||||
|
libraryId: this.libraryId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSONMinimal(sequence) {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
name: this.name,
|
||||||
|
sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Series
|
module.exports = Series
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ class User extends Model {
|
|||||||
accessAllLibraries: true,
|
accessAllLibraries: true,
|
||||||
accessAllTags: true,
|
accessAllTags: true,
|
||||||
accessExplicitContent: true,
|
accessExplicitContent: true,
|
||||||
|
selectedTagsNotAccessible: false,
|
||||||
librariesAccessible: [],
|
librariesAccessible: [],
|
||||||
itemTagsSelected: []
|
itemTagsSelected: []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
const uuidv4 = require('uuid').v4
|
||||||
const fs = require('../libs/fsExtra')
|
const fs = require('../libs/fsExtra')
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const LibraryFile = require('./files/LibraryFile')
|
const LibraryFile = require('./files/LibraryFile')
|
||||||
const Book = require('./mediaTypes/Book')
|
const Book = require('./mediaTypes/Book')
|
||||||
const Podcast = require('./mediaTypes/Podcast')
|
const Podcast = require('./mediaTypes/Podcast')
|
||||||
const Video = require('./mediaTypes/Video')
|
|
||||||
const Music = require('./mediaTypes/Music')
|
|
||||||
const { areEquivalent, copyValue } = require('../utils/index')
|
const { areEquivalent, copyValue } = require('../utils/index')
|
||||||
const { filePathToPOSIX, getFileTimestampsWithIno } = require('../utils/fileUtils')
|
const { filePathToPOSIX, getFileTimestampsWithIno } = require('../utils/fileUtils')
|
||||||
|
|
||||||
@@ -74,14 +72,10 @@ class LibraryItem {
|
|||||||
this.media = new Book(libraryItem.media)
|
this.media = new Book(libraryItem.media)
|
||||||
} else if (this.mediaType === 'podcast') {
|
} else if (this.mediaType === 'podcast') {
|
||||||
this.media = new Podcast(libraryItem.media)
|
this.media = new Podcast(libraryItem.media)
|
||||||
} else if (this.mediaType === 'video') {
|
|
||||||
this.media = new Video(libraryItem.media)
|
|
||||||
} else if (this.mediaType === 'music') {
|
|
||||||
this.media = new Music(libraryItem.media)
|
|
||||||
}
|
}
|
||||||
this.media.libraryItemId = this.id
|
this.media.libraryItemId = this.id
|
||||||
|
|
||||||
this.libraryFiles = libraryItem.libraryFiles.map(f => new LibraryFile(f))
|
this.libraryFiles = libraryItem.libraryFiles.map((f) => new LibraryFile(f))
|
||||||
|
|
||||||
// Migration for v2.2.23 to set ebook library files as supplementary
|
// Migration for v2.2.23 to set ebook library files as supplementary
|
||||||
if (this.isBook && this.media.ebookFile) {
|
if (this.isBook && this.media.ebookFile) {
|
||||||
@@ -91,7 +85,6 @@ class LibraryItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
@@ -115,7 +108,7 @@ class LibraryItem {
|
|||||||
isInvalid: !!this.isInvalid,
|
isInvalid: !!this.isInvalid,
|
||||||
mediaType: this.mediaType,
|
mediaType: this.mediaType,
|
||||||
media: this.media.toJSON(),
|
media: this.media.toJSON(),
|
||||||
libraryFiles: this.libraryFiles.map(f => f.toJSON())
|
libraryFiles: this.libraryFiles.map((f) => f.toJSON())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,21 +158,24 @@ class LibraryItem {
|
|||||||
isInvalid: !!this.isInvalid,
|
isInvalid: !!this.isInvalid,
|
||||||
mediaType: this.mediaType,
|
mediaType: this.mediaType,
|
||||||
media: this.media.toJSONExpanded(),
|
media: this.media.toJSONExpanded(),
|
||||||
libraryFiles: this.libraryFiles.map(f => f.toJSON()),
|
libraryFiles: this.libraryFiles.map((f) => f.toJSON()),
|
||||||
size: this.size
|
size: this.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isPodcast() { return this.mediaType === 'podcast' }
|
get isPodcast() {
|
||||||
get isBook() { return this.mediaType === 'book' }
|
return this.mediaType === 'podcast'
|
||||||
get isMusic() { return this.mediaType === 'music' }
|
}
|
||||||
|
get isBook() {
|
||||||
|
return this.mediaType === 'book'
|
||||||
|
}
|
||||||
get size() {
|
get size() {
|
||||||
let total = 0
|
let total = 0
|
||||||
this.libraryFiles.forEach((lf) => total += lf.metadata.size)
|
this.libraryFiles.forEach((lf) => (total += lf.metadata.size))
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
get hasAudioFiles() {
|
get hasAudioFiles() {
|
||||||
return this.libraryFiles.some(lf => lf.fileType === 'audio')
|
return this.libraryFiles.some((lf) => lf.fileType === 'audio')
|
||||||
}
|
}
|
||||||
get hasMediaEntities() {
|
get hasMediaEntities() {
|
||||||
return this.media.hasMediaEntities
|
return this.media.hasMediaEntities
|
||||||
@@ -201,17 +197,16 @@ class LibraryItem {
|
|||||||
|
|
||||||
for (const key in payload) {
|
for (const key in payload) {
|
||||||
if (key === 'libraryFiles') {
|
if (key === 'libraryFiles') {
|
||||||
this.libraryFiles = payload.libraryFiles.map(lf => lf.clone())
|
this.libraryFiles = payload.libraryFiles.map((lf) => lf.clone())
|
||||||
|
|
||||||
// Set cover image
|
// Set cover image
|
||||||
const imageFiles = this.libraryFiles.filter(lf => lf.fileType === 'image')
|
const imageFiles = this.libraryFiles.filter((lf) => lf.fileType === 'image')
|
||||||
const coverMatch = imageFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = imageFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
if (coverMatch) {
|
if (coverMatch) {
|
||||||
this.media.coverPath = coverMatch.metadata.path
|
this.media.coverPath = coverMatch.metadata.path
|
||||||
} else if (imageFiles.length) {
|
} else if (imageFiles.length) {
|
||||||
this.media.coverPath = imageFiles[0].metadata.path
|
this.media.coverPath = imageFiles[0].metadata.path
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (this[key] !== undefined && key !== 'media') {
|
} else if (this[key] !== undefined && key !== 'media') {
|
||||||
this[key] = payload[key]
|
this[key] = payload[key]
|
||||||
}
|
}
|
||||||
@@ -283,46 +278,50 @@ class LibraryItem {
|
|||||||
|
|
||||||
const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
|
const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
|
||||||
|
|
||||||
return fs.writeFile(metadataFilePath, JSON.stringify(this.media.toJSONForMetadataFile(), null, 2)).then(async () => {
|
return fs
|
||||||
// Add metadata.json to libraryFiles array if it is new
|
.writeFile(metadataFilePath, JSON.stringify(this.media.toJSONForMetadataFile(), null, 2))
|
||||||
let metadataLibraryFile = this.libraryFiles.find(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
.then(async () => {
|
||||||
if (storeMetadataWithItem) {
|
// Add metadata.json to libraryFiles array if it is new
|
||||||
if (!metadataLibraryFile) {
|
let metadataLibraryFile = this.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
||||||
metadataLibraryFile = new LibraryFile()
|
if (storeMetadataWithItem) {
|
||||||
await metadataLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
if (!metadataLibraryFile) {
|
||||||
this.libraryFiles.push(metadataLibraryFile)
|
metadataLibraryFile = new LibraryFile()
|
||||||
} else {
|
await metadataLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
||||||
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
this.libraryFiles.push(metadataLibraryFile)
|
||||||
if (fileTimestamps) {
|
} else {
|
||||||
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
||||||
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
if (fileTimestamps) {
|
||||||
metadataLibraryFile.metadata.size = fileTimestamps.size
|
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
||||||
metadataLibraryFile.ino = fileTimestamps.ino
|
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
||||||
|
metadataLibraryFile.metadata.size = fileTimestamps.size
|
||||||
|
metadataLibraryFile.ino = fileTimestamps.ino
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const libraryItemDirTimestamps = await getFileTimestampsWithIno(this.path)
|
||||||
|
if (libraryItemDirTimestamps) {
|
||||||
|
this.mtimeMs = libraryItemDirTimestamps.mtimeMs
|
||||||
|
this.ctimeMs = libraryItemDirTimestamps.ctimeMs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const libraryItemDirTimestamps = await getFileTimestampsWithIno(this.path)
|
|
||||||
if (libraryItemDirTimestamps) {
|
|
||||||
this.mtimeMs = libraryItemDirTimestamps.mtimeMs
|
|
||||||
this.ctimeMs = libraryItemDirTimestamps.ctimeMs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataFilePath}"`)
|
Logger.debug(`[LibraryItem] Success saving abmetadata to "${metadataFilePath}"`)
|
||||||
|
|
||||||
return metadataLibraryFile
|
return metadataLibraryFile
|
||||||
}).catch((error) => {
|
})
|
||||||
Logger.error(`[LibraryItem] Failed to save json file at "${metadataFilePath}"`, error)
|
.catch((error) => {
|
||||||
return null
|
Logger.error(`[LibraryItem] Failed to save json file at "${metadataFilePath}"`, error)
|
||||||
}).finally(() => {
|
return null
|
||||||
this.isSavingMetadata = false
|
})
|
||||||
})
|
.finally(() => {
|
||||||
|
this.isSavingMetadata = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLibraryFile(ino) {
|
removeLibraryFile(ino) {
|
||||||
if (!ino) return false
|
if (!ino) return false
|
||||||
const libraryFile = this.libraryFiles.find(lf => lf.ino === ino)
|
const libraryFile = this.libraryFiles.find((lf) => lf.ino === ino)
|
||||||
if (libraryFile) {
|
if (libraryFile) {
|
||||||
this.libraryFiles = this.libraryFiles.filter(lf => lf.ino !== ino)
|
this.libraryFiles = this.libraryFiles.filter((lf) => lf.ino !== ino)
|
||||||
this.updatedAt = Date.now()
|
this.updatedAt = Date.now()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -333,15 +332,15 @@ class LibraryItem {
|
|||||||
* Set the EBookFile from a LibraryFile
|
* Set the EBookFile from a LibraryFile
|
||||||
* If null then ebookFile will be removed from the book
|
* If null then ebookFile will be removed from the book
|
||||||
* all ebook library files that are not primary are marked as supplementary
|
* all ebook library files that are not primary are marked as supplementary
|
||||||
*
|
*
|
||||||
* @param {LibraryFile} [libraryFile]
|
* @param {LibraryFile} [libraryFile]
|
||||||
*/
|
*/
|
||||||
setPrimaryEbook(ebookLibraryFile = null) {
|
setPrimaryEbook(ebookLibraryFile = null) {
|
||||||
const ebookLibraryFiles = this.libraryFiles.filter(lf => lf.isEBookFile)
|
const ebookLibraryFiles = this.libraryFiles.filter((lf) => lf.isEBookFile)
|
||||||
for (const libraryFile of ebookLibraryFiles) {
|
for (const libraryFile of ebookLibraryFiles) {
|
||||||
libraryFile.isSupplementary = ebookLibraryFile?.ino !== libraryFile.ino
|
libraryFile.isSupplementary = ebookLibraryFile?.ino !== libraryFile.ino
|
||||||
}
|
}
|
||||||
this.media.setEbookFile(ebookLibraryFile)
|
this.media.setEbookFile(ebookLibraryFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = LibraryItem
|
module.exports = LibraryItem
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ const serverVersion = require('../../package.json').version
|
|||||||
const BookMetadata = require('./metadata/BookMetadata')
|
const BookMetadata = require('./metadata/BookMetadata')
|
||||||
const PodcastMetadata = require('./metadata/PodcastMetadata')
|
const PodcastMetadata = require('./metadata/PodcastMetadata')
|
||||||
const DeviceInfo = require('./DeviceInfo')
|
const DeviceInfo = require('./DeviceInfo')
|
||||||
const VideoMetadata = require('./metadata/VideoMetadata')
|
|
||||||
|
|
||||||
class PlaybackSession {
|
class PlaybackSession {
|
||||||
constructor(session) {
|
constructor(session) {
|
||||||
@@ -41,7 +40,6 @@ class PlaybackSession {
|
|||||||
// Not saved in DB
|
// Not saved in DB
|
||||||
this.lastSave = 0
|
this.lastSave = 0
|
||||||
this.audioTracks = []
|
this.audioTracks = []
|
||||||
this.videoTrack = null
|
|
||||||
this.stream = null
|
this.stream = null
|
||||||
// Used for share sessions
|
// Used for share sessions
|
||||||
this.shareSessionId = null
|
this.shareSessionId = null
|
||||||
@@ -114,7 +112,6 @@ class PlaybackSession {
|
|||||||
startedAt: this.startedAt,
|
startedAt: this.startedAt,
|
||||||
updatedAt: this.updatedAt,
|
updatedAt: this.updatedAt,
|
||||||
audioTracks: this.audioTracks.map((at) => at.toJSON?.() || { ...at }),
|
audioTracks: this.audioTracks.map((at) => at.toJSON?.() || { ...at }),
|
||||||
videoTrack: this.videoTrack?.toJSON() || null,
|
|
||||||
libraryItem: libraryItem?.toJSONExpanded() || null
|
libraryItem: libraryItem?.toJSONExpanded() || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,8 +154,6 @@ class PlaybackSession {
|
|||||||
this.mediaMetadata = new BookMetadata(session.mediaMetadata)
|
this.mediaMetadata = new BookMetadata(session.mediaMetadata)
|
||||||
} else if (this.mediaType === 'podcast') {
|
} else if (this.mediaType === 'podcast') {
|
||||||
this.mediaMetadata = new PodcastMetadata(session.mediaMetadata)
|
this.mediaMetadata = new PodcastMetadata(session.mediaMetadata)
|
||||||
} else if (this.mediaType === 'video') {
|
|
||||||
this.mediaMetadata = new VideoMetadata(session.mediaMetadata)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.displayTitle = session.displayTitle || ''
|
this.displayTitle = session.displayTitle || ''
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
|
||||||
const { getTitleIgnorePrefix, getTitlePrefixAtEnd } = require('../../utils/index')
|
|
||||||
|
|
||||||
class Series {
|
|
||||||
constructor(series) {
|
|
||||||
this.id = null
|
|
||||||
this.name = null
|
|
||||||
this.description = null
|
|
||||||
this.addedAt = null
|
|
||||||
this.updatedAt = null
|
|
||||||
this.libraryId = null
|
|
||||||
|
|
||||||
if (series) {
|
|
||||||
this.construct(series)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(series) {
|
|
||||||
this.id = series.id
|
|
||||||
this.name = series.name
|
|
||||||
this.description = series.description || null
|
|
||||||
this.addedAt = series.addedAt
|
|
||||||
this.updatedAt = series.updatedAt
|
|
||||||
this.libraryId = series.libraryId
|
|
||||||
}
|
|
||||||
|
|
||||||
get nameIgnorePrefix() {
|
|
||||||
if (!this.name) return ''
|
|
||||||
return getTitleIgnorePrefix(this.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
nameIgnorePrefix: getTitlePrefixAtEnd(this.name),
|
|
||||||
description: this.description,
|
|
||||||
addedAt: this.addedAt,
|
|
||||||
updatedAt: this.updatedAt,
|
|
||||||
libraryId: this.libraryId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinimal(sequence) {
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
name: this.name,
|
|
||||||
sequence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(data, libraryId) {
|
|
||||||
this.id = uuidv4()
|
|
||||||
this.name = data.name
|
|
||||||
this.description = data.description || null
|
|
||||||
this.addedAt = Date.now()
|
|
||||||
this.updatedAt = Date.now()
|
|
||||||
this.libraryId = libraryId
|
|
||||||
}
|
|
||||||
|
|
||||||
update(series) {
|
|
||||||
if (!series) return false
|
|
||||||
const keysToUpdate = ['name', 'description']
|
|
||||||
let hasUpdated = false
|
|
||||||
for (const key of keysToUpdate) {
|
|
||||||
if (series[key] !== undefined && series[key] !== this[key]) {
|
|
||||||
this[key] = series[key]
|
|
||||||
hasUpdated = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdated
|
|
||||||
}
|
|
||||||
|
|
||||||
checkNameEquals(name) {
|
|
||||||
if (!name || !this.name) return false
|
|
||||||
return this.name.toLowerCase() == name.toLowerCase().trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = Series
|
|
||||||
@@ -43,14 +43,13 @@ class LibraryFile {
|
|||||||
if (globals.SupportedImageTypes.includes(this.metadata.format)) return 'image'
|
if (globals.SupportedImageTypes.includes(this.metadata.format)) return 'image'
|
||||||
if (globals.SupportedAudioTypes.includes(this.metadata.format)) return 'audio'
|
if (globals.SupportedAudioTypes.includes(this.metadata.format)) return 'audio'
|
||||||
if (globals.SupportedEbookTypes.includes(this.metadata.format)) return 'ebook'
|
if (globals.SupportedEbookTypes.includes(this.metadata.format)) return 'ebook'
|
||||||
if (globals.SupportedVideoTypes.includes(this.metadata.format)) return 'video'
|
|
||||||
if (globals.TextFileTypes.includes(this.metadata.format)) return 'text'
|
if (globals.TextFileTypes.includes(this.metadata.format)) return 'text'
|
||||||
if (globals.MetadataFileTypes.includes(this.metadata.format)) return 'metadata'
|
if (globals.MetadataFileTypes.includes(this.metadata.format)) return 'metadata'
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
}
|
}
|
||||||
|
|
||||||
get isMediaFile() {
|
get isMediaFile() {
|
||||||
return this.fileType === 'audio' || this.fileType === 'ebook' || this.fileType === 'video'
|
return this.fileType === 'audio' || this.fileType === 'ebook'
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEBookFile() {
|
get isEBookFile() {
|
||||||
@@ -75,4 +74,4 @@ class LibraryFile {
|
|||||||
this.updatedAt = Date.now()
|
this.updatedAt = Date.now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = LibraryFile
|
module.exports = LibraryFile
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
const { VideoMimeType } = require('../../utils/constants')
|
|
||||||
const FileMetadata = require('../metadata/FileMetadata')
|
|
||||||
|
|
||||||
class VideoFile {
|
|
||||||
constructor(data) {
|
|
||||||
this.index = null
|
|
||||||
this.ino = null
|
|
||||||
this.metadata = null
|
|
||||||
this.addedAt = null
|
|
||||||
this.updatedAt = null
|
|
||||||
|
|
||||||
this.format = null
|
|
||||||
this.duration = null
|
|
||||||
this.bitRate = null
|
|
||||||
this.language = null
|
|
||||||
this.codec = null
|
|
||||||
this.timeBase = null
|
|
||||||
this.frameRate = null
|
|
||||||
this.width = null
|
|
||||||
this.height = null
|
|
||||||
this.embeddedCoverArt = null
|
|
||||||
|
|
||||||
this.invalid = false
|
|
||||||
this.error = null
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
this.construct(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
index: this.index,
|
|
||||||
ino: this.ino,
|
|
||||||
metadata: this.metadata.toJSON(),
|
|
||||||
addedAt: this.addedAt,
|
|
||||||
updatedAt: this.updatedAt,
|
|
||||||
invalid: !!this.invalid,
|
|
||||||
error: this.error || null,
|
|
||||||
format: this.format,
|
|
||||||
duration: this.duration,
|
|
||||||
bitRate: this.bitRate,
|
|
||||||
language: this.language,
|
|
||||||
codec: this.codec,
|
|
||||||
timeBase: this.timeBase,
|
|
||||||
frameRate: this.frameRate,
|
|
||||||
width: this.width,
|
|
||||||
height: this.height,
|
|
||||||
embeddedCoverArt: this.embeddedCoverArt,
|
|
||||||
mimeType: this.mimeType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(data) {
|
|
||||||
this.index = data.index
|
|
||||||
this.ino = data.ino
|
|
||||||
this.metadata = new FileMetadata(data.metadata || {})
|
|
||||||
this.addedAt = data.addedAt
|
|
||||||
this.updatedAt = data.updatedAt
|
|
||||||
this.invalid = !!data.invalid
|
|
||||||
this.error = data.error || null
|
|
||||||
|
|
||||||
this.format = data.format
|
|
||||||
this.duration = data.duration
|
|
||||||
this.bitRate = data.bitRate
|
|
||||||
this.language = data.language
|
|
||||||
this.codec = data.codec || null
|
|
||||||
this.timeBase = data.timeBase
|
|
||||||
this.frameRate = data.frameRate
|
|
||||||
this.width = data.width
|
|
||||||
this.height = data.height
|
|
||||||
this.embeddedCoverArt = data.embeddedCoverArt || null
|
|
||||||
}
|
|
||||||
|
|
||||||
get mimeType() {
|
|
||||||
var format = this.metadata.format.toUpperCase()
|
|
||||||
if (VideoMimeType[format]) {
|
|
||||||
return VideoMimeType[format]
|
|
||||||
} else {
|
|
||||||
return VideoMimeType.MP4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clone() {
|
|
||||||
return new VideoFile(this.toJSON())
|
|
||||||
}
|
|
||||||
|
|
||||||
setDataFromProbe(libraryFile, probeData) {
|
|
||||||
this.ino = libraryFile.ino || null
|
|
||||||
|
|
||||||
this.metadata = libraryFile.metadata.clone()
|
|
||||||
this.addedAt = Date.now()
|
|
||||||
this.updatedAt = Date.now()
|
|
||||||
|
|
||||||
const videoStream = probeData.videoStream
|
|
||||||
|
|
||||||
this.format = probeData.format
|
|
||||||
this.duration = probeData.duration
|
|
||||||
this.bitRate = videoStream.bit_rate || probeData.bitRate || null
|
|
||||||
this.language = probeData.language
|
|
||||||
this.codec = videoStream.codec || null
|
|
||||||
this.timeBase = videoStream.time_base
|
|
||||||
this.frameRate = videoStream.frame_rate || null
|
|
||||||
this.width = videoStream.width || null
|
|
||||||
this.height = videoStream.height || null
|
|
||||||
this.embeddedCoverArt = probeData.embeddedCoverArt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = VideoFile
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
const Path = require('path')
|
|
||||||
const { encodeUriPath } = require('../../utils/fileUtils')
|
|
||||||
|
|
||||||
class VideoTrack {
|
|
||||||
constructor() {
|
|
||||||
this.index = null
|
|
||||||
this.duration = null
|
|
||||||
this.title = null
|
|
||||||
this.contentUrl = null
|
|
||||||
this.mimeType = null
|
|
||||||
this.codec = null
|
|
||||||
this.metadata = null
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
index: this.index,
|
|
||||||
duration: this.duration,
|
|
||||||
title: this.title,
|
|
||||||
contentUrl: this.contentUrl,
|
|
||||||
mimeType: this.mimeType,
|
|
||||||
codec: this.codec,
|
|
||||||
metadata: this.metadata ? this.metadata.toJSON() : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(itemId, videoFile) {
|
|
||||||
this.index = videoFile.index
|
|
||||||
this.duration = videoFile.duration
|
|
||||||
this.title = videoFile.metadata.filename || ''
|
|
||||||
this.contentUrl = Path.join(`${global.RouterBasePath}/api/items/${itemId}/file/${videoFile.ino}`, encodeUriPath(videoFile.metadata.relPath))
|
|
||||||
this.mimeType = videoFile.mimeType
|
|
||||||
this.codec = videoFile.codec
|
|
||||||
this.metadata = videoFile.metadata.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
setFromStream(title, duration, contentUrl) {
|
|
||||||
this.index = 1
|
|
||||||
this.duration = duration
|
|
||||||
this.title = title
|
|
||||||
this.contentUrl = contentUrl
|
|
||||||
this.mimeType = 'application/vnd.apple.mpegurl'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = VideoTrack
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
const Logger = require('../../Logger')
|
|
||||||
const AudioFile = require('../files/AudioFile')
|
|
||||||
const AudioTrack = require('../files/AudioTrack')
|
|
||||||
const MusicMetadata = require('../metadata/MusicMetadata')
|
|
||||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
|
||||||
const { filePathToPOSIX } = require('../../utils/fileUtils')
|
|
||||||
|
|
||||||
class Music {
|
|
||||||
constructor(music) {
|
|
||||||
this.libraryItemId = null
|
|
||||||
this.metadata = null
|
|
||||||
this.coverPath = null
|
|
||||||
this.tags = []
|
|
||||||
this.audioFile = null
|
|
||||||
|
|
||||||
if (music) {
|
|
||||||
this.construct(music)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(music) {
|
|
||||||
this.libraryItemId = music.libraryItemId
|
|
||||||
this.metadata = new MusicMetadata(music.metadata)
|
|
||||||
this.coverPath = music.coverPath
|
|
||||||
this.tags = [...music.tags]
|
|
||||||
this.audioFile = new AudioFile(music.audioFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
libraryItemId: this.libraryItemId,
|
|
||||||
metadata: this.metadata.toJSON(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
audioFile: this.audioFile.toJSON(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinified() {
|
|
||||||
return {
|
|
||||||
metadata: this.metadata.toJSONMinified(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
audioFile: this.audioFile.toJSON(),
|
|
||||||
duration: this.duration,
|
|
||||||
size: this.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONExpanded() {
|
|
||||||
return {
|
|
||||||
libraryItemId: this.libraryItemId,
|
|
||||||
metadata: this.metadata.toJSONExpanded(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
audioFile: this.audioFile.toJSON(),
|
|
||||||
duration: this.duration,
|
|
||||||
size: this.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get size() {
|
|
||||||
return this.audioFile.metadata.size
|
|
||||||
}
|
|
||||||
get hasMediaEntities() {
|
|
||||||
return !!this.audioFile
|
|
||||||
}
|
|
||||||
get duration() {
|
|
||||||
return this.audioFile.duration || 0
|
|
||||||
}
|
|
||||||
get audioTrack() {
|
|
||||||
const audioTrack = new AudioTrack()
|
|
||||||
audioTrack.setData(this.libraryItemId, this.audioFile, 0)
|
|
||||||
return audioTrack
|
|
||||||
}
|
|
||||||
get numTracks() {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
const json = this.toJSON()
|
|
||||||
delete json.episodes // do not update media entities here
|
|
||||||
let hasUpdates = false
|
|
||||||
for (const key in json) {
|
|
||||||
if (payload[key] !== undefined) {
|
|
||||||
if (key === 'metadata') {
|
|
||||||
if (this.metadata.update(payload.metadata)) {
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
} else if (!areEquivalent(payload[key], json[key])) {
|
|
||||||
this[key] = copyValue(payload[key])
|
|
||||||
Logger.debug('[Podcast] Key updated', key, this[key])
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCover(coverPath) {
|
|
||||||
coverPath = filePathToPOSIX(coverPath)
|
|
||||||
if (this.coverPath === coverPath) return false
|
|
||||||
this.coverPath = coverPath
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFileWithInode(inode) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
findFileWithInode(inode) {
|
|
||||||
return (this.audioFile && this.audioFile.ino === inode) ? this.audioFile : null
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(mediaData) {
|
|
||||||
this.metadata = new MusicMetadata()
|
|
||||||
if (mediaData.metadata) {
|
|
||||||
this.metadata.setData(mediaData.metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.coverPath = mediaData.coverPath || null
|
|
||||||
}
|
|
||||||
|
|
||||||
setAudioFile(audioFile) {
|
|
||||||
this.audioFile = audioFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only checks container format
|
|
||||||
checkCanDirectPlay(payload) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
getDirectPlayTracklist() {
|
|
||||||
return [this.audioTrack]
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlaybackTitle() {
|
|
||||||
return this.metadata.title
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlaybackAuthor() {
|
|
||||||
return this.metadata.artist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = Music
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
const Logger = require('../../Logger')
|
|
||||||
const VideoFile = require('../files/VideoFile')
|
|
||||||
const VideoTrack = require('../files/VideoTrack')
|
|
||||||
const VideoMetadata = require('../metadata/VideoMetadata')
|
|
||||||
const { areEquivalent, copyValue } = require('../../utils/index')
|
|
||||||
const { filePathToPOSIX } = require('../../utils/fileUtils')
|
|
||||||
|
|
||||||
class Video {
|
|
||||||
constructor(video) {
|
|
||||||
this.libraryItemId = null
|
|
||||||
this.metadata = null
|
|
||||||
this.coverPath = null
|
|
||||||
this.tags = []
|
|
||||||
this.episodes = []
|
|
||||||
|
|
||||||
this.autoDownloadEpisodes = false
|
|
||||||
this.lastEpisodeCheck = 0
|
|
||||||
|
|
||||||
this.lastCoverSearch = null
|
|
||||||
this.lastCoverSearchQuery = null
|
|
||||||
|
|
||||||
if (video) {
|
|
||||||
this.construct(video)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(video) {
|
|
||||||
this.libraryItemId = video.libraryItemId
|
|
||||||
this.metadata = new VideoMetadata(video.metadata)
|
|
||||||
this.coverPath = video.coverPath
|
|
||||||
this.tags = [...video.tags]
|
|
||||||
this.videoFile = new VideoFile(video.videoFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
libraryItemId: this.libraryItemId,
|
|
||||||
metadata: this.metadata.toJSONExpanded(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
videoFile: this.videoFile.toJSON()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinified() {
|
|
||||||
return {
|
|
||||||
metadata: this.metadata.toJSONMinified(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
videoFile: this.videoFile.toJSON(),
|
|
||||||
size: this.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONExpanded() {
|
|
||||||
return {
|
|
||||||
libraryItemId: this.libraryItemId,
|
|
||||||
metadata: this.metadata.toJSONExpanded(),
|
|
||||||
coverPath: this.coverPath,
|
|
||||||
tags: [...this.tags],
|
|
||||||
videoFile: this.videoFile.toJSON(),
|
|
||||||
size: this.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get size() {
|
|
||||||
return this.videoFile.metadata.size
|
|
||||||
}
|
|
||||||
get hasMediaEntities() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
get duration() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
var json = this.toJSON()
|
|
||||||
var hasUpdates = false
|
|
||||||
for (const key in json) {
|
|
||||||
if (payload[key] !== undefined) {
|
|
||||||
if (key === 'metadata') {
|
|
||||||
if (this.metadata.update(payload.metadata)) {
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
} else if (!areEquivalent(payload[key], json[key])) {
|
|
||||||
this[key] = copyValue(payload[key])
|
|
||||||
Logger.debug('[Video] Key updated', key, this[key])
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCover(coverPath) {
|
|
||||||
coverPath = filePathToPOSIX(coverPath)
|
|
||||||
if (this.coverPath === coverPath) return false
|
|
||||||
this.coverPath = coverPath
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFileWithInode(inode) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
findFileWithInode(inode) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
setVideoFile(videoFile) {
|
|
||||||
this.videoFile = videoFile
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(mediaMetadata) {
|
|
||||||
this.metadata = new VideoMetadata()
|
|
||||||
if (mediaMetadata.metadata) {
|
|
||||||
this.metadata.setData(mediaMetadata.metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.coverPath = mediaMetadata.coverPath || null
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlaybackTitle() {
|
|
||||||
return this.metadata.title
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlaybackAuthor() {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
getVideoTrack() {
|
|
||||||
var track = new VideoTrack()
|
|
||||||
track.setData(this.libraryItemId, this.videoFile)
|
|
||||||
return track
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = Video
|
|
||||||
@@ -1,307 +0,0 @@
|
|||||||
const Logger = require('../../Logger')
|
|
||||||
const { areEquivalent, copyValue, getTitleIgnorePrefix, getTitlePrefixAtEnd } = require('../../utils/index')
|
|
||||||
|
|
||||||
class MusicMetadata {
|
|
||||||
constructor(metadata) {
|
|
||||||
this.title = null
|
|
||||||
this.artists = [] // Array of strings
|
|
||||||
this.album = null
|
|
||||||
this.albumArtist = null
|
|
||||||
this.genres = [] // Array of strings
|
|
||||||
this.composer = null
|
|
||||||
this.originalYear = null
|
|
||||||
this.releaseDate = null
|
|
||||||
this.releaseCountry = null
|
|
||||||
this.releaseType = null
|
|
||||||
this.releaseStatus = null
|
|
||||||
this.recordLabel = null
|
|
||||||
this.language = null
|
|
||||||
this.explicit = false
|
|
||||||
|
|
||||||
this.discNumber = null
|
|
||||||
this.discTotal = null
|
|
||||||
this.trackNumber = null
|
|
||||||
this.trackTotal = null
|
|
||||||
|
|
||||||
this.isrc = null
|
|
||||||
this.musicBrainzTrackId = null
|
|
||||||
this.musicBrainzAlbumId = null
|
|
||||||
this.musicBrainzAlbumArtistId = null
|
|
||||||
this.musicBrainzArtistId = null
|
|
||||||
|
|
||||||
if (metadata) {
|
|
||||||
this.construct(metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(metadata) {
|
|
||||||
this.title = metadata.title
|
|
||||||
this.artists = metadata.artists ? [...metadata.artists] : []
|
|
||||||
this.album = metadata.album
|
|
||||||
this.albumArtist = metadata.albumArtist
|
|
||||||
this.genres = metadata.genres ? [...metadata.genres] : []
|
|
||||||
this.composer = metadata.composer || null
|
|
||||||
this.originalYear = metadata.originalYear || null
|
|
||||||
this.releaseDate = metadata.releaseDate || null
|
|
||||||
this.releaseCountry = metadata.releaseCountry || null
|
|
||||||
this.releaseType = metadata.releaseType || null
|
|
||||||
this.releaseStatus = metadata.releaseStatus || null
|
|
||||||
this.recordLabel = metadata.recordLabel || null
|
|
||||||
this.language = metadata.language || null
|
|
||||||
this.explicit = !!metadata.explicit
|
|
||||||
this.discNumber = metadata.discNumber || null
|
|
||||||
this.discTotal = metadata.discTotal || null
|
|
||||||
this.trackNumber = metadata.trackNumber || null
|
|
||||||
this.trackTotal = metadata.trackTotal || null
|
|
||||||
this.isrc = metadata.isrc || null
|
|
||||||
this.musicBrainzTrackId = metadata.musicBrainzTrackId || null
|
|
||||||
this.musicBrainzAlbumId = metadata.musicBrainzAlbumId || null
|
|
||||||
this.musicBrainzAlbumArtistId = metadata.musicBrainzAlbumArtistId || null
|
|
||||||
this.musicBrainzArtistId = metadata.musicBrainzArtistId || null
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
title: this.title,
|
|
||||||
artists: [...this.artists],
|
|
||||||
album: this.album,
|
|
||||||
albumArtist: this.albumArtist,
|
|
||||||
genres: [...this.genres],
|
|
||||||
composer: this.composer,
|
|
||||||
originalYear: this.originalYear,
|
|
||||||
releaseDate: this.releaseDate,
|
|
||||||
releaseCountry: this.releaseCountry,
|
|
||||||
releaseType: this.releaseType,
|
|
||||||
releaseStatus: this.releaseStatus,
|
|
||||||
recordLabel: this.recordLabel,
|
|
||||||
language: this.language,
|
|
||||||
explicit: this.explicit,
|
|
||||||
discNumber: this.discNumber,
|
|
||||||
discTotal: this.discTotal,
|
|
||||||
trackNumber: this.trackNumber,
|
|
||||||
trackTotal: this.trackTotal,
|
|
||||||
isrc: this.isrc,
|
|
||||||
musicBrainzTrackId: this.musicBrainzTrackId,
|
|
||||||
musicBrainzAlbumId: this.musicBrainzAlbumId,
|
|
||||||
musicBrainzAlbumArtistId: this.musicBrainzAlbumArtistId,
|
|
||||||
musicBrainzArtistId: this.musicBrainzArtistId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinified() {
|
|
||||||
return {
|
|
||||||
title: this.title,
|
|
||||||
titleIgnorePrefix: this.titlePrefixAtEnd,
|
|
||||||
artists: [...this.artists],
|
|
||||||
album: this.album,
|
|
||||||
albumArtist: this.albumArtist,
|
|
||||||
genres: [...this.genres],
|
|
||||||
composer: this.composer,
|
|
||||||
originalYear: this.originalYear,
|
|
||||||
releaseDate: this.releaseDate,
|
|
||||||
releaseCountry: this.releaseCountry,
|
|
||||||
releaseType: this.releaseType,
|
|
||||||
releaseStatus: this.releaseStatus,
|
|
||||||
recordLabel: this.recordLabel,
|
|
||||||
language: this.language,
|
|
||||||
explicit: this.explicit,
|
|
||||||
discNumber: this.discNumber,
|
|
||||||
discTotal: this.discTotal,
|
|
||||||
trackNumber: this.trackNumber,
|
|
||||||
trackTotal: this.trackTotal,
|
|
||||||
isrc: this.isrc,
|
|
||||||
musicBrainzTrackId: this.musicBrainzTrackId,
|
|
||||||
musicBrainzAlbumId: this.musicBrainzAlbumId,
|
|
||||||
musicBrainzAlbumArtistId: this.musicBrainzAlbumArtistId,
|
|
||||||
musicBrainzArtistId: this.musicBrainzArtistId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONExpanded() {
|
|
||||||
return this.toJSONMinified()
|
|
||||||
}
|
|
||||||
|
|
||||||
clone() {
|
|
||||||
return new MusicMetadata(this.toJSON())
|
|
||||||
}
|
|
||||||
|
|
||||||
get titleIgnorePrefix() {
|
|
||||||
return getTitleIgnorePrefix(this.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
get titlePrefixAtEnd() {
|
|
||||||
return getTitlePrefixAtEnd(this.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(mediaMetadata = {}) {
|
|
||||||
this.title = mediaMetadata.title || null
|
|
||||||
this.artist = mediaMetadata.artist || null
|
|
||||||
this.album = mediaMetadata.album || null
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
const json = this.toJSON()
|
|
||||||
let hasUpdates = false
|
|
||||||
for (const key in json) {
|
|
||||||
if (payload[key] !== undefined) {
|
|
||||||
if (!areEquivalent(payload[key], json[key])) {
|
|
||||||
this[key] = copyValue(payload[key])
|
|
||||||
Logger.debug('[MusicMetadata] Key updated', key, this[key])
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
|
|
||||||
parseArtistsTag(artistsTag) {
|
|
||||||
if (!artistsTag || !artistsTag.length) return []
|
|
||||||
const separators = ['/', '//', ';']
|
|
||||||
for (let i = 0; i < separators.length; i++) {
|
|
||||||
if (artistsTag.includes(separators[i])) {
|
|
||||||
return artistsTag.split(separators[i]).map(artist => artist.trim()).filter(a => !!a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [artistsTag]
|
|
||||||
}
|
|
||||||
|
|
||||||
parseGenresTag(genreTag) {
|
|
||||||
if (!genreTag || !genreTag.length) return []
|
|
||||||
const separators = ['/', '//', ';']
|
|
||||||
for (let i = 0; i < separators.length; i++) {
|
|
||||||
if (genreTag.includes(separators[i])) {
|
|
||||||
return genreTag.split(separators[i]).map(genre => genre.trim()).filter(g => !!g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [genreTag]
|
|
||||||
}
|
|
||||||
|
|
||||||
setDataFromAudioMetaTags(audioFileMetaTags, overrideExistingDetails = false) {
|
|
||||||
const MetadataMapArray = [
|
|
||||||
{
|
|
||||||
tag: 'tagTitle',
|
|
||||||
key: 'title',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagArtist',
|
|
||||||
key: 'artists'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagAlbumArtist',
|
|
||||||
key: 'albumArtist'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagAlbum',
|
|
||||||
key: 'album',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagPublisher',
|
|
||||||
key: 'recordLabel'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagComposer',
|
|
||||||
key: 'composer'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagDate',
|
|
||||||
key: 'releaseDate'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagReleaseCountry',
|
|
||||||
key: 'releaseCountry'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagReleaseType',
|
|
||||||
key: 'releaseType'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagReleaseStatus',
|
|
||||||
key: 'releaseStatus'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagOriginalYear',
|
|
||||||
key: 'originalYear'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagGenre',
|
|
||||||
key: 'genres'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagLanguage',
|
|
||||||
key: 'language'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagLanguage',
|
|
||||||
key: 'language'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagISRC',
|
|
||||||
key: 'isrc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagMusicBrainzTrackId',
|
|
||||||
key: 'musicBrainzTrackId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagMusicBrainzAlbumId',
|
|
||||||
key: 'musicBrainzAlbumId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagMusicBrainzAlbumArtistId',
|
|
||||||
key: 'musicBrainzAlbumArtistId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'tagMusicBrainzArtistId',
|
|
||||||
key: 'musicBrainzArtistId'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'trackNumber',
|
|
||||||
key: 'trackNumber'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'trackTotal',
|
|
||||||
key: 'trackTotal'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'discNumber',
|
|
||||||
key: 'discNumber'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: 'discTotal',
|
|
||||||
key: 'discTotal'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const updatePayload = {}
|
|
||||||
|
|
||||||
// Metadata is only mapped to the music track if it is empty
|
|
||||||
MetadataMapArray.forEach((mapping) => {
|
|
||||||
let value = audioFileMetaTags[mapping.tag]
|
|
||||||
|
|
||||||
// let tagToUse = mapping.tag
|
|
||||||
if (!value && mapping.altTag) {
|
|
||||||
value = audioFileMetaTags[mapping.altTag]
|
|
||||||
// tagToUse = mapping.altTag
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value && (typeof value === 'string' || typeof value === 'number')) {
|
|
||||||
value = value.toString().trim() // Trim whitespace
|
|
||||||
|
|
||||||
if (mapping.key === 'artists' && (!this.artists.length || overrideExistingDetails)) {
|
|
||||||
updatePayload.artists = this.parseArtistsTag(value)
|
|
||||||
} else if (mapping.key === 'genres' && (!this.genres.length || overrideExistingDetails)) {
|
|
||||||
updatePayload.genres = this.parseGenresTag(value)
|
|
||||||
} else if (!this[mapping.key] || overrideExistingDetails) {
|
|
||||||
updatePayload[mapping.key] = value
|
|
||||||
// Logger.debug(`[Book] Mapping metadata to key ${tagToUse} => ${mapping.key}: ${updatePayload[mapping.key]}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Object.keys(updatePayload).length) {
|
|
||||||
return this.update(updatePayload)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = MusicMetadata
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
const Logger = require('../../Logger')
|
|
||||||
const { areEquivalent, copyValue, getTitleIgnorePrefix, getTitlePrefixAtEnd } = require('../../utils/index')
|
|
||||||
|
|
||||||
class VideoMetadata {
|
|
||||||
constructor(metadata) {
|
|
||||||
this.title = null
|
|
||||||
this.description = null
|
|
||||||
this.explicit = false
|
|
||||||
this.language = null
|
|
||||||
|
|
||||||
if (metadata) {
|
|
||||||
this.construct(metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
construct(metadata) {
|
|
||||||
this.title = metadata.title
|
|
||||||
this.description = metadata.description
|
|
||||||
this.explicit = metadata.explicit
|
|
||||||
this.language = metadata.language || null
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
return {
|
|
||||||
title: this.title,
|
|
||||||
description: this.description,
|
|
||||||
explicit: this.explicit,
|
|
||||||
language: this.language
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONMinified() {
|
|
||||||
return {
|
|
||||||
title: this.title,
|
|
||||||
titleIgnorePrefix: this.titlePrefixAtEnd,
|
|
||||||
description: this.description,
|
|
||||||
explicit: this.explicit,
|
|
||||||
language: this.language
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSONExpanded() {
|
|
||||||
return this.toJSONMinified()
|
|
||||||
}
|
|
||||||
|
|
||||||
clone() {
|
|
||||||
return new VideoMetadata(this.toJSON())
|
|
||||||
}
|
|
||||||
|
|
||||||
get titleIgnorePrefix() {
|
|
||||||
return getTitleIgnorePrefix(this.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
get titlePrefixAtEnd() {
|
|
||||||
return getTitlePrefixAtEnd(this.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
setData(mediaMetadata = {}) {
|
|
||||||
this.title = mediaMetadata.title || null
|
|
||||||
this.description = mediaMetadata.description || null
|
|
||||||
this.explicit = !!mediaMetadata.explicit
|
|
||||||
this.language = mediaMetadata.language || null
|
|
||||||
}
|
|
||||||
|
|
||||||
update(payload) {
|
|
||||||
var json = this.toJSON()
|
|
||||||
var hasUpdates = false
|
|
||||||
for (const key in json) {
|
|
||||||
if (payload[key] !== undefined) {
|
|
||||||
if (!areEquivalent(payload[key], json[key])) {
|
|
||||||
this[key] = copyValue(payload[key])
|
|
||||||
Logger.debug('[VideoMetadata] Key updated', key, this[key])
|
|
||||||
hasUpdates = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasUpdates
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = VideoMetadata
|
|
||||||
@@ -33,7 +33,7 @@ const CustomMetadataProviderController = require('../controllers/CustomMetadataP
|
|||||||
const MiscController = require('../controllers/MiscController')
|
const MiscController = require('../controllers/MiscController')
|
||||||
const ShareController = require('../controllers/ShareController')
|
const ShareController = require('../controllers/ShareController')
|
||||||
|
|
||||||
const Series = require('../objects/entities/Series')
|
const { getTitleIgnorePrefix } = require('../utils/index')
|
||||||
|
|
||||||
class ApiRouter {
|
class ApiRouter {
|
||||||
constructor(Server) {
|
constructor(Server) {
|
||||||
@@ -524,13 +524,15 @@ class ApiRouter {
|
|||||||
async removeEmptySeries(series) {
|
async removeEmptySeries(series) {
|
||||||
await this.rssFeedManager.closeFeedForEntityId(series.id)
|
await this.rssFeedManager.closeFeedForEntityId(series.id)
|
||||||
Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`)
|
Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`)
|
||||||
await Database.removeSeries(series.id)
|
|
||||||
// Remove series from library filter data
|
// Remove series from library filter data
|
||||||
Database.removeSeriesFromFilterData(series.libraryId, series.id)
|
Database.removeSeriesFromFilterData(series.libraryId, series.id)
|
||||||
SocketAuthority.emitter('series_removed', {
|
SocketAuthority.emitter('series_removed', {
|
||||||
id: series.id,
|
id: series.id,
|
||||||
libraryId: series.libraryId
|
libraryId: series.libraryId
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await series.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserListeningSessionsHelper(userId) {
|
async getUserListeningSessionsHelper(userId) {
|
||||||
@@ -619,6 +621,7 @@ class ApiRouter {
|
|||||||
if (!author) {
|
if (!author) {
|
||||||
author = await Database.authorModel.create({
|
author = await Database.authorModel.create({
|
||||||
name: authorName,
|
name: authorName,
|
||||||
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||||
libraryId
|
libraryId
|
||||||
})
|
})
|
||||||
Logger.debug(`[ApiRouter] Creating new author "${author.name}"`)
|
Logger.debug(`[ApiRouter] Creating new author "${author.name}"`)
|
||||||
@@ -663,11 +666,14 @@ class ApiRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mediaMetadata.series[i].id) {
|
if (!mediaMetadata.series[i].id) {
|
||||||
let seriesItem = await Database.seriesModel.getOldByNameAndLibrary(seriesName, libraryId)
|
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesName, libraryId)
|
||||||
if (!seriesItem) {
|
if (!seriesItem) {
|
||||||
seriesItem = new Series()
|
seriesItem = await Database.seriesModel.create({
|
||||||
seriesItem.setData(mediaMetadata.series[i], libraryId)
|
name: seriesName,
|
||||||
Logger.debug(`[ApiRouter] Created new series "${seriesItem.name}"`)
|
nameIgnorePrefix: getTitleIgnorePrefix(seriesName),
|
||||||
|
libraryId
|
||||||
|
})
|
||||||
|
Logger.debug(`[ApiRouter] Creating new series "${seriesItem.name}"`)
|
||||||
newSeries.push(seriesItem)
|
newSeries.push(seriesItem)
|
||||||
// Update filter data
|
// Update filter data
|
||||||
Database.addSeriesToFilterData(libraryId, seriesItem.name, seriesItem.id)
|
Database.addSeriesToFilterData(libraryId, seriesItem.name, seriesItem.id)
|
||||||
@@ -680,10 +686,9 @@ class ApiRouter {
|
|||||||
// Remove series without an id
|
// Remove series without an id
|
||||||
mediaMetadata.series = mediaMetadata.series.filter((se) => se.id)
|
mediaMetadata.series = mediaMetadata.series.filter((se) => se.id)
|
||||||
if (newSeries.length) {
|
if (newSeries.length) {
|
||||||
await Database.createBulkSeries(newSeries)
|
|
||||||
SocketAuthority.emitter(
|
SocketAuthority.emitter(
|
||||||
'multiple_series_added',
|
'multiple_series_added',
|
||||||
newSeries.map((se) => se.toJSON())
|
newSeries.map((se) => se.toOldJSON())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+133
-125
@@ -1,4 +1,4 @@
|
|||||||
const uuidv4 = require("uuid").v4
|
const uuidv4 = require('uuid').v4
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const sequelize = require('sequelize')
|
const sequelize = require('sequelize')
|
||||||
const { LogLevel } = require('../utils/constants')
|
const { LogLevel } = require('../utils/constants')
|
||||||
@@ -13,14 +13,14 @@ const AudioFile = require('../objects/files/AudioFile')
|
|||||||
const CoverManager = require('../managers/CoverManager')
|
const CoverManager = require('../managers/CoverManager')
|
||||||
const LibraryFile = require('../objects/files/LibraryFile')
|
const LibraryFile = require('../objects/files/LibraryFile')
|
||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const fsExtra = require("../libs/fsExtra")
|
const fsExtra = require('../libs/fsExtra')
|
||||||
const BookFinder = require('../finders/BookFinder')
|
const BookFinder = require('../finders/BookFinder')
|
||||||
|
|
||||||
const LibraryScan = require("./LibraryScan")
|
const LibraryScan = require('./LibraryScan')
|
||||||
const OpfFileScanner = require('./OpfFileScanner')
|
const OpfFileScanner = require('./OpfFileScanner')
|
||||||
const NfoFileScanner = require('./NfoFileScanner')
|
const NfoFileScanner = require('./NfoFileScanner')
|
||||||
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
|
||||||
const EBookFile = require("../objects/files/EBookFile")
|
const EBookFile = require('../objects/files/EBookFile')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata for books pulled from files
|
* Metadata for books pulled from files
|
||||||
@@ -46,13 +46,13 @@ const EBookFile = require("../objects/files/EBookFile")
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class BookScanner {
|
class BookScanner {
|
||||||
constructor() { }
|
constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('../models/LibraryItem')} existingLibraryItem
|
* @param {import('../models/LibraryItem')} existingLibraryItem
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @returns {Promise<{libraryItem:import('../models/LibraryItem'), wasUpdated:boolean}>}
|
* @returns {Promise<{libraryItem:import('../models/LibraryItem'), wasUpdated:boolean}>}
|
||||||
*/
|
*/
|
||||||
async rescanExistingBookLibraryItem(existingLibraryItem, libraryItemData, librarySettings, libraryScan) {
|
async rescanExistingBookLibraryItem(existingLibraryItem, libraryItemData, librarySettings, libraryScan) {
|
||||||
@@ -81,19 +81,23 @@ class BookScanner {
|
|||||||
let hasMediaChanges = libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== media.audioFiles.length
|
let hasMediaChanges = libraryItemData.hasAudioFileChanges || libraryItemData.audioLibraryFiles.length !== media.audioFiles.length
|
||||||
if (hasMediaChanges) {
|
if (hasMediaChanges) {
|
||||||
// Filter out audio files that were removed
|
// Filter out audio files that were removed
|
||||||
media.audioFiles = media.audioFiles.filter(af => !libraryItemData.checkAudioFileRemoved(af))
|
media.audioFiles = media.audioFiles.filter((af) => !libraryItemData.checkAudioFileRemoved(af))
|
||||||
|
|
||||||
// Update audio files that were modified
|
// Update audio files that were modified
|
||||||
if (libraryItemData.audioLibraryFilesModified.length) {
|
if (libraryItemData.audioLibraryFilesModified.length) {
|
||||||
let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(existingLibraryItem.mediaType, libraryItemData, libraryItemData.audioLibraryFilesModified.map(lf => lf.new))
|
let scannedAudioFiles = await AudioFileScanner.executeMediaFileScans(
|
||||||
|
existingLibraryItem.mediaType,
|
||||||
|
libraryItemData,
|
||||||
|
libraryItemData.audioLibraryFilesModified.map((lf) => lf.new)
|
||||||
|
)
|
||||||
media.audioFiles = media.audioFiles.map((audioFileObj) => {
|
media.audioFiles = media.audioFiles.map((audioFileObj) => {
|
||||||
let matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.metadata.path === audioFileObj.metadata.path)
|
let matchedScannedAudioFile = scannedAudioFiles.find((saf) => saf.metadata.path === audioFileObj.metadata.path)
|
||||||
if (!matchedScannedAudioFile) {
|
if (!matchedScannedAudioFile) {
|
||||||
matchedScannedAudioFile = scannedAudioFiles.find(saf => saf.ino === audioFileObj.ino)
|
matchedScannedAudioFile = scannedAudioFiles.find((saf) => saf.ino === audioFileObj.ino)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedScannedAudioFile) {
|
if (matchedScannedAudioFile) {
|
||||||
scannedAudioFiles = scannedAudioFiles.filter(saf => saf !== matchedScannedAudioFile)
|
scannedAudioFiles = scannedAudioFiles.filter((saf) => saf !== matchedScannedAudioFile)
|
||||||
const audioFile = new AudioFile(audioFileObj)
|
const audioFile = new AudioFile(audioFileObj)
|
||||||
audioFile.updateFromScan(matchedScannedAudioFile)
|
audioFile.updateFromScan(matchedScannedAudioFile)
|
||||||
return audioFile.toJSON()
|
return audioFile.toJSON()
|
||||||
@@ -115,7 +119,7 @@ class BookScanner {
|
|||||||
// Add audio library files that are not already set on the book (safety check)
|
// Add audio library files that are not already set on the book (safety check)
|
||||||
let audioLibraryFilesToAdd = []
|
let audioLibraryFilesToAdd = []
|
||||||
for (const audioLibraryFile of libraryItemData.audioLibraryFiles) {
|
for (const audioLibraryFile of libraryItemData.audioLibraryFiles) {
|
||||||
if (!media.audioFiles.some(af => af.ino === audioLibraryFile.ino)) {
|
if (!media.audioFiles.some((af) => af.ino === audioLibraryFile.ino)) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Existing audio library file "${audioLibraryFile.metadata.relPath}" was not set on book "${media.title}" so setting it now`)
|
libraryScan.addLog(LogLevel.DEBUG, `Existing audio library file "${audioLibraryFile.metadata.relPath}" was not set on book "${media.title}" so setting it now`)
|
||||||
|
|
||||||
audioLibraryFilesToAdd.push(audioLibraryFile)
|
audioLibraryFilesToAdd.push(audioLibraryFile)
|
||||||
@@ -139,14 +143,14 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if cover was removed
|
// Check if cover was removed
|
||||||
if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some(lf => lf.metadata.path === media.coverPath) && !(await fsExtra.pathExists(media.coverPath))) {
|
if (media.coverPath && libraryItemData.imageLibraryFilesRemoved.some((lf) => lf.metadata.path === media.coverPath) && !(await fsExtra.pathExists(media.coverPath))) {
|
||||||
media.coverPath = null
|
media.coverPath = null
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cover if it was modified
|
// Update cover if it was modified
|
||||||
if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) {
|
if (media.coverPath && libraryItemData.imageLibraryFilesModified.length) {
|
||||||
let coverMatch = libraryItemData.imageLibraryFilesModified.find(iFile => iFile.old.metadata.path === media.coverPath)
|
let coverMatch = libraryItemData.imageLibraryFilesModified.find((iFile) => iFile.old.metadata.path === media.coverPath)
|
||||||
if (coverMatch) {
|
if (coverMatch) {
|
||||||
const coverPath = coverMatch.new.metadata.path
|
const coverPath = coverMatch.new.metadata.path
|
||||||
if (coverPath !== media.coverPath) {
|
if (coverPath !== media.coverPath) {
|
||||||
@@ -161,7 +165,7 @@ class BookScanner {
|
|||||||
// Check if cover is not set and image files were found
|
// Check if cover is not set and image files were found
|
||||||
if (!media.coverPath && libraryItemData.imageLibraryFiles.length) {
|
if (!media.coverPath && libraryItemData.imageLibraryFiles.length) {
|
||||||
// Prefer using a cover image with the name "cover" otherwise use the first image
|
// Prefer using a cover image with the name "cover" otherwise use the first image
|
||||||
const coverMatch = libraryItemData.imageLibraryFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = libraryItemData.imageLibraryFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
media.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
media.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
@@ -174,7 +178,7 @@ class BookScanner {
|
|||||||
|
|
||||||
// Update ebook if it was modified
|
// Update ebook if it was modified
|
||||||
if (media.ebookFile && libraryItemData.ebookLibraryFilesModified.length) {
|
if (media.ebookFile && libraryItemData.ebookLibraryFilesModified.length) {
|
||||||
let ebookMatch = libraryItemData.ebookLibraryFilesModified.find(eFile => eFile.old.metadata.path === media.ebookFile.metadata.path)
|
let ebookMatch = libraryItemData.ebookLibraryFilesModified.find((eFile) => eFile.old.metadata.path === media.ebookFile.metadata.path)
|
||||||
if (ebookMatch) {
|
if (ebookMatch) {
|
||||||
const ebookFile = new EBookFile(ebookMatch.new)
|
const ebookFile = new EBookFile(ebookMatch.new)
|
||||||
ebookFile.ebookFormat = ebookFile.metadata.ext.slice(1).toLowerCase()
|
ebookFile.ebookFormat = ebookFile.metadata.ext.slice(1).toLowerCase()
|
||||||
@@ -188,7 +192,7 @@ class BookScanner {
|
|||||||
// Check if ebook is not set and ebooks were found
|
// Check if ebook is not set and ebooks were found
|
||||||
if (!media.ebookFile && !librarySettings.audiobooksOnly && libraryItemData.ebookLibraryFiles.length) {
|
if (!media.ebookFile && !librarySettings.audiobooksOnly && libraryItemData.ebookLibraryFiles.length) {
|
||||||
// Prefer to use an epub ebook then fallback to the first ebook found
|
// Prefer to use an epub ebook then fallback to the first ebook found
|
||||||
let ebookLibraryFile = libraryItemData.ebookLibraryFiles.find(lf => lf.metadata.ext.slice(1).toLowerCase() === 'epub')
|
let ebookLibraryFile = libraryItemData.ebookLibraryFiles.find((lf) => lf.metadata.ext.slice(1).toLowerCase() === 'epub')
|
||||||
if (!ebookLibraryFile) ebookLibraryFile = libraryItemData.ebookLibraryFiles[0]
|
if (!ebookLibraryFile) ebookLibraryFile = libraryItemData.ebookLibraryFiles[0]
|
||||||
ebookLibraryFile = ebookLibraryFile.toJSON()
|
ebookLibraryFile = ebookLibraryFile.toJSON()
|
||||||
// Ebook file is the same as library file except for additional `ebookFormat`
|
// Ebook file is the same as library file except for additional `ebookFormat`
|
||||||
@@ -213,7 +217,7 @@ class BookScanner {
|
|||||||
if (key === 'authors') {
|
if (key === 'authors') {
|
||||||
// Check for authors added
|
// Check for authors added
|
||||||
for (const authorName of bookMetadata.authors) {
|
for (const authorName of bookMetadata.authors) {
|
||||||
if (!media.authors.some(au => au.name === authorName)) {
|
if (!media.authors.some((au) => au.name === authorName)) {
|
||||||
const existingAuthorId = await Database.getAuthorIdByName(libraryItemData.libraryId, authorName)
|
const existingAuthorId = await Database.getAuthorIdByName(libraryItemData.libraryId, authorName)
|
||||||
if (existingAuthorId) {
|
if (existingAuthorId) {
|
||||||
await Database.bookAuthorModel.create({
|
await Database.bookAuthorModel.create({
|
||||||
@@ -225,7 +229,7 @@ class BookScanner {
|
|||||||
} else {
|
} else {
|
||||||
const newAuthor = await Database.authorModel.create({
|
const newAuthor = await Database.authorModel.create({
|
||||||
name: authorName,
|
name: authorName,
|
||||||
lastFirst: parseNameString.nameToLastFirst(authorName),
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||||
libraryId: libraryItemData.libraryId
|
libraryId: libraryItemData.libraryId
|
||||||
})
|
})
|
||||||
await media.addAuthor(newAuthor)
|
await media.addAuthor(newAuthor)
|
||||||
@@ -247,7 +251,7 @@ class BookScanner {
|
|||||||
} else if (key === 'series') {
|
} else if (key === 'series') {
|
||||||
// Check for series added
|
// Check for series added
|
||||||
for (const seriesObj of bookMetadata.series) {
|
for (const seriesObj of bookMetadata.series) {
|
||||||
const existingBookSeries = media.series.find(se => se.name === seriesObj.name)
|
const existingBookSeries = media.series.find((se) => se.name === seriesObj.name)
|
||||||
if (!existingBookSeries) {
|
if (!existingBookSeries) {
|
||||||
const existingSeriesId = await Database.getSeriesIdByName(libraryItemData.libraryId, seriesObj.name)
|
const existingSeriesId = await Database.getSeriesIdByName(libraryItemData.libraryId, seriesObj.name)
|
||||||
if (existingSeriesId) {
|
if (existingSeriesId) {
|
||||||
@@ -278,7 +282,7 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
// Check for series removed
|
// Check for series removed
|
||||||
for (const series of media.series) {
|
for (const series of media.series) {
|
||||||
if (!bookMetadata.series.some(se => se.name === series.name)) {
|
if (!bookMetadata.series.some((se) => se.name === series.name)) {
|
||||||
await series.bookSeries.destroy()
|
await series.bookSeries.destroy()
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed series "${series.name}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" removed series "${series.name}"`)
|
||||||
seriesUpdated = true
|
seriesUpdated = true
|
||||||
@@ -287,21 +291,21 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
} else if (key === 'genres') {
|
} else if (key === 'genres') {
|
||||||
const existingGenres = media.genres || []
|
const existingGenres = media.genres || []
|
||||||
if (bookMetadata.genres.some(g => !existingGenres.includes(g)) || existingGenres.some(g => !bookMetadata.genres.includes(g))) {
|
if (bookMetadata.genres.some((g) => !existingGenres.includes(g)) || existingGenres.some((g) => !bookMetadata.genres.includes(g))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book genres "${existingGenres.join(',')}" => "${bookMetadata.genres.join(',')}" for book "${bookMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book genres "${existingGenres.join(',')}" => "${bookMetadata.genres.join(',')}" for book "${bookMetadata.title}"`)
|
||||||
media.genres = bookMetadata.genres
|
media.genres = bookMetadata.genres
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
} else if (key === 'tags') {
|
} else if (key === 'tags') {
|
||||||
const existingTags = media.tags || []
|
const existingTags = media.tags || []
|
||||||
if (bookMetadata.tags.some(t => !existingTags.includes(t)) || existingTags.some(t => !bookMetadata.tags.includes(t))) {
|
if (bookMetadata.tags.some((t) => !existingTags.includes(t)) || existingTags.some((t) => !bookMetadata.tags.includes(t))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book tags "${existingTags.join(',')}" => "${bookMetadata.tags.join(',')}" for book "${bookMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book tags "${existingTags.join(',')}" => "${bookMetadata.tags.join(',')}" for book "${bookMetadata.title}"`)
|
||||||
media.tags = bookMetadata.tags
|
media.tags = bookMetadata.tags
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
}
|
}
|
||||||
} else if (key === 'narrators') {
|
} else if (key === 'narrators') {
|
||||||
const existingNarrators = media.narrators || []
|
const existingNarrators = media.narrators || []
|
||||||
if (bookMetadata.narrators.some(t => !existingNarrators.includes(t)) || existingNarrators.some(t => !bookMetadata.narrators.includes(t))) {
|
if (bookMetadata.narrators.some((t) => !existingNarrators.includes(t)) || existingNarrators.some((t) => !bookMetadata.narrators.includes(t))) {
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Updating book narrators "${existingNarrators.join(',')}" => "${bookMetadata.narrators.join(',')}" for book "${bookMetadata.title}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Updating book narrators "${existingNarrators.join(',')}" => "${bookMetadata.narrators.join(',')}" for book "${bookMetadata.title}"`)
|
||||||
media.narrators = bookMetadata.narrators
|
media.narrators = bookMetadata.narrators
|
||||||
hasMediaChanges = true
|
hasMediaChanges = true
|
||||||
@@ -333,17 +337,13 @@ class BookScanner {
|
|||||||
if (authorsUpdated) {
|
if (authorsUpdated) {
|
||||||
media.authors = await media.getAuthors({
|
media.authors = await media.getAuthors({
|
||||||
joinTableAttributes: ['createdAt'],
|
joinTableAttributes: ['createdAt'],
|
||||||
order: [
|
order: [sequelize.literal(`bookAuthor.createdAt ASC`)]
|
||||||
sequelize.literal(`bookAuthor.createdAt ASC`)
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (seriesUpdated) {
|
if (seriesUpdated) {
|
||||||
media.series = await media.getSeries({
|
media.series = await media.getSeries({
|
||||||
joinTableAttributes: ['sequence', 'createdAt'],
|
joinTableAttributes: ['sequence', 'createdAt'],
|
||||||
order: [
|
order: [sequelize.literal(`bookSeries.createdAt ASC`)]
|
||||||
sequelize.literal(`bookSeries.createdAt ASC`)
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +367,10 @@ class BookScanner {
|
|||||||
|
|
||||||
// If no cover then search for cover if enabled in server settings
|
// If no cover then search for cover if enabled in server settings
|
||||||
if (!media.coverPath && Database.serverSettings.scannerFindCovers) {
|
if (!media.coverPath && Database.serverSettings.scannerFindCovers) {
|
||||||
const authorName = media.authors.map(au => au.name).filter(au => au).join(', ')
|
const authorName = media.authors
|
||||||
|
.map((au) => au.name)
|
||||||
|
.filter((au) => au)
|
||||||
|
.join(', ')
|
||||||
const coverPath = await this.searchForCover(existingLibraryItem.id, libraryItemDir, media.title, authorName, libraryScan)
|
const coverPath = await this.searchForCover(existingLibraryItem.id, libraryItemDir, media.title, authorName, libraryScan)
|
||||||
if (coverPath) {
|
if (coverPath) {
|
||||||
media.coverPath = coverPath
|
media.coverPath = coverPath
|
||||||
@@ -428,10 +431,10 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @returns {Promise<import('../models/LibraryItem')>}
|
* @returns {Promise<import('../models/LibraryItem')>}
|
||||||
*/
|
*/
|
||||||
async scanNewBookLibraryItem(libraryItemData, librarySettings, libraryScan) {
|
async scanNewBookLibraryItem(libraryItemData, librarySettings, libraryScan) {
|
||||||
@@ -440,7 +443,7 @@ class BookScanner {
|
|||||||
scannedAudioFiles = AudioFileScanner.runSmartTrackOrder(libraryItemData.relPath, scannedAudioFiles)
|
scannedAudioFiles = AudioFileScanner.runSmartTrackOrder(libraryItemData.relPath, scannedAudioFiles)
|
||||||
|
|
||||||
// Find ebook file (prefer epub)
|
// Find ebook file (prefer epub)
|
||||||
let ebookLibraryFile = librarySettings.audiobooksOnly ? null : libraryItemData.ebookLibraryFiles.find(lf => lf.metadata.ext.slice(1).toLowerCase() === 'epub') || libraryItemData.ebookLibraryFiles[0]
|
let ebookLibraryFile = librarySettings.audiobooksOnly ? null : libraryItemData.ebookLibraryFiles.find((lf) => lf.metadata.ext.slice(1).toLowerCase() === 'epub') || libraryItemData.ebookLibraryFiles[0]
|
||||||
|
|
||||||
// Do not add library items that have no valid audio files and no ebook file
|
// Do not add library items that have no valid audio files and no ebook file
|
||||||
if (!ebookLibraryFile && !scannedAudioFiles.length) {
|
if (!ebookLibraryFile && !scannedAudioFiles.length) {
|
||||||
@@ -460,7 +463,7 @@ class BookScanner {
|
|||||||
bookMetadata.abridged = !!bookMetadata.abridged // Ensure boolean
|
bookMetadata.abridged = !!bookMetadata.abridged // Ensure boolean
|
||||||
|
|
||||||
let duration = 0
|
let duration = 0
|
||||||
scannedAudioFiles.forEach((af) => duration += (!isNaN(af.duration) ? Number(af.duration) : 0))
|
scannedAudioFiles.forEach((af) => (duration += !isNaN(af.duration) ? Number(af.duration) : 0))
|
||||||
const bookObject = {
|
const bookObject = {
|
||||||
...bookMetadata,
|
...bookMetadata,
|
||||||
audioFiles: scannedAudioFiles,
|
audioFiles: scannedAudioFiles,
|
||||||
@@ -482,7 +485,7 @@ class BookScanner {
|
|||||||
author: {
|
author: {
|
||||||
libraryId: libraryItemData.libraryId,
|
libraryId: libraryItemData.libraryId,
|
||||||
name: authorName,
|
name: authorName,
|
||||||
lastFirst: parseNameString.nameToLastFirst(authorName)
|
lastFirst: Database.authorModel.getLastFirst(authorName)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -619,11 +622,11 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
||||||
* @param {import('../utils/parsers/parseEbookMetadata').EBookFileScanData} ebookFileScanData
|
* @param {import('../utils/parsers/parseEbookMetadata').EBookFileScanData} ebookFileScanData
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
|
||||||
* @param {string} [existingLibraryItemId]
|
* @param {string} [existingLibraryItemId]
|
||||||
* @returns {Promise<BookMetadataObject>}
|
* @returns {Promise<BookMetadataObject>}
|
||||||
@@ -664,7 +667,7 @@ class BookScanner {
|
|||||||
|
|
||||||
// Set cover from library file if one is found otherwise check audiofile
|
// Set cover from library file if one is found otherwise check audiofile
|
||||||
if (libraryItemData.imageLibraryFiles.length) {
|
if (libraryItemData.imageLibraryFiles.length) {
|
||||||
const coverMatch = libraryItemData.imageLibraryFiles.find(iFile => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
const coverMatch = libraryItemData.imageLibraryFiles.find((iFile) => /\/cover\.[^.\/]*$/.test(iFile.metadata.path))
|
||||||
bookMetadata.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
bookMetadata.coverPath = coverMatch?.metadata.path || libraryItemData.imageLibraryFiles[0].metadata.path
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,16 +676,15 @@ class BookScanner {
|
|||||||
return bookMetadata
|
return bookMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static BookMetadataSourceHandler = class {
|
static BookMetadataSourceHandler = class {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Object} bookMetadata
|
* @param {Object} bookMetadata
|
||||||
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
* @param {import('../models/Book').AudioFileObject[]} audioFiles
|
||||||
* @param {import('../utils/parsers/parseEbookMetadata').EBookFileScanData} ebookFileScanData
|
* @param {import('../utils/parsers/parseEbookMetadata').EBookFileScanData} ebookFileScanData
|
||||||
* @param {import('./LibraryItemScanData')} libraryItemData
|
* @param {import('./LibraryItemScanData')} libraryItemData
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @param {string} existingLibraryItemId
|
* @param {string} existingLibraryItemId
|
||||||
*/
|
*/
|
||||||
constructor(bookMetadata, audioFiles, ebookFileScanData, libraryItemData, libraryScan, existingLibraryItemId) {
|
constructor(bookMetadata, audioFiles, ebookFileScanData, libraryItemData, libraryScan, existingLibraryItemId) {
|
||||||
this.bookMetadata = bookMetadata
|
this.bookMetadata = bookMetadata
|
||||||
@@ -785,8 +787,8 @@ class BookScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('../models/LibraryItem')} libraryItem
|
* @param {import('../models/LibraryItem')} libraryItem
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
@@ -805,12 +807,12 @@ class BookScanner {
|
|||||||
|
|
||||||
const jsonObject = {
|
const jsonObject = {
|
||||||
tags: libraryItem.media.tags || [],
|
tags: libraryItem.media.tags || [],
|
||||||
chapters: libraryItem.media.chapters?.map(c => ({ ...c })) || [],
|
chapters: libraryItem.media.chapters?.map((c) => ({ ...c })) || [],
|
||||||
title: libraryItem.media.title,
|
title: libraryItem.media.title,
|
||||||
subtitle: libraryItem.media.subtitle,
|
subtitle: libraryItem.media.subtitle,
|
||||||
authors: libraryItem.media.authors.map(a => a.name),
|
authors: libraryItem.media.authors.map((a) => a.name),
|
||||||
narrators: libraryItem.media.narrators,
|
narrators: libraryItem.media.narrators,
|
||||||
series: libraryItem.media.series.map(se => {
|
series: libraryItem.media.series.map((se) => {
|
||||||
const sequence = se.bookSeries?.sequence || ''
|
const sequence = se.bookSeries?.sequence || ''
|
||||||
if (!sequence) return se.name
|
if (!sequence) return se.name
|
||||||
return `${se.name} #${sequence}`
|
return `${se.name} #${sequence}`
|
||||||
@@ -826,70 +828,75 @@ class BookScanner {
|
|||||||
explicit: !!libraryItem.media.explicit,
|
explicit: !!libraryItem.media.explicit,
|
||||||
abridged: !!libraryItem.media.abridged
|
abridged: !!libraryItem.media.abridged
|
||||||
}
|
}
|
||||||
return fsExtra.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2)).then(async () => {
|
return fsExtra
|
||||||
// Add metadata.json to libraryFiles array if it is new
|
.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2))
|
||||||
let metadataLibraryFile = libraryItem.libraryFiles.find(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
.then(async () => {
|
||||||
if (storeMetadataWithItem) {
|
// Add metadata.json to libraryFiles array if it is new
|
||||||
if (!metadataLibraryFile) {
|
let metadataLibraryFile = libraryItem.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(metadataFilePath))
|
||||||
const newLibraryFile = new LibraryFile()
|
if (storeMetadataWithItem) {
|
||||||
await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
if (!metadataLibraryFile) {
|
||||||
metadataLibraryFile = newLibraryFile.toJSON()
|
const newLibraryFile = new LibraryFile()
|
||||||
libraryItem.libraryFiles.push(metadataLibraryFile)
|
await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
|
||||||
} else {
|
metadataLibraryFile = newLibraryFile.toJSON()
|
||||||
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
libraryItem.libraryFiles.push(metadataLibraryFile)
|
||||||
if (fileTimestamps) {
|
} else {
|
||||||
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
|
||||||
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
if (fileTimestamps) {
|
||||||
metadataLibraryFile.metadata.size = fileTimestamps.size
|
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
|
||||||
metadataLibraryFile.ino = fileTimestamps.ino
|
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
|
||||||
|
metadataLibraryFile.metadata.size = fileTimestamps.size
|
||||||
|
metadataLibraryFile.ino = fileTimestamps.ino
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const libraryItemDirTimestamps = await getFileTimestampsWithIno(libraryItem.path)
|
||||||
|
if (libraryItemDirTimestamps) {
|
||||||
|
libraryItem.mtime = libraryItemDirTimestamps.mtimeMs
|
||||||
|
libraryItem.ctime = libraryItemDirTimestamps.ctimeMs
|
||||||
|
let size = 0
|
||||||
|
libraryItem.libraryFiles.forEach((lf) => (size += !isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
||||||
|
libraryItem.size = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const libraryItemDirTimestamps = await getFileTimestampsWithIno(libraryItem.path)
|
|
||||||
if (libraryItemDirTimestamps) {
|
|
||||||
libraryItem.mtime = libraryItemDirTimestamps.mtimeMs
|
|
||||||
libraryItem.ctime = libraryItemDirTimestamps.ctimeMs
|
|
||||||
let size = 0
|
|
||||||
libraryItem.libraryFiles.forEach((lf) => size += (!isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
|
|
||||||
libraryItem.size = size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
libraryScan.addLog(LogLevel.DEBUG, `Success saving abmetadata to "${metadataFilePath}"`)
|
||||||
|
|
||||||
return metadataLibraryFile
|
return metadataLibraryFile
|
||||||
}).catch((error) => {
|
})
|
||||||
libraryScan.addLog(LogLevel.ERROR, `Failed to save json file at "${metadataFilePath}"`, error)
|
.catch((error) => {
|
||||||
return null
|
libraryScan.addLog(LogLevel.ERROR, `Failed to save json file at "${metadataFilePath}"`, error)
|
||||||
})
|
return null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check authors that were removed from a book and remove them if they no longer have any books
|
* Check authors that were removed from a book and remove them if they no longer have any books
|
||||||
* keep authors without books that have a asin, description or imagePath
|
* keep authors without books that have a asin, description or imagePath
|
||||||
* @param {string} libraryId
|
* @param {string} libraryId
|
||||||
* @param {import('./ScanLogger')} scanLogger
|
* @param {import('./ScanLogger')} scanLogger
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async checkAuthorsRemovedFromBooks(libraryId, scanLogger) {
|
async checkAuthorsRemovedFromBooks(libraryId, scanLogger) {
|
||||||
const bookAuthorsToRemove = (await Database.authorModel.findAll({
|
const bookAuthorsToRemove = (
|
||||||
where: [
|
await Database.authorModel.findAll({
|
||||||
{
|
where: [
|
||||||
id: scanLogger.authorsRemovedFromBooks,
|
{
|
||||||
asin: {
|
id: scanLogger.authorsRemovedFromBooks,
|
||||||
[sequelize.Op.or]: [null, ""]
|
asin: {
|
||||||
|
[sequelize.Op.or]: [null, '']
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
[sequelize.Op.or]: [null, '']
|
||||||
|
},
|
||||||
|
imagePath: {
|
||||||
|
[sequelize.Op.or]: [null, '']
|
||||||
|
}
|
||||||
},
|
},
|
||||||
description: {
|
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 0)
|
||||||
[sequelize.Op.or]: [null, ""]
|
],
|
||||||
},
|
attributes: ['id'],
|
||||||
imagePath: {
|
raw: true
|
||||||
[sequelize.Op.or]: [null, ""]
|
})
|
||||||
}
|
).map((au) => au.id)
|
||||||
},
|
|
||||||
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 0)
|
|
||||||
],
|
|
||||||
attributes: ['id'],
|
|
||||||
raw: true
|
|
||||||
})).map(au => au.id)
|
|
||||||
if (bookAuthorsToRemove.length) {
|
if (bookAuthorsToRemove.length) {
|
||||||
await Database.authorModel.destroy({
|
await Database.authorModel.destroy({
|
||||||
where: {
|
where: {
|
||||||
@@ -907,21 +914,23 @@ class BookScanner {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check series that were removed from books and remove them if they no longer have any books
|
* Check series that were removed from books and remove them if they no longer have any books
|
||||||
* @param {string} libraryId
|
* @param {string} libraryId
|
||||||
* @param {import('./ScanLogger')} scanLogger
|
* @param {import('./ScanLogger')} scanLogger
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async checkSeriesRemovedFromBooks(libraryId, scanLogger) {
|
async checkSeriesRemovedFromBooks(libraryId, scanLogger) {
|
||||||
const bookSeriesToRemove = (await Database.seriesModel.findAll({
|
const bookSeriesToRemove = (
|
||||||
where: [
|
await Database.seriesModel.findAll({
|
||||||
{
|
where: [
|
||||||
id: scanLogger.seriesRemovedFromBooks
|
{
|
||||||
},
|
id: scanLogger.seriesRemovedFromBooks
|
||||||
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)'), 0)
|
},
|
||||||
],
|
sequelize.where(sequelize.literal('(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)'), 0)
|
||||||
attributes: ['id'],
|
],
|
||||||
raw: true
|
attributes: ['id'],
|
||||||
})).map(se => se.id)
|
raw: true
|
||||||
|
})
|
||||||
|
).map((se) => se.id)
|
||||||
if (bookSeriesToRemove.length) {
|
if (bookSeriesToRemove.length) {
|
||||||
await Database.seriesModel.destroy({
|
await Database.seriesModel.destroy({
|
||||||
where: {
|
where: {
|
||||||
@@ -938,11 +947,11 @@ class BookScanner {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Search cover provider for matching cover
|
* Search cover provider for matching cover
|
||||||
* @param {string} libraryItemId
|
* @param {string} libraryItemId
|
||||||
* @param {string} libraryItemPath null if book isFile
|
* @param {string} libraryItemPath null if book isFile
|
||||||
* @param {string} title
|
* @param {string} title
|
||||||
* @param {string} author
|
* @param {string} author
|
||||||
* @param {LibraryScan} libraryScan
|
* @param {LibraryScan} libraryScan
|
||||||
* @returns {Promise<string>} path to downloaded cover or null if no cover found
|
* @returns {Promise<string>} path to downloaded cover or null if no cover found
|
||||||
*/
|
*/
|
||||||
async searchForCover(libraryItemId, libraryItemPath, title, author, libraryScan) {
|
async searchForCover(libraryItemId, libraryItemPath, title, author, libraryScan) {
|
||||||
@@ -956,7 +965,6 @@ class BookScanner {
|
|||||||
|
|
||||||
// If the first cover result fails, attempt to download the second
|
// If the first cover result fails, attempt to download the second
|
||||||
for (let i = 0; i < results.length && i < 2; i++) {
|
for (let i = 0; i < results.length && i < 2; i++) {
|
||||||
|
|
||||||
// Downloads and updates the book cover
|
// Downloads and updates the book cover
|
||||||
const result = await CoverManager.downloadCoverFromUrlNew(results[i], libraryItemId, libraryItemPath)
|
const result = await CoverManager.downloadCoverFromUrlNew(results[i], libraryItemId, libraryItemPath)
|
||||||
|
|
||||||
@@ -970,4 +978,4 @@ class BookScanner {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = new BookScanner()
|
module.exports = new BookScanner()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
|
const { getTitleIgnorePrefix } = require('../utils/index')
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
const { findMatchingEpisodesInFeed, getPodcastFeed } = require('../utils/podcastUtils')
|
const { findMatchingEpisodesInFeed, getPodcastFeed } = require('../utils/podcastUtils')
|
||||||
@@ -8,7 +9,6 @@ const { findMatchingEpisodesInFeed, getPodcastFeed } = require('../utils/podcast
|
|||||||
const BookFinder = require('../finders/BookFinder')
|
const BookFinder = require('../finders/BookFinder')
|
||||||
const PodcastFinder = require('../finders/PodcastFinder')
|
const PodcastFinder = require('../finders/PodcastFinder')
|
||||||
const LibraryScan = require('./LibraryScan')
|
const LibraryScan = require('./LibraryScan')
|
||||||
const Series = require('../objects/entities/Series')
|
|
||||||
const LibraryScanner = require('./LibraryScanner')
|
const LibraryScanner = require('./LibraryScanner')
|
||||||
const CoverManager = require('../managers/CoverManager')
|
const CoverManager = require('../managers/CoverManager')
|
||||||
const TaskManager = require('../managers/TaskManager')
|
const TaskManager = require('../managers/TaskManager')
|
||||||
@@ -209,6 +209,7 @@ class Scanner {
|
|||||||
if (!author) {
|
if (!author) {
|
||||||
author = await Database.authorModel.create({
|
author = await Database.authorModel.create({
|
||||||
name: authorName,
|
name: authorName,
|
||||||
|
lastFirst: Database.authorModel.getLastFirst(authorName),
|
||||||
libraryId: libraryItem.libraryId
|
libraryId: libraryItem.libraryId
|
||||||
})
|
})
|
||||||
SocketAuthority.emitter('author_added', author.toOldJSON())
|
SocketAuthority.emitter('author_added', author.toOldJSON())
|
||||||
@@ -225,14 +226,16 @@ class Scanner {
|
|||||||
if (!Array.isArray(matchData.series)) matchData.series = [{ series: matchData.series, sequence: matchData.sequence }]
|
if (!Array.isArray(matchData.series)) matchData.series = [{ series: matchData.series, sequence: matchData.sequence }]
|
||||||
const seriesPayload = []
|
const seriesPayload = []
|
||||||
for (const seriesMatchItem of matchData.series) {
|
for (const seriesMatchItem of matchData.series) {
|
||||||
let seriesItem = await Database.seriesModel.getOldByNameAndLibrary(seriesMatchItem.series, libraryItem.libraryId)
|
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesMatchItem.series, libraryItem.libraryId)
|
||||||
if (!seriesItem) {
|
if (!seriesItem) {
|
||||||
seriesItem = new Series()
|
seriesItem = await Database.seriesModel.create({
|
||||||
seriesItem.setData({ name: seriesMatchItem.series }, libraryItem.libraryId)
|
name: seriesMatchItem.series,
|
||||||
await Database.createSeries(seriesItem)
|
nameIgnorePrefix: getTitleIgnorePrefix(seriesMatchItem.series),
|
||||||
|
libraryId: libraryItem.libraryId
|
||||||
|
})
|
||||||
// Update filter data
|
// Update filter data
|
||||||
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
|
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
|
||||||
SocketAuthority.emitter('series_added', seriesItem.toJSON())
|
SocketAuthority.emitter('series_added', seriesItem.toOldJSON())
|
||||||
}
|
}
|
||||||
seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence))
|
seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,3 @@ module.exports.AudioMimeType = {
|
|||||||
AWB: 'audio/amr-wb',
|
AWB: 'audio/amr-wb',
|
||||||
CAF: 'audio/x-caf'
|
CAF: 'audio/x-caf'
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.VideoMimeType = {
|
|
||||||
MP4: 'video/mp4'
|
|
||||||
}
|
|
||||||
@@ -299,6 +299,12 @@ async function addCoverAndMetadataToFile(audioFilePath, coverFilePath, metadataF
|
|||||||
'-metadata:s:v',
|
'-metadata:s:v',
|
||||||
'comment=Cover' // add comment metadata to cover image stream
|
'comment=Cover' // add comment metadata to cover image stream
|
||||||
])
|
])
|
||||||
|
const ext = Path.extname(coverFilePath).toLowerCase()
|
||||||
|
if (ext === '.webp') {
|
||||||
|
ffmpeg.outputOptions([
|
||||||
|
'-c:v mjpeg' // convert webp images to jpeg
|
||||||
|
])
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ffmpeg.outputOptions([
|
ffmpeg.outputOptions([
|
||||||
'-map 0:v?' // retain video stream from input file if exists
|
'-map 0:v?' // retain video stream from input file if exists
|
||||||
|
|||||||
@@ -131,19 +131,6 @@ async function readTextFile(path) {
|
|||||||
}
|
}
|
||||||
module.exports.readTextFile = readTextFile
|
module.exports.readTextFile = readTextFile
|
||||||
|
|
||||||
function bytesPretty(bytes, decimals = 0) {
|
|
||||||
if (bytes === 0) {
|
|
||||||
return '0 Bytes'
|
|
||||||
}
|
|
||||||
const k = 1000
|
|
||||||
var dm = decimals < 0 ? 0 : decimals
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
||||||
if (i > 2 && dm === 0) dm = 1
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
|
||||||
}
|
|
||||||
module.exports.bytesPretty = bytesPretty
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get array of files inside dir
|
* Get array of files inside dir
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ const globals = {
|
|||||||
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
||||||
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf'],
|
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf'],
|
||||||
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||||
SupportedVideoTypes: ['mp4'],
|
|
||||||
TextFileTypes: ['txt', 'nfo'],
|
TextFileTypes: ['txt', 'nfo'],
|
||||||
MetadataFileTypes: ['opf', 'abs', 'xml', 'json']
|
MetadataFileTypes: ['opf', 'abs', 'xml', 'json']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ module.exports = {
|
|||||||
* @param {import('../../models/User')} user
|
* @param {import('../../models/User')} user
|
||||||
* @param {string[]} include
|
* @param {string[]} include
|
||||||
* @param {number} limit
|
* @param {number} limit
|
||||||
* @returns {{ series:import('../../objects/entities/Series')[], count:number}}
|
* @returns {{ series:any[], count:number}}
|
||||||
*/
|
*/
|
||||||
async getSeriesMostRecentlyAdded(library, user, include, limit) {
|
async getSeriesMostRecentlyAdded(library, user, include, limit) {
|
||||||
if (!library.isBook) return { series: [], count: 0 }
|
if (!library.isBook) return { series: [], count: 0 }
|
||||||
@@ -276,7 +276,7 @@ module.exports = {
|
|||||||
|
|
||||||
const allOldSeries = []
|
const allOldSeries = []
|
||||||
for (const s of series) {
|
for (const s of series) {
|
||||||
const oldSeries = s.getOldSeries().toJSON()
|
const oldSeries = s.toOldJSON()
|
||||||
|
|
||||||
if (s.feeds?.length) {
|
if (s.feeds?.length) {
|
||||||
oldSeries.rssFeed = Database.feedModel.getOldFeed(s.feeds[0]).toJSONMinified()
|
oldSeries.rssFeed = Database.feedModel.getOldFeed(s.feeds[0]).toJSONMinified()
|
||||||
|
|||||||
@@ -954,12 +954,12 @@ module.exports = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get library items for series
|
* Get library items for series
|
||||||
* @param {import('../../objects/entities/Series')} oldSeries
|
* @param {import('../../models/Series')} series
|
||||||
* @param {import('../../models/User')} [user]
|
* @param {import('../../models/User')} [user]
|
||||||
* @returns {Promise<import('../../objects/LibraryItem')[]>}
|
* @returns {Promise<import('../../objects/LibraryItem')[]>}
|
||||||
*/
|
*/
|
||||||
async getLibraryItemsForSeries(oldSeries, user) {
|
async getLibraryItemsForSeries(series, user) {
|
||||||
const { libraryItems } = await this.getFilteredLibraryItems(oldSeries.libraryId, user, 'series', oldSeries.id, null, null, false, [], null, null)
|
const { libraryItems } = await this.getFilteredLibraryItems(series.libraryId, user, 'series', series.id, null, null, false, [], null, null)
|
||||||
return libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li))
|
return libraryItems.map((li) => Database.libraryItemModel.getOldLibraryItem(li))
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1130,7 +1130,7 @@ module.exports = {
|
|||||||
return Database.libraryItemModel.getOldLibraryItem(libraryItem).toJSON()
|
return Database.libraryItemModel.getOldLibraryItem(libraryItem).toJSON()
|
||||||
})
|
})
|
||||||
seriesMatches.push({
|
seriesMatches.push({
|
||||||
series: series.getOldSeries().toJSON(),
|
series: series.toOldJSON(),
|
||||||
books
|
books
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ module.exports = {
|
|||||||
// Map series to old series
|
// Map series to old series
|
||||||
const allOldSeries = []
|
const allOldSeries = []
|
||||||
for (const s of series) {
|
for (const s of series) {
|
||||||
const oldSeries = s.getOldSeries().toJSON()
|
const oldSeries = s.toOldJSON()
|
||||||
|
|
||||||
if (s.dataValues.totalDuration) {
|
if (s.dataValues.totalDuration) {
|
||||||
oldSeries.totalDuration = s.dataValues.totalDuration
|
oldSeries.totalDuration = s.dataValues.totalDuration
|
||||||
|
|||||||
+83
-76
@@ -19,8 +19,7 @@ const parseNameString = require('./parsers/parseNameString')
|
|||||||
function isMediaFile(mediaType, ext, audiobooksOnly = false) {
|
function isMediaFile(mediaType, ext, audiobooksOnly = false) {
|
||||||
if (!ext) return false
|
if (!ext) return false
|
||||||
const extclean = ext.slice(1).toLowerCase()
|
const extclean = ext.slice(1).toLowerCase()
|
||||||
if (mediaType === 'podcast' || mediaType === 'music') return globals.SupportedAudioTypes.includes(extclean)
|
if (mediaType === 'podcast') return globals.SupportedAudioTypes.includes(extclean)
|
||||||
else if (mediaType === 'video') return globals.SupportedVideoTypes.includes(extclean)
|
|
||||||
else if (audiobooksOnly) return globals.SupportedAudioTypes.includes(extclean)
|
else if (audiobooksOnly) return globals.SupportedAudioTypes.includes(extclean)
|
||||||
return globals.SupportedAudioTypes.includes(extclean) || globals.SupportedEbookTypes.includes(extclean)
|
return globals.SupportedAudioTypes.includes(extclean) || globals.SupportedEbookTypes.includes(extclean)
|
||||||
}
|
}
|
||||||
@@ -35,29 +34,33 @@ module.exports.checkFilepathIsAudioFile = checkFilepathIsAudioFile
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Function needs to be re-done
|
* TODO: Function needs to be re-done
|
||||||
* @param {string} mediaType
|
* @param {string} mediaType
|
||||||
* @param {string[]} paths array of relative file paths
|
* @param {string[]} paths array of relative file paths
|
||||||
* @returns {Record<string,string[]>} map of files grouped into potential libarary item dirs
|
* @returns {Record<string,string[]>} map of files grouped into potential libarary item dirs
|
||||||
*/
|
*/
|
||||||
function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
||||||
// Step 1: Clean path, Remove leading "/", Filter out non-media files in root dir
|
// Step 1: Clean path, Remove leading "/", Filter out non-media files in root dir
|
||||||
var nonMediaFilePaths = []
|
var nonMediaFilePaths = []
|
||||||
var pathsFiltered = paths.map(path => {
|
var pathsFiltered = paths
|
||||||
return path.startsWith('/') ? path.slice(1) : path
|
.map((path) => {
|
||||||
}).filter(path => {
|
return path.startsWith('/') ? path.slice(1) : path
|
||||||
let parsedPath = Path.parse(path)
|
})
|
||||||
// Is not in root dir OR is a book media file
|
.filter((path) => {
|
||||||
if (parsedPath.dir) {
|
let parsedPath = Path.parse(path)
|
||||||
if (!isMediaFile(mediaType, parsedPath.ext, false)) { // Seperate out non-media files
|
// Is not in root dir OR is a book media file
|
||||||
nonMediaFilePaths.push(path)
|
if (parsedPath.dir) {
|
||||||
return false
|
if (!isMediaFile(mediaType, parsedPath.ext, false)) {
|
||||||
|
// Seperate out non-media files
|
||||||
|
nonMediaFilePaths.push(path)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else if (mediaType === 'book' && isMediaFile(mediaType, parsedPath.ext, false)) {
|
||||||
|
// (book media type supports single file audiobooks/ebooks in root dir)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return true
|
return false
|
||||||
} else if (mediaType === 'book' && isMediaFile(mediaType, parsedPath.ext, false)) { // (book media type supports single file audiobooks/ebooks in root dir)
|
})
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
// Step 2: Sort by least number of directories
|
// Step 2: Sort by least number of directories
|
||||||
pathsFiltered.sort((a, b) => {
|
pathsFiltered.sort((a, b) => {
|
||||||
@@ -69,7 +72,9 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
|||||||
// Step 3: Group files in dirs
|
// Step 3: Group files in dirs
|
||||||
var itemGroup = {}
|
var itemGroup = {}
|
||||||
pathsFiltered.forEach((path) => {
|
pathsFiltered.forEach((path) => {
|
||||||
var dirparts = Path.dirname(path).split('/').filter(p => !!p && p !== '.') // dirname returns . if no directory
|
var dirparts = Path.dirname(path)
|
||||||
|
.split('/')
|
||||||
|
.filter((p) => !!p && p !== '.') // dirname returns . if no directory
|
||||||
var numparts = dirparts.length
|
var numparts = dirparts.length
|
||||||
var _path = ''
|
var _path = ''
|
||||||
|
|
||||||
@@ -82,14 +87,17 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
|||||||
var dirpart = dirparts.shift()
|
var dirpart = dirparts.shift()
|
||||||
_path = Path.posix.join(_path, dirpart)
|
_path = Path.posix.join(_path, dirpart)
|
||||||
|
|
||||||
if (itemGroup[_path]) { // Directory already has files, add file
|
if (itemGroup[_path]) {
|
||||||
|
// Directory already has files, add file
|
||||||
var relpath = Path.posix.join(dirparts.join('/'), Path.basename(path))
|
var relpath = Path.posix.join(dirparts.join('/'), Path.basename(path))
|
||||||
itemGroup[_path].push(relpath)
|
itemGroup[_path].push(relpath)
|
||||||
return
|
return
|
||||||
} else if (!dirparts.length) { // This is the last directory, create group
|
} else if (!dirparts.length) {
|
||||||
|
// This is the last directory, create group
|
||||||
itemGroup[_path] = [Path.basename(path)]
|
itemGroup[_path] = [Path.basename(path)]
|
||||||
return
|
return
|
||||||
} else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) { // Next directory is the last and is a CD dir, create group
|
} else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) {
|
||||||
|
// Next directory is the last and is a CD dir, create group
|
||||||
itemGroup[_path] = [Path.posix.join(dirparts[0], Path.basename(path))]
|
itemGroup[_path] = [Path.posix.join(dirparts[0], Path.basename(path))]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -99,7 +107,6 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
|||||||
|
|
||||||
// Step 4: Add in non-media files if they fit into item group
|
// Step 4: Add in non-media files if they fit into item group
|
||||||
if (nonMediaFilePaths.length) {
|
if (nonMediaFilePaths.length) {
|
||||||
|
|
||||||
for (const nonMediaFilePath of nonMediaFilePaths) {
|
for (const nonMediaFilePath of nonMediaFilePaths) {
|
||||||
const pathDir = Path.dirname(nonMediaFilePath)
|
const pathDir = Path.dirname(nonMediaFilePath)
|
||||||
const filename = Path.basename(nonMediaFilePath)
|
const filename = Path.basename(nonMediaFilePath)
|
||||||
@@ -111,7 +118,8 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
|||||||
for (let i = 0; i < numparts; i++) {
|
for (let i = 0; i < numparts; i++) {
|
||||||
const dirpart = dirparts.shift()
|
const dirpart = dirparts.shift()
|
||||||
_path = Path.posix.join(_path, dirpart)
|
_path = Path.posix.join(_path, dirpart)
|
||||||
if (itemGroup[_path]) { // Directory is a group
|
if (itemGroup[_path]) {
|
||||||
|
// Directory is a group
|
||||||
const relpath = Path.posix.join(dirparts.join('/'), filename)
|
const relpath = Path.posix.join(dirparts.join('/'), filename)
|
||||||
itemGroup[_path].push(relpath)
|
itemGroup[_path].push(relpath)
|
||||||
} else if (!dirparts.length) {
|
} else if (!dirparts.length) {
|
||||||
@@ -126,31 +134,22 @@ function groupFilesIntoLibraryItemPaths(mediaType, paths) {
|
|||||||
module.exports.groupFilesIntoLibraryItemPaths = groupFilesIntoLibraryItemPaths
|
module.exports.groupFilesIntoLibraryItemPaths = groupFilesIntoLibraryItemPaths
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} mediaType
|
* @param {string} mediaType
|
||||||
* @param {{name:string, path:string, dirpath:string, reldirpath:string, fullpath:string, extension:string, deep:number}[]} fileItems (see recurseFiles)
|
* @param {{name:string, path:string, dirpath:string, reldirpath:string, fullpath:string, extension:string, deep:number}[]} fileItems (see recurseFiles)
|
||||||
* @param {boolean} [audiobooksOnly=false]
|
* @param {boolean} [audiobooksOnly=false]
|
||||||
* @returns {Record<string,string[]>} map of files grouped into potential libarary item dirs
|
* @returns {Record<string,string[]>} map of files grouped into potential libarary item dirs
|
||||||
*/
|
*/
|
||||||
function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly = false) {
|
function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly = false) {
|
||||||
// Handle music where every audio file is a library item
|
|
||||||
if (mediaType === 'music') {
|
|
||||||
const audioFileGroup = {}
|
|
||||||
fileItems.filter(i => isMediaFile(mediaType, i.extension, audiobooksOnly)).forEach((item) => {
|
|
||||||
audioFileGroup[item.path] = item.path
|
|
||||||
})
|
|
||||||
return audioFileGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1: Filter out non-book-media files in root dir (with depth of 0)
|
// Step 1: Filter out non-book-media files in root dir (with depth of 0)
|
||||||
const itemsFiltered = fileItems.filter(i => {
|
const itemsFiltered = fileItems.filter((i) => {
|
||||||
return i.deep > 0 || ((mediaType === 'book' || mediaType === 'video' || mediaType === 'music') && isMediaFile(mediaType, i.extension, audiobooksOnly))
|
return i.deep > 0 || (mediaType === 'book' && isMediaFile(mediaType, i.extension, audiobooksOnly))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Step 2: Seperate media files and other files
|
// Step 2: Seperate media files and other files
|
||||||
// - Directories without a media file will not be included
|
// - Directories without a media file will not be included
|
||||||
const mediaFileItems = []
|
const mediaFileItems = []
|
||||||
const otherFileItems = []
|
const otherFileItems = []
|
||||||
itemsFiltered.forEach(item => {
|
itemsFiltered.forEach((item) => {
|
||||||
if (isMediaFile(mediaType, item.extension, audiobooksOnly)) mediaFileItems.push(item)
|
if (isMediaFile(mediaType, item.extension, audiobooksOnly)) mediaFileItems.push(item)
|
||||||
else otherFileItems.push(item)
|
else otherFileItems.push(item)
|
||||||
})
|
})
|
||||||
@@ -158,7 +157,7 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly
|
|||||||
// Step 3: Group audio files in library items
|
// Step 3: Group audio files in library items
|
||||||
const libraryItemGroup = {}
|
const libraryItemGroup = {}
|
||||||
mediaFileItems.forEach((item) => {
|
mediaFileItems.forEach((item) => {
|
||||||
const dirparts = item.reldirpath.split('/').filter(p => !!p)
|
const dirparts = item.reldirpath.split('/').filter((p) => !!p)
|
||||||
const numparts = dirparts.length
|
const numparts = dirparts.length
|
||||||
let _path = ''
|
let _path = ''
|
||||||
|
|
||||||
@@ -171,14 +170,17 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly
|
|||||||
const dirpart = dirparts.shift()
|
const dirpart = dirparts.shift()
|
||||||
_path = Path.posix.join(_path, dirpart)
|
_path = Path.posix.join(_path, dirpart)
|
||||||
|
|
||||||
if (libraryItemGroup[_path]) { // Directory already has files, add file
|
if (libraryItemGroup[_path]) {
|
||||||
|
// Directory already has files, add file
|
||||||
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
||||||
libraryItemGroup[_path].push(relpath)
|
libraryItemGroup[_path].push(relpath)
|
||||||
return
|
return
|
||||||
} else if (!dirparts.length) { // This is the last directory, create group
|
} else if (!dirparts.length) {
|
||||||
|
// This is the last directory, create group
|
||||||
libraryItemGroup[_path] = [item.name]
|
libraryItemGroup[_path] = [item.name]
|
||||||
return
|
return
|
||||||
} else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) { // Next directory is the last and is a CD dir, create group
|
} else if (dirparts.length === 1 && /^cd\d{1,3}$/i.test(dirparts[0])) {
|
||||||
|
// Next directory is the last and is a CD dir, create group
|
||||||
libraryItemGroup[_path] = [Path.posix.join(dirparts[0], item.name)]
|
libraryItemGroup[_path] = [Path.posix.join(dirparts[0], item.name)]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -196,7 +198,8 @@ function groupFileItemsIntoLibraryItemDirs(mediaType, fileItems, audiobooksOnly
|
|||||||
for (let i = 0; i < numparts; i++) {
|
for (let i = 0; i < numparts; i++) {
|
||||||
const dirpart = dirparts.shift()
|
const dirpart = dirparts.shift()
|
||||||
_path = Path.posix.join(_path, dirpart)
|
_path = Path.posix.join(_path, dirpart)
|
||||||
if (libraryItemGroup[_path]) { // Directory is audiobook group
|
if (libraryItemGroup[_path]) {
|
||||||
|
// Directory is audiobook group
|
||||||
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
const relpath = Path.posix.join(dirparts.join('/'), item.name)
|
||||||
libraryItemGroup[_path].push(relpath)
|
libraryItemGroup[_path].push(relpath)
|
||||||
return
|
return
|
||||||
@@ -209,33 +212,35 @@ module.exports.groupFileItemsIntoLibraryItemDirs = groupFileItemsIntoLibraryItem
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get LibraryFile from filepath
|
* Get LibraryFile from filepath
|
||||||
* @param {string} libraryItemPath
|
* @param {string} libraryItemPath
|
||||||
* @param {string[]} files
|
* @param {string[]} files
|
||||||
* @returns {import('../objects/files/LibraryFile')}
|
* @returns {import('../objects/files/LibraryFile')}
|
||||||
*/
|
*/
|
||||||
function buildLibraryFile(libraryItemPath, files) {
|
function buildLibraryFile(libraryItemPath, files) {
|
||||||
return Promise.all(files.map(async (file) => {
|
return Promise.all(
|
||||||
const filePath = Path.posix.join(libraryItemPath, file)
|
files.map(async (file) => {
|
||||||
const newLibraryFile = new LibraryFile()
|
const filePath = Path.posix.join(libraryItemPath, file)
|
||||||
await newLibraryFile.setDataFromPath(filePath, file)
|
const newLibraryFile = new LibraryFile()
|
||||||
return newLibraryFile
|
await newLibraryFile.setDataFromPath(filePath, file)
|
||||||
}))
|
return newLibraryFile
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
module.exports.buildLibraryFile = buildLibraryFile
|
module.exports.buildLibraryFile = buildLibraryFile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get details parsed from filenames
|
* Get details parsed from filenames
|
||||||
*
|
*
|
||||||
* @param {string} relPath
|
* @param {string} relPath
|
||||||
* @param {boolean} parseSubtitle
|
* @param {boolean} parseSubtitle
|
||||||
* @returns {LibraryItemFilenameMetadata}
|
* @returns {LibraryItemFilenameMetadata}
|
||||||
*/
|
*/
|
||||||
function getBookDataFromDir(relPath, parseSubtitle = false) {
|
function getBookDataFromDir(relPath, parseSubtitle = false) {
|
||||||
const splitDir = relPath.split('/')
|
const splitDir = relPath.split('/')
|
||||||
|
|
||||||
var folder = splitDir.pop() // Audio files will always be in the directory named for the title
|
var folder = splitDir.pop() // Audio files will always be in the directory named for the title
|
||||||
series = (splitDir.length > 1) ? splitDir.pop() : null // If there are at least 2 more directories, next furthest will be the series
|
series = splitDir.length > 1 ? splitDir.pop() : null // If there are at least 2 more directories, next furthest will be the series
|
||||||
author = (splitDir.length > 0) ? splitDir.pop() : null // There could be many more directories, but only the top 3 are used for naming /author/series/title/
|
author = splitDir.length > 0 ? splitDir.pop() : null // There could be many more directories, but only the top 3 are used for naming /author/series/title/
|
||||||
|
|
||||||
// The may contain various other pieces of metadata, these functions extract it.
|
// The may contain various other pieces of metadata, these functions extract it.
|
||||||
var [folder, asin] = getASIN(folder)
|
var [folder, asin] = getASIN(folder)
|
||||||
@@ -244,7 +249,6 @@ function getBookDataFromDir(relPath, parseSubtitle = false) {
|
|||||||
var [folder, publishedYear] = getPublishedYear(folder)
|
var [folder, publishedYear] = getPublishedYear(folder)
|
||||||
var [title, subtitle] = parseSubtitle ? getSubtitle(folder) : [folder, null]
|
var [title, subtitle] = parseSubtitle ? getSubtitle(folder) : [folder, null]
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
@@ -260,8 +264,8 @@ module.exports.getBookDataFromDir = getBookDataFromDir
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract narrator from folder name
|
* Extract narrator from folder name
|
||||||
*
|
*
|
||||||
* @param {string} folder
|
* @param {string} folder
|
||||||
* @returns {[string, string]} [folder, narrator]
|
* @returns {[string, string]} [folder, narrator]
|
||||||
*/
|
*/
|
||||||
function getNarrator(folder) {
|
function getNarrator(folder) {
|
||||||
@@ -272,7 +276,7 @@ function getNarrator(folder) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract series sequence from folder name
|
* Extract series sequence from folder name
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* 'Book 2 - Title - Subtitle'
|
* 'Book 2 - Title - Subtitle'
|
||||||
* 'Title - Subtitle - Vol 12'
|
* 'Title - Subtitle - Vol 12'
|
||||||
@@ -283,8 +287,8 @@ function getNarrator(folder) {
|
|||||||
* '100 - Book Title'
|
* '100 - Book Title'
|
||||||
* '6. Title'
|
* '6. Title'
|
||||||
* '0.5 - Book Title'
|
* '0.5 - Book Title'
|
||||||
*
|
*
|
||||||
* @param {string} folder
|
* @param {string} folder
|
||||||
* @returns {[string, string]} [folder, sequence]
|
* @returns {[string, string]} [folder, sequence]
|
||||||
*/
|
*/
|
||||||
function getSequence(folder) {
|
function getSequence(folder) {
|
||||||
@@ -299,7 +303,9 @@ function getSequence(folder) {
|
|||||||
if (match && !(match.groups.suffix && !(match.groups.volumeLabel || match.groups.trailingDot))) {
|
if (match && !(match.groups.suffix && !(match.groups.volumeLabel || match.groups.trailingDot))) {
|
||||||
volumeNumber = isNaN(match.groups.sequence) ? match.groups.sequence : Number(match.groups.sequence).toString()
|
volumeNumber = isNaN(match.groups.sequence) ? match.groups.sequence : Number(match.groups.sequence).toString()
|
||||||
parts[i] = match.groups.suffix
|
parts[i] = match.groups.suffix
|
||||||
if (!parts[i]) { parts.splice(i, 1) }
|
if (!parts[i]) {
|
||||||
|
parts.splice(i, 1)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,8 +316,8 @@ function getSequence(folder) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract published year from folder name
|
* Extract published year from folder name
|
||||||
*
|
*
|
||||||
* @param {string} folder
|
* @param {string} folder
|
||||||
* @returns {[string, string]} [folder, publishedYear]
|
* @returns {[string, string]} [folder, publishedYear]
|
||||||
*/
|
*/
|
||||||
function getPublishedYear(folder) {
|
function getPublishedYear(folder) {
|
||||||
@@ -329,8 +335,8 @@ function getPublishedYear(folder) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract subtitle from folder name
|
* Extract subtitle from folder name
|
||||||
*
|
*
|
||||||
* @param {string} folder
|
* @param {string} folder
|
||||||
* @returns {[string, string]} [folder, subtitle]
|
* @returns {[string, string]} [folder, subtitle]
|
||||||
*/
|
*/
|
||||||
function getSubtitle(folder) {
|
function getSubtitle(folder) {
|
||||||
@@ -341,8 +347,8 @@ function getSubtitle(folder) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract asin from folder name
|
* Extract asin from folder name
|
||||||
*
|
*
|
||||||
* @param {string} folder
|
* @param {string} folder
|
||||||
* @returns {[string, string]} [folder, asin]
|
* @returns {[string, string]} [folder, asin]
|
||||||
*/
|
*/
|
||||||
function getASIN(folder) {
|
function getASIN(folder) {
|
||||||
@@ -358,8 +364,8 @@ function getASIN(folder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} relPath
|
* @param {string} relPath
|
||||||
* @returns {LibraryItemFilenameMetadata}
|
* @returns {LibraryItemFilenameMetadata}
|
||||||
*/
|
*/
|
||||||
function getPodcastDataFromDir(relPath) {
|
function getPodcastDataFromDir(relPath) {
|
||||||
@@ -373,10 +379,10 @@ function getPodcastDataFromDir(relPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} libraryMediaType
|
* @param {string} libraryMediaType
|
||||||
* @param {string} folderPath
|
* @param {string} folderPath
|
||||||
* @param {string} relPath
|
* @param {string} relPath
|
||||||
* @returns {{ mediaMetadata: LibraryItemFilenameMetadata, relPath: string, path: string}}
|
* @returns {{ mediaMetadata: LibraryItemFilenameMetadata, relPath: string, path: string}}
|
||||||
*/
|
*/
|
||||||
function getDataFromMediaDir(libraryMediaType, folderPath, relPath) {
|
function getDataFromMediaDir(libraryMediaType, folderPath, relPath) {
|
||||||
@@ -386,7 +392,8 @@ function getDataFromMediaDir(libraryMediaType, folderPath, relPath) {
|
|||||||
|
|
||||||
if (libraryMediaType === 'podcast') {
|
if (libraryMediaType === 'podcast') {
|
||||||
mediaMetadata = getPodcastDataFromDir(relPath)
|
mediaMetadata = getPodcastDataFromDir(relPath)
|
||||||
} else { // book
|
} else {
|
||||||
|
// book
|
||||||
mediaMetadata = getBookDataFromDir(relPath, !!global.ServerSettings.scannerParseSubtitle)
|
mediaMetadata = getBookDataFromDir(relPath, !!global.ServerSettings.scannerParseSubtitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user