mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-02 00:40:39 +02:00
Compare commits
231 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6baf06164 | |||
| 7e75845851 | |||
| 2a11932822 | |||
| 80fee92037 | |||
| d0c02a801a | |||
| 9e13c64408 | |||
| 826963bf00 | |||
| 39b6ede1e9 | |||
| 066d853156 | |||
| efae529fac | |||
| 934c0b9093 | |||
| f02992dd4d | |||
| 10011bd6a3 | |||
| a44ee913c4 | |||
| adccccbd7a | |||
| 05b1b2be36 | |||
| 7cc35a2cbe | |||
| 8d479b6e34 | |||
| 74d300f048 | |||
| 1dd1fe8994 | |||
| 03115e5e53 | |||
| b1c07834be | |||
| b9da3fa30e | |||
| 42ff3d8314 | |||
| e63aab95d8 | |||
| 9123dcb365 | |||
| 7567e91878 | |||
| 1b1bdea3c8 | |||
| 2df95c1712 | |||
| 4ad1cd2968 | |||
| 0ecfdab463 | |||
| 75276f5a44 | |||
| 4585d2816b | |||
| f8f94f2a6d | |||
| 2c8448d147 | |||
| ea1d051cfb | |||
| a38e43213d | |||
| 6cac8fcd6e | |||
| 8e65c78869 | |||
| a3899b68e1 | |||
| 1187f91063 | |||
| 7c288a5ff9 | |||
| e0dae44c7d | |||
| 754498958d | |||
| ec15978e26 | |||
| 469167df66 | |||
| e7c43a3f32 | |||
| 24989e73ae | |||
| 13427b9f70 | |||
| adafefecd4 | |||
| 6f96b069b5 | |||
| 6c1b4e3a36 | |||
| 21343ffbd1 | |||
| 4f94deefa0 | |||
| 332078e6c1 | |||
| ff0d6326d3 | |||
| 8d451217a3 | |||
| f21d69339f | |||
| c77cead9ae | |||
| b334d40998 | |||
| 4e4a976050 | |||
| 9d7d4c6902 | |||
| 7222171c5b | |||
| 361732a463 | |||
| 1ebe8a6f4c | |||
| a98942a361 | |||
| 0bc89cd40f | |||
| 2ae86ab5bb | |||
| c707bcf0f6 | |||
| 10040ba9fa | |||
| 7afda1295b | |||
| 6d6e8613cf | |||
| 3651fffbee | |||
| 8d03b23f46 | |||
| fc44c801f2 | |||
| 6056c14926 | |||
| f465193b9c | |||
| 09c9c28028 | |||
| f1130eb63a | |||
| db80cec168 | |||
| 38029d1202 | |||
| aac2879652 | |||
| 8c9fc3ddb5 | |||
| 33e04d0cbb | |||
| fbb5fd41fb | |||
| 43a5296dd7 | |||
| 345ff1aa66 | |||
| 56e3449db6 | |||
| 1372c24535 | |||
| 409c5f7b75 | |||
| 83d0db0607 | |||
| 91b6c4412d | |||
| 09eefae808 | |||
| 80b3bfea51 | |||
| 516298b5b2 | |||
| 8edab98163 | |||
| 58da095bcf | |||
| b9633691f4 | |||
| 7ec1d8ee5f | |||
| 83a1374e79 | |||
| 5ef00bac92 | |||
| 95c4b3862b | |||
| eeaf012cdc | |||
| 11120a3765 | |||
| 4d0acb30ba | |||
| 4dbe8d29d9 | |||
| 0ca4ff4fca | |||
| 8be1651c6b | |||
| af2db86d1a | |||
| 57c834f88d | |||
| 65fdebde20 | |||
| b58e42ebf3 | |||
| b2d45f598b | |||
| 09c4e690c6 | |||
| 67ba481dca | |||
| 710a62c2af | |||
| 5a9eed0a5a | |||
| 354e16e462 | |||
| 1d974375a0 | |||
| 1c40af3eef | |||
| daa8c4cd67 | |||
| d5da4441cd | |||
| 80aea0c82d | |||
| 14836eeb0d | |||
| 85e9883d3e | |||
| 80ca73e491 | |||
| 22323f606d | |||
| 01b65eb678 | |||
| d1d94c37a7 | |||
| 838a24c8a5 | |||
| 3f380b0839 | |||
| 7fdf1a1d7f | |||
| c2793fe29b | |||
| 38596d017f | |||
| 24b9ac6a68 | |||
| 9a5ed64fae | |||
| c2af96e7cd | |||
| 104cadb0b3 | |||
| 6814adffcc | |||
| 20c11e381e | |||
| b5952f16eb | |||
| 5b6878e5de | |||
| 89a25bcf39 | |||
| d0cd512be8 | |||
| 3543dea0fb | |||
| 1949e25ccb | |||
| b715ef3bfc | |||
| 954050df81 | |||
| e4aa7f10fa | |||
| 2afd0e2acd | |||
| 0829237166 | |||
| 541975f038 | |||
| 01bf58ab97 | |||
| d99b2c25e8 | |||
| a31df5ff81 | |||
| 63e5cf2e60 | |||
| 7beca048e7 | |||
| ec998dc1ac | |||
| ddc54c8811 | |||
| 72e306935f | |||
| 96a7c7f4d1 | |||
| 9c65d655b8 | |||
| b108f2241b | |||
| 9439acf300 | |||
| d181e66d83 | |||
| a87c3f2c77 | |||
| 2834f6077e | |||
| 918013ccb3 | |||
| 4c4672c6c1 | |||
| b3991574c7 | |||
| c881bcbe59 | |||
| 89aa4a8bdc | |||
| c5a4f63670 | |||
| 1b97582975 | |||
| 9b7aacf3ea | |||
| 47b9ee557e | |||
| e40e0bfa25 | |||
| d56e3a3617 | |||
| 78fe6d47ba | |||
| 995cf51ae3 | |||
| d838ff2f2e | |||
| f2f07ff534 | |||
| 8cff68ca64 | |||
| eb5331d34a | |||
| f425185575 | |||
| 9fc352a5a4 | |||
| e85ddc1aa1 | |||
| b9be7510f8 | |||
| f4497acd48 | |||
| f73a0cce72 | |||
| 254ba1f089 | |||
| 0a179e4eed | |||
| 0ac63b2678 | |||
| 1d13d0a553 | |||
| fc6ff016a7 | |||
| e378b79fbc | |||
| 7e377297d7 | |||
| 00a02921dd | |||
| b5d4c11f6f | |||
| a0bc959850 | |||
| a4b0f6c202 | |||
| 65cf928afe | |||
| cf7fd315b6 | |||
| d86a3b3dc2 | |||
| e07e2cd359 | |||
| 8140d7021a | |||
| bdbc5e3161 | |||
| bb9013541b | |||
| 1668153acd | |||
| aeba7674f8 | |||
| 5b0d105e21 | |||
| feb54d0629 | |||
| 3284fe8f31 | |||
| 18cb394884 | |||
| d0bce2949e | |||
| a0e80772cd | |||
| e44595521d | |||
| fdf647eb32 | |||
| 71369bd2a0 | |||
| 36b1f43f4c | |||
| a8bc1df3e7 | |||
| a96869f547 | |||
| 77b030199e | |||
| 0e1c6c0ba7 | |||
| c397422d3b | |||
| 15313826bf | |||
| c6405b9013 | |||
| d748d43efc | |||
| d54edb93d6 | |||
| b8ca6671fc | |||
| cb7fb646ba |
@@ -15,3 +15,4 @@ test/
|
|||||||
|
|
||||||
sw.*
|
sw.*
|
||||||
.DS_STORE
|
.DS_STORE
|
||||||
|
.idea/*
|
||||||
|
|||||||
+8
-6
@@ -10,11 +10,15 @@ FROM sandreas/tone:v0.1.5 AS tone
|
|||||||
FROM node:16-alpine
|
FROM node:16-alpine
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk add --no-cache --update \
|
apk add --no-cache --update \
|
||||||
curl \
|
curl \
|
||||||
tzdata \
|
tzdata \
|
||||||
ffmpeg
|
ffmpeg \
|
||||||
|
make \
|
||||||
|
python3 \
|
||||||
|
g++
|
||||||
|
|
||||||
COPY --from=tone /usr/local/bin/tone /usr/local/bin/
|
COPY --from=tone /usr/local/bin/tone /usr/local/bin/
|
||||||
COPY --from=build /client/dist /client/dist
|
COPY --from=build /client/dist /client/dist
|
||||||
@@ -23,10 +27,8 @@ COPY server server
|
|||||||
|
|
||||||
RUN npm ci --only=production
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
RUN apk del make python3 g++
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
HEALTHCHECK \
|
|
||||||
--interval=30s \
|
|
||||||
--timeout=3s \
|
|
||||||
--start-period=10s \
|
|
||||||
CMD curl -f http://127.0.0.1/healthcheck || exit 1
|
|
||||||
CMD ["node", "index.js"]
|
CMD ["node", "index.js"]
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ install_ffmpeg() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
$WGET
|
$WGET
|
||||||
tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1
|
tar xvf ffmpeg-git-amd64-static.tar.xz --strip-components=1 --no-same-owner
|
||||||
rm ffmpeg-git-amd64-static.tar.xz
|
rm ffmpeg-git-amd64-static.tar.xz
|
||||||
|
|
||||||
# Temp downloading tone library to the ffmpeg dir
|
# Temp downloading tone library to the ffmpeg dir
|
||||||
echo "Getting tone.."
|
echo "Getting tone.."
|
||||||
$WGET_TONE
|
$WGET_TONE
|
||||||
tar xvf tone-0.1.5-linux-x64.tar.gz --strip-components=1
|
tar xvf tone-0.1.5-linux-x64.tar.gz --strip-components=1 --no-same-owner
|
||||||
rm tone-0.1.5-linux-x64.tar.gz
|
rm tone-0.1.5-linux-x64.tar.gz
|
||||||
|
|
||||||
echo "Good to go on Ffmpeg (& tone)... hopefully"
|
echo "Good to go on Ffmpeg (& tone)... hopefully"
|
||||||
|
|||||||
@@ -303,13 +303,13 @@ export default {
|
|||||||
this.$axios
|
this.$axios
|
||||||
.patch(`/api/me/progress/batch/update`, updateProgressPayloads)
|
.patch(`/api/me/progress/batch/update`, updateProgressPayloads)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success('Batch update success!')
|
this.$toast.success(this.$strings.ToastBatchUpdateSuccess)
|
||||||
this.$store.commit('setProcessingBatch', false)
|
this.$store.commit('setProcessingBatch', false)
|
||||||
this.$store.commit('globals/resetSelectedMediaItems', [])
|
this.$store.commit('globals/resetSelectedMediaItems', [])
|
||||||
this.$eventBus.$emit('bookshelf_clear_selection')
|
this.$eventBus.$emit('bookshelf_clear_selection')
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.$toast.error('Batch update failed')
|
this.$toast.error(this.$strings.ToastBatchUpdateFailed)
|
||||||
console.error('Failed to batch update read/not read', error)
|
console.error('Failed to batch update read/not read', error)
|
||||||
this.$store.commit('setProcessingBatch', false)
|
this.$store.commit('setProcessingBatch', false)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ export default {
|
|||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
|
currentLibraryMediaType() {
|
||||||
|
return this.$store.getters['libraries/getCurrentLibraryMediaType']
|
||||||
|
},
|
||||||
libraryName() {
|
libraryName() {
|
||||||
return this.$store.getters['libraries/getCurrentLibraryName']
|
return this.$store.getters['libraries/getCurrentLibraryName']
|
||||||
},
|
},
|
||||||
@@ -168,7 +171,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async fetchCategories() {
|
async fetchCategories() {
|
||||||
const categories = await this.$axios
|
const categories = await this.$axios
|
||||||
.$get(`/api/libraries/${this.currentLibraryId}/personalized?include=rssfeed`)
|
.$get(`/api/libraries/${this.currentLibraryId}/personalized?include=rssfeed,numEpisodesIncomplete`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
@@ -346,8 +349,6 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
episodeAdded(episodeWithLibraryItem) {
|
episodeAdded(episodeWithLibraryItem) {
|
||||||
console.log('Podcast episode added', episodeWithLibraryItem)
|
|
||||||
|
|
||||||
const isThisLibrary = episodeWithLibraryItem.libraryItem?.libraryId === this.currentLibraryId
|
const isThisLibrary = episodeWithLibraryItem.libraryItem?.libraryId === this.currentLibraryId
|
||||||
if (!this.search && isThisLibrary) {
|
if (!this.search && isThisLibrary) {
|
||||||
this.fetchCategories()
|
this.fetchCategories()
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ export default {
|
|||||||
id: 'config-item-metadata-utils',
|
id: 'config-item-metadata-utils',
|
||||||
title: this.$strings.HeaderItemMetadataUtils,
|
title: this.$strings.HeaderItemMetadataUtils,
|
||||||
path: '/config/item-metadata-utils'
|
path: '/config/item-metadata-utils'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'config-rss-feeds',
|
||||||
|
title: this.$strings.HeaderRSSFeeds,
|
||||||
|
path: '/config/rss-feeds'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -313,12 +313,12 @@ export default {
|
|||||||
this.currentSFQueryString = this.buildSearchParams()
|
this.currentSFQueryString = this.buildSearchParams()
|
||||||
}
|
}
|
||||||
|
|
||||||
const entityPath = this.entityName === 'series-books' ? 'items' : this.entityName
|
let entityPath = this.entityName === 'series-books' ? 'items' : this.entityName
|
||||||
const sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : ''
|
const sfQueryString = this.currentSFQueryString ? this.currentSFQueryString + '&' : ''
|
||||||
const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1&include=rssfeed`
|
const fullQueryString = `?${sfQueryString}limit=${this.booksPerFetch}&page=${page}&minified=1&include=rssfeed,numEpisodesIncomplete`
|
||||||
|
|
||||||
const payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => {
|
const payload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/${entityPath}${fullQueryString}`).catch((error) => {
|
||||||
console.error('failed to fetch books', error)
|
console.error('failed to fetch items', error)
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -623,6 +623,11 @@ export default {
|
|||||||
return entitiesPerShelfBefore < this.entitiesPerShelf // Books per shelf has changed
|
return entitiesPerShelfBefore < this.entitiesPerShelf // Books per shelf has changed
|
||||||
},
|
},
|
||||||
async init(bookshelf) {
|
async init(bookshelf) {
|
||||||
|
if (this.entityName === 'series') {
|
||||||
|
this.booksPerFetch = 50
|
||||||
|
} else {
|
||||||
|
this.booksPerFetch = 100
|
||||||
|
}
|
||||||
this.checkUpdateSearchParams()
|
this.checkUpdateSearchParams()
|
||||||
this.initSizeData(bookshelf)
|
this.initSizeData(bookshelf)
|
||||||
|
|
||||||
|
|||||||
@@ -116,9 +116,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Podcast Num Episodes -->
|
<!-- Podcast Num Episodes -->
|
||||||
<div v-else-if="numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
<div v-else-if="!numEpisodesIncomplete && numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Podcast Num Episodes -->
|
||||||
|
<div v-else-if="numEpisodesIncomplete && !isHovering && !isSelectionMode" class="absolute rounded-full bg-yellow-400 text-black font-semibold box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||||
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodesIncomplete }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -214,7 +219,7 @@ export default {
|
|||||||
return this.mediaMetadata.series
|
return this.mediaMetadata.series
|
||||||
},
|
},
|
||||||
seriesSequence() {
|
seriesSequence() {
|
||||||
return this.series ? this.series.sequence : null
|
return this.series?.sequence || null
|
||||||
},
|
},
|
||||||
libraryId() {
|
libraryId() {
|
||||||
return this._libraryItem.libraryId
|
return this._libraryItem.libraryId
|
||||||
@@ -227,9 +232,11 @@ export default {
|
|||||||
return this.media.numTracks || 0 // toJSONMinified
|
return this.media.numTracks || 0 // toJSONMinified
|
||||||
},
|
},
|
||||||
numEpisodes() {
|
numEpisodes() {
|
||||||
if (!this.isPodcast) return 0
|
|
||||||
return this.media.numEpisodes || 0
|
return this.media.numEpisodes || 0
|
||||||
},
|
},
|
||||||
|
numEpisodesIncomplete() {
|
||||||
|
return this._libraryItem.numEpisodesIncomplete || 0
|
||||||
|
},
|
||||||
processingBatch() {
|
processingBatch() {
|
||||||
return this.store.state.processingBatch
|
return this.store.state.processingBatch
|
||||||
},
|
},
|
||||||
@@ -311,6 +318,7 @@ export default {
|
|||||||
if (this.orderBy === 'media.duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false)
|
if (this.orderBy === 'media.duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false)
|
||||||
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
|
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
|
||||||
if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} Episodes`
|
if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} Episodes`
|
||||||
|
if (this.orderBy === 'media.metadata.publishedYear' && this.mediaMetadata.publishedYear) return 'Published ' + this.mediaMetadata.publishedYear
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
episodeProgress() {
|
episodeProgress() {
|
||||||
@@ -680,7 +688,6 @@ export default {
|
|||||||
.$patch(apiEndpoint, updatePayload)
|
.$patch(apiEndpoint, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.processing = false
|
this.processing = false
|
||||||
toast.success(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedSuccess : this.$strings.ToastItemMarkedAsNotFinishedSuccess)
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
@@ -757,6 +764,8 @@ export default {
|
|||||||
this.store.commit('globals/setConfirmPrompt', payload)
|
this.store.commit('globals/setConfirmPrompt', payload)
|
||||||
},
|
},
|
||||||
removeSeriesFromContinueListening() {
|
removeSeriesFromContinueListening() {
|
||||||
|
if (!this.series) return
|
||||||
|
|
||||||
const axios = this.$axios || this.$nuxt.$axios
|
const axios = this.$axios || this.$nuxt.$axios
|
||||||
this.processing = true
|
this.processing = true
|
||||||
axios
|
axios
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default {
|
|||||||
return this.narrator?.name || ''
|
return this.narrator?.name || ''
|
||||||
},
|
},
|
||||||
numBooks() {
|
numBooks() {
|
||||||
return this.narrator?.books?.length || 0
|
return this.narrator?.numBooks || this.narrator?.books?.length || 0
|
||||||
},
|
},
|
||||||
userCanUpdate() {
|
userCanUpdate() {
|
||||||
return this.$store.getters['user/getUserCanUpdate']
|
return this.$store.getters['user/getUserCanUpdate']
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="narrators?.length" class="flex py-0.5 mt-4">
|
<div v-if="narrators?.length" class="flex py-0.5 mt-4">
|
||||||
<div class="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.LabelNarrators }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelNarrators }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-[calc(100vw-10rem)] overflow-hidden overflow-ellipsis">
|
<div class="max-w-[calc(100vw-10rem)] overflow-hidden overflow-ellipsis">
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="publishedYear" class="flex py-0.5">
|
<div v-if="publishedYear" class="flex py-0.5">
|
||||||
<div class="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.LabelPublishYear }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelPublishYear }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -20,15 +20,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="publisher" class="flex py-0.5">
|
<div v-if="publisher" class="flex py-0.5">
|
||||||
<div class="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.LabelPublisher }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelPublisher }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ publisher }}
|
<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 v-if="musicAlbum" class="flex py-0.5">
|
||||||
<div class="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">Album</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">Album</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="musicAlbumArtist" class="flex py-0.5">
|
<div v-if="musicAlbumArtist" class="flex py-0.5">
|
||||||
<div class="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">Album Artist</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">Album Artist</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="musicTrackPretty" class="flex py-0.5">
|
<div v-if="musicTrackPretty" class="flex py-0.5">
|
||||||
<div class="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">Track</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">Track</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="musicDiscPretty" class="flex py-0.5">
|
<div v-if="musicDiscPretty" class="flex py-0.5">
|
||||||
<div class="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">Disc</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">Disc</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="podcastType" class="flex py-0.5">
|
<div v-if="podcastType" class="flex py-0.5">
|
||||||
<div class="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>
|
||||||
</div>
|
</div>
|
||||||
<div class="capitalize">
|
<div class="capitalize">
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex py-0.5" v-if="genres.length">
|
<div class="flex py-0.5" v-if="genres.length">
|
||||||
<div class="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.LabelGenres }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelGenres }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-[calc(100vw-10rem)] overflow-hidden overflow-ellipsis">
|
<div class="max-w-[calc(100vw-10rem)] overflow-hidden overflow-ellipsis">
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex py-0.5" v-if="tags.length">
|
<div class="flex py-0.5" v-if="tags.length">
|
||||||
<div class="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.LabelTags }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelTags }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-[calc(100vw-10rem)] overflow-hidden overflow-ellipsis">
|
<div class="max-w-[calc(100vw-10rem)] overflow-hidden overflow-ellipsis">
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="tracks.length || audioFile || (isPodcast && totalPodcastDuration)" class="flex py-0.5">
|
<div v-if="tracks.length || audioFile || (isPodcast && totalPodcastDuration)" class="flex py-0.5">
|
||||||
<div class="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>
|
||||||
<div>
|
<div>
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex py-0.5">
|
<div class="flex py-0.5">
|
||||||
<div class="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.LabelSize }}</span>
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelSize }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export default {
|
|||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
totalResults() {
|
totalResults() {
|
||||||
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.podcastResults.length
|
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.podcastResults.length + this.narratorResults.length
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ export default {
|
|||||||
value: 'narrators',
|
value: 'narrators',
|
||||||
sublist: true
|
sublist: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelPublisher,
|
||||||
|
value: 'publishers',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: this.$strings.LabelLanguage,
|
text: this.$strings.LabelLanguage,
|
||||||
value: 'languages',
|
value: 'languages',
|
||||||
@@ -167,6 +172,11 @@ export default {
|
|||||||
value: 'narrators',
|
value: 'narrators',
|
||||||
sublist: true
|
sublist: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelPublisher,
|
||||||
|
value: 'publishers',
|
||||||
|
sublist: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: this.$strings.LabelLanguage,
|
text: this.$strings.LabelLanguage,
|
||||||
value: 'languages',
|
value: 'languages',
|
||||||
@@ -271,12 +281,16 @@ export default {
|
|||||||
let filterValue = null
|
let filterValue = null
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
const decoded = this.$decode(parts[1])
|
const decoded = this.$decode(parts[1])
|
||||||
if (decoded.startsWith('aut_')) {
|
if (parts[0] === 'authors') {
|
||||||
const author = this.authors.find((au) => au.id == decoded)
|
const author = this.authors.find((au) => au.id == decoded)
|
||||||
if (author) filterValue = author.name
|
if (author) filterValue = author.name
|
||||||
} else if (decoded.startsWith('ser_')) {
|
} else if (parts[0] === 'series') {
|
||||||
const series = this.series.find((se) => se.id == decoded)
|
if (decoded === 'no-series') {
|
||||||
if (series) filterValue = series.name
|
filterValue = this.$strings.MessageNoSeries
|
||||||
|
} else {
|
||||||
|
const series = this.series.find((se) => se.id == decoded)
|
||||||
|
if (series) filterValue = series.name
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
filterValue = decoded
|
filterValue = decoded
|
||||||
}
|
}
|
||||||
@@ -309,6 +323,9 @@ export default {
|
|||||||
languages() {
|
languages() {
|
||||||
return this.filterData.languages || []
|
return this.filterData.languages || []
|
||||||
},
|
},
|
||||||
|
publishers() {
|
||||||
|
return this.filterData.publishers || []
|
||||||
|
},
|
||||||
progress() {
|
progress() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -331,6 +348,10 @@ export default {
|
|||||||
},
|
},
|
||||||
tracks() {
|
tracks() {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
id: 'none',
|
||||||
|
name: this.$strings.LabelTracksNone
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'single',
|
id: 'single',
|
||||||
name: this.$strings.LabelTracksSingleTrack
|
name: this.$strings.LabelTracksSingleTrack
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
|
|
||||||
<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
<div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
||||||
<div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
|
<div class="w-full h-full border-2 border-error flex flex-col items-center justify-center">
|
||||||
<img src="/Logo.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" />
|
<img v-if="width > 100" src="/Logo.png" class="mb-2" :style="{ height: 40 * sizeMultiplier + 'px' }" />
|
||||||
<p class="text-center text-error" :style="{ fontSize: sizeMultiplier + 'rem' }">Invalid Cover</p>
|
<p class="text-center text-error" :style="{ fontSize: invalidCoverFontSize + 'rem' }">Invalid Cover</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -58,6 +58,9 @@ export default {
|
|||||||
sizeMultiplier() {
|
sizeMultiplier() {
|
||||||
return this.width / 120
|
return this.width / 120
|
||||||
},
|
},
|
||||||
|
invalidCoverFontSize() {
|
||||||
|
return Math.max(this.sizeMultiplier * 0.8, 0.5)
|
||||||
|
},
|
||||||
placeholderCoverPadding() {
|
placeholderCoverPadding() {
|
||||||
return 0.8 * this.sizeMultiplier
|
return 0.8 * this.sizeMultiplier
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<form @submit.prevent="submitForm">
|
<form @submit.prevent="submitForm">
|
||||||
<div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="min-height: 400px; max-height: 80vh">
|
<div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="min-height: 400px; max-height: 80vh">
|
||||||
<div class="w-full p-8">
|
<div class="w-full p-8">
|
||||||
<div class="flex py-2">
|
<div class="flex py-2">
|
||||||
<div class="w-1/2 px-2">
|
<div class="w-1/2 px-2">
|
||||||
@@ -103,7 +103,6 @@
|
|||||||
<ui-toggle-switch labeledBy="selected-tags-not-accessible--permissions-toggle" v-model="newUser.permissions.selectedTagsNotAccessible" />
|
<ui-toggle-switch labeledBy="selected-tags-not-accessible--permissions-toggle" v-model="newUser.permissions.selectedTagsNotAccessible" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -353,7 +352,8 @@ export default {
|
|||||||
accessAllTags: true,
|
accessAllTags: true,
|
||||||
selectedTagsNotAccessible: false
|
selectedTagsNotAccessible: false
|
||||||
},
|
},
|
||||||
librariesAccessible: []
|
librariesAccessible: [],
|
||||||
|
itemTagsSelected: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +1,99 @@
|
|||||||
<template>
|
<template>
|
||||||
<modals-modal v-model="show" name="audiofile-data-modal" :width="700" :height="'unset'">
|
<modals-modal v-model="show" name="audiofile-data-modal" :width="700" :height="'unset'">
|
||||||
<div v-if="audioFile" ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
|
<div v-if="audioFile" ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
|
||||||
<p class="text-base text-gray-200">{{ metadata.filename }}</p>
|
<div class="flex items-center justify-between">
|
||||||
|
<p class="text-base text-gray-200 truncate">{{ metadata.filename }}</p>
|
||||||
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
<ui-btn v-if="ffprobeData" small class="ml-2" @click="ffprobeData = null">{{ $strings.ButtonReset }}</ui-btn>
|
||||||
|
<ui-btn v-else-if="userIsAdminOrUp" small :loading="probingFile" class="ml-2" @click="getFFProbeData">Probe Audio File</ui-btn>
|
||||||
<ui-text-input-with-label :value="metadata.path" readonly :label="$strings.LabelPath" class="mb-4 text-sm" />
|
|
||||||
|
|
||||||
<div class="flex flex-col sm:flex-row text-sm">
|
|
||||||
<div class="w-full sm:w-1/2">
|
|
||||||
<div class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">
|
|
||||||
{{ $strings.LabelSize }}
|
|
||||||
</p>
|
|
||||||
<p>{{ $bytesPretty(metadata.size) }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">
|
|
||||||
{{ $strings.LabelDuration }}
|
|
||||||
</p>
|
|
||||||
<p>{{ $secondsToTimestamp(audioFile.duration) }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">{{ $strings.LabelFormat }}</p>
|
|
||||||
<p>{{ audioFile.format }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">
|
|
||||||
{{ $strings.LabelChapters }}
|
|
||||||
</p>
|
|
||||||
<p>{{ audioFile.chapters?.length || 0 }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-if="audioFile.embeddedCoverArt" class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">
|
|
||||||
{{ $strings.LabelEmbeddedCover }}
|
|
||||||
</p>
|
|
||||||
<p>{{ audioFile.embeddedCoverArt || '' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-full sm:w-1/2">
|
|
||||||
<div class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">
|
|
||||||
{{ $strings.LabelCodec }}
|
|
||||||
</p>
|
|
||||||
<p>{{ audioFile.codec }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">
|
|
||||||
{{ $strings.LabelChannels }}
|
|
||||||
</p>
|
|
||||||
<p>{{ audioFile.channels }} ({{ audioFile.channelLayout }})</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">
|
|
||||||
{{ $strings.LabelBitrate }}
|
|
||||||
</p>
|
|
||||||
<p>{{ $bytesPretty(audioFile.bitRate || 0, 0) }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">{{ $strings.LabelTimeBase }}</p>
|
|
||||||
<p>{{ audioFile.timeBase }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-if="audioFile.language" class="flex mb-1">
|
|
||||||
<p class="w-32 text-black-50">
|
|
||||||
{{ $strings.LabelLanguage }}
|
|
||||||
</p>
|
|
||||||
<p>{{ audioFile.language || '' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||||
|
|
||||||
<p class="font-bold mb-2">{{ $strings.LabelMetaTags }}</p>
|
<template v-if="!ffprobeData">
|
||||||
|
<ui-text-input-with-label :value="metadata.path" readonly :label="$strings.LabelPath" class="mb-4 text-sm" />
|
||||||
|
|
||||||
<div v-for="(value, key) in metaTags" :key="key" class="flex mb-1 text-sm">
|
<div class="flex flex-col sm:flex-row text-sm">
|
||||||
<p class="w-32 min-w-32 text-black-50 mb-1">
|
<div class="w-full sm:w-1/2">
|
||||||
{{ key.replace('tag', '') }}
|
<div class="flex mb-1">
|
||||||
</p>
|
<p class="w-32 text-black-50">
|
||||||
<p>{{ value }}</p>
|
{{ $strings.LabelSize }}
|
||||||
|
</p>
|
||||||
|
<p>{{ $bytesPretty(metadata.size) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex mb-1">
|
||||||
|
<p class="w-32 text-black-50">
|
||||||
|
{{ $strings.LabelDuration }}
|
||||||
|
</p>
|
||||||
|
<p>{{ $secondsToTimestamp(audioFile.duration) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex mb-1">
|
||||||
|
<p class="w-32 text-black-50">{{ $strings.LabelFormat }}</p>
|
||||||
|
<p>{{ audioFile.format }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex mb-1">
|
||||||
|
<p class="w-32 text-black-50">
|
||||||
|
{{ $strings.LabelChapters }}
|
||||||
|
</p>
|
||||||
|
<p>{{ audioFile.chapters?.length || 0 }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="audioFile.embeddedCoverArt" class="flex mb-1">
|
||||||
|
<p class="w-32 text-black-50">
|
||||||
|
{{ $strings.LabelEmbeddedCover }}
|
||||||
|
</p>
|
||||||
|
<p>{{ audioFile.embeddedCoverArt || '' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full sm:w-1/2">
|
||||||
|
<div class="flex mb-1">
|
||||||
|
<p class="w-32 text-black-50">
|
||||||
|
{{ $strings.LabelCodec }}
|
||||||
|
</p>
|
||||||
|
<p>{{ audioFile.codec }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex mb-1">
|
||||||
|
<p class="w-32 text-black-50">
|
||||||
|
{{ $strings.LabelChannels }}
|
||||||
|
</p>
|
||||||
|
<p>{{ audioFile.channels }} ({{ audioFile.channelLayout }})</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex mb-1">
|
||||||
|
<p class="w-32 text-black-50">
|
||||||
|
{{ $strings.LabelBitrate }}
|
||||||
|
</p>
|
||||||
|
<p>{{ $bytesPretty(audioFile.bitRate || 0, 0) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex mb-1">
|
||||||
|
<p class="w-32 text-black-50">{{ $strings.LabelTimeBase }}</p>
|
||||||
|
<p>{{ audioFile.timeBase }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="audioFile.language" class="flex mb-1">
|
||||||
|
<p class="w-32 text-black-50">
|
||||||
|
{{ $strings.LabelLanguage }}
|
||||||
|
</p>
|
||||||
|
<p>{{ audioFile.language || '' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||||
|
|
||||||
|
<p class="font-bold mb-2">{{ $strings.LabelMetaTags }}</p>
|
||||||
|
|
||||||
|
<div v-for="(value, key) in metaTags" :key="key" class="flex mb-1 text-sm">
|
||||||
|
<p class="w-32 min-w-32 text-black-50 mb-1">
|
||||||
|
{{ key.replace('tag', '') }}
|
||||||
|
</p>
|
||||||
|
<p>{{ value }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="w-full">
|
||||||
|
<div class="relative">
|
||||||
|
<ui-textarea-with-label :value="prettyFfprobeData" readonly :rows="30" class="text-xs" />
|
||||||
|
|
||||||
|
<button class="absolute top-4 right-4" :class="copiedToClipboard ? 'text-success' : 'text-white/50 hover:text-white/80'" @click.stop="copyFfprobeData">
|
||||||
|
<span class="material-icons">{{ copiedToClipboard ? 'check' : 'content_copy' }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
@@ -91,10 +106,24 @@ export default {
|
|||||||
audioFile: {
|
audioFile: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
}
|
},
|
||||||
|
libraryItemId: String
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {
|
||||||
|
probingFile: false,
|
||||||
|
ffprobeData: null,
|
||||||
|
copiedToClipboard: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show(newVal) {
|
||||||
|
if (newVal) {
|
||||||
|
this.ffprobeData = null
|
||||||
|
this.copiedToClipboard = false
|
||||||
|
this.probingFile = false
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
show: {
|
show: {
|
||||||
@@ -110,9 +139,36 @@ export default {
|
|||||||
},
|
},
|
||||||
metaTags() {
|
metaTags() {
|
||||||
return this.audioFile?.metaTags || {}
|
return this.audioFile?.metaTags || {}
|
||||||
|
},
|
||||||
|
userIsAdminOrUp() {
|
||||||
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
|
},
|
||||||
|
prettyFfprobeData() {
|
||||||
|
if (!this.ffprobeData) return ''
|
||||||
|
return JSON.stringify(this.ffprobeData, null, 2)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getFFProbeData() {
|
||||||
|
this.probingFile = true
|
||||||
|
this.$axios
|
||||||
|
.$get(`/api/items/${this.libraryItemId}/ffprobe/${this.audioFile.ino}`)
|
||||||
|
.then((data) => {
|
||||||
|
console.log('Got ffprobe data', data)
|
||||||
|
this.ffprobeData = data
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to get ffprobe data', error)
|
||||||
|
this.$toast.error('FFProbe failed')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.probingFile = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async copyFfprobeData() {
|
||||||
|
this.copiedToClipboard = await this.$copyToClipboard(this.prettyFfprobeData)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {},
|
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clickedOption(action) {
|
clickedOption(action) {
|
||||||
this.$emit('action', action)
|
this.$emit('action', { action })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<modals-modal v-model="show" name="listening-session-modal" :processing="processing" :width="700" :height="'unset'">
|
<modals-modal v-model="show" name="listening-session-modal" :processing="processing" :width="700" :height="'unset'">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||||
<p class="text-3xl text-white truncate">{{ $strings.HeaderSession }} {{ _session.id }}</p>
|
<p class="text-lg md:text-2xl text-white truncate">{{ $strings.HeaderSession }} {{ _session.id }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
|
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
|
||||||
@@ -50,19 +50,19 @@
|
|||||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelItem }}</p>
|
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelItem }}</p>
|
||||||
<div v-if="_session.libraryId" class="flex items-center -mx-1 mb-1">
|
<div v-if="_session.libraryId" class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelLibrary }} Id</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelLibrary }} Id</div>
|
||||||
<div class="px-1">
|
<div class="px-1 text-xs">
|
||||||
{{ _session.libraryId }}
|
{{ _session.libraryId }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center -mx-1 mb-1">
|
<div class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelLibraryItem }} Id</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelLibraryItem }} Id</div>
|
||||||
<div class="px-1">
|
<div class="px-1 text-xs">
|
||||||
{{ _session.libraryItemId }}
|
{{ _session.libraryItemId }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="_session.episodeId" class="flex items-center -mx-1 mb-1">
|
<div v-if="_session.episodeId" class="flex items-center -mx-1 mb-1">
|
||||||
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelEpisode }} Id</div>
|
<div class="w-40 px-1 text-gray-200">{{ $strings.LabelEpisode }} Id</div>
|
||||||
<div class="px-1">
|
<div class="px-1 text-xs">
|
||||||
{{ _session.episodeId }}
|
{{ _session.episodeId }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/3">
|
<div class="w-full md:w-1/3">
|
||||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
|
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
|
||||||
<p class="mb-1">{{ _session.userId }}</p>
|
<p class="mb-1 text-xs">{{ _session.userId }}</p>
|
||||||
|
|
||||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelMediaPlayer }}</p>
|
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelMediaPlayer }}</p>
|
||||||
<p class="mb-1">{{ playMethodName }}</p>
|
<p class="mb-1">{{ playMethodName }}</p>
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
<div ref="wrapper" class="modal modal-bg w-full h-full fixed top-0 left-0 bg-primary items-center justify-center opacity-0 hidden" :class="`z-${zIndex} bg-opacity-${bgOpacity}`">
|
<div ref="wrapper" class="modal modal-bg w-full h-full fixed top-0 left-0 bg-primary items-center justify-center opacity-0 hidden" :class="`z-${zIndex} bg-opacity-${bgOpacity}`">
|
||||||
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
|
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
|
||||||
|
|
||||||
<div class="absolute top-3 right-3 landscape:top-2 landscape:right-2 md:portrait:top-5 md:portrait:right-5 lg:top-5 lg:right-5 h-8 w-8 landscape:h-8 landscape:w-8 md:portrait:h-12 md:portrait:w-12 lg:w-12 lg:h-12 flex items-center justify-center cursor-pointer text-white hover:text-gray-300" @click="clickClose">
|
<button class="absolute top-4 right-4 landscape:top-4 landscape:right-4 md:portrait:top-5 md:portrait:right-5 lg:top-5 lg:right-5 inline-flex text-gray-200 hover:text-white" aria-label="Close modal" @click="clickClose">
|
||||||
<span class="material-icons text-2xl landscape:text-2xl md:portrait:text-4xl lg:text-4xl">close</span>
|
<span class="material-icons text-2xl landscape:text-2xl md:portrait:text-4xl lg:text-4xl">close</span>
|
||||||
</div>
|
</button>
|
||||||
<slot name="outer" />
|
<slot name="outer" />
|
||||||
<div ref="content" style="min-width: 380px; min-height: 200px; max-width: 100vw" class="relative text-white" :style="{ height: modalHeight, width: modalWidth, marginTop: contentMarginTop + 'px' }" @mousedown="mousedownModal" @mouseup="mouseupModal" v-click-outside="clickBg">
|
<div ref="content" style="min-width: 380px; min-height: 200px; max-width: 100vw" class="relative text-white" aria-modal="true" :style="{ height: modalHeight, width: modalWidth, marginTop: contentMarginTop + 'px' }" @mousedown="mousedownModal" @mouseup="mouseupModal" v-click-outside="clickBg">
|
||||||
<slot />
|
<slot />
|
||||||
<div v-if="processing" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-black bg-opacity-60 rounded-lg flex items-center justify-center">
|
<div v-if="processing" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-black bg-opacity-60 rounded-lg flex items-center justify-center">
|
||||||
<ui-loading-indicator />
|
<ui-loading-indicator />
|
||||||
|
|||||||
@@ -8,10 +8,9 @@
|
|||||||
<div class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
<div class="p-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden" style="min-height: 400px; max-height: 80vh">
|
||||||
<template v-if="!showImageUploader">
|
<template v-if="!showImageUploader">
|
||||||
<form @submit.prevent="submitForm">
|
<form @submit.prevent="submitForm">
|
||||||
<div class="flex">
|
<div class="flex flex-wrap">
|
||||||
<div>
|
<div class="w-full flex justify-center mb-2 md:w-auto md:mb-0 md:block">
|
||||||
<covers-collection-cover :book-items="books" :width="200" :height="100 * bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
<covers-collection-cover :book-items="books" :width="200" :height="100 * bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
<!-- <ui-btn type="button" @click="showImageUploader = true">Upload</ui-btn> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow px-4">
|
<div class="flex-grow px-4">
|
||||||
<ui-text-input-with-label v-model="newCollectionName" :label="$strings.LabelName" class="mb-2" />
|
<ui-text-input-with-label v-model="newCollectionName" :label="$strings.LabelName" class="mb-2" />
|
||||||
@@ -41,7 +40,6 @@
|
|||||||
<ui-btn color="success">Upload</ui-btn>
|
<ui-btn color="success">Upload</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- <modals-upload-image-modal v-model="showUploadImageModal" entity="collection" :entity-id="collection.id" /> -->
|
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full overflow-hidden overflow-y-auto px-2 sm:px-4 py-6 relative">
|
<div class="w-full h-full overflow-hidden overflow-y-auto px-2 sm:px-4 py-6 relative">
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap mb-4">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](libraryItemId, null, true)" :width="120" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrcById'](libraryItemId, libraryItemUpdatedAt, true)" :width="120" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||||
|
|
||||||
<!-- book cover overlay -->
|
<!-- book cover overlay -->
|
||||||
<div v-if="media.coverPath" class="absolute top-0 left-0 w-full h-full z-10 opacity-0 hover:opacity-100 transition-opacity duration-100">
|
<div v-if="media.coverPath" class="absolute top-0 left-0 w-full h-full z-10 opacity-0 hover:opacity-100 transition-opacity duration-100">
|
||||||
@@ -139,16 +139,19 @@ export default {
|
|||||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||||
},
|
},
|
||||||
libraryItemId() {
|
libraryItemId() {
|
||||||
return this.libraryItem ? this.libraryItem.id : null
|
return this.libraryItem?.id || null
|
||||||
|
},
|
||||||
|
libraryItemUpdatedAt() {
|
||||||
|
return this.libraryItem?.updatedAt || null
|
||||||
},
|
},
|
||||||
mediaType() {
|
mediaType() {
|
||||||
return this.libraryItem ? this.libraryItem.mediaType : null
|
return this.libraryItem?.mediaType || null
|
||||||
},
|
},
|
||||||
isPodcast() {
|
isPodcast() {
|
||||||
return this.mediaType == 'podcast'
|
return this.mediaType == 'podcast'
|
||||||
},
|
},
|
||||||
media() {
|
media() {
|
||||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
return this.libraryItem?.media || {}
|
||||||
},
|
},
|
||||||
coverPath() {
|
coverPath() {
|
||||||
return this.media.coverPath
|
return this.media.coverPath
|
||||||
@@ -157,7 +160,7 @@ export default {
|
|||||||
return this.media.metadata || {}
|
return this.media.metadata || {}
|
||||||
},
|
},
|
||||||
libraryFiles() {
|
libraryFiles() {
|
||||||
return this.libraryItem ? this.libraryItem.libraryFiles || [] : []
|
return this.libraryItem?.libraryFiles || []
|
||||||
},
|
},
|
||||||
userCanUpload() {
|
userCanUpload() {
|
||||||
return this.$store.getters['user/getUserCanUpload']
|
return this.$store.getters['user/getUserCanUpload']
|
||||||
@@ -280,9 +283,8 @@ export default {
|
|||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
this.$toast.success('Update Successful')
|
this.$toast.success('Update Successful')
|
||||||
// this.$emit('close')
|
} else if (this.media.coverPath) {
|
||||||
} else {
|
this.imageUrl = this.media.coverPath
|
||||||
this.imageUrl = this.media.coverPath || ''
|
|
||||||
}
|
}
|
||||||
this.isProcessing = false
|
this.isProcessing = false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,18 +20,14 @@
|
|||||||
<div v-if="!episodes.length" class="flex my-4 text-center justify-center text-xl">{{ $strings.MessageNoEpisodes }}</div>
|
<div v-if="!episodes.length" class="flex my-4 text-center justify-center text-xl">{{ $strings.MessageNoEpisodes }}</div>
|
||||||
<table v-else class="text-sm tracksTable">
|
<table v-else class="text-sm tracksTable">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-left">Sort #</th>
|
<th class="text-center w-20 min-w-20">{{ $strings.LabelEpisode }}</th>
|
||||||
<th class="text-left whitespace-nowrap">{{ $strings.LabelEpisode }}</th>
|
<th class="text-left">{{ $strings.LabelEpisodeTitle }}</th>
|
||||||
<th class="text-left">{{ $strings.EpisodeTitle }}</th>
|
<th class="text-center w-28">{{ $strings.LabelEpisodeDuration }}</th>
|
||||||
<th class="text-center w-28">{{ $strings.EpisodeDuration }}</th>
|
<th class="text-center w-28">{{ $strings.LabelEpisodeSize }}</th>
|
||||||
<th class="text-center w-28">{{ $strings.EpisodeSize }}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="episode in episodes" :key="episode.id">
|
<tr v-for="episode in episodes" :key="episode.id">
|
||||||
<td class="text-left">
|
<td class="text-center w-20 min-w-20">
|
||||||
<p class="px-4">{{ episode.index }}</p>
|
<p>{{ episode.episode }}</p>
|
||||||
</td>
|
|
||||||
<td class="text-left">
|
|
||||||
<p class="px-4">{{ episode.episode }}</p>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ episode.title }}
|
{{ episode.title }}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="py-3">
|
<div class="py-3">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="disableWatcher" @input="formUpdated" />
|
<ui-toggle-switch v-if="!globalWatcherDisabled" v-model="enableWatcher" @input="formUpdated" />
|
||||||
<ui-toggle-switch v-else disabled :value="false" />
|
<ui-toggle-switch v-else disabled :value="false" />
|
||||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsDisableWatcherForLibrary }}</p>
|
<p class="pl-4 text-base">{{ $strings.LabelSettingsEnableWatcherForLibrary }}</p>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*{{ $strings.MessageWatcherIsDisabledGlobally }}</p>
|
<p v-if="globalWatcherDisabled" class="text-xs text-warning">*{{ $strings.MessageWatcherIsDisabledGlobally }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,6 +38,17 @@
|
|||||||
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}</p>
|
<p class="pl-4 text-base">{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isBookLibrary" class="py-3">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ui-toggle-switch v-model="hideSingleBookSeries" @input="formUpdated" />
|
||||||
|
<ui-tooltip :text="$strings.LabelSettingsHideSingleBookSeriesHelp">
|
||||||
|
<p class="pl-4 text-base">
|
||||||
|
{{ $strings.LabelSettingsHideSingleBookSeries }}
|
||||||
|
<span class="material-icons icon-text text-sm">info_outlined</span>
|
||||||
|
</p>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -54,10 +65,11 @@ export default {
|
|||||||
return {
|
return {
|
||||||
provider: null,
|
provider: null,
|
||||||
useSquareBookCovers: false,
|
useSquareBookCovers: false,
|
||||||
disableWatcher: false,
|
enableWatcher: false,
|
||||||
skipMatchingMediaWithAsin: false,
|
skipMatchingMediaWithAsin: false,
|
||||||
skipMatchingMediaWithIsbn: false,
|
skipMatchingMediaWithIsbn: false,
|
||||||
audiobooksOnly: false
|
audiobooksOnly: false,
|
||||||
|
hideSingleBookSeries: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -83,10 +95,11 @@ export default {
|
|||||||
return {
|
return {
|
||||||
settings: {
|
settings: {
|
||||||
coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD,
|
coverAspectRatio: this.useSquareBookCovers ? this.$constants.BookCoverAspectRatio.SQUARE : this.$constants.BookCoverAspectRatio.STANDARD,
|
||||||
disableWatcher: !!this.disableWatcher,
|
disableWatcher: !this.enableWatcher,
|
||||||
skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin,
|
skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin,
|
||||||
skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn,
|
skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn,
|
||||||
audiobooksOnly: !!this.audiobooksOnly
|
audiobooksOnly: !!this.audiobooksOnly,
|
||||||
|
hideSingleBookSeries: !!this.hideSingleBookSeries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -95,10 +108,11 @@ export default {
|
|||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.useSquareBookCovers = this.librarySettings.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE
|
this.useSquareBookCovers = this.librarySettings.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE
|
||||||
this.disableWatcher = !!this.librarySettings.disableWatcher
|
this.enableWatcher = !this.librarySettings.disableWatcher
|
||||||
this.skipMatchingMediaWithAsin = !!this.librarySettings.skipMatchingMediaWithAsin
|
this.skipMatchingMediaWithAsin = !!this.librarySettings.skipMatchingMediaWithAsin
|
||||||
this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn
|
this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn
|
||||||
this.audiobooksOnly = !!this.librarySettings.audiobooksOnly
|
this.audiobooksOnly = !!this.librarySettings.audiobooksOnly
|
||||||
|
this.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end pt-4">
|
<div class="flex justify-end pt-4">
|
||||||
<ui-checkbox v-if="!allDownloaded" v-model="selectAll" @input="toggleSelectAll" label="Select all episodes" small checkbox-bg="primary" border-color="gray-600" class="mx-8" />
|
<ui-checkbox v-if="!allDownloaded" v-model="selectAll" @input="toggleSelectAll" :label="selectAllLabel" small checkbox-bg="primary" border-color="gray-600" class="mx-8" />
|
||||||
<ui-btn v-if="!allDownloaded" :disabled="!episodesSelected.length" @click="submit">{{ buttonText }}</ui-btn>
|
<ui-btn v-if="!allDownloaded" :disabled="!episodesSelected.length" @click="submit">{{ buttonText }}</ui-btn>
|
||||||
<p v-else class="text-success text-base px-2 py-4">All episodes are downloaded</p>
|
<p v-else class="text-success text-base px-2 py-4">All episodes are downloaded</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,46 +99,82 @@ export default {
|
|||||||
return Object.keys(this.selectedEpisodes).filter((key) => !!this.selectedEpisodes[key])
|
return Object.keys(this.selectedEpisodes).filter((key) => !!this.selectedEpisodes[key])
|
||||||
},
|
},
|
||||||
buttonText() {
|
buttonText() {
|
||||||
if (!this.episodesSelected.length) return 'No Episodes Selected'
|
if (!this.episodesSelected.length) return this.$strings.LabelNoEpisodesSelected
|
||||||
return `Download ${this.episodesSelected.length} Episode${this.episodesSelected.length > 1 ? 's' : ''}`
|
if (this.episodesSelected.length === 1) return `${this.$strings.LabelDownload} ${this.$strings.LabelEpisode.toLowerCase()}`
|
||||||
|
return this.$getString('LabelDownloadNEpisodes', [this.episodesSelected.length])
|
||||||
},
|
},
|
||||||
itemEpisodes() {
|
itemEpisodes() {
|
||||||
if (!this.libraryItem) return []
|
if (!this.libraryItem) return []
|
||||||
return this.libraryItem.media.episodes || []
|
return this.libraryItem.media.episodes || []
|
||||||
},
|
},
|
||||||
itemEpisodeMap() {
|
itemEpisodeMap() {
|
||||||
var map = {}
|
const map = {}
|
||||||
this.itemEpisodes.forEach((item) => {
|
this.itemEpisodes.forEach((item) => {
|
||||||
if (item.enclosure) map[item.enclosure.url.split('?')[0]] = true
|
if (item.enclosure) {
|
||||||
|
const cleanUrl = this.getCleanEpisodeUrl(item.enclosure.url)
|
||||||
|
map[cleanUrl] = true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return map
|
return map
|
||||||
},
|
},
|
||||||
episodesList() {
|
episodesList() {
|
||||||
return this.episodesCleaned.filter((episode) => {
|
return this.episodesCleaned.filter((episode) => {
|
||||||
if (!this.searchText) return true
|
if (!this.searchText) return true
|
||||||
return (episode.title && episode.title.toLowerCase().includes(this.searchText)) || (episode.subtitle && episode.subtitle.toLowerCase().includes(this.searchText))
|
return episode.title?.toLowerCase().includes(this.searchText) || episode.subtitle?.toLowerCase().includes(this.searchText)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
selectAllLabel() {
|
||||||
|
if (this.episodesList.length === this.episodesCleaned.length) {
|
||||||
|
return this.$strings.LabelSelectAllEpisodes
|
||||||
|
}
|
||||||
|
const episodesNotDownloaded = this.episodesList.filter((ep) => !this.itemEpisodeMap[ep.cleanUrl]).length
|
||||||
|
return this.$getString('LabelSelectEpisodesShowing', [episodesNotDownloaded])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* RSS feed episode url is used for matching with existing downloaded episodes.
|
||||||
|
* Some RSS feeds include timestamps in the episode url (e.g. patreon) that can change on requests.
|
||||||
|
* These need to be removed in order to detect the same episode each time the feed is pulled.
|
||||||
|
*
|
||||||
|
* An RSS feed may include an `id` in the query string. In these cases we want to leave the `id`.
|
||||||
|
* @see https://github.com/advplyr/audiobookshelf/issues/1896
|
||||||
|
*
|
||||||
|
* @param {string} url - rss feed episode url
|
||||||
|
* @returns {string} rss feed episode url without dynamic query strings
|
||||||
|
*/
|
||||||
|
getCleanEpisodeUrl(url) {
|
||||||
|
let queryString = url.split('?')[1]
|
||||||
|
if (!queryString) return url
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams(queryString)
|
||||||
|
for (const p of Array.from(searchParams.keys())) {
|
||||||
|
if (p !== 'id') searchParams.delete(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!searchParams.toString()) return url
|
||||||
|
return `${url}?${searchParams.toString()}`
|
||||||
|
},
|
||||||
inputUpdate() {
|
inputUpdate() {
|
||||||
clearTimeout(this.searchTimeout)
|
clearTimeout(this.searchTimeout)
|
||||||
this.searchTimeout = setTimeout(() => {
|
this.searchTimeout = setTimeout(() => {
|
||||||
if (!this.search || !this.search.trim()) {
|
if (!this.search?.trim()) {
|
||||||
this.searchText = ''
|
this.searchText = ''
|
||||||
|
this.checkSetIsSelectedAll()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.searchText = this.search.toLowerCase().trim()
|
this.searchText = this.search.toLowerCase().trim()
|
||||||
|
this.checkSetIsSelectedAll()
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
toggleSelectAll(val) {
|
toggleSelectAll(val) {
|
||||||
for (const episode of this.episodesCleaned) {
|
for (const episode of this.episodesList) {
|
||||||
if (this.itemEpisodeMap[episode.cleanUrl]) this.selectedEpisodes[episode.cleanUrl] = false
|
if (this.itemEpisodeMap[episode.cleanUrl]) this.selectedEpisodes[episode.cleanUrl] = false
|
||||||
else this.$set(this.selectedEpisodes, episode.cleanUrl, val)
|
else this.$set(this.selectedEpisodes, episode.cleanUrl, val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkSetIsSelectedAll() {
|
checkSetIsSelectedAll() {
|
||||||
for (const episode of this.episodesCleaned) {
|
for (const episode of this.episodesList) {
|
||||||
if (!this.itemEpisodeMap[episode.cleanUrl] && !this.selectedEpisodes[episode.cleanUrl]) {
|
if (!this.itemEpisodeMap[episode.cleanUrl] && !this.selectedEpisodes[episode.cleanUrl]) {
|
||||||
this.selectAll = false
|
this.selectAll = false
|
||||||
return
|
return
|
||||||
@@ -147,19 +183,19 @@ export default {
|
|||||||
this.selectAll = true
|
this.selectAll = true
|
||||||
},
|
},
|
||||||
toggleSelectEpisode(episode) {
|
toggleSelectEpisode(episode) {
|
||||||
if (this.itemEpisodeMap[episode.enclosure.url?.split('?')[0]]) return
|
if (this.itemEpisodeMap[episode.cleanUrl]) return
|
||||||
this.$set(this.selectedEpisodes, episode.cleanUrl, !this.selectedEpisodes[episode.cleanUrl])
|
this.$set(this.selectedEpisodes, episode.cleanUrl, !this.selectedEpisodes[episode.cleanUrl])
|
||||||
this.checkSetIsSelectedAll()
|
this.checkSetIsSelectedAll()
|
||||||
},
|
},
|
||||||
submit() {
|
submit() {
|
||||||
var episodesToDownload = []
|
let episodesToDownload = []
|
||||||
if (this.episodesSelected.length) {
|
if (this.episodesSelected.length) {
|
||||||
episodesToDownload = this.episodesSelected.map((cleanUrl) => this.episodesCleaned.find((ep) => ep.cleanUrl == cleanUrl))
|
episodesToDownload = this.episodesSelected.map((cleanUrl) => this.episodesCleaned.find((ep) => ep.cleanUrl == cleanUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
var payloadSize = JSON.stringify(episodesToDownload).length
|
const payloadSize = JSON.stringify(episodesToDownload).length
|
||||||
var sizeInMb = payloadSize / 1024 / 1024
|
const sizeInMb = payloadSize / 1024 / 1024
|
||||||
var sizeInMbPretty = sizeInMb.toFixed(2) + 'MB'
|
const sizeInMbPretty = sizeInMb.toFixed(2) + 'MB'
|
||||||
console.log('Request size', sizeInMb)
|
console.log('Request size', sizeInMb)
|
||||||
if (sizeInMb > 4.99) {
|
if (sizeInMb > 4.99) {
|
||||||
return this.$toast.error(`Request is too large (${sizeInMbPretty}) should be < 5Mb`)
|
return this.$toast.error(`Request is too large (${sizeInMbPretty}) should be < 5Mb`)
|
||||||
@@ -174,10 +210,9 @@ export default {
|
|||||||
this.show = false
|
this.show = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
var errorMsg = error.response && error.response.data ? error.response.data : 'Failed to download episodes'
|
|
||||||
console.error('Failed to download episodes', error)
|
console.error('Failed to download episodes', error)
|
||||||
this.processing = false
|
this.processing = false
|
||||||
this.$toast.error(errorMsg)
|
this.$toast.error(error.response?.data || 'Failed to download episodes')
|
||||||
|
|
||||||
this.selectedEpisodes = {}
|
this.selectedEpisodes = {}
|
||||||
this.selectAll = false
|
this.selectAll = false
|
||||||
@@ -189,7 +224,7 @@ export default {
|
|||||||
.map((_ep) => {
|
.map((_ep) => {
|
||||||
return {
|
return {
|
||||||
..._ep,
|
..._ep,
|
||||||
cleanUrl: _ep.enclosure.url.split('?')[0]
|
cleanUrl: this.getCleanEpisodeUrl(_ep.enclosure.url)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.episodesCleaned.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : -1))
|
this.episodesCleaned.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : -1))
|
||||||
|
|||||||
@@ -132,6 +132,8 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.processing = true
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
serverAddress: window.origin,
|
serverAddress: window.origin,
|
||||||
slug: this.newFeedSlug,
|
slug: this.newFeedSlug,
|
||||||
@@ -151,6 +153,9 @@ export default {
|
|||||||
const errorMsg = error.response ? error.response.data : null
|
const errorMsg = error.response ? error.response.data : null
|
||||||
this.$toast.error(errorMsg || 'Failed to open RSS Feed')
|
this.$toast.error(errorMsg || 'Failed to open RSS Feed')
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
},
|
},
|
||||||
copyToClipboard(str) {
|
copyToClipboard(str) {
|
||||||
this.$copyToClipboard(str, this)
|
this.$copyToClipboard(str, this)
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<modals-modal v-model="show" name="rss-feed-view-modal" :processing="processing" :width="700" :height="'unset'">
|
||||||
|
<div ref="wrapper" class="px-8 py-6 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-hidden">
|
||||||
|
<div v-if="feed" class="w-full">
|
||||||
|
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedGeneral }}</p>
|
||||||
|
|
||||||
|
<div class="w-full relative">
|
||||||
|
<ui-text-input v-model="feed.feedUrl" readonly />
|
||||||
|
<span class="material-icons absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feed.feedUrl)">content_copy</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="feed.meta" class="mt-5">
|
||||||
|
<div class="flex py-0.5">
|
||||||
|
<div class="w-48">
|
||||||
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelRSSFeedPreventIndexing }}</span>
|
||||||
|
</div>
|
||||||
|
<div>{{ feed.meta.preventIndexing ? 'Yes' : 'No' }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="feed.meta.ownerName" class="flex py-0.5">
|
||||||
|
<div class="w-48">
|
||||||
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelRSSFeedCustomOwnerName }}</span>
|
||||||
|
</div>
|
||||||
|
<div>{{ feed.meta.ownerName }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="feed.meta.ownerEmail" class="flex py-0.5">
|
||||||
|
<div class="w-48">
|
||||||
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelRSSFeedCustomOwnerEmail }}</span>
|
||||||
|
</div>
|
||||||
|
<div>{{ feed.meta.ownerEmail }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- -->
|
||||||
|
<div class="episodesTable mt-2">
|
||||||
|
<div class="bg-primary bg-opacity-40 h-12 header">
|
||||||
|
{{ $strings.LabelEpisodeTitle }}
|
||||||
|
</div>
|
||||||
|
<div class="scroller">
|
||||||
|
<div v-for="episode in feed.episodes" :key="episode.id" class="h-8 text-xs truncate">
|
||||||
|
{{ episode.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</modals-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: Boolean,
|
||||||
|
feed: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
processing: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_feed() {
|
||||||
|
return this.feed || {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
copyToClipboard(str) {
|
||||||
|
this.$copyToClipboard(str, this)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.episodesTable {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
border: 1px solid #474747;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episodesTable div.header {
|
||||||
|
background-color: #272727;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episodesTable .scroller {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 250px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episodesTable .scroller div {
|
||||||
|
background-color: #373838;
|
||||||
|
padding: 4px 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
height: 32px;
|
||||||
|
flex: 0 0 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.episodesTable .scroller div:nth-child(even) {
|
||||||
|
background-color: #2f2f2f;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -109,10 +109,10 @@ export default {
|
|||||||
return this.comicMetadata ? Object.keys(this.comicMetadata) : []
|
return this.comicMetadata ? Object.keys(this.comicMetadata) : []
|
||||||
},
|
},
|
||||||
canGoNext() {
|
canGoNext() {
|
||||||
return this.page < this.numPages - 1
|
return this.page < this.numPages
|
||||||
},
|
},
|
||||||
canGoPrev() {
|
canGoPrev() {
|
||||||
return this.page > 0
|
return this.page > 1
|
||||||
},
|
},
|
||||||
userMediaProgress() {
|
userMediaProgress() {
|
||||||
if (!this.libraryItemId) return
|
if (!this.libraryItemId) return
|
||||||
@@ -303,8 +303,8 @@ export default {
|
|||||||
},
|
},
|
||||||
parseImageFilename(filename) {
|
parseImageFilename(filename) {
|
||||||
var basename = Path.basename(filename, Path.extname(filename))
|
var basename = Path.basename(filename, Path.extname(filename))
|
||||||
var numbersinpath = basename.match(/\d{1,5}/g)
|
var numbersinpath = basename.match(/\d+/g)
|
||||||
if (!numbersinpath || !numbersinpath.length) {
|
if (!numbersinpath?.length) {
|
||||||
return {
|
return {
|
||||||
index: -1,
|
index: -1,
|
||||||
filename
|
filename
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="epub-reader" class="h-full w-full">
|
<div id="epub-reader" class="h-full w-full">
|
||||||
<div class="h-full flex items-center justify-center">
|
<div class="h-full flex items-center justify-center">
|
||||||
<div style="width: 100px; max-width: 100px" class="h-full hidden sm:flex items-center overflow-x-hidden justify-center">
|
<button type="button" aria-label="Previous page" class="w-24 max-w-24 h-full hidden sm:flex items-center overflow-x-hidden justify-center opacity-50 hover:opacity-100">
|
||||||
<span v-if="hasPrev" class="material-icons text-white text-opacity-50 hover:text-opacity-80 cursor-pointer text-6xl" @mousedown.prevent @click="prev">chevron_left</span>
|
<span v-if="hasPrev" class="material-icons text-6xl" @mousedown.prevent @click="prev">chevron_left</span>
|
||||||
</div>
|
</button>
|
||||||
<div id="frame" class="w-full" style="height: 80%">
|
<div id="frame" class="w-full" style="height: 80%">
|
||||||
<div id="viewer"></div>
|
<div id="viewer"></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 100px; max-width: 100px" class="h-full hidden sm:flex items-center justify-center overflow-x-hidden">
|
<button type="button" aria-label="Next page" class="w-24 max-w-24 h-full hidden sm:flex items-center justify-center overflow-x-hidden opacity-50 hover:opacity-100">
|
||||||
<span v-if="hasNext" class="material-icons text-white text-opacity-50 hover:text-opacity-80 cursor-pointer text-6xl" @mousedown.prevent @click="next">chevron_right</span>
|
<span v-if="hasNext" class="material-icons text-6xl" @mousedown.prevent @click="next">chevron_right</span>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -39,7 +39,13 @@ export default {
|
|||||||
/** @type {ePub.Book} */
|
/** @type {ePub.Book} */
|
||||||
book: null,
|
book: null,
|
||||||
/** @type {ePub.Rendition} */
|
/** @type {ePub.Rendition} */
|
||||||
rendition: null
|
rendition: null,
|
||||||
|
ereaderSettings: {
|
||||||
|
theme: 'dark',
|
||||||
|
fontScale: 100,
|
||||||
|
lineSpacing: 115,
|
||||||
|
spread: 'auto'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -63,7 +69,7 @@ export default {
|
|||||||
},
|
},
|
||||||
/** @returns {Array<ePub.NavItem>} */
|
/** @returns {Array<ePub.NavItem>} */
|
||||||
chapters() {
|
chapters() {
|
||||||
return this.book ? this.book.navigation.toc : []
|
return this.book?.navigation?.toc || []
|
||||||
},
|
},
|
||||||
userMediaProgress() {
|
userMediaProgress() {
|
||||||
if (!this.libraryItemId) return
|
if (!this.libraryItemId) return
|
||||||
@@ -92,16 +98,50 @@ export default {
|
|||||||
return `/api/items/${this.libraryItemId}/ebook/${this.fileId}`
|
return `/api/items/${this.libraryItemId}/ebook/${this.fileId}`
|
||||||
}
|
}
|
||||||
return `/api/items/${this.libraryItemId}/ebook`
|
return `/api/items/${this.libraryItemId}/ebook`
|
||||||
|
},
|
||||||
|
themeRules() {
|
||||||
|
const isDark = this.ereaderSettings.theme === 'dark'
|
||||||
|
const fontColor = isDark ? '#fff' : '#000'
|
||||||
|
const backgroundColor = isDark ? 'rgb(35 35 35)' : 'rgb(255, 255, 255)'
|
||||||
|
|
||||||
|
const lineSpacing = this.ereaderSettings.lineSpacing / 100
|
||||||
|
|
||||||
|
const fontScale = this.ereaderSettings.fontScale / 100
|
||||||
|
|
||||||
|
return {
|
||||||
|
'*': {
|
||||||
|
color: `${fontColor}!important`,
|
||||||
|
'background-color': `${backgroundColor}!important`,
|
||||||
|
'line-height': lineSpacing * fontScale + 'rem!important'
|
||||||
|
},
|
||||||
|
a: {
|
||||||
|
color: `${fontColor}!important`
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
updateSettings(settings) {
|
||||||
|
this.ereaderSettings = settings
|
||||||
|
|
||||||
|
if (!this.rendition) return
|
||||||
|
|
||||||
|
this.applyTheme()
|
||||||
|
|
||||||
|
const fontScale = settings.fontScale || 100
|
||||||
|
this.rendition.themes.fontSize(`${fontScale}%`)
|
||||||
|
this.rendition.spread(settings.spread || 'auto')
|
||||||
|
},
|
||||||
prev() {
|
prev() {
|
||||||
|
if (!this.rendition?.manager) return
|
||||||
return this.rendition?.prev()
|
return this.rendition?.prev()
|
||||||
},
|
},
|
||||||
next() {
|
next() {
|
||||||
|
if (!this.rendition?.manager) return
|
||||||
return this.rendition?.next()
|
return this.rendition?.next()
|
||||||
},
|
},
|
||||||
goToChapter(href) {
|
goToChapter(href) {
|
||||||
|
if (!this.rendition?.manager) return
|
||||||
return this.rendition?.display(href)
|
return this.rendition?.display(href)
|
||||||
},
|
},
|
||||||
keyUp(e) {
|
keyUp(e) {
|
||||||
@@ -242,35 +282,30 @@ export default {
|
|||||||
/** @type {ePub.Rendition} */
|
/** @type {ePub.Rendition} */
|
||||||
reader.rendition = reader.book.renderTo('viewer', {
|
reader.rendition = reader.book.renderTo('viewer', {
|
||||||
width: this.readerWidth,
|
width: this.readerWidth,
|
||||||
height: this.readerHeight * 0.8
|
height: this.readerHeight * 0.8,
|
||||||
|
spread: 'auto',
|
||||||
|
snap: true,
|
||||||
|
manager: 'continuous',
|
||||||
|
flow: 'paginated'
|
||||||
})
|
})
|
||||||
|
|
||||||
// load saved progress
|
// load saved progress
|
||||||
reader.rendition.display(this.savedEbookLocation || reader.book.locations.start)
|
reader.rendition.display(this.savedEbookLocation || reader.book.locations.start)
|
||||||
|
|
||||||
// load style
|
reader.rendition.on('rendered', () => {
|
||||||
reader.rendition.themes.default({ '*': { color: '#fff!important', 'background-color': 'rgb(35 35 35)!important' }, a: { color: '#fff!important' } })
|
this.applyTheme()
|
||||||
|
})
|
||||||
|
|
||||||
reader.book.ready.then(() => {
|
reader.book.ready.then(() => {
|
||||||
// set up event listeners
|
// set up event listeners
|
||||||
reader.rendition.on('relocated', reader.relocated)
|
reader.rendition.on('relocated', reader.relocated)
|
||||||
reader.rendition.on('keydown', reader.keyUp)
|
reader.rendition.on('keydown', reader.keyUp)
|
||||||
|
|
||||||
let touchStart = 0
|
|
||||||
let touchEnd = 0
|
|
||||||
reader.rendition.on('touchstart', (event) => {
|
reader.rendition.on('touchstart', (event) => {
|
||||||
touchStart = event.changedTouches[0].screenX
|
this.$emit('touchstart', event)
|
||||||
})
|
})
|
||||||
|
|
||||||
reader.rendition.on('touchend', (event) => {
|
reader.rendition.on('touchend', (event) => {
|
||||||
touchEnd = event.changedTouches[0].screenX
|
this.$emit('touchend', event)
|
||||||
const touchDistanceX = Math.abs(touchEnd - touchStart)
|
|
||||||
if (touchStart < touchEnd && touchDistanceX > 120) {
|
|
||||||
this.next()
|
|
||||||
}
|
|
||||||
if (touchStart > touchEnd && touchDistanceX > 120) {
|
|
||||||
this.prev()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// load ebook cfi locations
|
// load ebook cfi locations
|
||||||
@@ -288,6 +323,12 @@ export default {
|
|||||||
this.windowWidth = window.innerWidth
|
this.windowWidth = window.innerWidth
|
||||||
this.windowHeight = window.innerHeight
|
this.windowHeight = window.innerHeight
|
||||||
this.rendition?.resize(this.readerWidth, this.readerHeight * 0.8)
|
this.rendition?.resize(this.readerWidth, this.readerHeight * 0.8)
|
||||||
|
},
|
||||||
|
applyTheme() {
|
||||||
|
if (!this.rendition) return
|
||||||
|
this.rendition.getContents().forEach((c) => {
|
||||||
|
c.addStylesheetRules(this.themeRules)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute top-0 right-20 bg-bg text-gray-100 border-b border-l border-r border-gray-400 z-20 rounded-b-md px-2 h-9 flex items-center text-center">
|
<div class="absolute top-0 right-20 bg-bg text-gray-100 border-b border-l border-r border-gray-400 z-20 rounded-b-md px-2 h-9 hidden md:flex items-center text-center">
|
||||||
<p class="font-mono">{{ page }} / {{ numPages }}</p>
|
<p class="font-mono">{{ page }} / {{ numPages }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute top-0 right-40 bg-bg text-gray-100 border-b border-l border-r border-gray-400 z-20 rounded-b-md px-2 h-9 flex items-center text-center">
|
<div class="absolute top-0 right-40 bg-bg text-gray-100 border-b border-l border-r border-gray-400 z-20 rounded-b-md px-2 h-9 hidden md:flex items-center text-center">
|
||||||
<ui-icon-btn icon="zoom_out" :size="8" :disabled="!canScaleDown" borderless class="mr-px" @click="zoomOut" />
|
<ui-icon-btn icon="zoom_out" :size="8" :disabled="!canScaleDown" borderless class="mr-px" @click="zoomOut" />
|
||||||
<ui-icon-btn icon="zoom_in" :size="8" :disabled="!canScaleUp" borderless class="ml-px" @click="zoomIn" />
|
<ui-icon-btn icon="zoom_in" :size="8" :disabled="!canScaleUp" borderless class="ml-px" @click="zoomIn" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,36 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="show" id="reader" class="absolute top-0 left-0 w-full z-60 bg-primary text-white" :class="{ 'reader-player-open': !!streamLibraryItem }">
|
<div v-if="show" id="reader" :data-theme="ereaderTheme" class="group absolute top-0 left-0 w-full z-60 data-[theme=dark]:bg-primary data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black" :class="{ 'reader-player-open': !!streamLibraryItem }">
|
||||||
<div class="absolute top-4 left-4 z-20">
|
<div class="absolute top-4 left-4 z-20 flex items-center">
|
||||||
<span v-if="hasToC && !tocOpen" ref="tocButton" class="material-icons cursor-pointer text-2xl" @click="toggleToC">menu</span>
|
<button v-if="isEpub" @click="toggleToC" type="button" aria-label="Table of contents menu" class="inline-flex opacity-80 hover:opacity-100">
|
||||||
|
<span class="material-icons text-2xl">menu</span>
|
||||||
|
</button>
|
||||||
|
<button v-if="hasSettings" @click="openSettings" type="button" aria-label="Ereader settings" class="mx-4 inline-flex opacity-80 hover:opacity-100">
|
||||||
|
<span class="material-icons text-1.5xl">settings</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute top-4 left-1/2 transform -translate-x-1/2">
|
<div class="absolute top-4 left-1/2 transform -translate-x-1/2">
|
||||||
<h1 class="text-lg sm:text-xl md:text-2xl mb-1" style="line-height: 1.15; font-weight: 100">
|
<h1 :data-type="ebookType" class="text-lg sm:text-xl md:text-2xl mb-1 data-[type=comic]:hidden" style="line-height: 1.15; font-weight: 100">
|
||||||
<span style="font-weight: 600">{{ abTitle }}</span>
|
<span style="font-weight: 600">{{ abTitle }}</span>
|
||||||
<span v-if="abAuthor" style="display: inline"> – </span>
|
<span v-if="abAuthor" class="hidden md:inline"> – </span>
|
||||||
<span v-if="abAuthor">{{ abAuthor }}</span>
|
<span v-if="abAuthor" class="hidden md:inline">{{ abAuthor }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="absolute top-4 right-4 z-20">
|
<div class="absolute top-4 right-4 z-20">
|
||||||
<span v-if="hasSettings" class="material-icons cursor-pointer text-2xl" @click="openSettings">settings</span>
|
<button @click="close" type="button" aria-label="Close ereader" class="inline-flex opacity-80 hover:opacity-100">
|
||||||
<span class="material-icons cursor-pointer text-2xl" @click="close">close</span>
|
<span class="material-icons text-2xl">close</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<component v-if="componentName" ref="readerComponent" :is="componentName" :library-item="selectedLibraryItem" :player-open="!!streamLibraryItem" :keep-progress="keepProgress" :file-id="ebookFileId" />
|
<component v-if="componentName" ref="readerComponent" :is="componentName" :library-item="selectedLibraryItem" :player-open="!!streamLibraryItem" :keep-progress="keepProgress" :file-id="ebookFileId" @touchstart="touchstart" @touchend="touchend" @hook:mounted="readerMounted" />
|
||||||
|
|
||||||
<!-- TOC side nav -->
|
<!-- TOC side nav -->
|
||||||
<div v-if="tocOpen" class="w-full h-full fixed inset-0 bg-black/20 z-20" @click.stop.prevent="toggleToC"></div>
|
<div v-if="tocOpen" class="w-full h-full fixed inset-0 bg-black/20 z-20" @click.stop.prevent="toggleToC"></div>
|
||||||
<div v-if="hasToC" class="w-96 h-full max-h-full absolute top-0 left-0 bg-bg shadow-xl transition-transform z-30" :class="tocOpen ? 'translate-x-0' : '-translate-x-96'" @click.stop.prevent="toggleToC">
|
<div v-if="isEpub" class="w-96 h-full max-h-full absolute top-0 left-0 shadow-xl transition-transform z-30 group-data-[theme=dark]:bg-primary group-data-[theme=dark]:text-white group-data-[theme=light]:bg-white group-data-[theme=light]:text-black" :class="tocOpen ? 'translate-x-0' : '-translate-x-96'" @click.stop.prevent="toggleToC">
|
||||||
<div class="p-4 h-full">
|
<div class="p-4 h-full">
|
||||||
<p class="text-lg font-semibold mb-2">Table of Contents</p>
|
<div class="flex items-center mb-2">
|
||||||
|
<button @click.stop.prevent="toggleToC" type="button" aria-label="Close table of contents" class="inline-flex opacity-80 hover:opacity-100">
|
||||||
|
<span class="material-icons text-2xl">arrow_back</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p class="text-lg font-semibold ml-2">{{ $strings.HeaderTableOfContents }}</p>
|
||||||
|
</div>
|
||||||
<div class="tocContent">
|
<div class="tocContent">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="chapter in chapters" :key="chapter.id" class="py-1">
|
<li v-for="chapter in chapters" :key="chapter.id" class="py-1">
|
||||||
<a :href="chapter.href" class="text-white/70 hover:text-white" @click.prevent="$refs.readerComponent.goToChapter(chapter.href)">{{ chapter.label }}</a>
|
<a :href="chapter.href" class="opacity-80 hover:opacity-100" @click.prevent="$refs.readerComponent.goToChapter(chapter.href)">{{ chapter.label }}</a>
|
||||||
<ul v-if="chapter.subitems.length">
|
<ul v-if="chapter.subitems.length">
|
||||||
<li v-for="subchapter in chapter.subitems" :key="subchapter.id" class="py-1 pl-4">
|
<li v-for="subchapter in chapter.subitems" :key="subchapter.id" class="py-1 pl-4">
|
||||||
<a :href="subchapter.href" class="text-white/70 hover:text-white" @click.prevent="$refs.readerComponent.goToChapter(subchapter.href)">{{ subchapter.label }}</a>
|
<a :href="subchapter.href" class="opacity-80 hover:opacity-100" @click.prevent="$refs.readerComponent.goToChapter(subchapter.href)">{{ subchapter.label }}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@@ -38,6 +50,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ereader settings modal -->
|
||||||
|
<modals-modal v-model="showSettings" name="ereader-settings-modal" :width="500" :height="'unset'" :processing="false">
|
||||||
|
<template #outer>
|
||||||
|
<div class="absolute top-0 left-0 p-5 w-3/4 overflow-hidden">
|
||||||
|
<p class="text-xl md:text-3xl text-white truncate">{{ $strings.HeaderEreaderSettings }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="px-2 py-4 md:p-8 w-full text-base rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-x-hidden overflow-y-auto" style="max-height: 80vh">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="w-40">
|
||||||
|
<p class="text-lg">{{ $strings.LabelTheme }}:</p>
|
||||||
|
</div>
|
||||||
|
<ui-toggle-btns v-model="ereaderSettings.theme" :items="themeItems" @input="settingsUpdated" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="w-40">
|
||||||
|
<p class="text-lg">{{ $strings.LabelFontScale }}:</p>
|
||||||
|
</div>
|
||||||
|
<ui-range-input v-model="ereaderSettings.fontScale" :min="5" :max="300" :step="5" @input="settingsUpdated" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="w-40">
|
||||||
|
<p class="text-lg">{{ $strings.LabelLineSpacing }}:</p>
|
||||||
|
</div>
|
||||||
|
<ui-range-input v-model="ereaderSettings.lineSpacing" :min="100" :max="300" :step="5" @input="settingsUpdated" />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-40">
|
||||||
|
<p class="text-lg">{{ $strings.LabelLayout }}:</p>
|
||||||
|
</div>
|
||||||
|
<ui-toggle-btns v-model="ereaderSettings.spread" :items="spreadItems" @input="settingsUpdated" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</modals-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -45,8 +92,21 @@
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
touchstartX: 0,
|
||||||
|
touchstartY: 0,
|
||||||
|
touchendX: 0,
|
||||||
|
touchendY: 0,
|
||||||
|
touchstartTime: 0,
|
||||||
|
touchIdentifier: null,
|
||||||
chapters: [],
|
chapters: [],
|
||||||
tocOpen: false
|
tocOpen: false,
|
||||||
|
showSettings: false,
|
||||||
|
ereaderSettings: {
|
||||||
|
theme: 'dark',
|
||||||
|
fontScale: 100,
|
||||||
|
lineSpacing: 115,
|
||||||
|
spread: 'auto'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -65,6 +125,34 @@ export default {
|
|||||||
this.$store.commit('setShowEReader', val)
|
this.$store.commit('setShowEReader', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ereaderTheme() {
|
||||||
|
if (this.isEpub) return this.ereaderSettings.theme
|
||||||
|
return 'dark'
|
||||||
|
},
|
||||||
|
spreadItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelLayoutSinglePage,
|
||||||
|
value: 'none'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelLayoutSplitPage,
|
||||||
|
value: 'auto'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
themeItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelThemeDark,
|
||||||
|
value: 'dark'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelThemeLight,
|
||||||
|
value: 'light'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
componentName() {
|
componentName() {
|
||||||
if (this.ebookType === 'epub') return 'readers-epub-reader'
|
if (this.ebookType === 'epub') return 'readers-epub-reader'
|
||||||
else if (this.ebookType === 'mobi') return 'readers-mobi-reader'
|
else if (this.ebookType === 'mobi') return 'readers-mobi-reader'
|
||||||
@@ -75,11 +163,8 @@ export default {
|
|||||||
streamLibraryItem() {
|
streamLibraryItem() {
|
||||||
return this.$store.state.streamLibraryItem
|
return this.$store.state.streamLibraryItem
|
||||||
},
|
},
|
||||||
hasToC() {
|
|
||||||
return this.isEpub
|
|
||||||
},
|
|
||||||
hasSettings() {
|
hasSettings() {
|
||||||
return false
|
return this.isEpub
|
||||||
},
|
},
|
||||||
abTitle() {
|
abTitle() {
|
||||||
return this.mediaMetadata.title
|
return this.mediaMetadata.title
|
||||||
@@ -144,14 +229,28 @@ export default {
|
|||||||
},
|
},
|
||||||
ebookFileId() {
|
ebookFileId() {
|
||||||
return this.$store.state.ereaderFileId
|
return this.$store.state.ereaderFileId
|
||||||
|
},
|
||||||
|
isDarkTheme() {
|
||||||
|
return this.ereaderSettings.theme === 'dark'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
readerMounted() {
|
||||||
|
if (this.isEpub) {
|
||||||
|
this.loadEreaderSettings()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settingsUpdated() {
|
||||||
|
this.$refs.readerComponent?.updateSettings?.(this.ereaderSettings)
|
||||||
|
localStorage.setItem('ereaderSettings', JSON.stringify(this.ereaderSettings))
|
||||||
|
},
|
||||||
toggleToC() {
|
toggleToC() {
|
||||||
this.tocOpen = !this.tocOpen
|
this.tocOpen = !this.tocOpen
|
||||||
this.chapters = this.$refs.readerComponent.chapters
|
this.chapters = this.$refs.readerComponent.chapters
|
||||||
},
|
},
|
||||||
openSettings() {},
|
openSettings() {
|
||||||
|
this.showSettings = true
|
||||||
|
},
|
||||||
hotkey(action) {
|
hotkey(action) {
|
||||||
if (!this.$refs.readerComponent) return
|
if (!this.$refs.readerComponent) return
|
||||||
|
|
||||||
@@ -169,11 +268,72 @@ export default {
|
|||||||
prev() {
|
prev() {
|
||||||
if (this.$refs.readerComponent?.prev) this.$refs.readerComponent.prev()
|
if (this.$refs.readerComponent?.prev) this.$refs.readerComponent.prev()
|
||||||
},
|
},
|
||||||
|
handleGesture() {
|
||||||
|
// Touch must be less than 1s. Must be > 60px drag and X distance > Y distance
|
||||||
|
const touchTimeMs = Date.now() - this.touchstartTime
|
||||||
|
if (touchTimeMs >= 1000) {
|
||||||
|
console.log('Touch too long', touchTimeMs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const touchDistanceX = Math.abs(this.touchendX - this.touchstartX)
|
||||||
|
const touchDistanceY = Math.abs(this.touchendY - this.touchstartY)
|
||||||
|
const touchDistance = Math.sqrt(Math.pow(this.touchstartX - this.touchendX, 2) + Math.pow(this.touchstartY - this.touchendY, 2))
|
||||||
|
if (touchDistance < 60) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (touchDistanceX < 60 || touchDistanceY > touchDistanceX) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.touchendX < this.touchstartX) {
|
||||||
|
this.next()
|
||||||
|
}
|
||||||
|
if (this.touchendX > this.touchstartX) {
|
||||||
|
this.prev()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchstart(e) {
|
||||||
|
// Ignore rapid touch
|
||||||
|
if (this.touchstartTime && Date.now() - this.touchstartTime < 250) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.touchstartX = e.touches[0].screenX
|
||||||
|
this.touchstartY = e.touches[0].screenY
|
||||||
|
this.touchstartTime = Date.now()
|
||||||
|
this.touchIdentifier = e.touches[0].identifier
|
||||||
|
},
|
||||||
|
touchend(e) {
|
||||||
|
if (this.touchIdentifier !== e.changedTouches[0].identifier) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.touchendX = e.changedTouches[0].screenX
|
||||||
|
this.touchendY = e.changedTouches[0].screenY
|
||||||
|
this.handleGesture()
|
||||||
|
},
|
||||||
registerListeners() {
|
registerListeners() {
|
||||||
this.$eventBus.$on('reader-hotkey', this.hotkey)
|
this.$eventBus.$on('reader-hotkey', this.hotkey)
|
||||||
|
document.body.addEventListener('touchstart', this.touchstart)
|
||||||
|
document.body.addEventListener('touchend', this.touchend)
|
||||||
},
|
},
|
||||||
unregisterListeners() {
|
unregisterListeners() {
|
||||||
this.$eventBus.$off('reader-hotkey', this.hotkey)
|
this.$eventBus.$off('reader-hotkey', this.hotkey)
|
||||||
|
document.body.removeEventListener('touchstart', this.touchstart)
|
||||||
|
document.body.removeEventListener('touchend', this.touchend)
|
||||||
|
},
|
||||||
|
loadEreaderSettings() {
|
||||||
|
try {
|
||||||
|
const settings = localStorage.getItem('ereaderSettings')
|
||||||
|
if (settings) {
|
||||||
|
this.ereaderSettings = JSON.parse(settings)
|
||||||
|
this.settingsUpdated()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load ereader settings', error)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.registerListeners()
|
this.registerListeners()
|
||||||
|
|||||||
@@ -235,7 +235,6 @@ export default {
|
|||||||
style: `transform:translate(${x}px,${y}px);background-color:${bgColor};outline:1px solid ${outlineColor};outline-offset:-1px;`
|
style: `transform:translate(${x}px,${y}px);background-color:${bgColor};outline:1px solid ${outlineColor};outline-offset:-1px;`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
console.log('Data', this.data)
|
|
||||||
|
|
||||||
this.monthLabels = []
|
this.monthLabels = []
|
||||||
var lastMonth = null
|
var lastMonth = null
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex px-4">
|
<div v-if="isBookLibrary" class="flex px-4">
|
||||||
<svg class="h-14 w-14 md:h-18 md:w-18" viewBox="0 0 24 24">
|
<svg class="h-14 w-14 md:h-18 md:w-18" viewBox="0 0 24 24">
|
||||||
<path fill="currentColor" d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z" />
|
<path fill="currentColor" d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -58,26 +58,32 @@ export default {
|
|||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
currentLibraryMediaType() {
|
||||||
|
return this.$store.getters['libraries/getCurrentLibraryMediaType']
|
||||||
|
},
|
||||||
|
isBookLibrary() {
|
||||||
|
return this.currentLibraryMediaType === 'book'
|
||||||
|
},
|
||||||
user() {
|
user() {
|
||||||
return this.$store.state.user.user
|
return this.$store.state.user.user
|
||||||
},
|
},
|
||||||
totalItems() {
|
totalItems() {
|
||||||
return this.libraryStats ? this.libraryStats.totalItems : 0
|
return this.libraryStats?.totalItems || 0
|
||||||
},
|
},
|
||||||
totalAuthors() {
|
totalAuthors() {
|
||||||
return this.libraryStats ? this.libraryStats.totalAuthors : 0
|
return this.libraryStats?.totalAuthors || 0
|
||||||
},
|
},
|
||||||
numAudioTracks() {
|
numAudioTracks() {
|
||||||
return this.libraryStats ? this.libraryStats.numAudioTracks : 0
|
return this.libraryStats?.numAudioTracks || 0
|
||||||
},
|
},
|
||||||
totalDuration() {
|
totalDuration() {
|
||||||
return this.libraryStats ? this.libraryStats.totalDuration : 0
|
return this.libraryStats?.totalDuration || 0
|
||||||
},
|
},
|
||||||
totalHours() {
|
totalHours() {
|
||||||
return Math.round(this.totalDuration / (60 * 60))
|
return Math.round(this.totalDuration / (60 * 60))
|
||||||
},
|
},
|
||||||
totalSizePretty() {
|
totalSizePretty() {
|
||||||
var totalSize = this.libraryStats ? this.libraryStats.totalSize : 0
|
var totalSize = this.libraryStats?.totalSize || 0
|
||||||
return this.$bytesPretty(totalSize, 1)
|
return this.$bytesPretty(totalSize, 1)
|
||||||
},
|
},
|
||||||
totalSizeNum() {
|
totalSizeNum() {
|
||||||
|
|||||||
@@ -21,14 +21,14 @@
|
|||||||
<td class="hidden sm:table-cell font-mono md:text-sm text-xs">{{ $bytesPretty(backup.fileSize) }}</td>
|
<td class="hidden sm:table-cell font-mono md:text-sm text-xs">{{ $bytesPretty(backup.fileSize) }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="w-full flex flex-row items-center justify-center">
|
<div class="w-full flex flex-row items-center justify-center">
|
||||||
<ui-btn v-if="backup.serverVersion" small color="primary" @click="applyBackup(backup)">{{ $strings.ButtonRestore }}</ui-btn>
|
<ui-btn v-if="backup.serverVersion && backup.key" small color="primary" @click="applyBackup(backup)">{{ $strings.ButtonRestore }}</ui-btn>
|
||||||
|
|
||||||
<a v-if="backup.serverVersion" :href="`/metadata/${$encodeUriPath(backup.path)}?token=${userToken}`" class="mx-1 pt-1 hover:text-opacity-100 text-opacity-70 text-white" download><span class="material-icons text-xl">download</span></a>
|
|
||||||
<ui-tooltip v-else text="This backup was created with an old version of audiobookshelf no longer supported" direction="bottom" class="mx-2 flex items-center">
|
<ui-tooltip v-else text="This backup was created with an old version of audiobookshelf no longer supported" direction="bottom" class="mx-2 flex items-center">
|
||||||
<span class="material-icons-outlined text-2xl text-error">error_outline</span>
|
<span class="material-icons-outlined text-2xl text-error">error_outline</span>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
|
|
||||||
<span class="material-icons text-xl hover:text-error hover:text-opacity-100 text-opacity-70 text-white cursor-pointer mx-1" @click="deleteBackupClick(backup)">delete</span>
|
<button aria-label="Download Backup" class="inline-flex material-icons text-xl mx-1 mt-1 text-white/70 hover:text-white/100" @click.stop="downloadBackup(backup)">download</button>
|
||||||
|
|
||||||
|
<button aria-label="Delete Backup" class="inline-flex material-icons text-xl mx-1 text-white/70 hover:text-error" @click="deleteBackupClick(backup)">delete</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -80,6 +80,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
downloadBackup(backup) {
|
||||||
|
this.$downloadFile(`${process.env.serverUrl}/api/backups/${backup.id}/download?token=${this.userToken}`)
|
||||||
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
this.showConfirmApply = false
|
this.showConfirmApply = false
|
||||||
|
|
||||||
@@ -91,8 +94,9 @@ export default {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.isBackingUp = false
|
this.isBackingUp = false
|
||||||
console.error('Failed', error)
|
console.error('Failed to apply backup', error)
|
||||||
this.$toast.error(this.$strings.ToastBackupRestoreFailed)
|
const errorMsg = error.response.data || this.$strings.ToastBackupRestoreFailed
|
||||||
|
this.$toast.error(errorMsg)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
deleteBackupClick(backup) {
|
deleteBackupClick(backup) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<th class="text-left px-4 w-24">
|
<th class="text-left px-4 w-24">
|
||||||
{{ $strings.LabelRead }} <ui-tooltip :text="$strings.LabelReadEbookWithoutProgress" direction="top" class="inline-block"><span class="material-icons-outlined text-sm align-middle">info</span></ui-tooltip>
|
{{ $strings.LabelRead }} <ui-tooltip :text="$strings.LabelReadEbookWithoutProgress" direction="top" class="inline-block"><span class="material-icons-outlined text-sm align-middle">info</span></ui-tooltip>
|
||||||
</th>
|
</th>
|
||||||
<th v-if="userCanDelete || userCanDownload || userIsAdmin" class="text-center w-16"></th>
|
<th v-if="showMoreColumn" class="text-center w-16"></th>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-for="file in ebookFiles">
|
<template v-for="file in ebookFiles">
|
||||||
<tables-ebook-files-table-row :key="file.path" :libraryItemId="libraryItemId" :showFullPath="showFullPath" :file="file" @read="readEbook" />
|
<tables-ebook-files-table-row :key="file.path" :libraryItemId="libraryItemId" :showFullPath="showFullPath" :file="file" @read="readEbook" />
|
||||||
@@ -58,20 +58,20 @@ export default {
|
|||||||
userCanDelete() {
|
userCanDelete() {
|
||||||
return this.$store.getters['user/getUserCanDelete']
|
return this.$store.getters['user/getUserCanDelete']
|
||||||
},
|
},
|
||||||
|
userCanUpdate() {
|
||||||
|
return this.$store.getters['user/getUserCanUpdate']
|
||||||
|
},
|
||||||
userIsAdmin() {
|
userIsAdmin() {
|
||||||
return this.$store.getters['user/getIsAdminOrUp']
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
},
|
},
|
||||||
|
libraryIsAudiobooksOnly() {
|
||||||
|
return this.$store.getters['libraries/getLibraryIsAudiobooksOnly']
|
||||||
|
},
|
||||||
|
showMoreColumn() {
|
||||||
|
return this.userCanDelete || this.userCanDownload || (this.userCanUpdate && !this.libraryIsAudiobooksOnly)
|
||||||
|
},
|
||||||
ebookFiles() {
|
ebookFiles() {
|
||||||
return (this.libraryItem.libraryFiles || []).filter((lf) => lf.fileType === 'ebook')
|
return (this.libraryItem.libraryFiles || []).filter((lf) => lf.fileType === 'ebook')
|
||||||
},
|
|
||||||
ebookFileIno() {
|
|
||||||
return this.libraryItem.media.ebookFile?.ino
|
|
||||||
},
|
|
||||||
audioFiles() {
|
|
||||||
if (this.libraryItem.mediaType === 'podcast') {
|
|
||||||
return this.libraryItem.media?.episodes.map((ep) => ep.audioFile) || []
|
|
||||||
}
|
|
||||||
return this.libraryItem.media?.audioFiles || []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<modals-audio-file-data-modal v-model="showAudioFileDataModal" :audio-file="selectedAudioFile" />
|
<modals-audio-file-data-modal v-model="showAudioFileDataModal" :library-item-id="libraryItemId" :audio-file="selectedAudioFile" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<modals-audio-file-data-modal v-model="showAudioFileDataModal" :audio-file="selectedAudioFile" />
|
<modals-audio-file-data-modal v-model="showAudioFileDataModal" :library-item-id="libraryItemId" :audio-file="selectedAudioFile" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,13 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-sm">{{ user.type }}</td>
|
<td class="text-sm">{{ user.type }}</td>
|
||||||
<td class="hidden lg:table-cell">
|
<td class="hidden lg:table-cell">
|
||||||
<div v-if="usersOnline[user.id]">
|
<div v-if="usersOnline[user.id]?.session?.displayTitle">
|
||||||
<p v-if="usersOnline[user.id].session && usersOnline[user.id].session.libraryItem" class="truncate text-xs">Listening: {{ usersOnline[user.id].session.libraryItem.media.metadata.title || '' }}</p>
|
<p class="truncate text-xs">Listening: {{ usersOnline[user.id].session.displayTitle || '' }}</p>
|
||||||
<p v-else-if="usersOnline[user.id].mostRecent && usersOnline[user.id].mostRecent.media" class="truncate text-xs">Last: {{ usersOnline[user.id].mostRecent.media.metadata.title }}</p>
|
<p class="truncate text-xs text-gray-300">{{ getDeviceInfoString(usersOnline[user.id].session.deviceInfo) }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="user.latestSession?.displayTitle">
|
||||||
|
<p class="truncate text-xs">Last: {{ user.latestSession.displayTitle || '' }}</p>
|
||||||
|
<p class="truncate text-xs text-gray-300">{{ getDeviceInfoString(user.latestSession.deviceInfo) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-xs font-mono hidden sm:table-cell">
|
<td class="text-xs font-mono hidden sm:table-cell">
|
||||||
@@ -83,6 +87,12 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getDeviceInfoString(deviceInfo) {
|
||||||
|
if (!deviceInfo) return ''
|
||||||
|
if (deviceInfo.manufacturer && deviceInfo.model) return `${deviceInfo.manufacturer} ${deviceInfo.model}`
|
||||||
|
|
||||||
|
return `${deviceInfo.osName || 'Unknown'} ${deviceInfo.osVersion || ''} ${deviceInfo.browserName || ''}`
|
||||||
|
},
|
||||||
deleteUserClick(user) {
|
deleteUserClick(user) {
|
||||||
if (this.isDeletingUser) return
|
if (this.isDeletingUser) return
|
||||||
if (confirm(this.$getString('MessageRemoveUserWarning', [user.username]))) {
|
if (confirm(this.$getString('MessageRemoveUserWarning', [user.username]))) {
|
||||||
@@ -114,11 +124,12 @@ export default {
|
|||||||
},
|
},
|
||||||
loadUsers() {
|
loadUsers() {
|
||||||
this.$axios
|
this.$axios
|
||||||
.$get('/api/users')
|
.$get('/api/users?include=latestSession')
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.users = res.users.sort((a, b) => {
|
this.users = res.users.sort((a, b) => {
|
||||||
return a.createdAt - b.createdAt
|
return a.createdAt - b.createdAt
|
||||||
})
|
})
|
||||||
|
console.log('Loaded users', this.users)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ export default {
|
|||||||
.$patch(`/api/me/progress/${this.book.id}`, updatePayload)
|
.$patch(`/api/me/progress/${this.book.id}`, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.success(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedSuccess : this.$strings.ToastItemMarkedAsNotFinishedSuccess)
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
|
|||||||
@@ -11,10 +11,6 @@
|
|||||||
<ui-btn @click="clickAddLibrary">{{ $strings.ButtonAddYourFirstLibrary }}</ui-btn>
|
<ui-btn @click="clickAddLibrary">{{ $strings.ButtonAddYourFirstLibrary }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p v-if="libraries.length" class="text-xs mt-4 text-gray-200">
|
|
||||||
*<strong>{{ $strings.ButtonForceReScan }}</strong> {{ $strings.MessageForceReScanDescription }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p v-if="libraries.length && libraries.some((li) => li.mediaType === 'book')" class="text-xs mt-4 text-gray-200">
|
<p v-if="libraries.length && libraries.some((li) => li.mediaType === 'book')" class="text-xs mt-4 text-gray-200">
|
||||||
**<strong>{{ $strings.ButtonMatchBooks }}</strong> {{ $strings.MessageMatchBooksDescription }}
|
**<strong>{{ $strings.ButtonMatchBooks }}</strong> {{ $strings.MessageMatchBooksDescription }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -71,11 +71,6 @@ export default {
|
|||||||
text: this.$strings.ButtonScan,
|
text: this.$strings.ButtonScan,
|
||||||
action: 'scan',
|
action: 'scan',
|
||||||
value: 'scan'
|
value: 'scan'
|
||||||
},
|
|
||||||
{
|
|
||||||
text: this.$strings.ButtonForceReScan,
|
|
||||||
action: 'force-scan',
|
|
||||||
value: 'force-scan'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
if (this.isBookLibrary) {
|
if (this.isBookLibrary) {
|
||||||
@@ -137,26 +132,6 @@ export default {
|
|||||||
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
|
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
forceScan() {
|
|
||||||
const payload = {
|
|
||||||
message: this.$strings.MessageConfirmForceReScan,
|
|
||||||
callback: (confirmed) => {
|
|
||||||
if (confirmed) {
|
|
||||||
this.$store
|
|
||||||
.dispatch('libraries/requestLibraryScan', { libraryId: this.library.id, force: 1 })
|
|
||||||
.then(() => {
|
|
||||||
this.$toast.success(this.$strings.ToastLibraryScanStarted)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to start scan', error)
|
|
||||||
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
type: 'yesNo'
|
|
||||||
}
|
|
||||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
|
||||||
},
|
|
||||||
deleteClick() {
|
deleteClick() {
|
||||||
const payload = {
|
const payload = {
|
||||||
message: this.$getString('MessageConfirmDeleteLibrary', [this.library.name]),
|
message: this.$getString('MessageConfirmDeleteLibrary', [this.library.name]),
|
||||||
|
|||||||
@@ -198,7 +198,6 @@ export default {
|
|||||||
.$patch(routepath, updatePayload)
|
.$patch(routepath, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.success(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedSuccess : this.$strings.ToastItemMarkedAsNotFinishedSuccess)
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
|
|||||||
@@ -183,7 +183,6 @@ export default {
|
|||||||
.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.success(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedSuccess : this.$strings.ToastItemMarkedAsNotFinishedSuccess)
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full py-6">
|
<div class="w-full py-6">
|
||||||
<p class="text-lg mb-2 font-semibold md:hidden">{{ $strings.HeaderEpisodes }}</p>
|
<div class="flex flex-wrap flex-col md:flex-row md:items-center mb-4">
|
||||||
<div class="flex items-center mb-4">
|
<div class="flex items-center flex-nowrap whitespace-nowrap mb-2 md:mb-0">
|
||||||
<p class="text-lg mb-0 font-semibold hidden md:block">{{ $strings.HeaderEpisodes }}</p>
|
<p class="text-lg mb-0 font-semibold">{{ $strings.HeaderEpisodes }}</p>
|
||||||
|
<div class="inline-flex bg-white/5 px-1 mx-2 rounded-md text-sm text-gray-100">
|
||||||
|
<p v-if="episodesList.length === episodes.length">{{ episodes.length }}</p>
|
||||||
|
<p v-else>{{ episodesList.length }} / {{ episodes.length }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="flex-grow hidden md:block" />
|
<div class="flex-grow hidden md:block" />
|
||||||
<template v-if="isSelectionMode">
|
<div class="flex items-center">
|
||||||
<ui-tooltip :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
|
<template v-if="isSelectionMode">
|
||||||
<ui-read-icon-btn :disabled="processing" :is-read="selectedIsFinished" @click="toggleBatchFinished" class="mx-1.5" />
|
<ui-tooltip :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom">
|
||||||
</ui-tooltip>
|
<ui-read-icon-btn :disabled="processing" :is-read="selectedIsFinished" @click="toggleBatchFinished" class="mx-1.5" />
|
||||||
<ui-btn color="error" :disabled="processing" small class="h-9" @click="removeSelectedEpisodes">{{ $getString('MessageRemoveEpisodes', [selectedEpisodes.length]) }}</ui-btn>
|
</ui-tooltip>
|
||||||
<ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">{{ $strings.ButtonCancel }}</ui-btn>
|
<ui-btn color="error" :disabled="processing" small class="h-9" @click="removeSelectedEpisodes">{{ $getString('MessageRemoveEpisodes', [selectedEpisodes.length]) }}</ui-btn>
|
||||||
</template>
|
<ui-btn :disabled="processing" small class="ml-2 h-9" @click="clearSelected">{{ $strings.ButtonCancel }}</ui-btn>
|
||||||
<template v-else>
|
</template>
|
||||||
<controls-filter-select v-model="filterKey" :items="filterItems" class="w-36 h-9 sm:ml-4" />
|
<template v-else>
|
||||||
<controls-sort-select v-model="sortKey" :descending.sync="sortDesc" :items="sortItems" class="w-44 md:w-48 h-9 ml-1 sm:ml-4" />
|
<controls-filter-select v-model="filterKey" :items="filterItems" class="w-36 h-9 md:ml-4" />
|
||||||
<div class="flex-grow md:hidden" />
|
<controls-sort-select v-model="sortKey" :descending.sync="sortDesc" :items="sortItems" class="w-44 md:w-48 h-9 ml-1 sm:ml-4" />
|
||||||
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" class="ml-1" @action="contextMenuAction" />
|
<div class="flex-grow md:hidden" />
|
||||||
</template>
|
<ui-context-menu-dropdown v-if="contextMenuItems.length" :items="contextMenuItems" class="ml-1" @action="contextMenuAction" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!episodes.length" class="py-4 text-center text-lg">{{ $strings.MessageNoEpisodes }}</p>
|
<p v-if="!episodes.length" class="py-4 text-center text-lg">{{ $strings.MessageNoEpisodes }}</p>
|
||||||
<div v-if="episodes.length" class="w-full py-3 mx-auto flex">
|
<div v-if="episodes.length" class="w-full py-3 mx-auto flex">
|
||||||
@@ -51,7 +58,6 @@ export default {
|
|||||||
selectedEpisodes: [],
|
selectedEpisodes: [],
|
||||||
episodesToRemove: [],
|
episodesToRemove: [],
|
||||||
processing: false,
|
processing: false,
|
||||||
quickMatchingEpisodes: false,
|
|
||||||
search: null,
|
search: null,
|
||||||
searchTimeout: null,
|
searchTimeout: null,
|
||||||
searchText: null
|
searchText: null
|
||||||
@@ -71,6 +77,10 @@ export default {
|
|||||||
{
|
{
|
||||||
text: 'Quick match all episodes',
|
text: 'Quick match all episodes',
|
||||||
action: 'quick-match-episodes'
|
action: 'quick-match-episodes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.allEpisodesFinished ? this.$strings.MessageMarkAllEpisodesNotFinished : this.$strings.MessageMarkAllEpisodesFinished,
|
||||||
|
action: 'batch-mark-as-finished'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -157,14 +167,20 @@ export default {
|
|||||||
episodesList() {
|
episodesList() {
|
||||||
return this.episodesSorted.filter((episode) => {
|
return this.episodesSorted.filter((episode) => {
|
||||||
if (!this.searchText) return true
|
if (!this.searchText) return true
|
||||||
return (episode.title && episode.title.toLowerCase().includes(this.searchText)) || (episode.subtitle && episode.subtitle.toLowerCase().includes(this.searchText))
|
return episode.title?.toLowerCase().includes(this.searchText) || episode.subtitle?.toLowerCase().includes(this.searchText)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
selectedIsFinished() {
|
selectedIsFinished() {
|
||||||
// Find an item that is not finished, if none then all items finished
|
// Find an item that is not finished, if none then all items finished
|
||||||
return !this.selectedEpisodes.find((episode) => {
|
return !this.selectedEpisodes.some((episode) => {
|
||||||
var itemProgress = this.$store.getters['user/getUserMediaProgress'](this.libraryItem.id, episode.id)
|
const itemProgress = this.$store.getters['user/getUserMediaProgress'](this.libraryItem.id, episode.id)
|
||||||
return !itemProgress || !itemProgress.isFinished
|
return !itemProgress?.isFinished
|
||||||
|
})
|
||||||
|
},
|
||||||
|
allEpisodesFinished() {
|
||||||
|
return !this.episodesSorted.some((episode) => {
|
||||||
|
const itemProgress = this.$store.getters['user/getUserMediaProgress'](this.libraryItem.id, episode.id)
|
||||||
|
return !itemProgress?.isFinished
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
dateFormat() {
|
dateFormat() {
|
||||||
@@ -187,17 +203,34 @@ export default {
|
|||||||
},
|
},
|
||||||
contextMenuAction({ action }) {
|
contextMenuAction({ action }) {
|
||||||
if (action === 'quick-match-episodes') {
|
if (action === 'quick-match-episodes') {
|
||||||
if (this.quickMatchingEpisodes) return
|
if (this.processing) return
|
||||||
|
|
||||||
this.quickMatchAllEpisodes()
|
this.quickMatchAllEpisodes()
|
||||||
|
} else if (action === 'batch-mark-as-finished') {
|
||||||
|
if (this.processing) return
|
||||||
|
|
||||||
|
this.markAllEpisodesFinished()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
markAllEpisodesFinished() {
|
||||||
|
const newIsFinished = !this.allEpisodesFinished
|
||||||
|
const payload = {
|
||||||
|
message: newIsFinished ? this.$strings.MessageConfirmMarkAllEpisodesFinished : this.$strings.MessageConfirmMarkAllEpisodesNotFinished,
|
||||||
|
callback: (confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.batchUpdateEpisodesFinished(this.episodesSorted, newIsFinished)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: 'yesNo'
|
||||||
|
}
|
||||||
|
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||||
|
},
|
||||||
quickMatchAllEpisodes() {
|
quickMatchAllEpisodes() {
|
||||||
if (!this.mediaMetadata.feedUrl) {
|
if (!this.mediaMetadata.feedUrl) {
|
||||||
this.$toast.error(this.$strings.MessagePodcastHasNoRSSFeedForMatching)
|
this.$toast.error(this.$strings.MessagePodcastHasNoRSSFeedForMatching)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.quickMatchingEpisodes = true
|
this.processing = true
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
message: 'Quick matching episodes will overwrite details if a match is found. Only unmatched episodes will be updated. Are you sure?',
|
message: 'Quick matching episodes will overwrite details if a match is found. Only unmatched episodes will be updated. Are you sure?',
|
||||||
@@ -217,7 +250,7 @@ export default {
|
|||||||
this.$toast.error('Failed to match episodes')
|
this.$toast.error('Failed to match episodes')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.quickMatchingEpisodes = false
|
this.processing = false
|
||||||
},
|
},
|
||||||
type: 'yesNo'
|
type: 'yesNo'
|
||||||
}
|
}
|
||||||
@@ -241,17 +274,19 @@ export default {
|
|||||||
this.$store.commit('addItemToQueue', queueItem)
|
this.$store.commit('addItemToQueue', queueItem)
|
||||||
},
|
},
|
||||||
toggleBatchFinished() {
|
toggleBatchFinished() {
|
||||||
|
this.batchUpdateEpisodesFinished(this.selectedEpisodes, !this.selectedIsFinished)
|
||||||
|
},
|
||||||
|
batchUpdateEpisodesFinished(episodes, newIsFinished) {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
var newIsFinished = !this.selectedIsFinished
|
|
||||||
var updateProgressPayloads = this.selectedEpisodes.map((episode) => {
|
const updateProgressPayloads = episodes.map((episode) => {
|
||||||
return {
|
return {
|
||||||
libraryItemId: this.libraryItem.id,
|
libraryItemId: this.libraryItem.id,
|
||||||
episodeId: episode.id,
|
episodeId: episode.id,
|
||||||
isFinished: newIsFinished
|
isFinished: newIsFinished
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return this.$axios
|
||||||
this.$axios
|
|
||||||
.patch(`/api/me/progress/batch/update`, updateProgressPayloads)
|
.patch(`/api/me/progress/batch/update`, updateProgressPayloads)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$toast.success(this.$strings.ToastBatchUpdateSuccess)
|
this.$toast.success(this.$strings.ToastBatchUpdateSuccess)
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
.btn::before {
|
.btn::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div class="inline-flex">
|
||||||
|
<input v-model="input" type="range" :min="min" :max="max" :step="step" />
|
||||||
|
|
||||||
|
<p class="text-sm ml-2">{{ input }}%</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: [String, Number],
|
||||||
|
min: Number,
|
||||||
|
max: Number,
|
||||||
|
step: Number
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
input: {
|
||||||
|
get() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input[type='range'] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
input[type='range']:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* chromium */
|
||||||
|
input[type='range']::-webkit-slider-runnable-track {
|
||||||
|
background-color: rgb(0 0 0 / 0.25);
|
||||||
|
border-radius: 9999px;
|
||||||
|
height: 0.75rem;
|
||||||
|
}
|
||||||
|
input[type='range']::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
margin-top: -0.25rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background-color: rgb(255 255 255 / 0.7);
|
||||||
|
height: 1.25rem;
|
||||||
|
width: 1.25rem;
|
||||||
|
}
|
||||||
|
input[type='range']:focus::-webkit-slider-thumb {
|
||||||
|
border: 1px solid #6b6b6b;
|
||||||
|
outline: 3px solid #6b6b6b;
|
||||||
|
outline-offset: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* firefox */
|
||||||
|
input[type='range']::-moz-range-track {
|
||||||
|
background-color: rgb(0 0 0 / 0.25);
|
||||||
|
border-radius: 9999px;
|
||||||
|
height: 0.75rem;
|
||||||
|
}
|
||||||
|
input[type='range']::-moz-range-thumb {
|
||||||
|
border: none;
|
||||||
|
border-radius: 9999px;
|
||||||
|
margin-top: -0.25rem;
|
||||||
|
background-color: rgb(255 255 255 / 0.7);
|
||||||
|
height: 1.25rem;
|
||||||
|
width: 1.25rem;
|
||||||
|
}
|
||||||
|
input[type='range']:focus::-moz-range-thumb {
|
||||||
|
border: 1px solid #6b6b6b;
|
||||||
|
outline: 3px solid #6b6b6b;
|
||||||
|
outline-offset: 0.125rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">{{ label }}</p>
|
<p class="px-1 text-sm font-semibold" :class="disabled ? 'text-gray-400' : ''">{{ label }}</p>
|
||||||
<ui-textarea-input ref="input" v-model="inputValue" :disabled="disabled" :rows="rows" class="w-full" />
|
<ui-textarea-input ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :rows="rows" class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ export default {
|
|||||||
value: [String, Number],
|
value: [String, Number],
|
||||||
label: String,
|
label: String,
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
|
readonly: Boolean,
|
||||||
rows: {
|
rows: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 2
|
default: 2
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="inline-flex toggle-btn-wrapper shadow-md">
|
||||||
|
<button v-for="item in items" :key="item.value" type="button" class="toggle-btn outline-none relative border border-gray-600 px-4 py-1" :class="{ selected: item.value === value }" @click.stop="clickBtn(item.value)">
|
||||||
|
{{ item.text }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: String,
|
||||||
|
/**
|
||||||
|
* [{ "text", "", "value": "" }]
|
||||||
|
*/
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
default: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {
|
||||||
|
clickBtn(value) {
|
||||||
|
this.$emit('input', value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.toggle-btn-wrapper .toggle-btn:first-child {
|
||||||
|
border-top-left-radius: 0.375rem /* 6px */;
|
||||||
|
border-bottom-left-radius: 0.375rem /* 6px */;
|
||||||
|
}
|
||||||
|
.toggle-btn-wrapper .toggle-btn:last-child {
|
||||||
|
border-top-right-radius: 0.375rem /* 6px */;
|
||||||
|
border-bottom-right-radius: 0.375rem /* 6px */;
|
||||||
|
}
|
||||||
|
.toggle-btn-wrapper .toggle-btn:first-child::before {
|
||||||
|
border-top-left-radius: 0.375rem /* 6px */;
|
||||||
|
border-bottom-left-radius: 0.375rem /* 6px */;
|
||||||
|
}
|
||||||
|
.toggle-btn-wrapper .toggle-btn:last-child::before {
|
||||||
|
border-top-right-radius: 0.375rem /* 6px */;
|
||||||
|
border-bottom-right-radius: 0.375rem /* 6px */;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-btn-wrapper .toggle-btn:not(:first-child) {
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0);
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
.toggle-btn:hover:not(:disabled)::before {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.toggle-btn:hover:not(:disabled) {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-btn {
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
.toggle-btn.selected {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.toggle-btn.selected::before {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
button.toggle-btn:disabled::before {
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -343,6 +343,10 @@ export default {
|
|||||||
}
|
}
|
||||||
this.$store.commit('libraries/removeCollection', collection)
|
this.$store.commit('libraries/removeCollection', collection)
|
||||||
},
|
},
|
||||||
|
seriesRemoved({ id, libraryId }) {
|
||||||
|
if (this.currentLibraryId !== libraryId) return
|
||||||
|
this.$store.commit('libraries/removeSeriesFromFilterData', id)
|
||||||
|
},
|
||||||
playlistAdded(playlist) {
|
playlistAdded(playlist) {
|
||||||
if (playlist.userId !== this.user.id || this.currentLibraryId !== playlist.libraryId) return
|
if (playlist.userId !== this.user.id || this.currentLibraryId !== playlist.libraryId) return
|
||||||
this.$store.commit('libraries/addUpdateUserPlaylist', playlist)
|
this.$store.commit('libraries/addUpdateUserPlaylist', playlist)
|
||||||
@@ -442,6 +446,9 @@ export default {
|
|||||||
this.socket.on('collection_updated', this.collectionUpdated)
|
this.socket.on('collection_updated', this.collectionUpdated)
|
||||||
this.socket.on('collection_removed', this.collectionRemoved)
|
this.socket.on('collection_removed', this.collectionRemoved)
|
||||||
|
|
||||||
|
// Series Listeners
|
||||||
|
this.socket.on('series_removed', this.seriesRemoved)
|
||||||
|
|
||||||
// User Playlist Listeners
|
// User Playlist Listeners
|
||||||
this.socket.on('playlist_added', this.playlistAdded)
|
this.socket.on('playlist_added', this.playlistAdded)
|
||||||
this.socket.on('playlist_updated', this.playlistUpdated)
|
this.socket.on('playlist_updated', this.playlistUpdated)
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
|
|
||||||
proxy: {
|
proxy: {
|
||||||
'/s/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' },
|
'/api/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' },
|
||||||
'/api/': { target: process.env.NODE_ENV !== 'production' ? 'http://localhost:3333' : '/' }
|
'/dev/': { target: 'http://localhost:3333', pathRewrite: { '^/dev/': '' } }
|
||||||
},
|
},
|
||||||
|
|
||||||
io: {
|
io: {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.2.23",
|
"version": "2.3.4",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.2.23",
|
"version": "2.3.3",
|
||||||
"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.2.23",
|
"version": "2.3.4",
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -112,17 +112,17 @@
|
|||||||
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
|
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
|
||||||
<div class="flex-grow">{{ $strings.LabelFilename }}</div>
|
<div class="flex-grow">{{ $strings.LabelFilename }}</div>
|
||||||
<div class="w-20">{{ $strings.LabelDuration }}</div>
|
<div class="w-20">{{ $strings.LabelDuration }}</div>
|
||||||
<div class="w-20 text-center">{{ $strings.HeaderChapters }}</div>
|
<div class="w-20 hidden md:block text-center">{{ $strings.HeaderChapters }}</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-for="track in audioTracks">
|
<template v-for="track in audioTracks">
|
||||||
<div :key="track.ino" class="flex items-center py-2" :class="currentTrackIndex === track.index && isPlayingChapter ? 'bg-success bg-opacity-10' : ''">
|
<div :key="track.ino" class="flex items-center py-2" :class="currentTrackIndex === track.index && isPlayingChapter ? 'bg-success bg-opacity-10' : ''">
|
||||||
<div class="flex-grow">
|
<div class="flex-grow max-w-[calc(100%-80px)] pr-2">
|
||||||
<p class="text-xs truncate max-w-sm">{{ track.metadata.filename }}</p>
|
<p class="text-xs truncate max-w-sm">{{ track.metadata.filename }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-20" style="min-width: 80px">
|
<div class="w-20" style="min-width: 80px">
|
||||||
<p class="text-xs font-mono text-gray-200">{{ $secondsToTimestamp(Math.round(track.duration), false, true) }}</p>
|
<p class="text-xs font-mono text-gray-200">{{ $secondsToTimestamp(Math.round(track.duration), false, true) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-20 flex justify-center" style="min-width: 80px">
|
<div class="w-20 hidden md:flex justify-center" style="min-width: 80px">
|
||||||
<span v-if="(track.chapters || []).length" class="material-icons text-success text-sm">check</span>
|
<span v-if="(track.chapters || []).length" class="material-icons text-success text-sm">check</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
<transition name="slide">
|
<transition name="slide">
|
||||||
<div v-if="showEncodeOptions" class="mb-4 pb-4 border-b border-white/10">
|
<div v-if="showEncodeOptions" class="mb-4 pb-4 border-b border-white/10">
|
||||||
<div class="flex flex-wrap -mx-2">
|
<div class="flex flex-wrap -mx-2">
|
||||||
<ui-text-input-with-label ref="bitrateInput" v-model="encodingOptions.bitrate" :disabled="processing || isTaskFinished" :label="'Audio Bitrate (e.g. 64k)'" class="m-2 max-w-40" />
|
<ui-text-input-with-label ref="bitrateInput" v-model="encodingOptions.bitrate" :disabled="processing || isTaskFinished" :label="'Audio Bitrate (e.g. 128k)'" class="m-2 max-w-40" />
|
||||||
<ui-text-input-with-label ref="channelsInput" v-model="encodingOptions.channels" :disabled="processing || isTaskFinished" :label="'Audio Channels (1 or 2)'" class="m-2 max-w-40" />
|
<ui-text-input-with-label ref="channelsInput" v-model="encodingOptions.channels" :disabled="processing || isTaskFinished" :label="'Audio Channels (1 or 2)'" class="m-2 max-w-40" />
|
||||||
<ui-text-input-with-label ref="codecInput" v-model="encodingOptions.codec" :disabled="processing || isTaskFinished" :label="'Audio Codec'" class="m-2 max-w-40" />
|
<ui-text-input-with-label ref="codecInput" v-model="encodingOptions.codec" :disabled="processing || isTaskFinished" :label="'Audio Codec'" class="m-2 max-w-40" />
|
||||||
</div>
|
</div>
|
||||||
@@ -214,7 +214,7 @@ export default {
|
|||||||
showEncodeOptions: false,
|
showEncodeOptions: false,
|
||||||
shouldBackupAudioFiles: true,
|
shouldBackupAudioFiles: true,
|
||||||
encodingOptions: {
|
encodingOptions: {
|
||||||
bitrate: '64k',
|
bitrate: '128k',
|
||||||
channels: '2',
|
channels: '2',
|
||||||
codec: 'aac'
|
codec: 'aac'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export default {
|
|||||||
else if (pageName === 'library-stats') return this.$strings.HeaderLibraryStats
|
else if (pageName === 'library-stats') return this.$strings.HeaderLibraryStats
|
||||||
else if (pageName === 'users') return this.$strings.HeaderUsers
|
else if (pageName === 'users') return this.$strings.HeaderUsers
|
||||||
else if (pageName === 'item-metadata-utils') return this.$strings.HeaderItemMetadataUtils
|
else if (pageName === 'item-metadata-utils') return this.$strings.HeaderItemMetadataUtils
|
||||||
|
else if (pageName === 'rss-feeds') return this.$strings.HeaderRSSFeeds
|
||||||
else if (pageName === 'email') return this.$strings.HeaderEmail
|
else if (pageName === 'email') return this.$strings.HeaderEmail
|
||||||
}
|
}
|
||||||
return this.$strings.HeaderSettings
|
return this.$strings.HeaderSettings
|
||||||
|
|||||||
@@ -11,14 +11,18 @@
|
|||||||
<div v-if="enableBackups" class="mb-6">
|
<div v-if="enableBackups" class="mb-6">
|
||||||
<div class="flex items-center pl-6 mb-2">
|
<div class="flex items-center pl-6 mb-2">
|
||||||
<span class="material-icons-outlined text-2xl text-black-50 mr-2">schedule</span>
|
<span class="material-icons-outlined text-2xl text-black-50 mr-2">schedule</span>
|
||||||
<div class="w-48"><span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.HeaderSchedule }}:</span></div>
|
<div class="w-48">
|
||||||
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.HeaderSchedule }}:</span>
|
||||||
|
</div>
|
||||||
<div class="text-gray-100">{{ scheduleDescription }}</div>
|
<div class="text-gray-100">{{ scheduleDescription }}</div>
|
||||||
<span class="material-icons text-lg text-black-50 hover:text-yellow-500 cursor-pointer ml-2" @click="showCronBuilder = !showCronBuilder">edit</span>
|
<span class="material-icons text-lg text-black-50 hover:text-yellow-500 cursor-pointer ml-2" @click="showCronBuilder = !showCronBuilder">edit</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="nextBackupDate" class="flex items-center pl-6 py-0.5 px-2">
|
<div v-if="nextBackupDate" class="flex items-center pl-6 py-0.5 px-2">
|
||||||
<span class="material-icons-outlined text-2xl text-black-50 mr-2">event</span>
|
<span class="material-icons-outlined text-2xl text-black-50 mr-2">event</span>
|
||||||
<div class="w-48"><span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelNextBackupDate }}:</span></div>
|
<div class="w-48">
|
||||||
|
<span class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelNextBackupDate }}:</span>
|
||||||
|
</div>
|
||||||
<div class="text-gray-100">{{ nextBackupDate }}</div>
|
<div class="text-gray-100">{{ nextBackupDate }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,6 +52,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
updatingServerSettings: false,
|
updatingServerSettings: false,
|
||||||
@@ -98,7 +107,7 @@ export default {
|
|||||||
this.$toast.error('Invalid number of backups to keep')
|
this.$toast.error('Invalid number of backups to keep')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var updatePayload = {
|
const updatePayload = {
|
||||||
backupSchedule: this.enableBackups ? this.cronExpression : false,
|
backupSchedule: this.enableBackups ? this.cronExpression : false,
|
||||||
backupsToKeep: Number(this.backupsToKeep),
|
backupsToKeep: Number(this.backupsToKeep),
|
||||||
maxBackupSize: Number(this.maxBackupSize)
|
maxBackupSize: Number(this.maxBackupSize)
|
||||||
@@ -108,15 +117,15 @@ export default {
|
|||||||
updateServerSettings(payload) {
|
updateServerSettings(payload) {
|
||||||
this.updatingServerSettings = true
|
this.updatingServerSettings = true
|
||||||
this.$store
|
this.$store
|
||||||
.dispatch('updateServerSettings', payload)
|
.dispatch('updateServerSettings', payload)
|
||||||
.then((success) => {
|
.then((success) => {
|
||||||
console.log('Updated Server Settings', success)
|
console.log('Updated Server Settings', success)
|
||||||
this.updatingServerSettings = false
|
this.updatingServerSettings = false
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed to update server settings', error)
|
console.error('Failed to update server settings', error)
|
||||||
this.updatingServerSettings = false
|
this.updatingServerSettings = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
initServerSettings() {
|
initServerSettings() {
|
||||||
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
||||||
|
|||||||
@@ -34,10 +34,14 @@
|
|||||||
<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="fromInput" v-model="newSettings.fromAddress" :disabled="savingSettings" :label="$strings.LabelEmailSettingsFromAddress" />
|
<ui-text-input-with-label ref="fromInput" v-model="newSettings.fromAddress" :disabled="savingSettings" :label="$strings.LabelEmailSettingsFromAddress" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-full md:w-1/2 px-1">
|
||||||
|
<ui-text-input-with-label ref="testInput" v-model="newSettings.testAddress" :disabled="savingSettings" :label="$strings.LabelEmailSettingsTestAddress" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between pt-4">
|
<div class="flex items-center justify-between pt-4">
|
||||||
<ui-btn :loading="sendingTest" :disabled="savingSettings || !newSettings.host" type="button" @click="sendTestClick">{{ $strings.ButtonTest }}</ui-btn>
|
<ui-btn v-if="hasUpdates" :disabled="savingSettings" type="button" @click="resetChanges">{{ $strings.ButtonReset }}</ui-btn>
|
||||||
|
<ui-btn v-else :loading="sendingTest" :disabled="savingSettings || !newSettings.host" type="button" @click="sendTestClick">{{ $strings.ButtonTest }}</ui-btn>
|
||||||
<ui-btn :loading="savingSettings" :disabled="!hasUpdates" type="submit">{{ $strings.ButtonSave }}</ui-btn>
|
<ui-btn :loading="savingSettings" :disabled="!hasUpdates" type="submit">{{ $strings.ButtonSave }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -47,7 +51,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</app-settings-content>
|
</app-settings-content>
|
||||||
|
|
||||||
<app-settings-content :header-text="$strings.HeaderEReaderDevices" showAddButton :description="''" @clicked="addNewDeviceClick">
|
<app-settings-content :header-text="$strings.HeaderEreaderDevices" showAddButton :description="''" @clicked="addNewDeviceClick">
|
||||||
<table v-if="existingEReaderDevices.length" class="tracksTable my-4">
|
<table v-if="existingEReaderDevices.length" class="tracksTable my-4">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-left">{{ $strings.LabelName }}</th>
|
<th class="text-left">{{ $strings.LabelName }}</th>
|
||||||
@@ -80,6 +84,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -93,6 +102,7 @@ export default {
|
|||||||
secure: true,
|
secure: true,
|
||||||
user: null,
|
user: null,
|
||||||
pass: null,
|
pass: null,
|
||||||
|
testAddress: null,
|
||||||
fromAddress: null
|
fromAddress: null
|
||||||
},
|
},
|
||||||
newEReaderDevice: {
|
newEReaderDevice: {
|
||||||
@@ -117,6 +127,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
resetChanges() {
|
||||||
|
this.newSettings = {
|
||||||
|
...this.settings
|
||||||
|
}
|
||||||
|
},
|
||||||
editDeviceClick(device) {
|
editDeviceClick(device) {
|
||||||
this.selectedEReaderDevice = device
|
this.selectedEReaderDevice = device
|
||||||
this.showEReaderDeviceModal = true
|
this.showEReaderDeviceModal = true
|
||||||
@@ -139,7 +154,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.deletingDeviceName = device.name
|
this.deletingDeviceName = device.name
|
||||||
this.$axios
|
this.$axios
|
||||||
.$patch(`/emails/ereader-devices`, payload)
|
.$post(`/api/emails/ereader-devices`, payload)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.ereaderDevicesUpdated(data.ereaderDevices)
|
this.ereaderDevicesUpdated(data.ereaderDevices)
|
||||||
this.$toast.success('Device deleted')
|
this.$toast.success('Device deleted')
|
||||||
@@ -196,6 +211,7 @@ export default {
|
|||||||
secure: this.newSettings.secure,
|
secure: this.newSettings.secure,
|
||||||
user: this.newSettings.user,
|
user: this.newSettings.user,
|
||||||
pass: this.newSettings.pass,
|
pass: this.newSettings.pass,
|
||||||
|
testAddress: this.newSettings.testAddress,
|
||||||
fromAddress: this.newSettings.fromAddress
|
fromAddress: this.newSettings.fromAddress
|
||||||
}
|
}
|
||||||
this.savingSettings = true
|
this.savingSettings = true
|
||||||
|
|||||||
@@ -36,7 +36,10 @@
|
|||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="newServerSettings.sortingIgnorePrefix" class="w-72 ml-14 mb-2">
|
<div v-if="newServerSettings.sortingIgnorePrefix" class="w-72 ml-14 mb-2">
|
||||||
<ui-multi-select v-model="newServerSettings.sortingPrefixes" small :items="newServerSettings.sortingPrefixes" :label="$strings.LabelPrefixesToIgnore" @input="updateSortingPrefixes" :disabled="updatingServerSettings" />
|
<ui-multi-select v-model="newServerSettings.sortingPrefixes" small :items="newServerSettings.sortingPrefixes" :label="$strings.LabelPrefixesToIgnore" @input="sortingPrefixesUpdated" :disabled="savingPrefixes" />
|
||||||
|
<div class="flex justify-end py-1">
|
||||||
|
<ui-btn v-if="hasPrefixesChanged" color="success" :loading="savingPrefixes" small @click="updateSortingPrefixes">Save</ui-btn>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2 mb-2">
|
<div class="flex items-center py-2 mb-2">
|
||||||
@@ -157,10 +160,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div class="flex items-center py-2">
|
||||||
<ui-toggle-switch labeledBy="settings-disable-watcher" v-model="newServerSettings.scannerDisableWatcher" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerDisableWatcher', val)" />
|
<ui-toggle-switch labeledBy="settings-disable-watcher" v-model="scannerEnableWatcher" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('scannerDisableWatcher', !val)" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsDisableWatcherHelp">
|
<ui-tooltip :text="$strings.LabelSettingsEnableWatcherHelp">
|
||||||
<p class="pl-4">
|
<p class="pl-4">
|
||||||
<span id="settings-disable-watcher">{{ $strings.LabelSettingsDisableWatcher }}</span>
|
<span id="settings-disable-watcher">{{ $strings.LabelSettingsEnableWatcher }}</span>
|
||||||
<span class="material-icons icon-text">info_outlined</span>
|
<span class="material-icons icon-text">info_outlined</span>
|
||||||
</p>
|
</p>
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
@@ -192,7 +195,6 @@
|
|||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn color="bg" small :padding-x="4" class="mr-2 text-xs md:text-sm" :loading="isPurgingCache" @click.stop="purgeCache">{{ $strings.ButtonPurgeAllCache }}</ui-btn>
|
<ui-btn color="bg" small :padding-x="4" class="mr-2 text-xs md:text-sm" :loading="isPurgingCache" @click.stop="purgeCache">{{ $strings.ButtonPurgeAllCache }}</ui-btn>
|
||||||
<ui-btn color="bg" small :padding-x="4" class="mr-2 text-xs md:text-sm" :loading="isPurgingCache" @click.stop="purgeItemsCache">{{ $strings.ButtonPurgeItemsCache }}</ui-btn>
|
<ui-btn color="bg" small :padding-x="4" class="mr-2 text-xs md:text-sm" :loading="isPurgingCache" @click.stop="purgeItemsCache">{{ $strings.ButtonPurgeItemsCache }}</ui-btn>
|
||||||
<ui-btn color="bg" small :padding-x="4" class="mr-2 text-xs md:text-sm" :loading="isResettingLibraryItems" @click="resetLibraryItems">{{ $strings.ButtonRemoveAllLibraryItems }}</ui-btn>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-4">
|
<div class="flex items-center py-4">
|
||||||
@@ -249,15 +251,23 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isResettingLibraryItems: false,
|
isResettingLibraryItems: false,
|
||||||
updatingServerSettings: false,
|
updatingServerSettings: false,
|
||||||
homepageUseBookshelfView: false,
|
homepageUseBookshelfView: false,
|
||||||
useBookshelfView: false,
|
useBookshelfView: false,
|
||||||
|
scannerEnableWatcher: false,
|
||||||
isPurgingCache: false,
|
isPurgingCache: false,
|
||||||
|
hasPrefixesChanged: false,
|
||||||
newServerSettings: {},
|
newServerSettings: {},
|
||||||
showConfirmPurgeCache: false,
|
showConfirmPurgeCache: false,
|
||||||
|
savingPrefixes: false,
|
||||||
metadataFileFormats: [
|
metadataFileFormats: [
|
||||||
{
|
{
|
||||||
text: '.json',
|
text: '.json',
|
||||||
@@ -300,15 +310,36 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateSortingPrefixes(val) {
|
sortingPrefixesUpdated(val) {
|
||||||
if (!val || !val.length) {
|
const prefixes = [...new Set(val?.map((prefix) => prefix.trim().toLowerCase()) || [])]
|
||||||
|
this.newServerSettings.sortingPrefixes = prefixes
|
||||||
|
const serverPrefixes = this.serverSettings.sortingPrefixes || []
|
||||||
|
this.hasPrefixesChanged = prefixes.some((p) => !serverPrefixes.includes(p)) || serverPrefixes.some((p) => !prefixes.includes(p))
|
||||||
|
},
|
||||||
|
updateSortingPrefixes() {
|
||||||
|
const prefixes = [...new Set(this.newServerSettings.sortingPrefixes.map((prefix) => prefix.trim().toLowerCase()) || [])]
|
||||||
|
if (!prefixes.length) {
|
||||||
this.$toast.error('Must have at least 1 prefix')
|
this.$toast.error('Must have at least 1 prefix')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var prefixes = val.map((prefix) => prefix.trim().toLowerCase())
|
|
||||||
this.updateServerSettings({
|
this.savingPrefixes = true
|
||||||
sortingPrefixes: prefixes
|
this.$axios
|
||||||
})
|
.$patch(`/api/sorting-prefixes`, { sortingPrefixes: prefixes })
|
||||||
|
.then((data) => {
|
||||||
|
this.$toast.success(`Sorting prefixes updated. ${data.rowsUpdated} rows`)
|
||||||
|
if (data.serverSettings) {
|
||||||
|
this.$store.commit('setServerSettings', data.serverSettings)
|
||||||
|
}
|
||||||
|
this.hasPrefixesChanged = false
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to update prefixes', error)
|
||||||
|
this.$toast.error('Failed to update sorting prefixes')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.savingPrefixes = false
|
||||||
|
})
|
||||||
},
|
},
|
||||||
updateScannerCoverProvider(val) {
|
updateScannerCoverProvider(val) {
|
||||||
this.updateServerSettings({
|
this.updateServerSettings({
|
||||||
@@ -333,6 +364,9 @@ export default {
|
|||||||
this.updateSettingsKey('metadataFileFormat', val)
|
this.updateSettingsKey('metadataFileFormat', val)
|
||||||
},
|
},
|
||||||
updateSettingsKey(key, val) {
|
updateSettingsKey(key, val) {
|
||||||
|
if (key === 'scannerDisableWatcher') {
|
||||||
|
this.newServerSettings.scannerDisableWatcher = val
|
||||||
|
}
|
||||||
this.updateServerSettings({
|
this.updateServerSettings({
|
||||||
[key]: val
|
[key]: val
|
||||||
})
|
})
|
||||||
@@ -359,27 +393,11 @@ export default {
|
|||||||
initServerSettings() {
|
initServerSettings() {
|
||||||
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
||||||
this.newServerSettings.sortingPrefixes = [...(this.newServerSettings.sortingPrefixes || [])]
|
this.newServerSettings.sortingPrefixes = [...(this.newServerSettings.sortingPrefixes || [])]
|
||||||
|
this.scannerEnableWatcher = !this.newServerSettings.scannerDisableWatcher
|
||||||
|
|
||||||
this.homepageUseBookshelfView = this.newServerSettings.homeBookshelfView != this.$constants.BookshelfView.DETAIL
|
this.homepageUseBookshelfView = this.newServerSettings.homeBookshelfView != this.$constants.BookshelfView.DETAIL
|
||||||
this.useBookshelfView = this.newServerSettings.bookshelfView != this.$constants.BookshelfView.DETAIL
|
this.useBookshelfView = this.newServerSettings.bookshelfView != this.$constants.BookshelfView.DETAIL
|
||||||
},
|
},
|
||||||
resetLibraryItems() {
|
|
||||||
if (confirm(this.$strings.MessageRemoveAllItemsWarning)) {
|
|
||||||
this.isResettingLibraryItems = true
|
|
||||||
this.$axios
|
|
||||||
.$delete('/api/items/all')
|
|
||||||
.then(() => {
|
|
||||||
this.isResettingLibraryItems = false
|
|
||||||
this.$toast.success('Successfully reset items')
|
|
||||||
location.reload()
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('failed to reset items', error)
|
|
||||||
this.isResettingLibraryItems = false
|
|
||||||
this.$toast.error('Failed to reset items - manually remove the /config/libraryItems folder')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
purgeCache() {
|
purgeCache() {
|
||||||
this.showConfirmPurgeCache = true
|
this.showConfirmPurgeCache = true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,6 +38,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@@ -19,6 +19,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,6 +38,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showLibraryModal: false,
|
showLibraryModal: false,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-80 my-6 mx-auto">
|
<div v-if="isBookLibrary" class="w-80 my-6 mx-auto">
|
||||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsTop10Authors }}</h1>
|
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsTop10Authors }}</h1>
|
||||||
<p v-if="!top10Authors.length">{{ $strings.MessageNoAuthors }}</p>
|
<p v-if="!top10Authors.length">{{ $strings.MessageNoAuthors }}</p>
|
||||||
<template v-for="(author, index) in top10Authors">
|
<template v-for="(author, index) in top10Authors">
|
||||||
@@ -87,6 +87,11 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
asyncData({ redirect, store }) {
|
asyncData({ redirect, store }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!store.state.libraries.currentLibraryId) {
|
if (!store.state.libraries.currentLibraryId) {
|
||||||
return redirect('/config')
|
return redirect('/config')
|
||||||
}
|
}
|
||||||
@@ -109,43 +114,49 @@ export default {
|
|||||||
return this.$store.state.user.user
|
return this.$store.state.user.user
|
||||||
},
|
},
|
||||||
totalItems() {
|
totalItems() {
|
||||||
return this.libraryStats ? this.libraryStats.totalItems : 0
|
return this.libraryStats?.totalItems || 0
|
||||||
},
|
},
|
||||||
genresWithCount() {
|
genresWithCount() {
|
||||||
return this.libraryStats ? this.libraryStats.genresWithCount : []
|
return this.libraryStats?.genresWithCount || []
|
||||||
},
|
},
|
||||||
top5Genres() {
|
top5Genres() {
|
||||||
return this.genresWithCount.slice(0, 5)
|
return this.genresWithCount?.slice(0, 5) || []
|
||||||
},
|
},
|
||||||
top10LongestItems() {
|
top10LongestItems() {
|
||||||
return this.libraryStats ? this.libraryStats.longestItems || [] : []
|
return this.libraryStats?.longestItems || []
|
||||||
},
|
},
|
||||||
longestItemDuration() {
|
longestItemDuration() {
|
||||||
if (!this.top10LongestItems.length) return 0
|
if (!this.top10LongestItems.length) return 0
|
||||||
return this.top10LongestItems[0].duration
|
return this.top10LongestItems[0].duration
|
||||||
},
|
},
|
||||||
top10LargestItems() {
|
top10LargestItems() {
|
||||||
return this.libraryStats ? this.libraryStats.largestItems || [] : []
|
return this.libraryStats?.largestItems || []
|
||||||
},
|
},
|
||||||
largestItemSize() {
|
largestItemSize() {
|
||||||
if (!this.top10LargestItems.length) return 0
|
if (!this.top10LargestItems.length) return 0
|
||||||
return this.top10LargestItems[0].size
|
return this.top10LargestItems[0].size
|
||||||
},
|
},
|
||||||
authorsWithCount() {
|
authorsWithCount() {
|
||||||
return this.libraryStats ? this.libraryStats.authorsWithCount : []
|
return this.libraryStats?.authorsWithCount || []
|
||||||
},
|
},
|
||||||
mostUsedAuthorCount() {
|
mostUsedAuthorCount() {
|
||||||
if (!this.authorsWithCount.length) return 0
|
if (!this.authorsWithCount.length) return 0
|
||||||
return this.authorsWithCount[0].count
|
return this.authorsWithCount[0].count
|
||||||
},
|
},
|
||||||
top10Authors() {
|
top10Authors() {
|
||||||
return this.authorsWithCount.slice(0, 10)
|
return this.authorsWithCount?.slice(0, 10) || []
|
||||||
},
|
},
|
||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
currentLibraryName() {
|
currentLibraryName() {
|
||||||
return this.$store.getters['libraries/getCurrentLibraryName']
|
return this.$store.getters['libraries/getCurrentLibraryName']
|
||||||
|
},
|
||||||
|
currentLibraryMediaType() {
|
||||||
|
return this.$store.getters['libraries/getCurrentLibraryMediaType']
|
||||||
|
},
|
||||||
|
isBookLibrary() {
|
||||||
|
return this.currentLibraryMediaType === 'book'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -28,6 +28,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
search: null,
|
search: null,
|
||||||
|
|||||||
@@ -46,6 +46,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<app-settings-content :header-text="$strings.HeaderRSSFeeds">
|
||||||
|
<div v-if="feeds.length" class="block max-w-full">
|
||||||
|
<table class="rssFeedsTable text-xs">
|
||||||
|
<tr class="bg-primary bg-opacity-40 h-12">
|
||||||
|
<th class="w-16 min-w-16"></th>
|
||||||
|
<th class="w-48 max-w-64 min-w-24 text-left truncate">{{ $strings.LabelTitle }}</th>
|
||||||
|
<th class="w-48 min-w-24 text-left hidden xl:table-cell">{{ $strings.LabelSlug }}</th>
|
||||||
|
<th class="w-24 min-w-16 text-left hidden md:table-cell">{{ $strings.LabelType }}</th>
|
||||||
|
<th class="w-16 min-w-16 text-center">{{ $strings.HeaderEpisodes }}</th>
|
||||||
|
<th class="w-16 min-w-16 text-center hidden lg:table-cell">{{ $strings.LabelRSSFeedPreventIndexing }}</th>
|
||||||
|
<th class="w-48 min-w-24 flex-grow hidden md:table-cell">{{ $strings.LabelLastUpdate }}</th>
|
||||||
|
<th class="w-16 text-left"></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-for="feed in feeds" :key="feed.id" class="cursor-pointer h-12" @click="showFeed(feed)">
|
||||||
|
<!-- -->
|
||||||
|
<td>
|
||||||
|
<img :src="coverUrl(feed)" class="h-full w-full" />
|
||||||
|
</td>
|
||||||
|
<!-- -->
|
||||||
|
<td class="w-48 max-w-64 min-w-24 text-left truncate">
|
||||||
|
<p class="truncate">{{ feed.meta.title }}</p>
|
||||||
|
</td>
|
||||||
|
<!-- -->
|
||||||
|
<td class="hidden xl:table-cell">
|
||||||
|
<p class="truncate">{{ feed.slug }}</p>
|
||||||
|
</td>
|
||||||
|
<!-- -->
|
||||||
|
<td class="hidden md:table-cell">
|
||||||
|
<p class="">{{ getEntityType(feed.entityType) }}</p>
|
||||||
|
</td>
|
||||||
|
<!-- -->
|
||||||
|
<td class="text-center">
|
||||||
|
<p class="">{{ feed.episodes.length }}</p>
|
||||||
|
</td>
|
||||||
|
<!-- -->
|
||||||
|
<td class="text-center leading-none hidden lg:table-cell">
|
||||||
|
<p v-if="feed.meta.preventIndexing" class="">
|
||||||
|
<span class="material-icons text-2xl">check</span>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
<!-- -->
|
||||||
|
<td class="text-center hidden md:table-cell">
|
||||||
|
<ui-tooltip v-if="feed.updatedAt" direction="top" :text="$formatDatetime(feed.updatedAt, dateFormat, timeFormat)">
|
||||||
|
<p class="text-gray-200">{{ $dateDistanceFromNow(feed.updatedAt) }}</p>
|
||||||
|
</ui-tooltip>
|
||||||
|
</td>
|
||||||
|
<!-- -->
|
||||||
|
<td class="text-center">
|
||||||
|
<ui-icon-btn icon="delete" class="mx-0.5" :size="7" bg-color="error" outlined @click.stop="deleteFeedClick(feed)" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</app-settings-content>
|
||||||
|
<modals-rssfeed-view-feed-modal v-model="showFeedModal" :feed="selectedFeed" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showFeedModal: false,
|
||||||
|
selectedFeed: null,
|
||||||
|
feeds: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dateFormat() {
|
||||||
|
return this.$store.state.serverSettings.dateFormat
|
||||||
|
},
|
||||||
|
timeFormat() {
|
||||||
|
return this.$store.state.serverSettings.timeFormat
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showFeed(feed) {
|
||||||
|
this.selectedFeed = feed
|
||||||
|
this.showFeedModal = true
|
||||||
|
},
|
||||||
|
deleteFeedClick(feed) {
|
||||||
|
const payload = {
|
||||||
|
message: this.$strings.MessageConfirmCloseFeed,
|
||||||
|
callback: (confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
this.deleteFeed(feed)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
type: 'yesNo'
|
||||||
|
}
|
||||||
|
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||||
|
},
|
||||||
|
deleteFeed(feed) {
|
||||||
|
this.processing = true
|
||||||
|
this.$axios
|
||||||
|
.$post(`/api/feeds/${feed.id}/close`)
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success(this.$strings.ToastRSSFeedCloseSuccess)
|
||||||
|
this.show = false
|
||||||
|
this.loadFeeds()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to close RSS feed', error)
|
||||||
|
this.$toast.error(this.$strings.ToastRSSFeedCloseFailed)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.processing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getEntityType(entityType) {
|
||||||
|
if (entityType === 'libraryItem') return this.$strings.LabelItem
|
||||||
|
else if (entityType === 'series') return this.$strings.LabelSeries
|
||||||
|
else if (entityType === 'collection') return this.$strings.LabelCollection
|
||||||
|
return this.$strings.LabelUnknown
|
||||||
|
},
|
||||||
|
coverUrl(feed) {
|
||||||
|
if (!feed.coverPath) return `${this.$config.routerBasePath}/Logo.png`
|
||||||
|
return `${feed.feedUrl}/cover`
|
||||||
|
},
|
||||||
|
async loadFeeds() {
|
||||||
|
const data = await this.$axios.$get(`/api/feeds`).catch((err) => {
|
||||||
|
console.error('Failed to load RSS feeds', err)
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
if (!data) {
|
||||||
|
this.$toast.error('Failed to load RSS feeds')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.feeds = data.feeds
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.loadFeeds()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.rssFeedsTable {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
border: 1px solid #474747;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rssFeedsTable tr:first-child {
|
||||||
|
background-color: #272727;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rssFeedsTable tr:not(:first-child) {
|
||||||
|
background-color: #373838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rssFeedsTable tr:not(:first-child):nth-child(odd) {
|
||||||
|
background-color: #2f2f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rssFeedsTable tr:hover:not(:first-child) {
|
||||||
|
background-color: #474747;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rssFeedsTable td {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rssFeedsTable th {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -104,7 +104,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
async asyncData({ params, redirect, app }) {
|
async asyncData({ store, redirect, app }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const users = await app.$axios
|
const users = await app.$axios
|
||||||
.$get('/api/users')
|
.$get('/api/users')
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<div class="flex mb-4 items-center">
|
<div class="flex mb-4 items-center">
|
||||||
<h1 class="text-2xl">{{ $strings.HeaderStatsRecentSessions }}</h1>
|
<h1 class="text-2xl">{{ $strings.HeaderStatsRecentSessions }}</h1>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<ui-btn :to="`/config/users/${user.id}/sessions`" class="text-xs" :padding-x="1.5" :padding-y="1">{{ $strings.ButtonViewAll }}</ui-btn>
|
<ui-btn v-if="isAdminOrUp" :to="`/config/users/${user.id}/sessions`" class="text-xs" :padding-x="1.5" :padding-y="1">{{ $strings.ButtonViewAll }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!mostRecentListeningSessions.length">{{ $strings.MessageNoListeningSessions }}</p>
|
<p v-if="!mostRecentListeningSessions.length">{{ $strings.MessageNoListeningSessions }}</p>
|
||||||
<template v-for="(item, index) in mostRecentListeningSessions">
|
<template v-for="(item, index) in mostRecentListeningSessions">
|
||||||
@@ -82,6 +82,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isAdminOrUp() {
|
||||||
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
|
},
|
||||||
user() {
|
user() {
|
||||||
return this.$store.state.user.user
|
return this.$store.state.user.user
|
||||||
},
|
},
|
||||||
@@ -116,7 +119,6 @@ export default {
|
|||||||
console.error('Failed to load listening sesions', err)
|
console.error('Failed to load listening sesions', err)
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
console.log('Loaded user listening data', this.listeningStats)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -47,13 +47,7 @@
|
|||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<h1 class="text-lg mb-2 text-white text-opacity-90 px-2 sm:px-0">{{ $strings.HeaderSavedMediaProgress }}</h1>
|
<h1 class="text-lg mb-2 text-white text-opacity-90 px-2 sm:px-0">{{ $strings.HeaderSavedMediaProgress }}</h1>
|
||||||
|
|
||||||
<div v-if="mediaProgressWithoutMedia.length" class="flex items-center py-2 mb-2">
|
<table v-if="mediaProgress.length" class="userAudiobooksTable">
|
||||||
<p class="text-error">User has media progress for {{ mediaProgressWithoutMedia.length }} items that no longer exist.</p>
|
|
||||||
<div class="flex-grow" />
|
|
||||||
<ui-btn small :loading="purgingMediaProgress" @click.stop="purgeMediaProgress">{{ $strings.ButtonPurgeMediaProgress }}</ui-btn>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table v-if="mediaProgressWithMedia.length" class="userAudiobooksTable">
|
|
||||||
<tr class="bg-primary bg-opacity-40">
|
<tr class="bg-primary bg-opacity-40">
|
||||||
<th class="w-16 text-left">{{ $strings.LabelItem }}</th>
|
<th class="w-16 text-left">{{ $strings.LabelItem }}</th>
|
||||||
<th class="text-left"></th>
|
<th class="text-left"></th>
|
||||||
@@ -61,19 +55,14 @@
|
|||||||
<th class="w-40 hidden sm:table-cell">{{ $strings.LabelStartedAt }}</th>
|
<th class="w-40 hidden sm:table-cell">{{ $strings.LabelStartedAt }}</th>
|
||||||
<th class="w-40 hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th>
|
<th class="w-40 hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="item in mediaProgressWithMedia" :key="item.id" :class="!item.isFinished ? '' : 'isFinished'">
|
<tr v-for="item in mediaProgress" :key="item.id" :class="!item.isFinished ? '' : 'isFinished'">
|
||||||
<td>
|
<td>
|
||||||
<covers-book-cover :width="50" :library-item="item" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
<covers-preview-cover v-if="item.coverPath" :width="50" :src="$store.getters['globals/getLibraryItemCoverSrcById'](item.libraryItemId, item.mediaUpdatedAt)" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="false" />
|
||||||
|
<div v-else class="bg-primary flex items-center justify-center text-center text-xs text-gray-400 p-1" :style="{ width: '50px', height: 50 * bookCoverAspectRatio + 'px' }">No Cover</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="item.media && item.media.metadata && item.episode">
|
<p>{{ item.displayTitle || 'Unknown' }}</p>
|
||||||
<p>{{ item.episode.title || 'Unknown' }}</p>
|
<p v-if="item.displaySubtitle" class="text-white text-opacity-50 text-sm font-sans">{{ item.displaySubtitle }}</p>
|
||||||
<p class="text-white text-opacity-50 text-sm font-sans">{{ item.media.metadata.title }}</p>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="item.media && item.media.metadata">
|
|
||||||
<p>{{ item.media.metadata.title || 'Unknown' }}</p>
|
|
||||||
<p v-if="item.media.metadata.authorName" class="text-white text-opacity-50 text-sm font-sans">by {{ item.media.metadata.authorName }}</p>
|
|
||||||
</template>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<p class="text-sm">{{ Math.floor(item.progress * 100) }}%</p>
|
<p class="text-sm">{{ Math.floor(item.progress * 100) }}%</p>
|
||||||
@@ -111,8 +100,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
listeningSessions: {},
|
listeningSessions: {},
|
||||||
listeningStats: {},
|
listeningStats: {}
|
||||||
purgingMediaProgress: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -131,12 +119,6 @@ export default {
|
|||||||
mediaProgress() {
|
mediaProgress() {
|
||||||
return this.user.mediaProgress.sort((a, b) => b.lastUpdate - a.lastUpdate)
|
return this.user.mediaProgress.sort((a, b) => b.lastUpdate - a.lastUpdate)
|
||||||
},
|
},
|
||||||
mediaProgressWithMedia() {
|
|
||||||
return this.mediaProgress.filter((mp) => mp.media)
|
|
||||||
},
|
|
||||||
mediaProgressWithoutMedia() {
|
|
||||||
return this.mediaProgress.filter((mp) => !mp.media)
|
|
||||||
},
|
|
||||||
totalListeningTime() {
|
totalListeningTime() {
|
||||||
return this.listeningStats.totalTime || 0
|
return this.listeningStats.totalTime || 0
|
||||||
},
|
},
|
||||||
@@ -176,24 +158,6 @@ export default {
|
|||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
console.log('Loaded user listening data', this.listeningSessions, this.listeningStats)
|
console.log('Loaded user listening data', this.listeningSessions, this.listeningStats)
|
||||||
},
|
|
||||||
purgeMediaProgress() {
|
|
||||||
this.purgingMediaProgress = true
|
|
||||||
|
|
||||||
this.$axios
|
|
||||||
.$post(`/api/users/${this.user.id}/purge-media-progress`)
|
|
||||||
.then((updatedUser) => {
|
|
||||||
console.log('Updated user', updatedUser)
|
|
||||||
this.$toast.success('Media progress purged')
|
|
||||||
this.user = updatedUser
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed to purge media progress', error)
|
|
||||||
this.$toast.error('Failed to purge media progress')
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.purgingMediaProgress = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
asyncData({ store, redirect }) {
|
||||||
|
if (!store.getters['user/getIsAdminOrUp']) {
|
||||||
|
redirect('/')
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Include episode downloads for podcasts
|
// Include episode downloads for podcasts
|
||||||
var item = await app.$axios.$get(`/api/items/${params.id}?expanded=1&include=authors,downloads,rssfeed`).catch((error) => {
|
var item = await app.$axios.$get(`/api/items/${params.id}?expanded=1&include=downloads,rssfeed`).catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
@@ -533,7 +533,6 @@ export default {
|
|||||||
.$patch(`/api/me/progress/${this.libraryItemId}`, updatePayload)
|
.$patch(`/api/me/progress/${this.libraryItemId}`, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isProcessingReadUpdate = false
|
this.isProcessingReadUpdate = false
|
||||||
this.$toast.success(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedSuccess : this.$strings.ToastItemMarkedAsNotFinishedSuccess)
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
@@ -762,6 +761,7 @@ export default {
|
|||||||
if (this.libraryId) {
|
if (this.libraryId) {
|
||||||
this.$store.commit('libraries/setCurrentLibrary', this.libraryId)
|
this.$store.commit('libraries/setCurrentLibrary', this.libraryId)
|
||||||
}
|
}
|
||||||
|
this.$eventBus.$on(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
|
||||||
this.$root.socket.on('item_updated', this.libraryItemUpdated)
|
this.$root.socket.on('item_updated', this.libraryItemUpdated)
|
||||||
this.$root.socket.on('rss_feed_open', this.rssFeedOpen)
|
this.$root.socket.on('rss_feed_open', this.rssFeedOpen)
|
||||||
this.$root.socket.on('rss_feed_closed', this.rssFeedClosed)
|
this.$root.socket.on('rss_feed_closed', this.rssFeedClosed)
|
||||||
@@ -770,6 +770,7 @@ export default {
|
|||||||
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
|
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
this.$eventBus.$off(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
|
||||||
this.$root.socket.off('item_updated', this.libraryItemUpdated)
|
this.$root.socket.off('item_updated', this.libraryItemUpdated)
|
||||||
this.$root.socket.off('rss_feed_open', this.rssFeedOpen)
|
this.$root.socket.off('rss_feed_open', this.rssFeedOpen)
|
||||||
this.$root.socket.off('rss_feed_closed', this.rssFeedClosed)
|
this.$root.socket.off('rss_feed_closed', this.rssFeedClosed)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<a :href="podcast.pageUrl" class="text-base md:text-lg text-gray-200 hover:underline" target="_blank" @click.stop>{{ podcast.title }}</a>
|
<a :href="podcast.pageUrl" class="text-base md:text-lg text-gray-200 hover:underline" target="_blank" @click.stop>{{ podcast.title }}</a>
|
||||||
<widgets-explicit-indicator :explicit="podcast.explicit" />
|
<widgets-explicit-indicator :explicit="podcast.explicit" />
|
||||||
<widgets-already-in-library-indicator :already-in-library="podcast.alreadyInLibrary"/>
|
<widgets-already-in-library-indicator :already-in-library="podcast.alreadyInLibrary" />
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm md:text-base text-gray-300 whitespace-nowrap truncate">by {{ podcast.artistName }}</p>
|
<p class="text-sm md:text-base text-gray-300 whitespace-nowrap truncate">by {{ podcast.artistName }}</p>
|
||||||
<p class="text-xs text-gray-400 leading-5">{{ podcast.genres.join(', ') }}</p>
|
<p class="text-xs text-gray-400 leading-5">{{ podcast.genres.join(', ') }}</p>
|
||||||
@@ -146,11 +146,15 @@ export default {
|
|||||||
async submitSearch(term) {
|
async submitSearch(term) {
|
||||||
this.processing = true
|
this.processing = true
|
||||||
this.termSearched = ''
|
this.termSearched = ''
|
||||||
var results = await this.$axios.$get(`/api/search/podcast?term=${encodeURIComponent(term)}`).catch((error) => {
|
let results = await this.$axios.$get(`/api/search/podcast?term=${encodeURIComponent(term)}`).catch((error) => {
|
||||||
console.error('Search request failed', error)
|
console.error('Search request failed', error)
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
console.log('Got results', results)
|
console.log('Got results', results)
|
||||||
|
|
||||||
|
// Filter out podcasts without an RSS feed
|
||||||
|
results = results.filter((r) => r.feedUrl)
|
||||||
|
|
||||||
for (let result of results) {
|
for (let result of results) {
|
||||||
let podcast = this.existentPodcasts.find((p) => p.itunesId === result.id || p.title === result.title.toLowerCase())
|
let podcast = this.existentPodcasts.find((p) => p.itunesId === result.id || p.title === result.title.toLowerCase())
|
||||||
if (podcast) {
|
if (podcast) {
|
||||||
@@ -164,7 +168,7 @@ export default {
|
|||||||
},
|
},
|
||||||
async selectPodcast(podcast) {
|
async selectPodcast(podcast) {
|
||||||
console.log('Selected podcast', podcast)
|
console.log('Selected podcast', podcast)
|
||||||
if(podcast.existentId){
|
if (podcast.existentId) {
|
||||||
this.$router.push(`/item/${podcast.existentId}`)
|
this.$router.push(`/item/${podcast.existentId}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -173,7 +177,7 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.processing = true
|
this.processing = true
|
||||||
var payload = await this.$axios.$post(`/api/podcasts/feed`, {rssFeed: podcast.feedUrl}).catch((error) => {
|
const payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed: podcast.feedUrl }).catch((error) => {
|
||||||
console.error('Failed to get feed', error)
|
console.error('Failed to get feed', error)
|
||||||
this.$toast.error('Failed to get podcast feed')
|
this.$toast.error('Failed to get podcast feed')
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default {
|
|||||||
return redirect(`/library/${libraryId}`)
|
return redirect(`/library/${libraryId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const series = await app.$axios.$get(`/api/series/${params.id}?include=progress,rssfeed`).catch((error) => {
|
const series = await app.$axios.$get(`/api/libraries/${library.id}/series/${params.id}?include=progress,rssfeed`).catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|||||||
+14
-6
@@ -74,9 +74,17 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
this.$router.replace('/oops?message=No libraries available')
|
this.$router.replace('/oops?message=No libraries available')
|
||||||
}
|
}
|
||||||
} else if (this.$route.query.redirect) {
|
|
||||||
this.$router.replace(this.$route.query.redirect)
|
|
||||||
} else {
|
} else {
|
||||||
|
if (this.$route.query.redirect) {
|
||||||
|
const isAdminUser = this.$store.getters['user/getIsAdminOrUp']
|
||||||
|
const redirect = this.$route.query.redirect
|
||||||
|
// If not admin user then do not redirect to config pages other than your stats
|
||||||
|
if (isAdminUser || !redirect.startsWith('/config/') || redirect === '/config/stats') {
|
||||||
|
this.$router.replace(redirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.$router.replace(`/library/${this.$store.state.libraries.currentLibraryId}`)
|
this.$router.replace(`/library/${this.$store.state.libraries.currentLibraryId}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,17 +152,17 @@ export default {
|
|||||||
this.error = null
|
this.error = null
|
||||||
this.processing = true
|
this.processing = true
|
||||||
|
|
||||||
var payload = {
|
const payload = {
|
||||||
username: this.username,
|
username: this.username,
|
||||||
password: this.password || ''
|
password: this.password || ''
|
||||||
}
|
}
|
||||||
var authRes = await this.$axios.$post('/login', payload).catch((error) => {
|
const authRes = await this.$axios.$post('/login', payload).catch((error) => {
|
||||||
console.error('Failed', error.response)
|
console.error('Failed', error.response)
|
||||||
if (error.response) this.error = error.response.data
|
if (error.response) this.error = error.response.data
|
||||||
else this.error = 'Unknown Error'
|
else this.error = 'Unknown Error'
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
if (authRes && authRes.error) {
|
if (authRes?.error) {
|
||||||
this.error = authRes.error
|
this.error = authRes.error
|
||||||
} else if (authRes) {
|
} else if (authRes) {
|
||||||
this.setUser(authRes)
|
this.setUser(authRes)
|
||||||
@@ -162,7 +170,7 @@ export default {
|
|||||||
this.processing = false
|
this.processing = false
|
||||||
},
|
},
|
||||||
checkAuth() {
|
checkAuth() {
|
||||||
var token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
if (!token) return false
|
if (!token) return false
|
||||||
|
|
||||||
this.processing = true
|
this.processing = true
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ export default class PlayerHandler {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
deviceInfo: {
|
deviceInfo: {
|
||||||
|
clientName: 'Abs Web',
|
||||||
deviceId: this.getDeviceId()
|
deviceId: this.getDeviceId()
|
||||||
},
|
},
|
||||||
supportedMimeTypes: this.player.playableMimeTypes,
|
supportedMimeTypes: this.player.playableMimeTypes,
|
||||||
@@ -281,6 +282,10 @@ export default class PlayerHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First sync happens after 20 seconds
|
||||||
|
* subsequent syncs happen every 10 seconds
|
||||||
|
*/
|
||||||
startPlayInterval() {
|
startPlayInterval() {
|
||||||
clearInterval(this.playInterval)
|
clearInterval(this.playInterval)
|
||||||
let lastTick = Date.now()
|
let lastTick = Date.now()
|
||||||
@@ -293,7 +298,7 @@ export default class PlayerHandler {
|
|||||||
const exactTimeElapsed = ((Date.now() - lastTick) / 1000)
|
const exactTimeElapsed = ((Date.now() - lastTick) / 1000)
|
||||||
lastTick = Date.now()
|
lastTick = Date.now()
|
||||||
this.listeningTimeSinceSync += exactTimeElapsed
|
this.listeningTimeSinceSync += exactTimeElapsed
|
||||||
const TimeToWaitBeforeSync = this.lastSyncTime > 0 ? 5 : 20
|
const TimeToWaitBeforeSync = this.lastSyncTime > 0 ? 10 : 20
|
||||||
if (this.listeningTimeSinceSync >= TimeToWaitBeforeSync) {
|
if (this.listeningTimeSinceSync >= TimeToWaitBeforeSync) {
|
||||||
this.sendProgressSync(currentTime)
|
this.sendProgressSync(currentTime)
|
||||||
}
|
}
|
||||||
@@ -315,7 +320,7 @@ export default class PlayerHandler {
|
|||||||
}
|
}
|
||||||
this.listeningTimeSinceSync = 0
|
this.listeningTimeSinceSync = 0
|
||||||
this.lastSyncTime = 0
|
this.lastSyncTime = 0
|
||||||
return this.ctx.$axios.$post(`/api/session/${this.currentSessionId}/close`, syncData, { timeout: 1000 }).catch((error) => {
|
return this.ctx.$axios.$post(`/api/session/${this.currentSessionId}/close`, syncData, { timeout: 6000 }).catch((error) => {
|
||||||
console.error('Failed to close session', error)
|
console.error('Failed to close session', error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -335,12 +340,13 @@ export default class PlayerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.listeningTimeSinceSync = 0
|
this.listeningTimeSinceSync = 0
|
||||||
this.ctx.$axios.$post(`/api/session/${this.currentSessionId}/sync`, syncData, { timeout: 3000 }).then(() => {
|
this.ctx.$axios.$post(`/api/session/${this.currentSessionId}/sync`, syncData, { timeout: 9000 }).then(() => {
|
||||||
this.failedProgressSyncs = 0
|
this.failedProgressSyncs = 0
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.error('Failed to update session progress', error)
|
console.error('Failed to update session progress', error)
|
||||||
|
// After 4 failed sync attempts show an alert toast
|
||||||
this.failedProgressSyncs++
|
this.failedProgressSyncs++
|
||||||
if (this.failedProgressSyncs >= 2) {
|
if (this.failedProgressSyncs >= 4) {
|
||||||
this.ctx.showFailedProgressSyncs()
|
this.ctx.showFailedProgressSyncs()
|
||||||
this.failedProgressSyncs = 0
|
this.failedProgressSyncs = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ export default function ({ $axios, store, $config }) {
|
|||||||
if (bearerToken) {
|
if (bearerToken) {
|
||||||
config.headers.common['Authorization'] = `Bearer ${bearerToken}`
|
config.headers.common['Authorization'] = `Bearer ${bearerToken}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
config.url = `/dev${config.url}`
|
||||||
|
console.log('Making request to ' + config.url)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$axios.onError(error => {
|
$axios.onError(error => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const languageCodeMap = {
|
|||||||
'fr': { label: 'Français', dateFnsLocale: 'fr' },
|
'fr': { label: 'Français', dateFnsLocale: 'fr' },
|
||||||
'hr': { label: 'Hrvatski', dateFnsLocale: 'hr' },
|
'hr': { label: 'Hrvatski', dateFnsLocale: 'hr' },
|
||||||
'it': { label: 'Italiano', dateFnsLocale: 'it' },
|
'it': { label: 'Italiano', dateFnsLocale: 'it' },
|
||||||
|
'lt': { label: 'Lietuvių', dateFnsLocale: 'lt' },
|
||||||
'nl': { label: 'Nederlands', dateFnsLocale: 'nl' },
|
'nl': { label: 'Nederlands', dateFnsLocale: 'nl' },
|
||||||
'pl': { label: 'Polski', dateFnsLocale: 'pl' },
|
'pl': { label: 'Polski', dateFnsLocale: 'pl' },
|
||||||
'ru': { label: 'Русский', dateFnsLocale: 'ru' },
|
'ru': { label: 'Русский', dateFnsLocale: 'ru' },
|
||||||
@@ -34,7 +35,7 @@ Vue.prototype.$strings = { ...enUsStrings }
|
|||||||
|
|
||||||
Vue.prototype.$getString = (key, subs) => {
|
Vue.prototype.$getString = (key, subs) => {
|
||||||
if (!Vue.prototype.$strings[key]) return ''
|
if (!Vue.prototype.$strings[key]) return ''
|
||||||
if (subs && Array.isArray(subs) && subs.length) {
|
if (subs?.length && Array.isArray(subs)) {
|
||||||
return supplant(Vue.prototype.$strings[key], subs)
|
return supplant(Vue.prototype.$strings[key], subs)
|
||||||
}
|
}
|
||||||
return Vue.prototype.$strings[key]
|
return Vue.prototype.$strings[key]
|
||||||
|
|||||||
@@ -24,20 +24,20 @@ Vue.prototype.$formatJsDate = (jsdate, fnsFormat = 'MM/dd/yyyy HH:mm') => {
|
|||||||
return format(jsdate, fnsFormat)
|
return format(jsdate, fnsFormat)
|
||||||
}
|
}
|
||||||
Vue.prototype.$formatTime = (unixms, fnsFormat = 'HH:mm') => {
|
Vue.prototype.$formatTime = (unixms, fnsFormat = 'HH:mm') => {
|
||||||
if (!unixms) return ''
|
if (!unixms) return ''
|
||||||
return format(unixms, fnsFormat)
|
return format(unixms, fnsFormat)
|
||||||
}
|
}
|
||||||
Vue.prototype.$formatJsTime = (jsdate, fnsFormat = 'HH:mm') => {
|
Vue.prototype.$formatJsTime = (jsdate, fnsFormat = 'HH:mm') => {
|
||||||
if (!jsdate || !isDate(jsdate)) return ''
|
if (!jsdate || !isDate(jsdate)) return ''
|
||||||
return format(jsdate, fnsFormat)
|
return format(jsdate, fnsFormat)
|
||||||
}
|
}
|
||||||
Vue.prototype.$formatDatetime = (unixms, fnsDateFormart = 'MM/dd/yyyy', fnsTimeFormat = 'HH:mm') => {
|
Vue.prototype.$formatDatetime = (unixms, fnsDateFormart = 'MM/dd/yyyy', fnsTimeFormat = 'HH:mm') => {
|
||||||
if (!unixms) return ''
|
if (!unixms) return ''
|
||||||
return format(unixms, `${fnsDateFormart} ${fnsTimeFormat}`)
|
return format(unixms, `${fnsDateFormart} ${fnsTimeFormat}`)
|
||||||
}
|
}
|
||||||
Vue.prototype.$formatJsDatetime = (jsdate, fnsDateFormart = 'MM/dd/yyyy', fnsTimeFormat = 'HH:mm') => {
|
Vue.prototype.$formatJsDatetime = (jsdate, fnsDateFormart = 'MM/dd/yyyy', fnsTimeFormat = 'HH:mm') => {
|
||||||
if (!jsdate || !isDate(jsdate)) return ''
|
if (!jsdate || !isDate(jsdate)) return ''
|
||||||
return format(jsdate, `${fnsDateFormart} ${fnsTimeFormat}`)
|
return format(jsdate, `${fnsDateFormart} ${fnsTimeFormat}`)
|
||||||
}
|
}
|
||||||
Vue.prototype.$addDaysToToday = (daysToAdd) => {
|
Vue.prototype.$addDaysToToday = (daysToAdd) => {
|
||||||
var date = addDays(new Date(), daysToAdd)
|
var date = addDays(new Date(), daysToAdd)
|
||||||
@@ -132,8 +132,10 @@ Vue.prototype.$copyToClipboard = (str, ctx) => {
|
|||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
navigator.clipboard.writeText(str).then(() => {
|
navigator.clipboard.writeText(str).then(() => {
|
||||||
if (ctx) ctx.$toast.success('Copied to clipboard')
|
if (ctx) ctx.$toast.success('Copied to clipboard')
|
||||||
|
resolve(true)
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error('Clipboard copy failed', str, err)
|
console.error('Clipboard copy failed', str, err)
|
||||||
|
resolve(false)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const el = document.createElement('textarea')
|
const el = document.createElement('textarea')
|
||||||
@@ -147,6 +149,7 @@ Vue.prototype.$copyToClipboard = (str, ctx) => {
|
|||||||
document.body.removeChild(el)
|
document.body.removeChild(el)
|
||||||
|
|
||||||
if (ctx) ctx.$toast.success('Copied to clipboard')
|
if (ctx) ctx.$toast.success('Copied to clipboard')
|
||||||
|
resolve(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ export const getters = {
|
|||||||
getLibraryItemCoverSrc: (state, getters, rootState, rootGetters) => (libraryItem, placeholder = null) => {
|
getLibraryItemCoverSrc: (state, getters, rootState, rootGetters) => (libraryItem, placeholder = null) => {
|
||||||
if (!placeholder) placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
if (!placeholder) placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
||||||
if (!libraryItem) return placeholder
|
if (!libraryItem) return placeholder
|
||||||
var media = libraryItem.media
|
const media = libraryItem.media
|
||||||
if (!media || !media.coverPath || media.coverPath === placeholder) return placeholder
|
if (!media?.coverPath || media.coverPath === placeholder) return placeholder
|
||||||
|
|
||||||
// Absolute URL covers (should no longer be used)
|
// Absolute URL covers (should no longer be used)
|
||||||
if (media.coverPath.startsWith('http:') || media.coverPath.startsWith('https:')) return media.coverPath
|
if (media.coverPath.startsWith('http:') || media.coverPath.startsWith('https:')) return media.coverPath
|
||||||
@@ -99,14 +99,14 @@ export const getters = {
|
|||||||
|
|
||||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}&ts=${lastUpdate}`
|
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}&ts=${lastUpdate}`
|
||||||
},
|
},
|
||||||
getLibraryItemCoverSrcById: (state, getters, rootState, rootGetters) => (libraryItemId, placeholder = null, raw = false) => {
|
getLibraryItemCoverSrcById: (state, getters, rootState, rootGetters) => (libraryItemId, timestamp = null, raw = false) => {
|
||||||
if (!placeholder) placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
const placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
||||||
if (!libraryItemId) return placeholder
|
if (!libraryItemId) return placeholder
|
||||||
var userToken = rootGetters['user/getToken']
|
const userToken = rootGetters['user/getToken']
|
||||||
if (process.env.NODE_ENV !== 'production') { // Testing
|
if (process.env.NODE_ENV !== 'production') { // Testing
|
||||||
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}`
|
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
||||||
}
|
}
|
||||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}`
|
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
||||||
},
|
},
|
||||||
getIsBatchSelectingMediaItems: (state) => {
|
getIsBatchSelectingMediaItems: (state) => {
|
||||||
return state.selectedMediaItems.length
|
return state.selectedMediaItems.length
|
||||||
|
|||||||
+25
-15
@@ -234,25 +234,31 @@ export const mutations = {
|
|||||||
setNumUserPlaylists(state, numUserPlaylists) {
|
setNumUserPlaylists(state, numUserPlaylists) {
|
||||||
state.numUserPlaylists = numUserPlaylists
|
state.numUserPlaylists = numUserPlaylists
|
||||||
},
|
},
|
||||||
|
removeSeriesFromFilterData(state, seriesId) {
|
||||||
|
if (!seriesId || !state.filterData) return
|
||||||
|
state.filterData.series = state.filterData.series.filter(se => se.id !== seriesId)
|
||||||
|
},
|
||||||
updateFilterDataWithItem(state, libraryItem) {
|
updateFilterDataWithItem(state, libraryItem) {
|
||||||
if (!libraryItem || !state.filterData) return
|
if (!libraryItem || !state.filterData) return
|
||||||
if (state.currentLibraryId !== libraryItem.libraryId) return
|
if (state.currentLibraryId !== libraryItem.libraryId) return
|
||||||
/*
|
/*
|
||||||
var data = {
|
structure of filterData:
|
||||||
|
{
|
||||||
authors: [],
|
authors: [],
|
||||||
genres: [],
|
genres: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
series: [],
|
series: [],
|
||||||
narrators: [],
|
narrators: [],
|
||||||
languages: []
|
languages: [],
|
||||||
|
publishers: []
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
var mediaMetadata = libraryItem.media.metadata
|
const mediaMetadata = libraryItem.media.metadata
|
||||||
|
|
||||||
// Add/update book authors
|
// Add/update book authors
|
||||||
if (mediaMetadata.authors && mediaMetadata.authors.length) {
|
if (mediaMetadata.authors?.length) {
|
||||||
mediaMetadata.authors.forEach((author) => {
|
mediaMetadata.authors.forEach((author) => {
|
||||||
var indexOf = state.filterData.authors.findIndex(au => au.id === author.id)
|
const indexOf = state.filterData.authors.findIndex(au => au.id === author.id)
|
||||||
if (indexOf >= 0) {
|
if (indexOf >= 0) {
|
||||||
state.filterData.authors.splice(indexOf, 1, author)
|
state.filterData.authors.splice(indexOf, 1, author)
|
||||||
} else {
|
} else {
|
||||||
@@ -263,9 +269,9 @@ export const mutations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add/update series
|
// Add/update series
|
||||||
if (mediaMetadata.series && mediaMetadata.series.length) {
|
if (mediaMetadata.series?.length) {
|
||||||
mediaMetadata.series.forEach((series) => {
|
mediaMetadata.series.forEach((series) => {
|
||||||
var indexOf = state.filterData.series.findIndex(se => se.id === series.id)
|
const indexOf = state.filterData.series.findIndex(se => se.id === series.id)
|
||||||
if (indexOf >= 0) {
|
if (indexOf >= 0) {
|
||||||
state.filterData.series.splice(indexOf, 1, { id: series.id, name: series.name })
|
state.filterData.series.splice(indexOf, 1, { id: series.id, name: series.name })
|
||||||
} else {
|
} else {
|
||||||
@@ -276,7 +282,7 @@ export const mutations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add genres
|
// Add genres
|
||||||
if (mediaMetadata.genres && mediaMetadata.genres.length) {
|
if (mediaMetadata.genres?.length) {
|
||||||
mediaMetadata.genres.forEach((genre) => {
|
mediaMetadata.genres.forEach((genre) => {
|
||||||
if (!state.filterData.genres.includes(genre)) {
|
if (!state.filterData.genres.includes(genre)) {
|
||||||
state.filterData.genres.push(genre)
|
state.filterData.genres.push(genre)
|
||||||
@@ -286,7 +292,7 @@ export const mutations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add tags
|
// Add tags
|
||||||
if (libraryItem.media.tags && libraryItem.media.tags.length) {
|
if (libraryItem.media.tags?.length) {
|
||||||
libraryItem.media.tags.forEach((tag) => {
|
libraryItem.media.tags.forEach((tag) => {
|
||||||
if (!state.filterData.tags.includes(tag)) {
|
if (!state.filterData.tags.includes(tag)) {
|
||||||
state.filterData.tags.push(tag)
|
state.filterData.tags.push(tag)
|
||||||
@@ -296,7 +302,7 @@ export const mutations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add narrators
|
// Add narrators
|
||||||
if (mediaMetadata.narrators && mediaMetadata.narrators.length) {
|
if (mediaMetadata.narrators?.length) {
|
||||||
mediaMetadata.narrators.forEach((narrator) => {
|
mediaMetadata.narrators.forEach((narrator) => {
|
||||||
if (!state.filterData.narrators.includes(narrator)) {
|
if (!state.filterData.narrators.includes(narrator)) {
|
||||||
state.filterData.narrators.push(narrator)
|
state.filterData.narrators.push(narrator)
|
||||||
@@ -305,12 +311,16 @@ export const mutations = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add publishers
|
||||||
|
if (mediaMetadata.publisher && !state.filterData.publishers.includes(mediaMetadata.publisher)) {
|
||||||
|
state.filterData.publishers.push(mediaMetadata.publisher)
|
||||||
|
state.filterData.publishers.sort((a, b) => a.localeCompare(b))
|
||||||
|
}
|
||||||
|
|
||||||
// Add language
|
// Add language
|
||||||
if (mediaMetadata.language) {
|
if (mediaMetadata.language && !state.filterData.languages.includes(mediaMetadata.language)) {
|
||||||
if (!state.filterData.languages.includes(mediaMetadata.language)) {
|
state.filterData.languages.push(mediaMetadata.language)
|
||||||
state.filterData.languages.push(mediaMetadata.language)
|
state.filterData.languages.sort((a, b) => a.localeCompare(b))
|
||||||
state.filterData.languages.sort((a, b) => a.localeCompare(b))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setCollections(state, collections) {
|
setCollections(state, collections) {
|
||||||
|
|||||||
@@ -33,22 +33,22 @@ export const getters = {
|
|||||||
return state.user.bookmarks.filter(bm => bm.libraryItemId === libraryItemId)
|
return state.user.bookmarks.filter(bm => bm.libraryItemId === libraryItemId)
|
||||||
},
|
},
|
||||||
getUserSetting: (state) => (key) => {
|
getUserSetting: (state) => (key) => {
|
||||||
return state.settings ? state.settings[key] : null
|
return state.settings?.[key] || null
|
||||||
},
|
},
|
||||||
getUserCanUpdate: (state) => {
|
getUserCanUpdate: (state) => {
|
||||||
return state.user && state.user.permissions ? !!state.user.permissions.update : false
|
return !!state.user?.permissions?.update
|
||||||
},
|
},
|
||||||
getUserCanDelete: (state) => {
|
getUserCanDelete: (state) => {
|
||||||
return state.user && state.user.permissions ? !!state.user.permissions.delete : false
|
return !!state.user?.permissions?.delete
|
||||||
},
|
},
|
||||||
getUserCanDownload: (state) => {
|
getUserCanDownload: (state) => {
|
||||||
return state.user && state.user.permissions ? !!state.user.permissions.download : false
|
return !!state.user?.permissions?.download
|
||||||
},
|
},
|
||||||
getUserCanUpload: (state) => {
|
getUserCanUpload: (state) => {
|
||||||
return state.user && state.user.permissions ? !!state.user.permissions.upload : false
|
return !!state.user?.permissions?.upload
|
||||||
},
|
},
|
||||||
getUserCanAccessAllLibraries: (state) => {
|
getUserCanAccessAllLibraries: (state) => {
|
||||||
return state.user && state.user.permissions ? !!state.user.permissions.accessAllLibraries : false
|
return !!state.user?.permissions?.accessAllLibraries
|
||||||
},
|
},
|
||||||
getLibrariesAccessible: (state, getters) => {
|
getLibrariesAccessible: (state, getters) => {
|
||||||
if (!state.user) return []
|
if (!state.user) return []
|
||||||
@@ -80,7 +80,7 @@ export const actions = {
|
|||||||
if (state.settings.orderBy == 'media.metadata.publishedYear') {
|
if (state.settings.orderBy == 'media.metadata.publishedYear') {
|
||||||
settingsUpdate.orderBy = 'media.metadata.title'
|
settingsUpdate.orderBy = 'media.metadata.title'
|
||||||
}
|
}
|
||||||
const invalidFilters = ['series', 'authors', 'narrators', 'languages', 'progress', 'issues', 'ebooks', 'abridged']
|
const invalidFilters = ['series', 'authors', 'narrators', 'publishers', 'languages', 'progress', 'issues', 'ebooks', 'abridged']
|
||||||
const filterByFirstPart = (state.settings.filterBy || '').split('.').shift()
|
const filterByFirstPart = (state.settings.filterBy || '').split('.').shift()
|
||||||
if (invalidFilters.includes(filterByFirstPart)) {
|
if (invalidFilters.includes(filterByFirstPart)) {
|
||||||
settingsUpdate.filterBy = 'all'
|
settingsUpdate.filterBy = 'all'
|
||||||
|
|||||||
+63
-34
@@ -3,7 +3,7 @@
|
|||||||
"ButtonAddChapters": "Kapitel hinzufügen",
|
"ButtonAddChapters": "Kapitel hinzufügen",
|
||||||
"ButtonAddPodcasts": "Podcasts hinzufügen",
|
"ButtonAddPodcasts": "Podcasts hinzufügen",
|
||||||
"ButtonAddYourFirstLibrary": "Erstelle deine erste Bibliothek",
|
"ButtonAddYourFirstLibrary": "Erstelle deine erste Bibliothek",
|
||||||
"ButtonApply": "Anwenden",
|
"ButtonApply": "Übernehmen",
|
||||||
"ButtonApplyChapters": "Kapitel anwenden",
|
"ButtonApplyChapters": "Kapitel anwenden",
|
||||||
"ButtonAuthors": "Autoren",
|
"ButtonAuthors": "Autoren",
|
||||||
"ButtonBrowseForFolder": "Ordnersuche",
|
"ButtonBrowseForFolder": "Ordnersuche",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"ButtonMapChapterTitles": "Kapitelüberschriften zuordnen",
|
"ButtonMapChapterTitles": "Kapitelüberschriften zuordnen",
|
||||||
"ButtonMatchAllAuthors": "Online Metadaten-Abgleich (alle Autoren)",
|
"ButtonMatchAllAuthors": "Online Metadaten-Abgleich (alle Autoren)",
|
||||||
"ButtonMatchBooks": "Online Metadaten-Abgleich (alle Medien)",
|
"ButtonMatchBooks": "Online Metadaten-Abgleich (alle Medien)",
|
||||||
"ButtonNevermind": "Vergiss es",
|
"ButtonNevermind": "Abbrechen",
|
||||||
"ButtonOk": "Ok",
|
"ButtonOk": "Ok",
|
||||||
"ButtonOpenFeed": "Feed öffnen",
|
"ButtonOpenFeed": "Feed öffnen",
|
||||||
"ButtonOpenManager": "Manager öffnen",
|
"ButtonOpenManager": "Manager öffnen",
|
||||||
@@ -98,11 +98,12 @@
|
|||||||
"HeaderCurrentDownloads": "Aktuelle Downloads",
|
"HeaderCurrentDownloads": "Aktuelle Downloads",
|
||||||
"HeaderDetails": "Details",
|
"HeaderDetails": "Details",
|
||||||
"HeaderDownloadQueue": "Download Warteschlange",
|
"HeaderDownloadQueue": "Download Warteschlange",
|
||||||
"HeaderEbookFiles": "Ebook Files",
|
"HeaderEbookFiles": "E-Book Dateien",
|
||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Email Einstellungen",
|
||||||
"HeaderEpisodes": "Episoden",
|
"HeaderEpisodes": "Episoden",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Ereader Geräte",
|
||||||
|
"HeaderEreaderSettings": "Ereader Einstellungen",
|
||||||
"HeaderFiles": "Dateien",
|
"HeaderFiles": "Dateien",
|
||||||
"HeaderFindChapters": "Kapitel suchen",
|
"HeaderFindChapters": "Kapitel suchen",
|
||||||
"HeaderIgnoredFiles": "Ignorierte Dateien",
|
"HeaderIgnoredFiles": "Ignorierte Dateien",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Lösche {0} Episoden",
|
"HeaderRemoveEpisodes": "Lösche {0} Episoden",
|
||||||
"HeaderRSSFeedGeneral": "RSS Details",
|
"HeaderRSSFeedGeneral": "RSS Details",
|
||||||
"HeaderRSSFeedIsOpen": "RSS-Feed ist geöffnet",
|
"HeaderRSSFeedIsOpen": "RSS-Feed ist geöffnet",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Gespeicherte Hörfortschritte",
|
"HeaderSavedMediaProgress": "Gespeicherte Hörfortschritte",
|
||||||
"HeaderSchedule": "Zeitplan",
|
"HeaderSchedule": "Zeitplan",
|
||||||
"HeaderScheduleLibraryScans": "Automatische Bibliotheksscans",
|
"HeaderScheduleLibraryScans": "Automatische Bibliotheksscans",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Neueste Ereignisse",
|
"HeaderStatsRecentSessions": "Neueste Ereignisse",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Autoren",
|
"HeaderStatsTop10Authors": "Top 10 Autoren",
|
||||||
"HeaderStatsTop5Genres": "Top 5 Kategorien",
|
"HeaderStatsTop5Genres": "Top 5 Kategorien",
|
||||||
|
"HeaderTableOfContents": "Inhaltsverzeichnis",
|
||||||
"HeaderTools": "Werkzeuge",
|
"HeaderTools": "Werkzeuge",
|
||||||
"HeaderUpdateAccount": "Konto aktualisieren",
|
"HeaderUpdateAccount": "Konto aktualisieren",
|
||||||
"HeaderUpdateAuthor": "Autor aktualisieren",
|
"HeaderUpdateAuthor": "Autor aktualisieren",
|
||||||
@@ -193,17 +196,18 @@
|
|||||||
"LabelBooks": "Bücher",
|
"LabelBooks": "Bücher",
|
||||||
"LabelChangePassword": "Passwort ändern",
|
"LabelChangePassword": "Passwort ändern",
|
||||||
"LabelChannels": "Kanäle",
|
"LabelChannels": "Kanäle",
|
||||||
"LabelChapters": "Chapters",
|
"LabelChapters": "Kapitel",
|
||||||
"LabelChaptersFound": "gefundene Kapitel",
|
"LabelChaptersFound": "gefundene Kapitel",
|
||||||
"LabelChapterTitle": "Kapitelüberschrift",
|
"LabelChapterTitle": "Kapitelüberschrift",
|
||||||
"LabelClosePlayer": "Player schließen",
|
"LabelClosePlayer": "Player schließen",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Serien zusammenfassen",
|
"LabelCollapseSeries": "Serien zusammenfassen",
|
||||||
|
"LabelCollection": "Sammlung",
|
||||||
"LabelCollections": "Sammlungen",
|
"LabelCollections": "Sammlungen",
|
||||||
"LabelComplete": "Vollständig",
|
"LabelComplete": "Vollständig",
|
||||||
"LabelConfirmPassword": "Passwort bestätigen",
|
"LabelConfirmPassword": "Passwort bestätigen",
|
||||||
"LabelContinueListening": "Weiterhören",
|
"LabelContinueListening": "Weiterhören",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Lesen fortsetzen",
|
||||||
"LabelContinueSeries": "Serien fortsetzen",
|
"LabelContinueSeries": "Serien fortsetzen",
|
||||||
"LabelCover": "Titelbild",
|
"LabelCover": "Titelbild",
|
||||||
"LabelCoverImageURL": "URL des Titelbildes",
|
"LabelCoverImageURL": "URL des Titelbildes",
|
||||||
@@ -220,16 +224,19 @@
|
|||||||
"LabelDirectory": "Verzeichnis",
|
"LabelDirectory": "Verzeichnis",
|
||||||
"LabelDiscFromFilename": "CD aus dem Dateinamen",
|
"LabelDiscFromFilename": "CD aus dem Dateinamen",
|
||||||
"LabelDiscFromMetadata": "CD aus den Metadaten",
|
"LabelDiscFromMetadata": "CD aus den Metadaten",
|
||||||
|
"LabelDiscover": "Finden",
|
||||||
"LabelDownload": "Herunterladen",
|
"LabelDownload": "Herunterladen",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Laufzeit",
|
"LabelDuration": "Laufzeit",
|
||||||
"LabelDurationFound": "Gefundene Laufzeit:",
|
"LabelDurationFound": "Gefundene Laufzeit:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "E-Book",
|
||||||
"LabelEbooks": "Ebooks",
|
"LabelEbooks": "E-Books",
|
||||||
"LabelEdit": "Bearbeiten",
|
"LabelEdit": "Bearbeiten",
|
||||||
"LabelEmail": "Email",
|
"LabelEmail": "Email",
|
||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "Von Address",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Sicherheit",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "Wenn \"true\", verwendet die Verbindung TLS, wenn sie eine Verbindung zum Server herstellt. Bei \"false\" wird TLS verwendet, wenn der Server die STARTTLS-Erweiterung unterstützt. In den meisten Fällen setzen Sie diesen Wert auf \"true\", wenn Sie eine Verbindung zu Port 465 herstellen. Für Port 587 oder 25 behalten Sie den Wert \"false\" bei. (von nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Test Addresse",
|
||||||
"LabelEmbeddedCover": "Eingebettetes Cover",
|
"LabelEmbeddedCover": "Eingebettetes Cover",
|
||||||
"LabelEnable": "Aktivieren",
|
"LabelEnable": "Aktivieren",
|
||||||
"LabelEnd": "Ende",
|
"LabelEnd": "Ende",
|
||||||
@@ -240,7 +247,7 @@
|
|||||||
"LabelExplicit": "Explizit (Altersbeschränkung)",
|
"LabelExplicit": "Explizit (Altersbeschränkung)",
|
||||||
"LabelFeedURL": "Feed URL",
|
"LabelFeedURL": "Feed URL",
|
||||||
"LabelFile": "Datei",
|
"LabelFile": "Datei",
|
||||||
"LabelFileBirthtime": "Datei Geburtsdatum",
|
"LabelFileBirthtime": "Datei erstellt",
|
||||||
"LabelFileModified": "Datei geändert",
|
"LabelFileModified": "Datei geändert",
|
||||||
"LabelFilename": "Dateiname",
|
"LabelFilename": "Dateiname",
|
||||||
"LabelFilterByUser": "Nach Benutzern filtern",
|
"LabelFilterByUser": "Nach Benutzern filtern",
|
||||||
@@ -248,12 +255,13 @@
|
|||||||
"LabelFinished": "beendet",
|
"LabelFinished": "beendet",
|
||||||
"LabelFolder": "Ordner",
|
"LabelFolder": "Ordner",
|
||||||
"LabelFolders": "Verzeichnisse",
|
"LabelFolders": "Verzeichnisse",
|
||||||
|
"LabelFontScale": "Schriftgröße",
|
||||||
"LabelFormat": "Format",
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Kategorie",
|
"LabelGenre": "Kategorie",
|
||||||
"LabelGenres": "Kategorien",
|
"LabelGenres": "Kategorien",
|
||||||
"LabelHardDeleteFile": "Datei dauerhaft löschen",
|
"LabelHardDeleteFile": "Datei dauerhaft löschen",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "mit E-Book",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "mit zusätlichem E-Book",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Stunde",
|
"LabelHour": "Stunde",
|
||||||
"LabelIcon": "Symbol",
|
"LabelIcon": "Symbol",
|
||||||
@@ -270,7 +278,7 @@
|
|||||||
"LabelIntervalEveryDay": "Jeden Tag",
|
"LabelIntervalEveryDay": "Jeden Tag",
|
||||||
"LabelIntervalEveryHour": "Jede Stunde",
|
"LabelIntervalEveryHour": "Jede Stunde",
|
||||||
"LabelInvalidParts": "Ungültige Teile",
|
"LabelInvalidParts": "Ungültige Teile",
|
||||||
"LabelInvert": "Invert",
|
"LabelInvert": "Umkehren",
|
||||||
"LabelItem": "Medium",
|
"LabelItem": "Medium",
|
||||||
"LabelLanguage": "Sprache",
|
"LabelLanguage": "Sprache",
|
||||||
"LabelLanguageDefaultServer": "Standard-Server-Sprache",
|
"LabelLanguageDefaultServer": "Standard-Server-Sprache",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "Zuletzt angesehen",
|
"LabelLastSeen": "Zuletzt angesehen",
|
||||||
"LabelLastTime": "Letztes Mal",
|
"LabelLastTime": "Letztes Mal",
|
||||||
"LabelLastUpdate": "Letzte Aktualisierung",
|
"LabelLastUpdate": "Letzte Aktualisierung",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Eine Seite",
|
||||||
|
"LabelLayoutSplitPage": "Geteilte Seite",
|
||||||
"LabelLess": "Weniger",
|
"LabelLess": "Weniger",
|
||||||
"LabelLibrariesAccessibleToUser": "Für Benutzer zugängliche Bibliotheken",
|
"LabelLibrariesAccessibleToUser": "Für Benutzer zugängliche Bibliotheken",
|
||||||
"LabelLibrary": "Bibliothek",
|
"LabelLibrary": "Bibliothek",
|
||||||
"LabelLibraryItem": "Bibliothekseintrag",
|
"LabelLibraryItem": "Bibliothekseintrag",
|
||||||
"LabelLibraryName": "Bibliotheksname",
|
"LabelLibraryName": "Bibliotheksname",
|
||||||
"LabelLimit": "Begrenzung",
|
"LabelLimit": "Begrenzung",
|
||||||
|
"LabelLineSpacing": "Zeilenabstand",
|
||||||
"LabelListenAgain": "Erneut anhören",
|
"LabelListenAgain": "Erneut anhören",
|
||||||
"LabelLogLevelDebug": "Fehlersuche",
|
"LabelLogLevelDebug": "Fehlersuche",
|
||||||
"LabelLogLevelInfo": "Informationen",
|
"LabelLogLevelInfo": "Informationen",
|
||||||
@@ -299,7 +311,7 @@
|
|||||||
"LabelMissing": "Fehlend",
|
"LabelMissing": "Fehlend",
|
||||||
"LabelMissingParts": "Fehlende Teile",
|
"LabelMissingParts": "Fehlende Teile",
|
||||||
"LabelMore": "Mehr",
|
"LabelMore": "Mehr",
|
||||||
"LabelMoreInfo": "More Info",
|
"LabelMoreInfo": "Mehr Info",
|
||||||
"LabelName": "Name",
|
"LabelName": "Name",
|
||||||
"LabelNarrator": "Erzähler",
|
"LabelNarrator": "Erzähler",
|
||||||
"LabelNarrators": "Erzähler",
|
"LabelNarrators": "Erzähler",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "Neues Passwort",
|
"LabelNewPassword": "Neues Passwort",
|
||||||
"LabelNextBackupDate": "Nächstes Sicherungsdatum",
|
"LabelNextBackupDate": "Nächstes Sicherungsdatum",
|
||||||
"LabelNextScheduledRun": "Nächster planmäßiger Durchlauf",
|
"LabelNextScheduledRun": "Nächster planmäßiger Durchlauf",
|
||||||
|
"LabelNoEpisodesSelected": "Keine Episoden ausgewählt",
|
||||||
"LabelNotes": "Hinweise",
|
"LabelNotes": "Hinweise",
|
||||||
"LabelNotFinished": "nicht beendet",
|
"LabelNotFinished": "nicht beendet",
|
||||||
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
||||||
@@ -343,15 +356,15 @@
|
|||||||
"LabelPort": "Port",
|
"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": "Primary ebook",
|
"LabelPrimaryEbook": "Haupt-E-Book",
|
||||||
"LabelProgress": "Fortschritt",
|
"LabelProgress": "Fortschritt",
|
||||||
"LabelProvider": "Anbieter",
|
"LabelProvider": "Anbieter",
|
||||||
"LabelPubDate": "Veröffentlichungsdatum",
|
"LabelPubDate": "Veröffentlichungsdatum",
|
||||||
"LabelPublisher": "Herausgeber",
|
"LabelPublisher": "Herausgeber",
|
||||||
"LabelPublishYear": "Jahr",
|
"LabelPublishYear": "Jahr",
|
||||||
"LabelRead": "Read",
|
"LabelRead": "Lesen",
|
||||||
"LabelReadAgain": "Read Again",
|
"LabelReadAgain": "Nocheinmal Lesen",
|
||||||
"LabelReadEbookWithoutProgress": "Read ebook without keeping progress",
|
"LabelReadEbookWithoutProgress": "E-Book lesen und Fortschritt verwerfen",
|
||||||
"LabelRecentlyAdded": "Kürzlich hinzugefügt",
|
"LabelRecentlyAdded": "Kürzlich hinzugefügt",
|
||||||
"LabelRecentSeries": "Aktuelle Serien",
|
"LabelRecentSeries": "Aktuelle Serien",
|
||||||
"LabelRecommended": "Empfohlen",
|
"LabelRecommended": "Empfohlen",
|
||||||
@@ -368,25 +381,32 @@
|
|||||||
"LabelSearchTitle": "Titel",
|
"LabelSearchTitle": "Titel",
|
||||||
"LabelSearchTitleOrASIN": "Titel oder ASIN",
|
"LabelSearchTitleOrASIN": "Titel oder ASIN",
|
||||||
"LabelSeason": "Staffel",
|
"LabelSeason": "Staffel",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSelectAllEpisodes": "Alle Episoden auswählen",
|
||||||
|
"LabelSelectEpisodesShowing": "{0} ausgewählte Episoden werden angezeigt",
|
||||||
|
"LabelSendEbookToDevice": "E-Book senden an...",
|
||||||
"LabelSequence": "Reihenfolge",
|
"LabelSequence": "Reihenfolge",
|
||||||
"LabelSeries": "Serien",
|
"LabelSeries": "Serien",
|
||||||
"LabelSeriesName": "Serienname",
|
"LabelSeriesName": "Serienname",
|
||||||
"LabelSeriesProgress": "Serienfortschritt",
|
"LabelSeriesProgress": "Serienfortschritt",
|
||||||
"LabelSetEbookAsPrimary": "Set as primary",
|
"LabelSetEbookAsPrimary": "Setzen als Hauptbuch",
|
||||||
"LabelSetEbookAsSupplementary": "Set as supplementary",
|
"LabelSetEbookAsSupplementary": "Setzen als Ergänzung",
|
||||||
"LabelSettingsAudiobooksOnly": "Audiobooks only",
|
"LabelSettingsAudiobooksOnly": "nur Hörbücher",
|
||||||
"LabelSettingsAudiobooksOnlyHelp": "Enabling this setting will ignore ebook files unless they are inside an audiobook folder in which case they will be set as supplementary ebooks",
|
"LabelSettingsAudiobooksOnlyHelp": "Wenn Sie diese Einstellung aktivieren, werden E-Book-Dateien ignoriert, es sei denn, sie befinden sich in einem Hörbuchordner. In diesem Fall werden sie als zusätzliche E-Books festgelegt",
|
||||||
"LabelSettingsBookshelfViewHelp": "Skeumorphes Design mit Holzeinlegeböden",
|
"LabelSettingsBookshelfViewHelp": "Skeumorphes Design mit Holzeinlegeböden",
|
||||||
"LabelSettingsChromecastSupport": "Chromecastunterstützung",
|
"LabelSettingsChromecastSupport": "Chromecastunterstützung",
|
||||||
"LabelSettingsDateFormat": "Datumsformat",
|
"LabelSettingsDateFormat": "Datumsformat",
|
||||||
"LabelSettingsDisableWatcher": "Überwachung deaktivieren",
|
"LabelSettingsDisableWatcher": "Überwachung deaktivieren",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Ordnerüberwachung für die Bibliothek deaktivieren",
|
"LabelSettingsDisableWatcherForLibrary": "Ordnerüberwachung für die Bibliothek deaktivieren",
|
||||||
"LabelSettingsDisableWatcherHelp": "Deaktiviert das automatische Hinzufügen/Aktualisieren von Elementen, wenn Dateiänderungen erkannt werden. *Erfordert einen Server-Neustart",
|
"LabelSettingsDisableWatcherHelp": "Deaktiviert das automatische Hinzufügen/Aktualisieren von Elementen, wenn Dateiänderungen erkannt werden. *Erfordert einen Server-Neustart",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Experimentelle Funktionen",
|
"LabelSettingsExperimentalFeatures": "Experimentelle Funktionen",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Funktionen welche sich in der Entwicklung befinden, benötigen Ihr Feedback und Ihre Hilfe beim Testen. Klicken Sie hier, um die Github-Diskussion zu öffnen.",
|
"LabelSettingsExperimentalFeaturesHelp": "Funktionen welche sich in der Entwicklung befinden, benötigen Ihr Feedback und Ihre Hilfe beim Testen. Klicken Sie hier, um die Github-Diskussion zu öffnen.",
|
||||||
"LabelSettingsFindCovers": "Suche Titelbilder",
|
"LabelSettingsFindCovers": "Suche Titelbilder",
|
||||||
"LabelSettingsFindCoversHelp": "Wenn Ihr Medium kein eingebettetes Titelbild oder kein Titelbild im Ordner hat, versucht der Scanner, ein Titelbild online zu finden.<br>Hinweis: Dies verlängert die Scandauer",
|
"LabelSettingsFindCoversHelp": "Wenn Ihr Medium kein eingebettetes Titelbild oder kein Titelbild im Ordner hat, versucht der Scanner, ein Titelbild online zu finden.<br>Hinweis: Dies verlängert die Scandauer",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Ausblenden einzelzne Bücher",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Serien, die ein einzelnes Buch enthalten, werden in den Regalen der Serienseite und der Startseite ausgeblendet.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Startseite verwendet die Bücherregalansicht",
|
"LabelSettingsHomePageBookshelfView": "Startseite verwendet die Bücherregalansicht",
|
||||||
"LabelSettingsLibraryBookshelfView": "Bibliothek verwendet die Bücherregalansicht",
|
"LabelSettingsLibraryBookshelfView": "Bibliothek verwendet die Bücherregalansicht",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Verwende Overdrive Media Marker für Kapitel",
|
"LabelSettingsOverdriveMediaMarkers": "Verwende Overdrive Media Marker für Kapitel",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Alles anzeigen",
|
"LabelShowAll": "Alles anzeigen",
|
||||||
"LabelSize": "Größe",
|
"LabelSize": "Größe",
|
||||||
"LabelSleepTimer": "Einschlaf-Timer",
|
"LabelSleepTimer": "Einschlaf-Timer",
|
||||||
|
"LabelSlug": "URL Teil",
|
||||||
"LabelStart": "Start",
|
"LabelStart": "Start",
|
||||||
"LabelStarted": "Gestartet",
|
"LabelStarted": "Gestartet",
|
||||||
"LabelStartedAt": "Gestartet am",
|
"LabelStartedAt": "Gestartet am",
|
||||||
@@ -439,6 +460,9 @@
|
|||||||
"LabelTagsAccessibleToUser": "Für Benutzer zugängliche Schlagwörter",
|
"LabelTagsAccessibleToUser": "Für Benutzer zugängliche Schlagwörter",
|
||||||
"LabelTagsNotAccessibleToUser": "Für Benutzer nicht zugängliche Schlagwörter",
|
"LabelTagsNotAccessibleToUser": "Für Benutzer nicht zugängliche Schlagwörter",
|
||||||
"LabelTasks": "Laufende Aufgaben",
|
"LabelTasks": "Laufende Aufgaben",
|
||||||
|
"LabelTheme": "Theme",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeBase": "Basiszeit",
|
"LabelTimeBase": "Basiszeit",
|
||||||
"LabelTimeListened": "Gehörte Zeit",
|
"LabelTimeListened": "Gehörte Zeit",
|
||||||
"LabelTimeListenedToday": "Heute gehörte Zeit",
|
"LabelTimeListenedToday": "Heute gehörte Zeit",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Titel aus Metadaten",
|
"LabelTrackFromMetadata": "Titel aus Metadaten",
|
||||||
"LabelTracks": "Dateien",
|
"LabelTracks": "Dateien",
|
||||||
"LabelTracksMultiTrack": "Mehrfachdatei",
|
"LabelTracksMultiTrack": "Mehrfachdatei",
|
||||||
|
"LabelTracksNone": "Keine Dateien",
|
||||||
"LabelTracksSingleTrack": "Einzeldatei",
|
"LabelTracksSingleTrack": "Einzeldatei",
|
||||||
"LabelType": "Typ",
|
"LabelType": "Typ",
|
||||||
"LabelUnabridged": "Ungekürzt",
|
"LabelUnabridged": "Ungekürzt",
|
||||||
@@ -477,7 +502,7 @@
|
|||||||
"LabelViewBookmarks": "Lesezeichen anzeigen",
|
"LabelViewBookmarks": "Lesezeichen anzeigen",
|
||||||
"LabelViewChapters": "Kapitel anzeigen",
|
"LabelViewChapters": "Kapitel anzeigen",
|
||||||
"LabelViewQueue": "Spieler-Warteschlange anzeigen",
|
"LabelViewQueue": "Spieler-Warteschlange anzeigen",
|
||||||
"LabelVolume": "Volume",
|
"LabelVolume": "Volumen",
|
||||||
"LabelWeekdaysToRun": "Wochentage für die Ausführung",
|
"LabelWeekdaysToRun": "Wochentage für die Ausführung",
|
||||||
"LabelYourAudiobookDuration": "Laufzeit Ihres Mediums",
|
"LabelYourAudiobookDuration": "Laufzeit Ihres Mediums",
|
||||||
"LabelYourBookmarks": "Lesezeichen",
|
"LabelYourBookmarks": "Lesezeichen",
|
||||||
@@ -497,11 +522,14 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "Ungültige Kapitelstartzeit: Kapitelanfang < Kapitelanfang vorheriges Kapitel (Kapitelanfang liegt zeitlich vor dem Beginn des vorherigen Kapitels -> Lösung: Kapitelanfang >= Startzeit des vorherigen Kapitels)",
|
"MessageChapterErrorStartLtPrev": "Ungültige Kapitelstartzeit: Kapitelanfang < Kapitelanfang vorheriges Kapitel (Kapitelanfang liegt zeitlich vor dem Beginn des vorherigen Kapitels -> Lösung: Kapitelanfang >= Startzeit des vorherigen Kapitels)",
|
||||||
"MessageChapterStartIsAfter": "Ungültige Kapitelstartzeit: Kapitelanfang > Mediumende (Kapitelanfang liegt nach dem Ende des Mediums)",
|
"MessageChapterStartIsAfter": "Ungültige Kapitelstartzeit: Kapitelanfang > Mediumende (Kapitelanfang liegt nach dem Ende des Mediums)",
|
||||||
"MessageCheckingCron": "Überprüfe Cron...",
|
"MessageCheckingCron": "Überprüfe Cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Sind Sie sicher, dass Sie diesen Feed schließen wollen?",
|
||||||
"MessageConfirmDeleteBackup": "Sind Sie sicher, dass Sie die Sicherung für {0} löschen wollen?",
|
"MessageConfirmDeleteBackup": "Sind Sie sicher, dass Sie die Sicherung für {0} löschen wollen?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "Es wird die Datei vom System löschen. Sind Sie sicher?",
|
||||||
"MessageConfirmDeleteLibrary": "Sind Sie sicher, dass Sie die Bibliothek \"{0}\" dauerhaft löschen wollen?",
|
"MessageConfirmDeleteLibrary": "Sind Sie sicher, dass Sie die Bibliothek \"{0}\" dauerhaft löschen wollen?",
|
||||||
"MessageConfirmDeleteSession": "Sind Sie sicher, dass Sie diese Sitzung löschen möchten?",
|
"MessageConfirmDeleteSession": "Sind Sie sicher, dass Sie diese Sitzung löschen möchten?",
|
||||||
"MessageConfirmForceReScan": "Sind Sie sicher, dass Sie einen erneuten Scanvorgang erzwingen wollen?",
|
"MessageConfirmForceReScan": "Sind Sie sicher, dass Sie einen erneuten Scanvorgang erzwingen wollen?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Sind Sie sicher, dass Sie alle Episoden als abgeschlossen markieren möchten?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Sind Sie sicher, dass Sie alle Episoden als nicht abgeschlossen markieren möchten?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als abgeschlossen markieren wollen?",
|
"MessageConfirmMarkSeriesFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als abgeschlossen markieren wollen?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als nicht abgeschlossen markieren wollen?",
|
"MessageConfirmMarkSeriesNotFinished": "Sind Sie sicher, dass Sie alle Medien dieser Reihe als nicht abgeschlossen markieren wollen?",
|
||||||
"MessageConfirmRemoveAllChapters": "Sind Sie sicher, dass Sie alle Kapitel entfernen möchten?",
|
"MessageConfirmRemoveAllChapters": "Sind Sie sicher, dass Sie alle Kapitel entfernen möchten?",
|
||||||
@@ -516,7 +544,7 @@
|
|||||||
"MessageConfirmRenameTag": "Sind Sie sicher, dass Sie den Tag \"{0}\" in \"{1}\" für alle Hörbücher/Podcasts umbenennen wollen?",
|
"MessageConfirmRenameTag": "Sind Sie sicher, dass Sie den Tag \"{0}\" in \"{1}\" für alle Hörbücher/Podcasts umbenennen wollen?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Hinweis: Tag existiert bereits -> Tags werden zusammengelegt.",
|
"MessageConfirmRenameTagMergeNote": "Hinweis: Tag existiert bereits -> Tags werden zusammengelegt.",
|
||||||
"MessageConfirmRenameTagWarning": "Warnung! Ein ähnlicher Tag mit einem anderen Wortlaut existiert bereits: \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Warnung! Ein ähnlicher Tag mit einem anderen Wortlaut existiert bereits: \"{0}\".",
|
||||||
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "Sind Sie sicher, dass sie {0} ebook \"{1}\" auf das Gerät \"{2}\" senden wollen?",
|
||||||
"MessageDownloadingEpisode": "Episode herunterladen",
|
"MessageDownloadingEpisode": "Episode herunterladen",
|
||||||
"MessageDragFilesIntoTrackOrder": "Verschieben Sie die Dateien in die richtige Reihenfolge",
|
"MessageDragFilesIntoTrackOrder": "Verschieben Sie die Dateien in die richtige Reihenfolge",
|
||||||
"MessageEmbedFinished": "Einbettung abgeschlossen!",
|
"MessageEmbedFinished": "Einbettung abgeschlossen!",
|
||||||
@@ -535,9 +563,11 @@
|
|||||||
"MessageM4BFailed": "M4B fehlgeschlagen!",
|
"MessageM4BFailed": "M4B fehlgeschlagen!",
|
||||||
"MessageM4BFinished": "M4B beendet!",
|
"MessageM4BFinished": "M4B beendet!",
|
||||||
"MessageMapChapterTitles": "Zuordnen von Kapiteltiteln zu Ihren vorhandenen Medienkapiteln ohne Anpassung der Zeitangaben",
|
"MessageMapChapterTitles": "Zuordnen von Kapiteltiteln zu Ihren vorhandenen Medienkapiteln ohne Anpassung der Zeitangaben",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Alle Episoden als beendet markieren",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Alle Episoden als ungehört markieren",
|
||||||
"MessageMarkAsFinished": "Als beendet markieren",
|
"MessageMarkAsFinished": "Als beendet markieren",
|
||||||
"MessageMarkAsNotFinished": "Als nicht abgeschlossen markieren",
|
"MessageMarkAsNotFinished": "Als nicht abgeschlossen markieren",
|
||||||
"MessageMatchBooksDescription": "versucht, Bücher in der Bibliothek mit einem Buch des ausgewählten Suchanbieters abzugleichen und leere Details und das Titelbild auszufüllen. Details werden nicht überschrieben.",
|
"MessageMatchBooksDescription": "Es wird versucht die Bücher in der Bibliothek mit einem Buch des ausgewählten Suchanbieters abzugleichen um leere Details und das Titelbild auszufüllen. Vorhandene Details werden nicht überschrieben.",
|
||||||
"MessageNoAudioTracks": "Keine Audiodateien",
|
"MessageNoAudioTracks": "Keine Audiodateien",
|
||||||
"MessageNoAuthors": "Keine Autoren",
|
"MessageNoAuthors": "Keine Autoren",
|
||||||
"MessageNoBackups": "Keine Sicherungen",
|
"MessageNoBackups": "Keine Sicherungen",
|
||||||
@@ -573,9 +603,8 @@
|
|||||||
"MessagePauseChapter": "Kapitelwiedergabe pausieren",
|
"MessagePauseChapter": "Kapitelwiedergabe pausieren",
|
||||||
"MessagePlayChapter": "Kapitelanfang anhören",
|
"MessagePlayChapter": "Kapitelanfang anhören",
|
||||||
"MessagePlaylistCreateFromCollection": "Erstelle eine Wiedergabeliste aus der Sammlung",
|
"MessagePlaylistCreateFromCollection": "Erstelle eine Wiedergabeliste aus der Sammlung",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast hat keine RSS-Feed-Url welche für den Online-Abgleich verwendet werden kann",
|
"MessagePodcastHasNoRSSFeedForMatching": "Der Podcast hat keine RSS-Feed-Url welche für den Online-Abgleich verwendet werden kann",
|
||||||
"MessageQuickMatchDescription": "Füllt leere Details und Titelbilder mit dem ersten Treffer aus '{0}'. Überschreibt keine Details, es sei denn, die Server-Einstellung \"Passende Metadaten bevorzugen\" ist aktiviert.",
|
"MessageQuickMatchDescription": "Füllt leere Details und Titelbilder mit dem ersten Treffer aus '{0}'. Überschreibt keine Details, es sei denn, die Server-Einstellung \"Passende Metadaten bevorzugen\" ist aktiviert.",
|
||||||
"MessageRemoveAllItemsWarning": "WARNUNG! Bei dieser Aktion werden alle Bibliotheksobjekte aus der Datenbank entfernt, einschließlich aller Aktualisierungen oder Online-Abgleichs, die Sie vorgenommen haben. Ihre eigentlichen Dateien bleiben davon unberührt. Sind Sie sicher?",
|
|
||||||
"MessageRemoveChapter": "Kapitel löschen",
|
"MessageRemoveChapter": "Kapitel löschen",
|
||||||
"MessageRemoveEpisodes": "Entferne {0} Episode(n)",
|
"MessageRemoveEpisodes": "Entferne {0} Episode(n)",
|
||||||
"MessageRemoveFromPlayerQueue": "Aus der Abspielwarteliste löschen",
|
"MessageRemoveFromPlayerQueue": "Aus der Abspielwarteliste löschen",
|
||||||
@@ -671,8 +700,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Medium aus der Sammlung gelöscht",
|
"ToastRemoveItemFromCollectionSuccess": "Medium aus der Sammlung gelöscht",
|
||||||
"ToastRSSFeedCloseFailed": "RSS-Feed konnte nicht geschlossen werden",
|
"ToastRSSFeedCloseFailed": "RSS-Feed konnte nicht geschlossen werden",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS-Feed geschlossen",
|
"ToastRSSFeedCloseSuccess": "RSS-Feed geschlossen",
|
||||||
"ToastSendEbookToDeviceFailed": "Failed to send ebook to device",
|
"ToastSendEbookToDeviceFailed": "E-Book konnte nicht auf Gerät übertragen werden",
|
||||||
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
|
"ToastSendEbookToDeviceSuccess": "E-Book an Gerät senden \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
|
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
|
||||||
"ToastSeriesUpdateSuccess": "Serien aktualisiert",
|
"ToastSeriesUpdateSuccess": "Serien aktualisiert",
|
||||||
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
|
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
|
||||||
|
|||||||
@@ -102,7 +102,8 @@
|
|||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodes",
|
"HeaderEpisodes": "Episodes",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Ereader Devices",
|
||||||
|
"HeaderEreaderSettings": "Ereader Settings",
|
||||||
"HeaderFiles": "Files",
|
"HeaderFiles": "Files",
|
||||||
"HeaderFindChapters": "Find Chapters",
|
"HeaderFindChapters": "Find Chapters",
|
||||||
"HeaderIgnoredFiles": "Ignored Files",
|
"HeaderIgnoredFiles": "Ignored Files",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Remove {0} Episodes",
|
"HeaderRemoveEpisodes": "Remove {0} Episodes",
|
||||||
"HeaderRSSFeedGeneral": "RSS Details",
|
"HeaderRSSFeedGeneral": "RSS Details",
|
||||||
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
|
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Saved Media Progress",
|
"HeaderSavedMediaProgress": "Saved Media Progress",
|
||||||
"HeaderSchedule": "Schedule",
|
"HeaderSchedule": "Schedule",
|
||||||
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Recent Sessions",
|
"HeaderStatsRecentSessions": "Recent Sessions",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Authors",
|
"HeaderStatsTop10Authors": "Top 10 Authors",
|
||||||
"HeaderStatsTop5Genres": "Top 5 Genres",
|
"HeaderStatsTop5Genres": "Top 5 Genres",
|
||||||
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderTools": "Tools",
|
"HeaderTools": "Tools",
|
||||||
"HeaderUpdateAccount": "Update Account",
|
"HeaderUpdateAccount": "Update Account",
|
||||||
"HeaderUpdateAuthor": "Update Author",
|
"HeaderUpdateAuthor": "Update Author",
|
||||||
@@ -199,6 +202,7 @@
|
|||||||
"LabelClosePlayer": "Close player",
|
"LabelClosePlayer": "Close player",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Collapse Series",
|
"LabelCollapseSeries": "Collapse Series",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Collections",
|
"LabelCollections": "Collections",
|
||||||
"LabelComplete": "Complete",
|
"LabelComplete": "Complete",
|
||||||
"LabelConfirmPassword": "Confirm Password",
|
"LabelConfirmPassword": "Confirm Password",
|
||||||
@@ -220,7 +224,9 @@
|
|||||||
"LabelDirectory": "Directory",
|
"LabelDirectory": "Directory",
|
||||||
"LabelDiscFromFilename": "Disc from Filename",
|
"LabelDiscFromFilename": "Disc from Filename",
|
||||||
"LabelDiscFromMetadata": "Disc from Metadata",
|
"LabelDiscFromMetadata": "Disc from Metadata",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Download",
|
"LabelDownload": "Download",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Duration",
|
"LabelDuration": "Duration",
|
||||||
"LabelDurationFound": "Duration found:",
|
"LabelDurationFound": "Duration found:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Ebook",
|
||||||
@@ -230,6 +236,7 @@
|
|||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Test Address",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Enable",
|
"LabelEnable": "Enable",
|
||||||
"LabelEnd": "End",
|
"LabelEnd": "End",
|
||||||
@@ -248,6 +255,7 @@
|
|||||||
"LabelFinished": "Finished",
|
"LabelFinished": "Finished",
|
||||||
"LabelFolder": "Folder",
|
"LabelFolder": "Folder",
|
||||||
"LabelFolders": "Folders",
|
"LabelFolders": "Folders",
|
||||||
|
"LabelFontScale": "Font scale",
|
||||||
"LabelFormat": "Format",
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "Last Seen",
|
"LabelLastSeen": "Last Seen",
|
||||||
"LabelLastTime": "Last Time",
|
"LabelLastTime": "Last Time",
|
||||||
"LabelLastUpdate": "Last Update",
|
"LabelLastUpdate": "Last Update",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Single page",
|
||||||
|
"LabelLayoutSplitPage": "Split page",
|
||||||
"LabelLess": "Less",
|
"LabelLess": "Less",
|
||||||
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
|
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
|
||||||
"LabelLibrary": "Library",
|
"LabelLibrary": "Library",
|
||||||
"LabelLibraryItem": "Library Item",
|
"LabelLibraryItem": "Library Item",
|
||||||
"LabelLibraryName": "Library Name",
|
"LabelLibraryName": "Library Name",
|
||||||
"LabelLimit": "Limit",
|
"LabelLimit": "Limit",
|
||||||
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Listen Again",
|
"LabelListenAgain": "Listen Again",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "New Password",
|
"LabelNewPassword": "New Password",
|
||||||
"LabelNextBackupDate": "Next backup date",
|
"LabelNextBackupDate": "Next backup date",
|
||||||
"LabelNextScheduledRun": "Next scheduled run",
|
"LabelNextScheduledRun": "Next scheduled run",
|
||||||
|
"LabelNoEpisodesSelected": "No episodes selected",
|
||||||
"LabelNotes": "Notes",
|
"LabelNotes": "Notes",
|
||||||
"LabelNotFinished": "Not Finished",
|
"LabelNotFinished": "Not Finished",
|
||||||
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
||||||
@@ -368,6 +381,8 @@
|
|||||||
"LabelSearchTitle": "Search Title",
|
"LabelSearchTitle": "Search Title",
|
||||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||||
"LabelSeason": "Season",
|
"LabelSeason": "Season",
|
||||||
|
"LabelSelectAllEpisodes": "Select all episodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSendEbookToDevice": "Send Ebook to...",
|
||||||
"LabelSequence": "Sequence",
|
"LabelSequence": "Sequence",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
@@ -383,10 +398,15 @@
|
|||||||
"LabelSettingsDisableWatcher": "Disable Watcher",
|
"LabelSettingsDisableWatcher": "Disable Watcher",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Disable folder watcher for library",
|
"LabelSettingsDisableWatcherForLibrary": "Disable folder watcher for library",
|
||||||
"LabelSettingsDisableWatcherHelp": "Disables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
"LabelSettingsDisableWatcherHelp": "Disables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Experimental features",
|
"LabelSettingsExperimentalFeatures": "Experimental features",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
||||||
"LabelSettingsFindCovers": "Find covers",
|
"LabelSettingsFindCovers": "Find covers",
|
||||||
"LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.<br>Note: This will extend scan time",
|
"LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.<br>Note: This will extend scan time",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Hide single book series",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
|
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
|
||||||
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
|
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
|
"LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Show All",
|
"LabelShowAll": "Show All",
|
||||||
"LabelSize": "Size",
|
"LabelSize": "Size",
|
||||||
"LabelSleepTimer": "Sleep timer",
|
"LabelSleepTimer": "Sleep timer",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Start",
|
"LabelStart": "Start",
|
||||||
"LabelStarted": "Started",
|
"LabelStarted": "Started",
|
||||||
"LabelStartedAt": "Started At",
|
"LabelStartedAt": "Started At",
|
||||||
@@ -439,6 +460,9 @@
|
|||||||
"LabelTagsAccessibleToUser": "Tags Accessible to User",
|
"LabelTagsAccessibleToUser": "Tags Accessible to User",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
||||||
"LabelTasks": "Tasks Running",
|
"LabelTasks": "Tasks Running",
|
||||||
|
"LabelTheme": "Theme",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeBase": "Time Base",
|
"LabelTimeBase": "Time Base",
|
||||||
"LabelTimeListened": "Time Listened",
|
"LabelTimeListened": "Time Listened",
|
||||||
"LabelTimeListenedToday": "Time Listened Today",
|
"LabelTimeListenedToday": "Time Listened Today",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Track from Metadata",
|
"LabelTrackFromMetadata": "Track from Metadata",
|
||||||
"LabelTracks": "Tracks",
|
"LabelTracks": "Tracks",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnabridged": "Unabridged",
|
"LabelUnabridged": "Unabridged",
|
||||||
@@ -497,11 +522,14 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
||||||
"MessageCheckingCron": "Checking cron...",
|
"MessageCheckingCron": "Checking cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
||||||
"MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
|
||||||
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
|
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
|
||||||
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
|
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "M4B Failed!",
|
"MessageM4BFailed": "M4B Failed!",
|
||||||
"MessageM4BFinished": "M4B Finished!",
|
"MessageM4BFinished": "M4B Finished!",
|
||||||
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Mark all episodes finished",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished",
|
||||||
"MessageMarkAsFinished": "Mark as Finished",
|
"MessageMarkAsFinished": "Mark as Finished",
|
||||||
"MessageMarkAsNotFinished": "Mark as Not Finished",
|
"MessageMarkAsNotFinished": "Mark as Not Finished",
|
||||||
"MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
|
"MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
|
||||||
"MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
|
"MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
|
||||||
"MessageRemoveAllItemsWarning": "WARNING! This action will remove all library items from the database including any updates or matches you have made. This does not do anything to your actual files. Are you sure?",
|
|
||||||
"MessageRemoveChapter": "Remove chapter",
|
"MessageRemoveChapter": "Remove chapter",
|
||||||
"MessageRemoveEpisodes": "Remove {0} episode(s)",
|
"MessageRemoveEpisodes": "Remove {0} episode(s)",
|
||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
|
|||||||
+31
-2
@@ -102,7 +102,8 @@
|
|||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodios",
|
"HeaderEpisodes": "Episodios",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Ereader Devices",
|
||||||
|
"HeaderEreaderSettings": "Ereader Settings",
|
||||||
"HeaderFiles": "Elemento",
|
"HeaderFiles": "Elemento",
|
||||||
"HeaderFindChapters": "Buscar Capitulo",
|
"HeaderFindChapters": "Buscar Capitulo",
|
||||||
"HeaderIgnoredFiles": "Ignorar Elemento",
|
"HeaderIgnoredFiles": "Ignorar Elemento",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Remover {0} Episodios",
|
"HeaderRemoveEpisodes": "Remover {0} Episodios",
|
||||||
"HeaderRSSFeedGeneral": "Detalles RSS",
|
"HeaderRSSFeedGeneral": "Detalles RSS",
|
||||||
"HeaderRSSFeedIsOpen": "Fuente RSS esta abierta",
|
"HeaderRSSFeedIsOpen": "Fuente RSS esta abierta",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Guardar Progreso de multimedia",
|
"HeaderSavedMediaProgress": "Guardar Progreso de multimedia",
|
||||||
"HeaderSchedule": "Horario",
|
"HeaderSchedule": "Horario",
|
||||||
"HeaderScheduleLibraryScans": "Programar Escaneo Automático de Biblioteca",
|
"HeaderScheduleLibraryScans": "Programar Escaneo Automático de Biblioteca",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Sesiones Recientes",
|
"HeaderStatsRecentSessions": "Sesiones Recientes",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Autores",
|
"HeaderStatsTop10Authors": "Top 10 Autores",
|
||||||
"HeaderStatsTop5Genres": "Top 5 Géneros",
|
"HeaderStatsTop5Genres": "Top 5 Géneros",
|
||||||
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderTools": "Herramientas",
|
"HeaderTools": "Herramientas",
|
||||||
"HeaderUpdateAccount": "Actualizar Cuenta",
|
"HeaderUpdateAccount": "Actualizar Cuenta",
|
||||||
"HeaderUpdateAuthor": "Actualizar Autor",
|
"HeaderUpdateAuthor": "Actualizar Autor",
|
||||||
@@ -199,6 +202,7 @@
|
|||||||
"LabelClosePlayer": "Close player",
|
"LabelClosePlayer": "Close player",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Colapsar Series",
|
"LabelCollapseSeries": "Colapsar Series",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Colecciones",
|
"LabelCollections": "Colecciones",
|
||||||
"LabelComplete": "Completo",
|
"LabelComplete": "Completo",
|
||||||
"LabelConfirmPassword": "Confirmar Contraseña",
|
"LabelConfirmPassword": "Confirmar Contraseña",
|
||||||
@@ -220,7 +224,9 @@
|
|||||||
"LabelDirectory": "Directorio",
|
"LabelDirectory": "Directorio",
|
||||||
"LabelDiscFromFilename": "Disco a partir del Nombre del Archivo",
|
"LabelDiscFromFilename": "Disco a partir del Nombre del Archivo",
|
||||||
"LabelDiscFromMetadata": "Disco a partir de Metadata",
|
"LabelDiscFromMetadata": "Disco a partir de Metadata",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Descargar",
|
"LabelDownload": "Descargar",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Duración",
|
"LabelDuration": "Duración",
|
||||||
"LabelDurationFound": "Duración Comprobada:",
|
"LabelDurationFound": "Duración Comprobada:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Ebook",
|
||||||
@@ -230,6 +236,7 @@
|
|||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Test Address",
|
||||||
"LabelEmbeddedCover": "Portada Integrada",
|
"LabelEmbeddedCover": "Portada Integrada",
|
||||||
"LabelEnable": "Habilitar",
|
"LabelEnable": "Habilitar",
|
||||||
"LabelEnd": "Fin",
|
"LabelEnd": "Fin",
|
||||||
@@ -248,6 +255,7 @@
|
|||||||
"LabelFinished": "Terminado",
|
"LabelFinished": "Terminado",
|
||||||
"LabelFolder": "Carpeta",
|
"LabelFolder": "Carpeta",
|
||||||
"LabelFolders": "Carpetas",
|
"LabelFolders": "Carpetas",
|
||||||
|
"LabelFontScale": "Font scale",
|
||||||
"LabelFormat": "Formato",
|
"LabelFormat": "Formato",
|
||||||
"LabelGenre": "Genero",
|
"LabelGenre": "Genero",
|
||||||
"LabelGenres": "Géneros",
|
"LabelGenres": "Géneros",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "Ultima Vez Visto",
|
"LabelLastSeen": "Ultima Vez Visto",
|
||||||
"LabelLastTime": "Ultima Vez",
|
"LabelLastTime": "Ultima Vez",
|
||||||
"LabelLastUpdate": "Ultima Actualización",
|
"LabelLastUpdate": "Ultima Actualización",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Single page",
|
||||||
|
"LabelLayoutSplitPage": "Split page",
|
||||||
"LabelLess": "Menos",
|
"LabelLess": "Menos",
|
||||||
"LabelLibrariesAccessibleToUser": "Bibliotecas Disponibles para el Usuario",
|
"LabelLibrariesAccessibleToUser": "Bibliotecas Disponibles para el Usuario",
|
||||||
"LabelLibrary": "Biblioteca",
|
"LabelLibrary": "Biblioteca",
|
||||||
"LabelLibraryItem": "Elemento de Biblioteca",
|
"LabelLibraryItem": "Elemento de Biblioteca",
|
||||||
"LabelLibraryName": "Nombre de Biblioteca",
|
"LabelLibraryName": "Nombre de Biblioteca",
|
||||||
"LabelLimit": "Limites",
|
"LabelLimit": "Limites",
|
||||||
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Escuchar Otra Vez",
|
"LabelListenAgain": "Escuchar Otra Vez",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "Nueva Contraseña",
|
"LabelNewPassword": "Nueva Contraseña",
|
||||||
"LabelNextBackupDate": "Fecha del Siguiente Respaldo",
|
"LabelNextBackupDate": "Fecha del Siguiente Respaldo",
|
||||||
"LabelNextScheduledRun": "Próxima Ejecución Programada",
|
"LabelNextScheduledRun": "Próxima Ejecución Programada",
|
||||||
|
"LabelNoEpisodesSelected": "No episodes selected",
|
||||||
"LabelNotes": "Notas",
|
"LabelNotes": "Notas",
|
||||||
"LabelNotFinished": "No Terminado",
|
"LabelNotFinished": "No Terminado",
|
||||||
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
||||||
@@ -368,6 +381,8 @@
|
|||||||
"LabelSearchTitle": "Buscar Titulo",
|
"LabelSearchTitle": "Buscar Titulo",
|
||||||
"LabelSearchTitleOrASIN": "Buscar Titulo o ASIN",
|
"LabelSearchTitleOrASIN": "Buscar Titulo o ASIN",
|
||||||
"LabelSeason": "Temporada",
|
"LabelSeason": "Temporada",
|
||||||
|
"LabelSelectAllEpisodes": "Select all episodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSendEbookToDevice": "Send Ebook to...",
|
||||||
"LabelSequence": "Secuencia",
|
"LabelSequence": "Secuencia",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
@@ -383,10 +398,15 @@
|
|||||||
"LabelSettingsDisableWatcher": "Deshabilitar Watcher",
|
"LabelSettingsDisableWatcher": "Deshabilitar Watcher",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Deshabilitar Watcher de Carpetas para esta biblioteca",
|
"LabelSettingsDisableWatcherForLibrary": "Deshabilitar Watcher de Carpetas para esta biblioteca",
|
||||||
"LabelSettingsDisableWatcherHelp": "Deshabilitar la función automática de agregar/actualizar los elementos, cuando se detecta cambio en los archivos. *Require Reiniciar el Servidor",
|
"LabelSettingsDisableWatcherHelp": "Deshabilitar la función automática de agregar/actualizar los elementos, cuando se detecta cambio en los archivos. *Require Reiniciar el Servidor",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Funciones Experimentales",
|
"LabelSettingsExperimentalFeatures": "Funciones Experimentales",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Funciones en desarrollo sobre las que esperamos sus comentarios y experiencia. Haga click aquí para abrir una conversación en Github.",
|
"LabelSettingsExperimentalFeaturesHelp": "Funciones en desarrollo sobre las que esperamos sus comentarios y experiencia. Haga click aquí para abrir una conversación en Github.",
|
||||||
"LabelSettingsFindCovers": "Buscar Portadas",
|
"LabelSettingsFindCovers": "Buscar Portadas",
|
||||||
"LabelSettingsFindCoversHelp": "Si tu audiolibro no tiene una portada incluida o la portada no esta dentro de la carpeta, el escaneador tratara de encontrar una portada.<br>Nota: Esto extenderá el tiempo de escaneo",
|
"LabelSettingsFindCoversHelp": "Si tu audiolibro no tiene una portada incluida o la portada no esta dentro de la carpeta, el escaneador tratara de encontrar una portada.<br>Nota: Esto extenderá el tiempo de escaneo",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Hide single book series",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
|
||||||
"LabelSettingsHomePageBookshelfView": "La pagina de inicio usa la vista de librero",
|
"LabelSettingsHomePageBookshelfView": "La pagina de inicio usa la vista de librero",
|
||||||
"LabelSettingsLibraryBookshelfView": "La biblioteca usa la vista de librero",
|
"LabelSettingsLibraryBookshelfView": "La biblioteca usa la vista de librero",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Usar Markers de multimedia en Overdrive para estos capítulos",
|
"LabelSettingsOverdriveMediaMarkers": "Usar Markers de multimedia en Overdrive para estos capítulos",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Mostrar Todos",
|
"LabelShowAll": "Mostrar Todos",
|
||||||
"LabelSize": "Tamaño",
|
"LabelSize": "Tamaño",
|
||||||
"LabelSleepTimer": "Temporizador para Dormir",
|
"LabelSleepTimer": "Temporizador para Dormir",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Iniciar",
|
"LabelStart": "Iniciar",
|
||||||
"LabelStarted": "Indiciado",
|
"LabelStarted": "Indiciado",
|
||||||
"LabelStartedAt": "Iniciado En",
|
"LabelStartedAt": "Iniciado En",
|
||||||
@@ -439,6 +460,9 @@
|
|||||||
"LabelTagsAccessibleToUser": "Etiquetas Accessible para el Usuario",
|
"LabelTagsAccessibleToUser": "Etiquetas Accessible para el Usuario",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
||||||
"LabelTasks": "Tareas Corriendo",
|
"LabelTasks": "Tareas Corriendo",
|
||||||
|
"LabelTheme": "Theme",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeBase": "Time Base",
|
"LabelTimeBase": "Time Base",
|
||||||
"LabelTimeListened": "Tiempo Escuchando",
|
"LabelTimeListened": "Tiempo Escuchando",
|
||||||
"LabelTimeListenedToday": "Tiempo Escuchando Hoy",
|
"LabelTimeListenedToday": "Tiempo Escuchando Hoy",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Pista desde Metadata",
|
"LabelTrackFromMetadata": "Pista desde Metadata",
|
||||||
"LabelTracks": "Pistas",
|
"LabelTracks": "Pistas",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Tipo",
|
"LabelType": "Tipo",
|
||||||
"LabelUnabridged": "Unabridged",
|
"LabelUnabridged": "Unabridged",
|
||||||
@@ -497,11 +522,14 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "El tiempo de inicio no es válida debe ser mayor o igual que la hora de inicio del capítulo anterior",
|
"MessageChapterErrorStartLtPrev": "El tiempo de inicio no es válida debe ser mayor o igual que la hora 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": "Checking cron...",
|
"MessageCheckingCron": "Checking cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Esta seguro que desea eliminar el respaldo {0}?",
|
"MessageConfirmDeleteBackup": "Esta seguro que desea eliminar el respaldo {0}?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
||||||
"MessageConfirmDeleteLibrary": "Esta seguro que desea eliminar permanentemente la biblioteca \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Esta seguro que desea eliminar permanentemente la biblioteca \"{0}\"?",
|
||||||
"MessageConfirmDeleteSession": "Esta seguro que desea eliminar esta session?",
|
"MessageConfirmDeleteSession": "Esta seguro que desea eliminar esta session?",
|
||||||
"MessageConfirmForceReScan": "Esta seguro que desea forzar re-escanear?",
|
"MessageConfirmForceReScan": "Esta seguro que desea forzar re-escanear?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Esta seguro que desea marcar todos los libros en esta serie como terminados?",
|
"MessageConfirmMarkSeriesFinished": "Esta seguro que desea marcar todos los libros en esta serie como terminados?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Esta seguro que desea marcar todos los libros en esta serie como no terminados?",
|
"MessageConfirmMarkSeriesNotFinished": "Esta seguro que desea marcar todos los libros en esta serie como no terminados?",
|
||||||
"MessageConfirmRemoveAllChapters": "Esta seguro que desea remover todos los capitulos?",
|
"MessageConfirmRemoveAllChapters": "Esta seguro que desea remover todos los capitulos?",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "M4B Fallo!",
|
"MessageM4BFailed": "M4B Fallo!",
|
||||||
"MessageM4BFinished": "M4B Terminado!",
|
"MessageM4BFinished": "M4B Terminado!",
|
||||||
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Mark all episodes finished",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished",
|
||||||
"MessageMarkAsFinished": "Marcar como Terminado",
|
"MessageMarkAsFinished": "Marcar como Terminado",
|
||||||
"MessageMarkAsNotFinished": "Marcar como No Terminado",
|
"MessageMarkAsNotFinished": "Marcar como No Terminado",
|
||||||
"MessageMatchBooksDescription": "intentará hacer coincidir los libros de la biblioteca con un libro del proveedor de búsqueda seleccionado y rellenará los detalles vacíos y la portada. No sobrescribe los detalles.",
|
"MessageMatchBooksDescription": "intentará hacer coincidir los libros de la biblioteca con un libro del proveedor de búsqueda seleccionado y rellenará los detalles vacíos y la portada. No sobrescribe los detalles.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Crear lista de reproducción a partir de colección",
|
"MessagePlaylistCreateFromCollection": "Crear lista de reproducción a partir de colección",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "El podcast no tiene una URL de fuente RSS que pueda usar que coincida",
|
"MessagePodcastHasNoRSSFeedForMatching": "El podcast no tiene una URL de fuente RSS que pueda usar que coincida",
|
||||||
"MessageQuickMatchDescription": "Rellenar detalles de elementos vacíos y portada con los primeros resultados de '{0}'. No sobrescribe los detalles a menos que la configuración 'Prefer matched metadata' del servidor este habilita.",
|
"MessageQuickMatchDescription": "Rellenar detalles de elementos vacíos y portada con los primeros resultados de '{0}'. No sobrescribe los detalles a menos que la configuración 'Prefer matched metadata' del servidor este habilita.",
|
||||||
"MessageRemoveAllItemsWarning": "ADVERTENCIA! Esta acción eliminará todos los elementos de la biblioteca de la base de datos incluyendo cualquier actualización o match. Esto no hace nada a sus archivos reales. Esta seguro que desea continuar?",
|
|
||||||
"MessageRemoveChapter": "Remover capítulos",
|
"MessageRemoveChapter": "Remover capítulos",
|
||||||
"MessageRemoveEpisodes": "Remover {0} episodio(s)",
|
"MessageRemoveEpisodes": "Remover {0} episodio(s)",
|
||||||
"MessageRemoveFromPlayerQueue": "Romover la cola de reporduccion",
|
"MessageRemoveFromPlayerQueue": "Romover la cola de reporduccion",
|
||||||
|
|||||||
+59
-30
@@ -97,12 +97,13 @@
|
|||||||
"HeaderCover": "Couverture",
|
"HeaderCover": "Couverture",
|
||||||
"HeaderCurrentDownloads": "Téléchargements en cours",
|
"HeaderCurrentDownloads": "Téléchargements en cours",
|
||||||
"HeaderDetails": "Détails",
|
"HeaderDetails": "Détails",
|
||||||
"HeaderDownloadQueue": "File d'attente de téléchargements",
|
"HeaderDownloadQueue": "File d’attente de téléchargements",
|
||||||
"HeaderEbookFiles": "Ebook Files",
|
"HeaderEbookFiles": "Fichier des livres numériques",
|
||||||
"HeaderEmail": "E-mails",
|
"HeaderEmail": "Courriels",
|
||||||
"HeaderEmailSettings": "Configuration des e-mails",
|
"HeaderEmailSettings": "Configuration des courriels",
|
||||||
"HeaderEpisodes": "Épisodes",
|
"HeaderEpisodes": "Épisodes",
|
||||||
"HeaderEReaderDevices": "Lecteurs d'e-books",
|
"HeaderEreaderDevices": "Lecteur de livres numériques",
|
||||||
|
"HeaderEreaderSettings": "Options Ereader",
|
||||||
"HeaderFiles": "Fichiers",
|
"HeaderFiles": "Fichiers",
|
||||||
"HeaderFindChapters": "Trouver les chapitres",
|
"HeaderFindChapters": "Trouver les chapitres",
|
||||||
"HeaderIgnoredFiles": "Fichiers Ignorés",
|
"HeaderIgnoredFiles": "Fichiers Ignorés",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Suppression de {0} épisodes",
|
"HeaderRemoveEpisodes": "Suppression de {0} épisodes",
|
||||||
"HeaderRSSFeedGeneral": "Détails de flux RSS",
|
"HeaderRSSFeedGeneral": "Détails de flux RSS",
|
||||||
"HeaderRSSFeedIsOpen": "Le Flux RSS est actif",
|
"HeaderRSSFeedIsOpen": "Le Flux RSS est actif",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"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",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Sessions récentes",
|
"HeaderStatsRecentSessions": "Sessions récentes",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Auteurs",
|
"HeaderStatsTop10Authors": "Top 10 Auteurs",
|
||||||
"HeaderStatsTop5Genres": "Top 5 Genres",
|
"HeaderStatsTop5Genres": "Top 5 Genres",
|
||||||
|
"HeaderTableOfContents": "Table des matières",
|
||||||
"HeaderTools": "Outils",
|
"HeaderTools": "Outils",
|
||||||
"HeaderUpdateAccount": "Mettre à jour le compte",
|
"HeaderUpdateAccount": "Mettre à jour le compte",
|
||||||
"HeaderUpdateAuthor": "Mettre à jour l’auteur",
|
"HeaderUpdateAuthor": "Mettre à jour l’auteur",
|
||||||
@@ -199,11 +202,12 @@
|
|||||||
"LabelClosePlayer": "Fermer le lecteur",
|
"LabelClosePlayer": "Fermer le lecteur",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Réduire les séries",
|
"LabelCollapseSeries": "Réduire les séries",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Collections",
|
"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",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Continuer la lecture",
|
||||||
"LabelContinueSeries": "Continuer la série",
|
"LabelContinueSeries": "Continuer la série",
|
||||||
"LabelCover": "Couverture",
|
"LabelCover": "Couverture",
|
||||||
"LabelCoverImageURL": "URL vers l’image de couverture",
|
"LabelCoverImageURL": "URL vers l’image de couverture",
|
||||||
@@ -220,16 +224,19 @@
|
|||||||
"LabelDirectory": "Répertoire",
|
"LabelDirectory": "Répertoire",
|
||||||
"LabelDiscFromFilename": "Disque depuis le fichier",
|
"LabelDiscFromFilename": "Disque depuis le fichier",
|
||||||
"LabelDiscFromMetadata": "Disque depuis les métadonnées",
|
"LabelDiscFromMetadata": "Disque depuis les métadonnées",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Téléchargement",
|
"LabelDownload": "Téléchargement",
|
||||||
|
"LabelDownloadNEpisodes": "Télécharger {0} épisode(s)",
|
||||||
"LabelDuration": "Durée",
|
"LabelDuration": "Durée",
|
||||||
"LabelDurationFound": "Durée trouvée :",
|
"LabelDurationFound": "Durée trouvée :",
|
||||||
"LabelEbook": "E-book",
|
"LabelEbook": "Livre numérique",
|
||||||
"LabelEbooks": "Ebooks",
|
"LabelEbooks": "Livres numériques",
|
||||||
"LabelEdit": "Modifier",
|
"LabelEdit": "Modifier",
|
||||||
"LabelEmail": "E-mail",
|
"LabelEmail": "Courriel",
|
||||||
"LabelEmailSettingsFromAddress": "Expéditeur",
|
"LabelEmailSettingsFromAddress": "Expéditeur",
|
||||||
"LabelEmailSettingsSecure": "Sécurisé",
|
"LabelEmailSettingsSecure": "Sécurisé",
|
||||||
"LabelEmailSettingsSecureHelp": "Si coché, la connexion utilisera TLS lors de la connexion au serveur. Sinon TLS est utilisé si le serveur prend en charge l'extension STARTTLS. Dans la plupart des cas, cochez si vous vous connectez au port 465. Décochez pour le port 587 ou 25. (source: nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "Si coché, la connexion utilisera TLS lors de la connexion au serveur. Sinon TLS est utilisé si le serveur prend en charge l’extension STARTTLS. Dans la plupart des cas, cochez si vous vous connectez au port 465. Décochez pour le port 587 ou 25. (source: nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Test Address",
|
||||||
"LabelEmbeddedCover": "Couverture du livre intégrée",
|
"LabelEmbeddedCover": "Couverture du livre intégrée",
|
||||||
"LabelEnable": "Activer",
|
"LabelEnable": "Activer",
|
||||||
"LabelEnd": "Fin",
|
"LabelEnd": "Fin",
|
||||||
@@ -248,12 +255,13 @@
|
|||||||
"LabelFinished": "Fini(e)",
|
"LabelFinished": "Fini(e)",
|
||||||
"LabelFolder": "Dossier",
|
"LabelFolder": "Dossier",
|
||||||
"LabelFolders": "Dossiers",
|
"LabelFolders": "Dossiers",
|
||||||
|
"LabelFontScale": "Taille de la police de caractère",
|
||||||
"LabelFormat": "Format",
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
"LabelHardDeleteFile": "Suppression du fichier",
|
"LabelHardDeleteFile": "Suppression du fichier",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Dispose d’un livre numérique",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Dispose d’un livre numérique supplémentaire",
|
||||||
"LabelHost": "Hôte",
|
"LabelHost": "Hôte",
|
||||||
"LabelHour": "Heure",
|
"LabelHour": "Heure",
|
||||||
"LabelIcon": "Icone",
|
"LabelIcon": "Icone",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "Vu dernièrement",
|
"LabelLastSeen": "Vu dernièrement",
|
||||||
"LabelLastTime": "Progression",
|
"LabelLastTime": "Progression",
|
||||||
"LabelLastUpdate": "Dernière mise à jour",
|
"LabelLastUpdate": "Dernière mise à jour",
|
||||||
|
"LabelLayout": "Disposition",
|
||||||
|
"LabelLayoutSinglePage": "Vue unique",
|
||||||
|
"LabelLayoutSplitPage": "Vue partagée",
|
||||||
"LabelLess": "Moins",
|
"LabelLess": "Moins",
|
||||||
"LabelLibrariesAccessibleToUser": "Bibliothèque accessible à l’utilisateur",
|
"LabelLibrariesAccessibleToUser": "Bibliothèque accessible à l’utilisateur",
|
||||||
"LabelLibrary": "Bibliothèque",
|
"LabelLibrary": "Bibliothèque",
|
||||||
"LabelLibraryItem": "Article de bibliothèque",
|
"LabelLibraryItem": "Article de bibliothèque",
|
||||||
"LabelLibraryName": "Nom de la bibliothèque",
|
"LabelLibraryName": "Nom de la bibliothèque",
|
||||||
"LabelLimit": "Limite",
|
"LabelLimit": "Limite",
|
||||||
|
"LabelLineSpacing": "Interligne",
|
||||||
"LabelListenAgain": "Écouter à nouveau",
|
"LabelListenAgain": "Écouter à nouveau",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "Nouveau mot de passe",
|
"LabelNewPassword": "Nouveau mot de passe",
|
||||||
"LabelNextBackupDate": "Date de la prochaine sauvegarde",
|
"LabelNextBackupDate": "Date de la prochaine sauvegarde",
|
||||||
"LabelNextScheduledRun": "Prochain lancement prévu",
|
"LabelNextScheduledRun": "Prochain lancement prévu",
|
||||||
|
"LabelNoEpisodesSelected": "Aucun épisode sélectionné",
|
||||||
"LabelNotes": "Notes",
|
"LabelNotes": "Notes",
|
||||||
"LabelNotFinished": "Non terminé(e)",
|
"LabelNotFinished": "Non terminé(e)",
|
||||||
"LabelNotificationAppriseURL": "URL(s) d’Apprise",
|
"LabelNotificationAppriseURL": "URL(s) d’Apprise",
|
||||||
@@ -343,22 +356,22 @@
|
|||||||
"LabelPort": "Port",
|
"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": "Primary ebook",
|
"LabelPrimaryEbook": "Premier livre numérique",
|
||||||
"LabelProgress": "Progression",
|
"LabelProgress": "Progression",
|
||||||
"LabelProvider": "Fournisseur",
|
"LabelProvider": "Fournisseur",
|
||||||
"LabelPubDate": "Date de publication",
|
"LabelPubDate": "Date de publication",
|
||||||
"LabelPublisher": "Éditeur",
|
"LabelPublisher": "Éditeur",
|
||||||
"LabelPublishYear": "Année d’édition",
|
"LabelPublishYear": "Année d’édition",
|
||||||
"LabelRead": "Read",
|
"LabelRead": "Lire",
|
||||||
"LabelReadAgain": "Read Again",
|
"LabelReadAgain": "Lire à nouveau",
|
||||||
"LabelReadEbookWithoutProgress": "Read ebook without keeping progress",
|
"LabelReadEbookWithoutProgress": "Lire le livre numérique sans sauvegarder la progression",
|
||||||
"LabelRecentlyAdded": "Derniers ajouts",
|
"LabelRecentlyAdded": "Derniers ajouts",
|
||||||
"LabelRecentSeries": "Séries récentes",
|
"LabelRecentSeries": "Séries récentes",
|
||||||
"LabelRecommended": "Recommandé",
|
"LabelRecommended": "Recommandé",
|
||||||
"LabelRegion": "Région",
|
"LabelRegion": "Région",
|
||||||
"LabelReleaseDate": "Date de parution",
|
"LabelReleaseDate": "Date de parution",
|
||||||
"LabelRemoveCover": "Supprimer la couverture",
|
"LabelRemoveCover": "Supprimer la couverture",
|
||||||
"LabelRSSFeedCustomOwnerEmail": "E-mail propriétaire personnalisé",
|
"LabelRSSFeedCustomOwnerEmail": "Courriel du propriétaire personnalisé",
|
||||||
"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",
|
||||||
@@ -368,25 +381,32 @@
|
|||||||
"LabelSearchTitle": "Titre de recherche",
|
"LabelSearchTitle": "Titre de recherche",
|
||||||
"LabelSearchTitleOrASIN": "Recherche du titre ou ASIN",
|
"LabelSearchTitleOrASIN": "Recherche du titre ou ASIN",
|
||||||
"LabelSeason": "Saison",
|
"LabelSeason": "Saison",
|
||||||
"LabelSendEbookToDevice": "Envoyer l'e-book à...",
|
"LabelSelectAllEpisodes": "Sélectionner tous les épisodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Sélectionner {0} episode(s) en cours",
|
||||||
|
"LabelSendEbookToDevice": "Envoyer le livre numérique à...",
|
||||||
"LabelSequence": "Séquence",
|
"LabelSequence": "Séquence",
|
||||||
"LabelSeries": "Séries",
|
"LabelSeries": "Séries",
|
||||||
"LabelSeriesName": "Nom de la série",
|
"LabelSeriesName": "Nom de la série",
|
||||||
"LabelSeriesProgress": "Progression de séries",
|
"LabelSeriesProgress": "Progression de séries",
|
||||||
"LabelSetEbookAsPrimary": "Set as primary",
|
"LabelSetEbookAsPrimary": "Définir comme principale",
|
||||||
"LabelSetEbookAsSupplementary": "Set as supplementary",
|
"LabelSetEbookAsSupplementary": "Définir comme supplémentaire",
|
||||||
"LabelSettingsAudiobooksOnly": "Audiobooks only",
|
"LabelSettingsAudiobooksOnly": "Livres audios seulement",
|
||||||
"LabelSettingsAudiobooksOnlyHelp": "Enabling this setting will ignore ebook files unless they are inside an audiobook folder in which case they will be set as supplementary ebooks",
|
"LabelSettingsAudiobooksOnlyHelp": "L’activation de ce paramètre ignorera les fichiers “ ebook ”, à moins qu’ils ne se trouvent dans un dossier de livres audio, auquel cas ils seront définis comme des livres numériques supplémentaires.",
|
||||||
"LabelSettingsBookshelfViewHelp": "Interface skeuomorphique avec une étagère en bois",
|
"LabelSettingsBookshelfViewHelp": "Interface skeuomorphique avec une étagère en bois",
|
||||||
"LabelSettingsChromecastSupport": "Support du Chromecast",
|
"LabelSettingsChromecastSupport": "Support du Chromecast",
|
||||||
"LabelSettingsDateFormat": "Format de date",
|
"LabelSettingsDateFormat": "Format de date",
|
||||||
"LabelSettingsDisableWatcher": "Désactiver la surveillance",
|
"LabelSettingsDisableWatcher": "Désactiver la surveillance",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Désactiver la surveillance des dossiers pour la bibliothèque",
|
"LabelSettingsDisableWatcherForLibrary": "Désactiver la surveillance des dossiers pour la bibliothèque",
|
||||||
"LabelSettingsDisableWatcherHelp": "Désactive la mise à jour automatique lorsque les fichiers changent. *Nécessite un redémarrage*",
|
"LabelSettingsDisableWatcherHelp": "Désactive la mise à jour automatique lorsque les fichiers changent. *Nécessite un redémarrage*",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Fonctionnalités expérimentales",
|
"LabelSettingsExperimentalFeatures": "Fonctionnalités expérimentales",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Fonctionnalités en cours de développement sur lesquelles nous attendons votre retour et expérience. Cliquez pour ouvrir la discussion Github.",
|
"LabelSettingsExperimentalFeaturesHelp": "Fonctionnalités en cours de développement sur lesquelles nous attendons votre retour et expérience. Cliquez pour ouvrir la discussion Github.",
|
||||||
"LabelSettingsFindCovers": "Chercher des couvertures de livre",
|
"LabelSettingsFindCovers": "Chercher des couvertures de livre",
|
||||||
"LabelSettingsFindCoversHelp": "Si votre livre audio ne possède pas de couverture intégrée ou une image de couverture dans le dossier, l’analyseur tentera de récupérer une couverture.<br>Attention, cela peut augmenter le temps d’analyse.",
|
"LabelSettingsFindCoversHelp": "Si votre livre audio ne possède pas de couverture intégrée ou une image de couverture dans le dossier, l’analyseur tentera de récupérer une couverture.<br>Attention, cela peut augmenter le temps d’analyse.",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Masquer les séries de livres uniques",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Les séries qui ne comportent qu’un seul livre seront masquées sur la page de la série et sur les étagères de la page d’accueil.",
|
||||||
"LabelSettingsHomePageBookshelfView": "La page d’accueil utilise la vue étagère",
|
"LabelSettingsHomePageBookshelfView": "La page d’accueil utilise la vue étagère",
|
||||||
"LabelSettingsLibraryBookshelfView": "La bibliothèque utilise la vue étagère",
|
"LabelSettingsLibraryBookshelfView": "La bibliothèque utilise la vue étagère",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Utiliser Overdrive Media Marker pour les chapitres",
|
"LabelSettingsOverdriveMediaMarkers": "Utiliser Overdrive Media Marker pour les chapitres",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Afficher Tout",
|
"LabelShowAll": "Afficher Tout",
|
||||||
"LabelSize": "Taille",
|
"LabelSize": "Taille",
|
||||||
"LabelSleepTimer": "Minuterie",
|
"LabelSleepTimer": "Minuterie",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Démarrer",
|
"LabelStart": "Démarrer",
|
||||||
"LabelStarted": "Démarré",
|
"LabelStarted": "Démarré",
|
||||||
"LabelStartedAt": "Démarré à",
|
"LabelStartedAt": "Démarré à",
|
||||||
@@ -437,8 +458,11 @@
|
|||||||
"LabelTag": "Étiquette",
|
"LabelTag": "Étiquette",
|
||||||
"LabelTags": "Étiquettes",
|
"LabelTags": "Étiquettes",
|
||||||
"LabelTagsAccessibleToUser": "Étiquettes accessibles à l’utilisateur",
|
"LabelTagsAccessibleToUser": "Étiquettes accessibles à l’utilisateur",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
"LabelTagsNotAccessibleToUser": "Étiquettes non accessibles à l’utilisateur",
|
||||||
"LabelTasks": "Tâches en cours",
|
"LabelTasks": "Tâches en cours",
|
||||||
|
"LabelTheme": "Thème",
|
||||||
|
"LabelThemeDark": "Sombre",
|
||||||
|
"LabelThemeLight": "Clair",
|
||||||
"LabelTimeBase": "Base de temps",
|
"LabelTimeBase": "Base de temps",
|
||||||
"LabelTimeListened": "Temps d’écoute",
|
"LabelTimeListened": "Temps d’écoute",
|
||||||
"LabelTimeListenedToday": "Nombres d’écoutes Aujourd’hui",
|
"LabelTimeListenedToday": "Nombres d’écoutes Aujourd’hui",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Piste depuis les métadonnées",
|
"LabelTrackFromMetadata": "Piste depuis les métadonnées",
|
||||||
"LabelTracks": "Pistes",
|
"LabelTracks": "Pistes",
|
||||||
"LabelTracksMultiTrack": "Piste multiple",
|
"LabelTracksMultiTrack": "Piste multiple",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Piste simple",
|
"LabelTracksSingleTrack": "Piste simple",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnabridged": "Version intégrale",
|
"LabelUnabridged": "Version intégrale",
|
||||||
@@ -495,13 +520,16 @@
|
|||||||
"MessageChapterErrorFirstNotZero": "Le premier capitre doit débuter à 0",
|
"MessageChapterErrorFirstNotZero": "Le premier capitre doit débuter à 0",
|
||||||
"MessageChapterErrorStartGteDuration": "Horodatage invalide car il doit débuter avant la fin du livre",
|
"MessageChapterErrorStartGteDuration": "Horodatage invalide car il doit débuter avant la fin du livre",
|
||||||
"MessageChapterErrorStartLtPrev": "Horodatage invalide car il doit débuter au moins après le précédent chapitre",
|
"MessageChapterErrorStartLtPrev": "Horodatage invalide car il doit débuter au moins après le précédent chapitre",
|
||||||
"MessageChapterStartIsAfter": "Le Chapitre Début est situé au début de votre Livre Audio",
|
"MessageChapterStartIsAfter": "Le premier chapitre est situé au début de votre livre audio",
|
||||||
"MessageCheckingCron": "Vérification du cron…",
|
"MessageCheckingCron": "Vérification du cron…",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Êtes-vous sûr de vouloir supprimer la Sauvegarde de {0} ?",
|
"MessageConfirmDeleteBackup": "Êtes-vous sûr de vouloir supprimer la Sauvegarde de {0} ?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "Cela Le fichier sera supprimer de votre système. Êtes-vous sûr ?",
|
||||||
"MessageConfirmDeleteLibrary": "Êtes-vous sûr de vouloir supprimer définitivement la bibliothèque « {0} » ?",
|
"MessageConfirmDeleteLibrary": "Êtes-vous sûr de vouloir supprimer définitivement la bibliothèque « {0} » ?",
|
||||||
"MessageConfirmDeleteSession": "Êtes-vous sûr de vouloir supprimer cette session ?",
|
"MessageConfirmDeleteSession": "Êtes-vous sûr de vouloir supprimer cette session ?",
|
||||||
"MessageConfirmForceReScan": "Êtes-vous sûr de vouloir lancer une Analyse Forcée ?",
|
"MessageConfirmForceReScan": "Êtes-vous sûr de vouloir lancer une Analyse Forcée ?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Êtes-vous sûr de marquer tous les épisodes comme terminés ?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer comme terminé tous les livres de cette série ?",
|
"MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer comme terminé tous les livres de cette série ?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer comme non terminé tous les livres de cette série ?",
|
"MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer comme non terminé tous les livres de cette série ?",
|
||||||
"MessageConfirmRemoveAllChapters": "Êtes-vous sûr de vouloir supprimer tous les chapitres ?",
|
"MessageConfirmRemoveAllChapters": "Êtes-vous sûr de vouloir supprimer tous les chapitres ?",
|
||||||
@@ -516,7 +544,7 @@
|
|||||||
"MessageConfirmRenameTag": "Êtes-vous sûr de vouloir renommer l’étiquette « {0} » en « {1} » pour tous les articles ?",
|
"MessageConfirmRenameTag": "Êtes-vous sûr de vouloir renommer l’étiquette « {0} » en « {1} » pour tous les articles ?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Information: Cette étiquette existe déjà et sera fusionnée.",
|
"MessageConfirmRenameTagMergeNote": "Information: Cette étiquette existe déjà et sera fusionnée.",
|
||||||
"MessageConfirmRenameTagWarning": "Attention ! Une étiquette similaire avec une casse différente existe déjà « {0} ».",
|
"MessageConfirmRenameTagWarning": "Attention ! Une étiquette similaire avec une casse différente existe déjà « {0} ».",
|
||||||
"MessageConfirmSendEbookToDevice": "Êtes-vous sûr de vouloir envoyer l'ebook {0} \"{1}\" à l'appareil \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "Êtes-vous sûr de vouloir envoyer le livre numérique {0} \"{1}\" à l’appareil \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Téléchargement de l’épisode",
|
"MessageDownloadingEpisode": "Téléchargement de l’épisode",
|
||||||
"MessageDragFilesIntoTrackOrder": "Faire glisser les fichiers dans l’ordre correct",
|
"MessageDragFilesIntoTrackOrder": "Faire glisser les fichiers dans l’ordre correct",
|
||||||
"MessageEmbedFinished": "Intégration Terminée !",
|
"MessageEmbedFinished": "Intégration Terminée !",
|
||||||
@@ -535,8 +563,10 @@
|
|||||||
"MessageM4BFailed": "M4B en échec !",
|
"MessageM4BFailed": "M4B en échec !",
|
||||||
"MessageM4BFinished": "M4B terminé !",
|
"MessageM4BFinished": "M4B terminé !",
|
||||||
"MessageMapChapterTitles": "Faire correspondre les titres des chapitres aux chapitres existants de votre livre audio sans ajuster l’horodatage.",
|
"MessageMapChapterTitles": "Faire correspondre les titres des chapitres aux chapitres existants de votre livre audio sans ajuster l’horodatage.",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Marquer tous les épisodes terminés",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Marquer tous les épisodes non terminés",
|
||||||
"MessageMarkAsFinished": "Marquer comme terminé",
|
"MessageMarkAsFinished": "Marquer comme terminé",
|
||||||
"MessageMarkAsNotFinished": "Marquer comme non Terminé",
|
"MessageMarkAsNotFinished": "Marquer comme non terminé",
|
||||||
"MessageMatchBooksDescription": "tentera de faire correspondre les livres de la bibliothèque avec les livres du fournisseur sélectionné pour combler les détails et couverture manquants. N’écrase pas les données existantes.",
|
"MessageMatchBooksDescription": "tentera de faire correspondre les livres de la bibliothèque avec les livres du fournisseur sélectionné pour combler les détails et couverture manquants. N’écrase pas les données existantes.",
|
||||||
"MessageNoAudioTracks": "Aucune piste audio",
|
"MessageNoAudioTracks": "Aucune piste audio",
|
||||||
"MessageNoAuthors": "Aucun auteur",
|
"MessageNoAuthors": "Aucun auteur",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Créer une liste de lecture depuis la collection",
|
"MessagePlaylistCreateFromCollection": "Créer une liste de lecture depuis la collection",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Le Podcast n’a pas d’URL de flux RSS à utiliser pour la correspondance",
|
"MessagePodcastHasNoRSSFeedForMatching": "Le Podcast n’a pas d’URL de flux RSS à utiliser pour la correspondance",
|
||||||
"MessageQuickMatchDescription": "Renseigne les détails manquants ainsi que la couverture avec la première correspondance de « {0} ». N’écrase pas les données présentes à moins que le paramètre « Préférer les Métadonnées par correspondance » soit activé.",
|
"MessageQuickMatchDescription": "Renseigne les détails manquants ainsi que la couverture avec la première correspondance de « {0} ». N’écrase pas les données présentes à moins que le paramètre « Préférer les Métadonnées par correspondance » soit activé.",
|
||||||
"MessageRemoveAllItemsWarning": "ATTENTION ! Cette action supprimera toute la base de données de la bibliothèque ainsi que les mises à jour ou correspondances qui auraient été effectuées. Cela n’a aucune incidence sur les fichiers de la bibliothèque. Souhaitez-vous continuer ?",
|
|
||||||
"MessageRemoveChapter": "Supprimer le chapitre",
|
"MessageRemoveChapter": "Supprimer le chapitre",
|
||||||
"MessageRemoveEpisodes": "Suppression de {0} épisode(s)",
|
"MessageRemoveEpisodes": "Suppression de {0} épisode(s)",
|
||||||
"MessageRemoveFromPlayerQueue": "Supprimer de la liste d’écoute",
|
"MessageRemoveFromPlayerQueue": "Supprimer de la liste d’écoute",
|
||||||
@@ -671,8 +700,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Article supprimé de la collection",
|
"ToastRemoveItemFromCollectionSuccess": "Article supprimé de la collection",
|
||||||
"ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS",
|
"ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS",
|
||||||
"ToastRSSFeedCloseSuccess": "Flux RSS fermé",
|
"ToastRSSFeedCloseSuccess": "Flux RSS fermé",
|
||||||
"ToastSendEbookToDeviceFailed": "Échec de l'envoi de l'e-book à l'appareil",
|
"ToastSendEbookToDeviceFailed": "Échec de l’envoi du livre numérique à l’appareil",
|
||||||
"ToastSendEbookToDeviceSuccess": "E-book 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",
|
||||||
"ToastSessionDeleteFailed": "Échec de la suppression de session",
|
"ToastSessionDeleteFailed": "Échec de la suppression de session",
|
||||||
|
|||||||
+31
-2
@@ -102,7 +102,8 @@
|
|||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodes",
|
"HeaderEpisodes": "Episodes",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Ereader Devices",
|
||||||
|
"HeaderEreaderSettings": "Ereader Settings",
|
||||||
"HeaderFiles": "Files",
|
"HeaderFiles": "Files",
|
||||||
"HeaderFindChapters": "Find Chapters",
|
"HeaderFindChapters": "Find Chapters",
|
||||||
"HeaderIgnoredFiles": "Ignored Files",
|
"HeaderIgnoredFiles": "Ignored Files",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Remove {0} Episodes",
|
"HeaderRemoveEpisodes": "Remove {0} Episodes",
|
||||||
"HeaderRSSFeedGeneral": "RSS Details",
|
"HeaderRSSFeedGeneral": "RSS Details",
|
||||||
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
|
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Saved Media Progress",
|
"HeaderSavedMediaProgress": "Saved Media Progress",
|
||||||
"HeaderSchedule": "Schedule",
|
"HeaderSchedule": "Schedule",
|
||||||
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Recent Sessions",
|
"HeaderStatsRecentSessions": "Recent Sessions",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Authors",
|
"HeaderStatsTop10Authors": "Top 10 Authors",
|
||||||
"HeaderStatsTop5Genres": "Top 5 Genres",
|
"HeaderStatsTop5Genres": "Top 5 Genres",
|
||||||
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderTools": "Tools",
|
"HeaderTools": "Tools",
|
||||||
"HeaderUpdateAccount": "Update Account",
|
"HeaderUpdateAccount": "Update Account",
|
||||||
"HeaderUpdateAuthor": "Update Author",
|
"HeaderUpdateAuthor": "Update Author",
|
||||||
@@ -199,6 +202,7 @@
|
|||||||
"LabelClosePlayer": "Close player",
|
"LabelClosePlayer": "Close player",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Collapse Series",
|
"LabelCollapseSeries": "Collapse Series",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Collections",
|
"LabelCollections": "Collections",
|
||||||
"LabelComplete": "Complete",
|
"LabelComplete": "Complete",
|
||||||
"LabelConfirmPassword": "Confirm Password",
|
"LabelConfirmPassword": "Confirm Password",
|
||||||
@@ -220,7 +224,9 @@
|
|||||||
"LabelDirectory": "Directory",
|
"LabelDirectory": "Directory",
|
||||||
"LabelDiscFromFilename": "Disc from Filename",
|
"LabelDiscFromFilename": "Disc from Filename",
|
||||||
"LabelDiscFromMetadata": "Disc from Metadata",
|
"LabelDiscFromMetadata": "Disc from Metadata",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Download",
|
"LabelDownload": "Download",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Duration",
|
"LabelDuration": "Duration",
|
||||||
"LabelDurationFound": "Duration found:",
|
"LabelDurationFound": "Duration found:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Ebook",
|
||||||
@@ -230,6 +236,7 @@
|
|||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Test Address",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Enable",
|
"LabelEnable": "Enable",
|
||||||
"LabelEnd": "End",
|
"LabelEnd": "End",
|
||||||
@@ -248,6 +255,7 @@
|
|||||||
"LabelFinished": "Finished",
|
"LabelFinished": "Finished",
|
||||||
"LabelFolder": "Folder",
|
"LabelFolder": "Folder",
|
||||||
"LabelFolders": "Folders",
|
"LabelFolders": "Folders",
|
||||||
|
"LabelFontScale": "Font scale",
|
||||||
"LabelFormat": "Format",
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "Last Seen",
|
"LabelLastSeen": "Last Seen",
|
||||||
"LabelLastTime": "Last Time",
|
"LabelLastTime": "Last Time",
|
||||||
"LabelLastUpdate": "Last Update",
|
"LabelLastUpdate": "Last Update",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Single page",
|
||||||
|
"LabelLayoutSplitPage": "Split page",
|
||||||
"LabelLess": "Less",
|
"LabelLess": "Less",
|
||||||
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
|
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
|
||||||
"LabelLibrary": "Library",
|
"LabelLibrary": "Library",
|
||||||
"LabelLibraryItem": "Library Item",
|
"LabelLibraryItem": "Library Item",
|
||||||
"LabelLibraryName": "Library Name",
|
"LabelLibraryName": "Library Name",
|
||||||
"LabelLimit": "Limit",
|
"LabelLimit": "Limit",
|
||||||
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Listen Again",
|
"LabelListenAgain": "Listen Again",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "New Password",
|
"LabelNewPassword": "New Password",
|
||||||
"LabelNextBackupDate": "Next backup date",
|
"LabelNextBackupDate": "Next backup date",
|
||||||
"LabelNextScheduledRun": "Next scheduled run",
|
"LabelNextScheduledRun": "Next scheduled run",
|
||||||
|
"LabelNoEpisodesSelected": "No episodes selected",
|
||||||
"LabelNotes": "Notes",
|
"LabelNotes": "Notes",
|
||||||
"LabelNotFinished": "Not Finished",
|
"LabelNotFinished": "Not Finished",
|
||||||
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
||||||
@@ -368,6 +381,8 @@
|
|||||||
"LabelSearchTitle": "Search Title",
|
"LabelSearchTitle": "Search Title",
|
||||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||||
"LabelSeason": "Season",
|
"LabelSeason": "Season",
|
||||||
|
"LabelSelectAllEpisodes": "Select all episodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSendEbookToDevice": "Send Ebook to...",
|
||||||
"LabelSequence": "Sequence",
|
"LabelSequence": "Sequence",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
@@ -383,10 +398,15 @@
|
|||||||
"LabelSettingsDisableWatcher": "Disable Watcher",
|
"LabelSettingsDisableWatcher": "Disable Watcher",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Disable folder watcher for library",
|
"LabelSettingsDisableWatcherForLibrary": "Disable folder watcher for library",
|
||||||
"LabelSettingsDisableWatcherHelp": "Disables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
"LabelSettingsDisableWatcherHelp": "Disables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Experimental features",
|
"LabelSettingsExperimentalFeatures": "Experimental features",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
||||||
"LabelSettingsFindCovers": "Find covers",
|
"LabelSettingsFindCovers": "Find covers",
|
||||||
"LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.<br>Note: This will extend scan time",
|
"LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.<br>Note: This will extend scan time",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Hide single book series",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
|
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
|
||||||
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
|
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
|
"LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Show All",
|
"LabelShowAll": "Show All",
|
||||||
"LabelSize": "Size",
|
"LabelSize": "Size",
|
||||||
"LabelSleepTimer": "Sleep timer",
|
"LabelSleepTimer": "Sleep timer",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Start",
|
"LabelStart": "Start",
|
||||||
"LabelStarted": "Started",
|
"LabelStarted": "Started",
|
||||||
"LabelStartedAt": "Started At",
|
"LabelStartedAt": "Started At",
|
||||||
@@ -439,6 +460,9 @@
|
|||||||
"LabelTagsAccessibleToUser": "Tags Accessible to User",
|
"LabelTagsAccessibleToUser": "Tags Accessible to User",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
||||||
"LabelTasks": "Tasks Running",
|
"LabelTasks": "Tasks Running",
|
||||||
|
"LabelTheme": "Theme",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeBase": "Time Base",
|
"LabelTimeBase": "Time Base",
|
||||||
"LabelTimeListened": "Time Listened",
|
"LabelTimeListened": "Time Listened",
|
||||||
"LabelTimeListenedToday": "Time Listened Today",
|
"LabelTimeListenedToday": "Time Listened Today",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Track from Metadata",
|
"LabelTrackFromMetadata": "Track from Metadata",
|
||||||
"LabelTracks": "Tracks",
|
"LabelTracks": "Tracks",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnabridged": "Unabridged",
|
"LabelUnabridged": "Unabridged",
|
||||||
@@ -497,11 +522,14 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
||||||
"MessageCheckingCron": "Checking cron...",
|
"MessageCheckingCron": "Checking cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
||||||
"MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
|
||||||
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
|
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
|
||||||
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
|
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "M4B Failed!",
|
"MessageM4BFailed": "M4B Failed!",
|
||||||
"MessageM4BFinished": "M4B Finished!",
|
"MessageM4BFinished": "M4B Finished!",
|
||||||
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Mark all episodes finished",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished",
|
||||||
"MessageMarkAsFinished": "Mark as Finished",
|
"MessageMarkAsFinished": "Mark as Finished",
|
||||||
"MessageMarkAsNotFinished": "Mark as Not Finished",
|
"MessageMarkAsNotFinished": "Mark as Not Finished",
|
||||||
"MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
|
"MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
|
||||||
"MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
|
"MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
|
||||||
"MessageRemoveAllItemsWarning": "WARNING! This action will remove all library items from the database including any updates or matches you have made. This does not do anything to your actual files. Are you sure?",
|
|
||||||
"MessageRemoveChapter": "Remove chapter",
|
"MessageRemoveChapter": "Remove chapter",
|
||||||
"MessageRemoveEpisodes": "Remove {0} episode(s)",
|
"MessageRemoveEpisodes": "Remove {0} episode(s)",
|
||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
|
|||||||
+31
-2
@@ -102,7 +102,8 @@
|
|||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodes",
|
"HeaderEpisodes": "Episodes",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Ereader Devices",
|
||||||
|
"HeaderEreaderSettings": "Ereader Settings",
|
||||||
"HeaderFiles": "Files",
|
"HeaderFiles": "Files",
|
||||||
"HeaderFindChapters": "Find Chapters",
|
"HeaderFindChapters": "Find Chapters",
|
||||||
"HeaderIgnoredFiles": "Ignored Files",
|
"HeaderIgnoredFiles": "Ignored Files",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Remove {0} Episodes",
|
"HeaderRemoveEpisodes": "Remove {0} Episodes",
|
||||||
"HeaderRSSFeedGeneral": "RSS Details",
|
"HeaderRSSFeedGeneral": "RSS Details",
|
||||||
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
|
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Saved Media Progress",
|
"HeaderSavedMediaProgress": "Saved Media Progress",
|
||||||
"HeaderSchedule": "Schedule",
|
"HeaderSchedule": "Schedule",
|
||||||
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Recent Sessions",
|
"HeaderStatsRecentSessions": "Recent Sessions",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Authors",
|
"HeaderStatsTop10Authors": "Top 10 Authors",
|
||||||
"HeaderStatsTop5Genres": "Top 5 Genres",
|
"HeaderStatsTop5Genres": "Top 5 Genres",
|
||||||
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderTools": "Tools",
|
"HeaderTools": "Tools",
|
||||||
"HeaderUpdateAccount": "Update Account",
|
"HeaderUpdateAccount": "Update Account",
|
||||||
"HeaderUpdateAuthor": "Update Author",
|
"HeaderUpdateAuthor": "Update Author",
|
||||||
@@ -199,6 +202,7 @@
|
|||||||
"LabelClosePlayer": "Close player",
|
"LabelClosePlayer": "Close player",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Collapse Series",
|
"LabelCollapseSeries": "Collapse Series",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Collections",
|
"LabelCollections": "Collections",
|
||||||
"LabelComplete": "Complete",
|
"LabelComplete": "Complete",
|
||||||
"LabelConfirmPassword": "Confirm Password",
|
"LabelConfirmPassword": "Confirm Password",
|
||||||
@@ -220,7 +224,9 @@
|
|||||||
"LabelDirectory": "Directory",
|
"LabelDirectory": "Directory",
|
||||||
"LabelDiscFromFilename": "Disc from Filename",
|
"LabelDiscFromFilename": "Disc from Filename",
|
||||||
"LabelDiscFromMetadata": "Disc from Metadata",
|
"LabelDiscFromMetadata": "Disc from Metadata",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Download",
|
"LabelDownload": "Download",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Duration",
|
"LabelDuration": "Duration",
|
||||||
"LabelDurationFound": "Duration found:",
|
"LabelDurationFound": "Duration found:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Ebook",
|
||||||
@@ -230,6 +236,7 @@
|
|||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Test Address",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Enable",
|
"LabelEnable": "Enable",
|
||||||
"LabelEnd": "End",
|
"LabelEnd": "End",
|
||||||
@@ -248,6 +255,7 @@
|
|||||||
"LabelFinished": "Finished",
|
"LabelFinished": "Finished",
|
||||||
"LabelFolder": "Folder",
|
"LabelFolder": "Folder",
|
||||||
"LabelFolders": "Folders",
|
"LabelFolders": "Folders",
|
||||||
|
"LabelFontScale": "Font scale",
|
||||||
"LabelFormat": "Format",
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "Last Seen",
|
"LabelLastSeen": "Last Seen",
|
||||||
"LabelLastTime": "Last Time",
|
"LabelLastTime": "Last Time",
|
||||||
"LabelLastUpdate": "Last Update",
|
"LabelLastUpdate": "Last Update",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Single page",
|
||||||
|
"LabelLayoutSplitPage": "Split page",
|
||||||
"LabelLess": "Less",
|
"LabelLess": "Less",
|
||||||
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
|
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
|
||||||
"LabelLibrary": "Library",
|
"LabelLibrary": "Library",
|
||||||
"LabelLibraryItem": "Library Item",
|
"LabelLibraryItem": "Library Item",
|
||||||
"LabelLibraryName": "Library Name",
|
"LabelLibraryName": "Library Name",
|
||||||
"LabelLimit": "Limit",
|
"LabelLimit": "Limit",
|
||||||
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Listen Again",
|
"LabelListenAgain": "Listen Again",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "New Password",
|
"LabelNewPassword": "New Password",
|
||||||
"LabelNextBackupDate": "Next backup date",
|
"LabelNextBackupDate": "Next backup date",
|
||||||
"LabelNextScheduledRun": "Next scheduled run",
|
"LabelNextScheduledRun": "Next scheduled run",
|
||||||
|
"LabelNoEpisodesSelected": "No episodes selected",
|
||||||
"LabelNotes": "Notes",
|
"LabelNotes": "Notes",
|
||||||
"LabelNotFinished": "Not Finished",
|
"LabelNotFinished": "Not Finished",
|
||||||
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
||||||
@@ -368,6 +381,8 @@
|
|||||||
"LabelSearchTitle": "Search Title",
|
"LabelSearchTitle": "Search Title",
|
||||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||||
"LabelSeason": "Season",
|
"LabelSeason": "Season",
|
||||||
|
"LabelSelectAllEpisodes": "Select all episodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSendEbookToDevice": "Send Ebook to...",
|
||||||
"LabelSequence": "Sequence",
|
"LabelSequence": "Sequence",
|
||||||
"LabelSeries": "Series",
|
"LabelSeries": "Series",
|
||||||
@@ -383,10 +398,15 @@
|
|||||||
"LabelSettingsDisableWatcher": "Disable Watcher",
|
"LabelSettingsDisableWatcher": "Disable Watcher",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Disable folder watcher for library",
|
"LabelSettingsDisableWatcherForLibrary": "Disable folder watcher for library",
|
||||||
"LabelSettingsDisableWatcherHelp": "Disables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
"LabelSettingsDisableWatcherHelp": "Disables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Experimental features",
|
"LabelSettingsExperimentalFeatures": "Experimental features",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
||||||
"LabelSettingsFindCovers": "Find covers",
|
"LabelSettingsFindCovers": "Find covers",
|
||||||
"LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.<br>Note: This will extend scan time",
|
"LabelSettingsFindCoversHelp": "If your audiobook does not have an embedded cover or a cover image inside the folder, the scanner will attempt to find a cover.<br>Note: This will extend scan time",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Hide single book series",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
|
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
|
||||||
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
|
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
|
"LabelSettingsOverdriveMediaMarkers": "Use Overdrive Media Markers for chapters",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Show All",
|
"LabelShowAll": "Show All",
|
||||||
"LabelSize": "Size",
|
"LabelSize": "Size",
|
||||||
"LabelSleepTimer": "Sleep timer",
|
"LabelSleepTimer": "Sleep timer",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Start",
|
"LabelStart": "Start",
|
||||||
"LabelStarted": "Started",
|
"LabelStarted": "Started",
|
||||||
"LabelStartedAt": "Started At",
|
"LabelStartedAt": "Started At",
|
||||||
@@ -439,6 +460,9 @@
|
|||||||
"LabelTagsAccessibleToUser": "Tags Accessible to User",
|
"LabelTagsAccessibleToUser": "Tags Accessible to User",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
||||||
"LabelTasks": "Tasks Running",
|
"LabelTasks": "Tasks Running",
|
||||||
|
"LabelTheme": "Theme",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeBase": "Time Base",
|
"LabelTimeBase": "Time Base",
|
||||||
"LabelTimeListened": "Time Listened",
|
"LabelTimeListened": "Time Listened",
|
||||||
"LabelTimeListenedToday": "Time Listened Today",
|
"LabelTimeListenedToday": "Time Listened Today",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Track from Metadata",
|
"LabelTrackFromMetadata": "Track from Metadata",
|
||||||
"LabelTracks": "Tracks",
|
"LabelTracks": "Tracks",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnabridged": "Unabridged",
|
"LabelUnabridged": "Unabridged",
|
||||||
@@ -497,11 +522,14 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
"MessageChapterStartIsAfter": "Chapter start is after the end of your audiobook",
|
||||||
"MessageCheckingCron": "Checking cron...",
|
"MessageCheckingCron": "Checking cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
||||||
"MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
|
||||||
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
|
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
|
||||||
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
|
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "M4B Failed!",
|
"MessageM4BFailed": "M4B Failed!",
|
||||||
"MessageM4BFinished": "M4B Finished!",
|
"MessageM4BFinished": "M4B Finished!",
|
||||||
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Mark all episodes finished",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished",
|
||||||
"MessageMarkAsFinished": "Mark as Finished",
|
"MessageMarkAsFinished": "Mark as Finished",
|
||||||
"MessageMarkAsNotFinished": "Mark as Not Finished",
|
"MessageMarkAsNotFinished": "Mark as Not Finished",
|
||||||
"MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
|
"MessageMatchBooksDescription": "will attempt to match books in the library with a book from the selected search provider and fill in empty details and cover art. Does not overwrite details.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
|
||||||
"MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
|
"MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
|
||||||
"MessageRemoveAllItemsWarning": "WARNING! This action will remove all library items from the database including any updates or matches you have made. This does not do anything to your actual files. Are you sure?",
|
|
||||||
"MessageRemoveChapter": "Remove chapter",
|
"MessageRemoveChapter": "Remove chapter",
|
||||||
"MessageRemoveEpisodes": "Remove {0} episode(s)",
|
"MessageRemoveEpisodes": "Remove {0} episode(s)",
|
||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
|
|||||||
+31
-2
@@ -102,7 +102,8 @@
|
|||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Epizode",
|
"HeaderEpisodes": "Epizode",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Ereader Devices",
|
||||||
|
"HeaderEreaderSettings": "Ereader Settings",
|
||||||
"HeaderFiles": "Datoteke",
|
"HeaderFiles": "Datoteke",
|
||||||
"HeaderFindChapters": "Pronađi poglavlja",
|
"HeaderFindChapters": "Pronađi poglavlja",
|
||||||
"HeaderIgnoredFiles": "Zanemarene datoteke",
|
"HeaderIgnoredFiles": "Zanemarene datoteke",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Ukloni {0} epizoda/-e",
|
"HeaderRemoveEpisodes": "Ukloni {0} epizoda/-e",
|
||||||
"HeaderRSSFeedGeneral": "RSS Details",
|
"HeaderRSSFeedGeneral": "RSS Details",
|
||||||
"HeaderRSSFeedIsOpen": "RSS Feed je otvoren",
|
"HeaderRSSFeedIsOpen": "RSS Feed je otvoren",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Spremljen Media Progress",
|
"HeaderSavedMediaProgress": "Spremljen Media Progress",
|
||||||
"HeaderSchedule": "Schedule",
|
"HeaderSchedule": "Schedule",
|
||||||
"HeaderScheduleLibraryScans": "Zakaži automatsko skeniranje biblioteke",
|
"HeaderScheduleLibraryScans": "Zakaži automatsko skeniranje biblioteke",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Nedavne sesije",
|
"HeaderStatsRecentSessions": "Nedavne sesije",
|
||||||
"HeaderStatsTop10Authors": "Top 10 autora",
|
"HeaderStatsTop10Authors": "Top 10 autora",
|
||||||
"HeaderStatsTop5Genres": "Top 5 žanrova",
|
"HeaderStatsTop5Genres": "Top 5 žanrova",
|
||||||
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderTools": "Alati",
|
"HeaderTools": "Alati",
|
||||||
"HeaderUpdateAccount": "Aktualiziraj Korisnički račun",
|
"HeaderUpdateAccount": "Aktualiziraj Korisnički račun",
|
||||||
"HeaderUpdateAuthor": "Aktualiziraj autora",
|
"HeaderUpdateAuthor": "Aktualiziraj autora",
|
||||||
@@ -199,6 +202,7 @@
|
|||||||
"LabelClosePlayer": "Close player",
|
"LabelClosePlayer": "Close player",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Collapse Series",
|
"LabelCollapseSeries": "Collapse Series",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Kolekcije",
|
"LabelCollections": "Kolekcije",
|
||||||
"LabelComplete": "Complete",
|
"LabelComplete": "Complete",
|
||||||
"LabelConfirmPassword": "Potvrdi lozinku",
|
"LabelConfirmPassword": "Potvrdi lozinku",
|
||||||
@@ -220,7 +224,9 @@
|
|||||||
"LabelDirectory": "Direktorij",
|
"LabelDirectory": "Direktorij",
|
||||||
"LabelDiscFromFilename": "CD iz imena datoteke",
|
"LabelDiscFromFilename": "CD iz imena datoteke",
|
||||||
"LabelDiscFromMetadata": "CD iz metapodataka",
|
"LabelDiscFromMetadata": "CD iz metapodataka",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Preuzmi",
|
"LabelDownload": "Preuzmi",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Trajanje",
|
"LabelDuration": "Trajanje",
|
||||||
"LabelDurationFound": "Pronađeno trajanje:",
|
"LabelDurationFound": "Pronađeno trajanje:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Ebook",
|
||||||
@@ -230,6 +236,7 @@
|
|||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Test Address",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Uključi",
|
"LabelEnable": "Uključi",
|
||||||
"LabelEnd": "Kraj",
|
"LabelEnd": "Kraj",
|
||||||
@@ -248,6 +255,7 @@
|
|||||||
"LabelFinished": "Finished",
|
"LabelFinished": "Finished",
|
||||||
"LabelFolder": "Folder",
|
"LabelFolder": "Folder",
|
||||||
"LabelFolders": "Folderi",
|
"LabelFolders": "Folderi",
|
||||||
|
"LabelFontScale": "Font scale",
|
||||||
"LabelFormat": "Format",
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Žanrovi",
|
"LabelGenres": "Žanrovi",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "Zadnje pogledano",
|
"LabelLastSeen": "Zadnje pogledano",
|
||||||
"LabelLastTime": "Prošli put",
|
"LabelLastTime": "Prošli put",
|
||||||
"LabelLastUpdate": "Zadnja aktualizacija",
|
"LabelLastUpdate": "Zadnja aktualizacija",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Single page",
|
||||||
|
"LabelLayoutSplitPage": "Split page",
|
||||||
"LabelLess": "Manje",
|
"LabelLess": "Manje",
|
||||||
"LabelLibrariesAccessibleToUser": "Biblioteke pristupačne korisniku",
|
"LabelLibrariesAccessibleToUser": "Biblioteke pristupačne korisniku",
|
||||||
"LabelLibrary": "Biblioteka",
|
"LabelLibrary": "Biblioteka",
|
||||||
"LabelLibraryItem": "Stavka biblioteke",
|
"LabelLibraryItem": "Stavka biblioteke",
|
||||||
"LabelLibraryName": "Ime biblioteke",
|
"LabelLibraryName": "Ime biblioteke",
|
||||||
"LabelLimit": "Limit",
|
"LabelLimit": "Limit",
|
||||||
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Slušaj ponovno",
|
"LabelListenAgain": "Slušaj ponovno",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "Nova lozinka",
|
"LabelNewPassword": "Nova lozinka",
|
||||||
"LabelNextBackupDate": "Next backup date",
|
"LabelNextBackupDate": "Next backup date",
|
||||||
"LabelNextScheduledRun": "Next scheduled run",
|
"LabelNextScheduledRun": "Next scheduled run",
|
||||||
|
"LabelNoEpisodesSelected": "No episodes selected",
|
||||||
"LabelNotes": "Bilješke",
|
"LabelNotes": "Bilješke",
|
||||||
"LabelNotFinished": "Nedovršeno",
|
"LabelNotFinished": "Nedovršeno",
|
||||||
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
||||||
@@ -368,6 +381,8 @@
|
|||||||
"LabelSearchTitle": "Traži naslov",
|
"LabelSearchTitle": "Traži naslov",
|
||||||
"LabelSearchTitleOrASIN": "Traži naslov ili ASIN",
|
"LabelSearchTitleOrASIN": "Traži naslov ili ASIN",
|
||||||
"LabelSeason": "Sezona",
|
"LabelSeason": "Sezona",
|
||||||
|
"LabelSelectAllEpisodes": "Select all episodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSendEbookToDevice": "Send Ebook to...",
|
||||||
"LabelSequence": "Sekvenca",
|
"LabelSequence": "Sekvenca",
|
||||||
"LabelSeries": "Serije",
|
"LabelSeries": "Serije",
|
||||||
@@ -383,10 +398,15 @@
|
|||||||
"LabelSettingsDisableWatcher": "Isključi Watchera",
|
"LabelSettingsDisableWatcher": "Isključi Watchera",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Isključi folder watchera za biblioteku",
|
"LabelSettingsDisableWatcherForLibrary": "Isključi folder watchera za biblioteku",
|
||||||
"LabelSettingsDisableWatcherHelp": "Isključi automatsko dodavanje/aktualiziranje stavci ako su promjene prepoznate. *Potreban restart servera",
|
"LabelSettingsDisableWatcherHelp": "Isključi automatsko dodavanje/aktualiziranje stavci ako su promjene prepoznate. *Potreban restart servera",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Eksperimentalni features",
|
"LabelSettingsExperimentalFeatures": "Eksperimentalni features",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Features u razvoju trebaju vaš feedback i pomoć pri testiranju. Klikni da odeš to Github discussionsa.",
|
"LabelSettingsExperimentalFeaturesHelp": "Features u razvoju trebaju vaš feedback i pomoć pri testiranju. Klikni da odeš to Github discussionsa.",
|
||||||
"LabelSettingsFindCovers": "Pronađi covers",
|
"LabelSettingsFindCovers": "Pronađi covers",
|
||||||
"LabelSettingsFindCoversHelp": "Ako audiobook nema embedani cover or a cover sliku unutar foldera, skener će probati pronaći cover.<br>Bilješka: Ovo će produžiti trjanje skeniranja",
|
"LabelSettingsFindCoversHelp": "Ako audiobook nema embedani cover or a cover sliku unutar foldera, skener će probati pronaći cover.<br>Bilješka: Ovo će produžiti trjanje skeniranja",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Hide single book series",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Koristi bookshelf pogled za početnu stranicu",
|
"LabelSettingsHomePageBookshelfView": "Koristi bookshelf pogled za početnu stranicu",
|
||||||
"LabelSettingsLibraryBookshelfView": "Koristi bookshelf pogled za biblioteku",
|
"LabelSettingsLibraryBookshelfView": "Koristi bookshelf pogled za biblioteku",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Koristi Overdrive Media Markers za poglavlja",
|
"LabelSettingsOverdriveMediaMarkers": "Koristi Overdrive Media Markers za poglavlja",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Prikaži sve",
|
"LabelShowAll": "Prikaži sve",
|
||||||
"LabelSize": "Veličina",
|
"LabelSize": "Veličina",
|
||||||
"LabelSleepTimer": "Sleep timer",
|
"LabelSleepTimer": "Sleep timer",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Pokreni",
|
"LabelStart": "Pokreni",
|
||||||
"LabelStarted": "Pokrenuto",
|
"LabelStarted": "Pokrenuto",
|
||||||
"LabelStartedAt": "Pokrenuto",
|
"LabelStartedAt": "Pokrenuto",
|
||||||
@@ -439,6 +460,9 @@
|
|||||||
"LabelTagsAccessibleToUser": "Tags dostupni korisniku",
|
"LabelTagsAccessibleToUser": "Tags dostupni korisniku",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
||||||
"LabelTasks": "Tasks Running",
|
"LabelTasks": "Tasks Running",
|
||||||
|
"LabelTheme": "Theme",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeBase": "Time Base",
|
"LabelTimeBase": "Time Base",
|
||||||
"LabelTimeListened": "Vremena odslušano",
|
"LabelTimeListened": "Vremena odslušano",
|
||||||
"LabelTimeListenedToday": "Vremena odslušano danas",
|
"LabelTimeListenedToday": "Vremena odslušano danas",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Track iz metapodataka",
|
"LabelTrackFromMetadata": "Track iz metapodataka",
|
||||||
"LabelTracks": "Tracks",
|
"LabelTracks": "Tracks",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Tip",
|
"LabelType": "Tip",
|
||||||
"LabelUnabridged": "Unabridged",
|
"LabelUnabridged": "Unabridged",
|
||||||
@@ -497,11 +522,14 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "Početak poglavlja je nakon kraja audioknjige.",
|
"MessageChapterStartIsAfter": "Početak poglavlja je nakon kraja audioknjige.",
|
||||||
"MessageCheckingCron": "Provjeravam cron...",
|
"MessageCheckingCron": "Provjeravam cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Jeste li sigurni da želite obrisati backup za {0}?",
|
"MessageConfirmDeleteBackup": "Jeste li sigurni da želite obrisati backup za {0}?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
||||||
"MessageConfirmDeleteLibrary": "Jeste li sigurni da želite trajno obrisati biblioteku \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Jeste li sigurni da želite trajno obrisati biblioteku \"{0}\"?",
|
||||||
"MessageConfirmDeleteSession": "Jeste li sigurni da želite obrisati ovu sesiju?",
|
"MessageConfirmDeleteSession": "Jeste li sigurni da želite obrisati ovu sesiju?",
|
||||||
"MessageConfirmForceReScan": "Jeste li sigurni da želite ponovno skenirati?",
|
"MessageConfirmForceReScan": "Jeste li sigurni da želite ponovno skenirati?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "M4B neuspješan!",
|
"MessageM4BFailed": "M4B neuspješan!",
|
||||||
"MessageM4BFinished": "M4B završio!",
|
"MessageM4BFinished": "M4B završio!",
|
||||||
"MessageMapChapterTitles": "Mapiraj imena poglavlja u postoječa poglavlja bez izmijene timestampova.",
|
"MessageMapChapterTitles": "Mapiraj imena poglavlja u postoječa poglavlja bez izmijene timestampova.",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Mark all episodes finished",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished",
|
||||||
"MessageMarkAsFinished": "Označi kao završeno",
|
"MessageMarkAsFinished": "Označi kao završeno",
|
||||||
"MessageMarkAsNotFinished": "Označi kao nezavršeno",
|
"MessageMarkAsNotFinished": "Označi kao nezavršeno",
|
||||||
"MessageMatchBooksDescription": "će probati matchati knjige iz biblioteke sa knjigom od odabranog poslužitelja i popuniti prazne detalje i cover. Ne briše postojeće detalje.",
|
"MessageMatchBooksDescription": "će probati matchati knjige iz biblioteke sa knjigom od odabranog poslužitelja i popuniti prazne detalje i cover. Ne briše postojeće detalje.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nema RSS feed url za matchanje",
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nema RSS feed url za matchanje",
|
||||||
"MessageQuickMatchDescription": "Popuni prazne detalje stavki i cover sa prvim match rezultato iz '{0}'. Ne briše detalje osim ako 'Prefer matched metadata' server postavka nije uključena.",
|
"MessageQuickMatchDescription": "Popuni prazne detalje stavki i cover sa prvim match rezultato iz '{0}'. Ne briše detalje osim ako 'Prefer matched metadata' server postavka nije uključena.",
|
||||||
"MessageRemoveAllItemsWarning": "UPOZORENJE! Ova radnja briše sve stavke iz biblioteke uključujući bilokakve aktualizacije ili matcheve. Ovo ne mjenja vaše lokalne datoteke. Jeste li sigurni?",
|
|
||||||
"MessageRemoveChapter": "Remove chapter",
|
"MessageRemoveChapter": "Remove chapter",
|
||||||
"MessageRemoveEpisodes": "ukloni {0} epizoda/-e",
|
"MessageRemoveEpisodes": "ukloni {0} epizoda/-e",
|
||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
|
|||||||
+68
-39
@@ -55,7 +55,7 @@
|
|||||||
"ButtonRemoveAll": "Rimuovi Tutto",
|
"ButtonRemoveAll": "Rimuovi Tutto",
|
||||||
"ButtonRemoveAllLibraryItems": "Rimuovi tutto il contenuto della libreria",
|
"ButtonRemoveAllLibraryItems": "Rimuovi tutto il contenuto della libreria",
|
||||||
"ButtonRemoveFromContinueListening": "Rimuovi per proseguire l'ascolto",
|
"ButtonRemoveFromContinueListening": "Rimuovi per proseguire l'ascolto",
|
||||||
"ButtonRemoveFromContinueReading": "Remove from Continue Reading",
|
"ButtonRemoveFromContinueReading": "Rimuovi per proseguire la lettura",
|
||||||
"ButtonRemoveSeriesFromContinueSeries": "Rimuovi la Serie per Continuarla",
|
"ButtonRemoveSeriesFromContinueSeries": "Rimuovi la Serie per Continuarla",
|
||||||
"ButtonReScan": "Ri-scansiona",
|
"ButtonReScan": "Ri-scansiona",
|
||||||
"ButtonReset": "Reset",
|
"ButtonReset": "Reset",
|
||||||
@@ -95,14 +95,15 @@
|
|||||||
"HeaderCollection": "Raccolta",
|
"HeaderCollection": "Raccolta",
|
||||||
"HeaderCollectionItems": "Elementi della Raccolta",
|
"HeaderCollectionItems": "Elementi della Raccolta",
|
||||||
"HeaderCover": "Cover",
|
"HeaderCover": "Cover",
|
||||||
"HeaderCurrentDownloads": "Current Downloads",
|
"HeaderCurrentDownloads": "Download Correnti",
|
||||||
"HeaderDetails": "Dettagli",
|
"HeaderDetails": "Dettagli",
|
||||||
"HeaderDownloadQueue": "Download Queue",
|
"HeaderDownloadQueue": "Download Queue",
|
||||||
"HeaderEbookFiles": "Ebook Files",
|
"HeaderEbookFiles": "Ebook Files",
|
||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Episodi",
|
"HeaderEpisodes": "Episodi",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Dispositivo Ereader",
|
||||||
|
"HeaderEreaderSettings": "Impostazioni Ereader",
|
||||||
"HeaderFiles": "File",
|
"HeaderFiles": "File",
|
||||||
"HeaderFindChapters": "Trova Capitoli",
|
"HeaderFindChapters": "Trova Capitoli",
|
||||||
"HeaderIgnoredFiles": "File Ignorati",
|
"HeaderIgnoredFiles": "File Ignorati",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Rimuovi {0} Episodi",
|
"HeaderRemoveEpisodes": "Rimuovi {0} Episodi",
|
||||||
"HeaderRSSFeedGeneral": "RSS Details",
|
"HeaderRSSFeedGeneral": "RSS Details",
|
||||||
"HeaderRSSFeedIsOpen": "RSS Feed è aperto",
|
"HeaderRSSFeedIsOpen": "RSS Feed è aperto",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Progressi salvati",
|
"HeaderSavedMediaProgress": "Progressi salvati",
|
||||||
"HeaderSchedule": "Schedula",
|
"HeaderSchedule": "Schedula",
|
||||||
"HeaderScheduleLibraryScans": "Schedula la scansione della libreria",
|
"HeaderScheduleLibraryScans": "Schedula la scansione della libreria",
|
||||||
@@ -148,12 +150,13 @@
|
|||||||
"HeaderSettingsGeneral": "Generale",
|
"HeaderSettingsGeneral": "Generale",
|
||||||
"HeaderSettingsScanner": "Scanner",
|
"HeaderSettingsScanner": "Scanner",
|
||||||
"HeaderSleepTimer": "Sveglia",
|
"HeaderSleepTimer": "Sveglia",
|
||||||
"HeaderStatsLargestItems": "Largest Items",
|
"HeaderStatsLargestItems": "Oggetti Grandi",
|
||||||
"HeaderStatsLongestItems": "libri più lunghi (ore)",
|
"HeaderStatsLongestItems": "libri più lunghi (ore)",
|
||||||
"HeaderStatsMinutesListeningChart": "Minuti ascoltati (Ultimi 7 Giorni)",
|
"HeaderStatsMinutesListeningChart": "Minuti ascoltati (Ultimi 7 Giorni)",
|
||||||
"HeaderStatsRecentSessions": "Sessioni Recenti",
|
"HeaderStatsRecentSessions": "Sessioni Recenti",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Autori",
|
"HeaderStatsTop10Authors": "Top 10 Autori",
|
||||||
"HeaderStatsTop5Genres": "Top 5 Generi",
|
"HeaderStatsTop5Genres": "Top 5 Generi",
|
||||||
|
"HeaderTableOfContents": "Tabellla dei Contenuti",
|
||||||
"HeaderTools": "Strumenti",
|
"HeaderTools": "Strumenti",
|
||||||
"HeaderUpdateAccount": "Aggiorna Account",
|
"HeaderUpdateAccount": "Aggiorna Account",
|
||||||
"HeaderUpdateAuthor": "Aggiorna Autore",
|
"HeaderUpdateAuthor": "Aggiorna Autore",
|
||||||
@@ -161,13 +164,13 @@
|
|||||||
"HeaderUpdateLibrary": "Aggiorna Libreria",
|
"HeaderUpdateLibrary": "Aggiorna Libreria",
|
||||||
"HeaderUsers": "Utenti",
|
"HeaderUsers": "Utenti",
|
||||||
"HeaderYourStats": "Statistiche Personali",
|
"HeaderYourStats": "Statistiche Personali",
|
||||||
"LabelAbridged": "Abridged",
|
"LabelAbridged": "Abbreviato",
|
||||||
"LabelAccountType": "Tipo di Account",
|
"LabelAccountType": "Tipo di Account",
|
||||||
"LabelAccountTypeAdmin": "Admin",
|
"LabelAccountTypeAdmin": "Admin",
|
||||||
"LabelAccountTypeGuest": "Ospite",
|
"LabelAccountTypeGuest": "Ospite",
|
||||||
"LabelAccountTypeUser": "Utente",
|
"LabelAccountTypeUser": "Utente",
|
||||||
"LabelActivity": "Attività",
|
"LabelActivity": "Attività",
|
||||||
"LabelAdded": "Added",
|
"LabelAdded": "Aggiunto",
|
||||||
"LabelAddedAt": "Aggiunto il",
|
"LabelAddedAt": "Aggiunto il",
|
||||||
"LabelAddToCollection": "Aggiungi alla Raccolta",
|
"LabelAddToCollection": "Aggiungi alla Raccolta",
|
||||||
"LabelAddToCollectionBatch": "Aggiungi {0} Libri alla Raccolta",
|
"LabelAddToCollectionBatch": "Aggiungi {0} Libri alla Raccolta",
|
||||||
@@ -192,18 +195,19 @@
|
|||||||
"LabelBitrate": "Bitrate",
|
"LabelBitrate": "Bitrate",
|
||||||
"LabelBooks": "Libri",
|
"LabelBooks": "Libri",
|
||||||
"LabelChangePassword": "Cambia Password",
|
"LabelChangePassword": "Cambia Password",
|
||||||
"LabelChannels": "Channels",
|
"LabelChannels": "Canali",
|
||||||
"LabelChapters": "Chapters",
|
"LabelChapters": "Capitoli",
|
||||||
"LabelChaptersFound": "Capitoli Trovati",
|
"LabelChaptersFound": "Capitoli Trovati",
|
||||||
"LabelChapterTitle": "Titoli dei Capitoli",
|
"LabelChapterTitle": "Titoli dei Capitoli",
|
||||||
"LabelClosePlayer": "Chiudi player",
|
"LabelClosePlayer": "Chiudi player",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Comprimi Serie",
|
"LabelCollapseSeries": "Comprimi Serie",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Raccolte",
|
"LabelCollections": "Raccolte",
|
||||||
"LabelComplete": "Completo",
|
"LabelComplete": "Completo",
|
||||||
"LabelConfirmPassword": "Conferma Password",
|
"LabelConfirmPassword": "Conferma Password",
|
||||||
"LabelContinueListening": "Continua ad Ascoltare",
|
"LabelContinueListening": "Continua ad Ascoltare",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Continua la Lettura",
|
||||||
"LabelContinueSeries": "Continua Serie",
|
"LabelContinueSeries": "Continua Serie",
|
||||||
"LabelCover": "Cover",
|
"LabelCover": "Cover",
|
||||||
"LabelCoverImageURL": "Cover Image URL",
|
"LabelCoverImageURL": "Cover Image URL",
|
||||||
@@ -220,23 +224,26 @@
|
|||||||
"LabelDirectory": "Elenco",
|
"LabelDirectory": "Elenco",
|
||||||
"LabelDiscFromFilename": "Disco dal nome file",
|
"LabelDiscFromFilename": "Disco dal nome file",
|
||||||
"LabelDiscFromMetadata": "Disco dal Metadata",
|
"LabelDiscFromMetadata": "Disco dal Metadata",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Download",
|
"LabelDownload": "Download",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Durata",
|
"LabelDuration": "Durata",
|
||||||
"LabelDurationFound": "Durata Trovata:",
|
"LabelDurationFound": "Durata Trovata:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Ebook",
|
||||||
"LabelEbooks": "Ebooks",
|
"LabelEbooks": "Ebooks",
|
||||||
"LabelEdit": "Modifica",
|
"LabelEdit": "Modifica",
|
||||||
"LabelEmail": "Email",
|
"LabelEmail": "Email",
|
||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "Da Indirizzo",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "Se vero, la connessione utilizzerà TLS durante la connessione al server. Se false, viene utilizzato TLS se il server supporta l'estensione STARTTLS. Nella maggior parte dei casi impostare questo valore su true se ci si connette alla porta 465. Per la porta 587 o 25 mantenerlo false. (da nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmailSettingsTestAddress": "Test Indirizzo",
|
||||||
|
"LabelEmbeddedCover": "Cover Integrata",
|
||||||
"LabelEnable": "Abilita",
|
"LabelEnable": "Abilita",
|
||||||
"LabelEnd": "Fine",
|
"LabelEnd": "Fine",
|
||||||
"LabelEpisode": "Episodio",
|
"LabelEpisode": "Episodio",
|
||||||
"LabelEpisodeTitle": "Titolo Episodio",
|
"LabelEpisodeTitle": "Titolo Episodio",
|
||||||
"LabelEpisodeType": "Tipo Episodio",
|
"LabelEpisodeType": "Tipo Episodio",
|
||||||
"LabelExample": "Example",
|
"LabelExample": "Esempio",
|
||||||
"LabelExplicit": "Esplicito",
|
"LabelExplicit": "Esplicito",
|
||||||
"LabelFeedURL": "Feed URL",
|
"LabelFeedURL": "Feed URL",
|
||||||
"LabelFile": "File",
|
"LabelFile": "File",
|
||||||
@@ -248,12 +255,13 @@
|
|||||||
"LabelFinished": "Finita",
|
"LabelFinished": "Finita",
|
||||||
"LabelFolder": "Cartella",
|
"LabelFolder": "Cartella",
|
||||||
"LabelFolders": "Cartelle",
|
"LabelFolders": "Cartelle",
|
||||||
"LabelFormat": "Format",
|
"LabelFontScale": "Dimensione Font",
|
||||||
|
"LabelFormat": "Formato",
|
||||||
"LabelGenre": "Genere",
|
"LabelGenre": "Genere",
|
||||||
"LabelGenres": "Generi",
|
"LabelGenres": "Generi",
|
||||||
"LabelHardDeleteFile": "Elimina Definitivamente",
|
"LabelHardDeleteFile": "Elimina Definitivamente",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Un ebook",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Un ebook Supplementare",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Ora",
|
"LabelHour": "Ora",
|
||||||
"LabelIcon": "Icona",
|
"LabelIcon": "Icona",
|
||||||
@@ -270,21 +278,25 @@
|
|||||||
"LabelIntervalEveryDay": "Ogni Giorno",
|
"LabelIntervalEveryDay": "Ogni Giorno",
|
||||||
"LabelIntervalEveryHour": "Ogni ora",
|
"LabelIntervalEveryHour": "Ogni ora",
|
||||||
"LabelInvalidParts": "Parti Invalide",
|
"LabelInvalidParts": "Parti Invalide",
|
||||||
"LabelInvert": "Invert",
|
"LabelInvert": "Inverti",
|
||||||
"LabelItem": "Oggetti",
|
"LabelItem": "Oggetti",
|
||||||
"LabelLanguage": "Lingua",
|
"LabelLanguage": "Lingua",
|
||||||
"LabelLanguageDefaultServer": "Lingua di Default",
|
"LabelLanguageDefaultServer": "Lingua di Default",
|
||||||
"LabelLastBookAdded": "Last Book Added",
|
"LabelLastBookAdded": "Ultimo Libro Aggiunto",
|
||||||
"LabelLastBookUpdated": "Last Book Updated",
|
"LabelLastBookUpdated": "Ultimo Libro Aggiornato",
|
||||||
"LabelLastSeen": "Ultimi Visti",
|
"LabelLastSeen": "Ultimi Visti",
|
||||||
"LabelLastTime": "Ultima Volta",
|
"LabelLastTime": "Ultima Volta",
|
||||||
"LabelLastUpdate": "Ultimo Aggiornamento",
|
"LabelLastUpdate": "Ultimo Aggiornamento",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Pagina Singola",
|
||||||
|
"LabelLayoutSplitPage": "DIvidi Pagina",
|
||||||
"LabelLess": "Poco",
|
"LabelLess": "Poco",
|
||||||
"LabelLibrariesAccessibleToUser": "Librerie Accessibili agli Utenti",
|
"LabelLibrariesAccessibleToUser": "Librerie Accessibili agli Utenti",
|
||||||
"LabelLibrary": "Libreria",
|
"LabelLibrary": "Libreria",
|
||||||
"LabelLibraryItem": "Elementi della Library",
|
"LabelLibraryItem": "Elementi della Library",
|
||||||
"LabelLibraryName": "Nome Libreria",
|
"LabelLibraryName": "Nome Libreria",
|
||||||
"LabelLimit": "Limiti",
|
"LabelLimit": "Limiti",
|
||||||
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Ri-ascolta",
|
"LabelListenAgain": "Ri-ascolta",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
@@ -299,7 +311,7 @@
|
|||||||
"LabelMissing": "Altro",
|
"LabelMissing": "Altro",
|
||||||
"LabelMissingParts": "Parti rimantenti",
|
"LabelMissingParts": "Parti rimantenti",
|
||||||
"LabelMore": "Molto",
|
"LabelMore": "Molto",
|
||||||
"LabelMoreInfo": "More Info",
|
"LabelMoreInfo": "Più Info",
|
||||||
"LabelName": "Nome",
|
"LabelName": "Nome",
|
||||||
"LabelNarrator": "Narratore",
|
"LabelNarrator": "Narratore",
|
||||||
"LabelNarrators": "Narratori",
|
"LabelNarrators": "Narratori",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "Nuova Password",
|
"LabelNewPassword": "Nuova Password",
|
||||||
"LabelNextBackupDate": "Data Prossimo Backup",
|
"LabelNextBackupDate": "Data Prossimo Backup",
|
||||||
"LabelNextScheduledRun": "Data prossima esecuzione schedulata",
|
"LabelNextScheduledRun": "Data prossima esecuzione schedulata",
|
||||||
|
"LabelNoEpisodesSelected": "Nessun Episodio Selezionato",
|
||||||
"LabelNotes": "Note",
|
"LabelNotes": "Note",
|
||||||
"LabelNotFinished": "Da Completare",
|
"LabelNotFinished": "Da Completare",
|
||||||
"LabelNotificationAppriseURL": "Apprendi URL(s)",
|
"LabelNotificationAppriseURL": "Apprendi URL(s)",
|
||||||
@@ -339,19 +352,19 @@
|
|||||||
"LabelPlayMethod": "Metodo di riproduzione",
|
"LabelPlayMethod": "Metodo di riproduzione",
|
||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcasts": "Podcasts",
|
"LabelPodcasts": "Podcasts",
|
||||||
"LabelPodcastType": "Timo di Podcast",
|
"LabelPodcastType": "Tipo di Podcast",
|
||||||
"LabelPort": "Port",
|
"LabelPort": "Port",
|
||||||
"LabelPrefixesToIgnore": "Suffissi da ignorare (specificando maiuscole e minuscole)",
|
"LabelPrefixesToIgnore": "Suffissi da ignorare (specificando maiuscole e minuscole)",
|
||||||
"LabelPreventIndexing": "Impedisci che il tuo feed venga indicizzato da iTunes e dalle directory dei podcast di Google",
|
"LabelPreventIndexing": "Impedisci che il tuo feed venga indicizzato da iTunes e dalle directory dei podcast di Google",
|
||||||
"LabelPrimaryEbook": "Primary ebook",
|
"LabelPrimaryEbook": "Libri Principlae",
|
||||||
"LabelProgress": "Cominciati",
|
"LabelProgress": "Cominciati",
|
||||||
"LabelProvider": "Provider",
|
"LabelProvider": "Provider",
|
||||||
"LabelPubDate": "Data Pubblicazione",
|
"LabelPubDate": "Data Pubblicazione",
|
||||||
"LabelPublisher": "Editore",
|
"LabelPublisher": "Editore",
|
||||||
"LabelPublishYear": "Anno Pubblicazione",
|
"LabelPublishYear": "Anno Pubblicazione",
|
||||||
"LabelRead": "Read",
|
"LabelRead": "Leggi",
|
||||||
"LabelReadAgain": "Read Again",
|
"LabelReadAgain": "Leggi Ancora",
|
||||||
"LabelReadEbookWithoutProgress": "Read ebook without keeping progress",
|
"LabelReadEbookWithoutProgress": "Leggi l'ebook senza mantenere i progressi",
|
||||||
"LabelRecentlyAdded": "Aggiunti Recentemente",
|
"LabelRecentlyAdded": "Aggiunti Recentemente",
|
||||||
"LabelRecentSeries": "Serie Recenti",
|
"LabelRecentSeries": "Serie Recenti",
|
||||||
"LabelRecommended": "Raccomandati",
|
"LabelRecommended": "Raccomandati",
|
||||||
@@ -368,25 +381,32 @@
|
|||||||
"LabelSearchTitle": "Cerca Titolo",
|
"LabelSearchTitle": "Cerca Titolo",
|
||||||
"LabelSearchTitleOrASIN": "Cerca titolo o ASIN",
|
"LabelSearchTitleOrASIN": "Cerca titolo o ASIN",
|
||||||
"LabelSeason": "Stagione",
|
"LabelSeason": "Stagione",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSelectAllEpisodes": "Seleziona tutti gli Episodi",
|
||||||
|
"LabelSelectEpisodesShowing": "Episodi {0} selezionati ",
|
||||||
|
"LabelSendEbookToDevice": "Invia ebook a...",
|
||||||
"LabelSequence": "Sequenza",
|
"LabelSequence": "Sequenza",
|
||||||
"LabelSeries": "Serie",
|
"LabelSeries": "Serie",
|
||||||
"LabelSeriesName": "Nome Serie",
|
"LabelSeriesName": "Nome Serie",
|
||||||
"LabelSeriesProgress": "Cominciato",
|
"LabelSeriesProgress": "Cominciato",
|
||||||
"LabelSetEbookAsPrimary": "Set as primary",
|
"LabelSetEbookAsPrimary": "Immposta come Primario",
|
||||||
"LabelSetEbookAsSupplementary": "Set as supplementary",
|
"LabelSetEbookAsSupplementary": "Imposta come Suplementare",
|
||||||
"LabelSettingsAudiobooksOnly": "Audiobooks only",
|
"LabelSettingsAudiobooksOnly": "Solo Audiolibri",
|
||||||
"LabelSettingsAudiobooksOnlyHelp": "Enabling this setting will ignore ebook files unless they are inside an audiobook folder in which case they will be set as supplementary ebooks",
|
"LabelSettingsAudiobooksOnlyHelp": "L'abilitazione di questa impostazione ignorerà i file di ebook a meno che non si trovino all'interno di una cartella di audiolibri, nel qual caso verranno impostati come ebook supplementari",
|
||||||
"LabelSettingsBookshelfViewHelp": "Design con scaffali in legno",
|
"LabelSettingsBookshelfViewHelp": "Design con scaffali in legno",
|
||||||
"LabelSettingsChromecastSupport": "Supporto a Chromecast",
|
"LabelSettingsChromecastSupport": "Supporto a Chromecast",
|
||||||
"LabelSettingsDateFormat": "Formato Data",
|
"LabelSettingsDateFormat": "Formato Data",
|
||||||
"LabelSettingsDisableWatcher": "Disattiva Watcher",
|
"LabelSettingsDisableWatcher": "Disattiva Watcher",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Disattiva Watcher per le librerie",
|
"LabelSettingsDisableWatcherForLibrary": "Disattiva Watcher per le librerie",
|
||||||
"LabelSettingsDisableWatcherHelp": "Disattiva il controllo automatico libri nelle cartelle delle librerie. *Richiede il Riavvio del Server",
|
"LabelSettingsDisableWatcherHelp": "Disattiva il controllo automatico libri nelle cartelle delle librerie. *Richiede il Riavvio del Server",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Opzioni Sperimentali",
|
"LabelSettingsExperimentalFeatures": "Opzioni Sperimentali",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Funzionalità in fase di sviluppo che potrebbero utilizzare i tuoi feedback e aiutare i test. Fare clic per aprire la discussione github.",
|
"LabelSettingsExperimentalFeaturesHelp": "Funzionalità in fase di sviluppo che potrebbero utilizzare i tuoi feedback e aiutare i test. Fare clic per aprire la discussione github.",
|
||||||
"LabelSettingsFindCovers": "Trova covers",
|
"LabelSettingsFindCovers": "Trova covers",
|
||||||
"LabelSettingsFindCoversHelp": "Se il tuo audiolibro non ha una copertina incorporata o un'immagine di copertina all'interno della cartella, questa funzione tenterà di trovare una copertina.<br>Nota: aumenta il tempo di scansione",
|
"LabelSettingsFindCoversHelp": "Se il tuo audiolibro non ha una copertina incorporata o un'immagine di copertina all'interno della cartella, questa funzione tenterà di trovare una copertina.<br>Nota: aumenta il tempo di scansione",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Nascondi una singola serie di libri",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Le serie che hanno un solo libro saranno nascoste dalla pagina della serie e dagli scaffali della home page.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Home page con sfondo legno",
|
"LabelSettingsHomePageBookshelfView": "Home page con sfondo legno",
|
||||||
"LabelSettingsLibraryBookshelfView": "Libreria con sfondo legno",
|
"LabelSettingsLibraryBookshelfView": "Libreria con sfondo legno",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Usa Overdrive Media Markers per i capitoli",
|
"LabelSettingsOverdriveMediaMarkers": "Usa Overdrive Media Markers per i capitoli",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Mostra Tutto",
|
"LabelShowAll": "Mostra Tutto",
|
||||||
"LabelSize": "Dimensione",
|
"LabelSize": "Dimensione",
|
||||||
"LabelSleepTimer": "Sleep timer",
|
"LabelSleepTimer": "Sleep timer",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Inizo",
|
"LabelStart": "Inizo",
|
||||||
"LabelStarted": "Iniziato",
|
"LabelStarted": "Iniziato",
|
||||||
"LabelStartedAt": "Iniziato al",
|
"LabelStartedAt": "Iniziato al",
|
||||||
@@ -437,8 +458,11 @@
|
|||||||
"LabelTag": "Tag",
|
"LabelTag": "Tag",
|
||||||
"LabelTags": "Tags",
|
"LabelTags": "Tags",
|
||||||
"LabelTagsAccessibleToUser": "Tags permessi agli Utenti",
|
"LabelTagsAccessibleToUser": "Tags permessi agli Utenti",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
"LabelTagsNotAccessibleToUser": "Tags non accessibile agli Utenti",
|
||||||
"LabelTasks": "Processi in esecuzione",
|
"LabelTasks": "Processi in esecuzione",
|
||||||
|
"LabelTheme": "Tema",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeBase": "Time Base",
|
"LabelTimeBase": "Time Base",
|
||||||
"LabelTimeListened": "Tempo di Ascolto",
|
"LabelTimeListened": "Tempo di Ascolto",
|
||||||
"LabelTimeListenedToday": "Tempo di Ascolto Oggi",
|
"LabelTimeListenedToday": "Tempo di Ascolto Oggi",
|
||||||
@@ -457,9 +481,10 @@
|
|||||||
"LabelTrackFromMetadata": "Traccia da Metadata",
|
"LabelTrackFromMetadata": "Traccia da Metadata",
|
||||||
"LabelTracks": "Traccia",
|
"LabelTracks": "Traccia",
|
||||||
"LabelTracksMultiTrack": "Multi-traccia",
|
"LabelTracksMultiTrack": "Multi-traccia",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Traccia-singola",
|
"LabelTracksSingleTrack": "Traccia-singola",
|
||||||
"LabelType": "Tipo",
|
"LabelType": "Tipo",
|
||||||
"LabelUnabridged": "Unabridged",
|
"LabelUnabridged": "Integrale",
|
||||||
"LabelUnknown": "Sconosciuto",
|
"LabelUnknown": "Sconosciuto",
|
||||||
"LabelUpdateCover": "Aggiornamento Cover",
|
"LabelUpdateCover": "Aggiornamento Cover",
|
||||||
"LabelUpdateCoverHelp": "Consenti la sovrascrittura delle copertine esistenti per i libri selezionati quando viene trovata una corrispondenza",
|
"LabelUpdateCoverHelp": "Consenti la sovrascrittura delle copertine esistenti per i libri selezionati quando viene trovata una corrispondenza",
|
||||||
@@ -497,18 +522,21 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "L'ora di inizio non valida deve essere maggiore o uguale all'ora di inizio del capitolo precedente",
|
"MessageChapterErrorStartLtPrev": "L'ora di inizio non valida deve essere maggiore o uguale all'ora di inizio del capitolo precedente",
|
||||||
"MessageChapterStartIsAfter": "L'inizio del capitolo è dopo la fine del tuo audiolibro",
|
"MessageChapterStartIsAfter": "L'inizio del capitolo è dopo la fine del tuo audiolibro",
|
||||||
"MessageCheckingCron": "Controllo cron...",
|
"MessageCheckingCron": "Controllo cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Sei sicuro di voler eliminare il backup {0}?",
|
"MessageConfirmDeleteBackup": "Sei sicuro di voler eliminare il backup {0}?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "Questo eliminerà il file dal tuo file system. Sei sicuro?",
|
||||||
"MessageConfirmDeleteLibrary": "Sei sicuro di voler eliminare definitivamente la libreria \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Sei sicuro di voler eliminare definitivamente la libreria \"{0}\"?",
|
||||||
"MessageConfirmDeleteSession": "Sei sicuro di voler eliminare questa sessione?",
|
"MessageConfirmDeleteSession": "Sei sicuro di voler eliminare questa sessione?",
|
||||||
"MessageConfirmForceReScan": "Sei sicuro di voler forzare una nuova scansione?",
|
"MessageConfirmForceReScan": "Sei sicuro di voler forzare una nuova scansione?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Sei sicuro di voler contrassegnare tutti gli episodi come finiti?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come completati?",
|
"MessageConfirmMarkSeriesFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come completati?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come non completati?",
|
"MessageConfirmMarkSeriesNotFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come non completati?",
|
||||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
"MessageConfirmRemoveAllChapters": "Sei sicuro di voler rimuovere tutti i capitoli?",
|
||||||
"MessageConfirmRemoveCollection": "Sei sicuro di voler rimuovere la Raccolta \"{0}\"?",
|
"MessageConfirmRemoveCollection": "Sei sicuro di voler rimuovere la Raccolta \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "Sei sicuro di voler rimuovere l'episodio \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "Sei sicuro di voler rimuovere l'episodio \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisodes": "Sei sicuro di voler rimuovere {0} episodi?",
|
"MessageConfirmRemoveEpisodes": "Sei sicuro di voler rimuovere {0} episodi?",
|
||||||
"MessageConfirmRemoveNarrator": "Are you sure you want to remove narrator \"{0}\"?",
|
"MessageConfirmRemoveNarrator": "Sei sicuro di voler rimuovere il narratore \"{0}\"?",
|
||||||
"MessageConfirmRemovePlaylist": "Sei sicuro di voler rimuovere la tua playlist \"{0}\"?",
|
"MessageConfirmRemovePlaylist": "Sei sicuro di voler rimuovere la tua playlist \"{0}\"?",
|
||||||
"MessageConfirmRenameGenre": "Sei sicuro di voler rinominare il genere \"{0}\" in \"{1}\" per tutti gli oggetti?",
|
"MessageConfirmRenameGenre": "Sei sicuro di voler rinominare il genere \"{0}\" in \"{1}\" per tutti gli oggetti?",
|
||||||
"MessageConfirmRenameGenreMergeNote": "Note: Questo genere esiste già quindi verra unito.",
|
"MessageConfirmRenameGenreMergeNote": "Note: Questo genere esiste già quindi verra unito.",
|
||||||
@@ -516,7 +544,7 @@
|
|||||||
"MessageConfirmRenameTag": "Sei sicuro di voler rinominare il tag \"{0}\" in \"{1}\" per tutti gli oggetti?",
|
"MessageConfirmRenameTag": "Sei sicuro di voler rinominare il tag \"{0}\" in \"{1}\" per tutti gli oggetti?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Nota: Questo tag esiste già e verrà unito nel vecchio.",
|
"MessageConfirmRenameTagMergeNote": "Nota: Questo tag esiste già e verrà unito nel vecchio.",
|
||||||
"MessageConfirmRenameTagWarning": "Avvertimento! Esiste già un tag simile con un nome simile \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Avvertimento! Esiste già un tag simile con un nome simile \"{0}\".",
|
||||||
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "Sei sicuro di voler inviare {0} ebook \"{1}\" al Device \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Download episodio in corso",
|
"MessageDownloadingEpisode": "Download episodio in corso",
|
||||||
"MessageDragFilesIntoTrackOrder": "Trascina i file nell'ordine di traccia corretto",
|
"MessageDragFilesIntoTrackOrder": "Trascina i file nell'ordine di traccia corretto",
|
||||||
"MessageEmbedFinished": "Incorporamento finito!",
|
"MessageEmbedFinished": "Incorporamento finito!",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "M4B Fallito!",
|
"MessageM4BFailed": "M4B Fallito!",
|
||||||
"MessageM4BFinished": "M4B Finito!",
|
"MessageM4BFinished": "M4B Finito!",
|
||||||
"MessageMapChapterTitles": "Associa i titoli dei capitoli ai capitoli dell'audiolibro esistente senza modificare i timestamp",
|
"MessageMapChapterTitles": "Associa i titoli dei capitoli ai capitoli dell'audiolibro esistente senza modificare i timestamp",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Segna tutti gli episodi come finiti",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Segna tutti gli episodi come non finiti",
|
||||||
"MessageMarkAsFinished": "Segna come finito",
|
"MessageMarkAsFinished": "Segna come finito",
|
||||||
"MessageMarkAsNotFinished": "Segna come da completare",
|
"MessageMarkAsNotFinished": "Segna come da completare",
|
||||||
"MessageMatchBooksDescription": "tenterà di abbinare i libri nella biblioteca con un libro del provider di ricerca selezionato e inserirà i dettagli vuoti e la copertina. Non sovrascrive i dettagli.",
|
"MessageMatchBooksDescription": "tenterà di abbinare i libri nella biblioteca con un libro del provider di ricerca selezionato e inserirà i dettagli vuoti e la copertina. Non sovrascrive i dettagli.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Crea playlist da una Raccolta",
|
"MessagePlaylistCreateFromCollection": "Crea playlist da una Raccolta",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast non ha l'URL del feed RSS da utilizzare per il match",
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast non ha l'URL del feed RSS da utilizzare per il match",
|
||||||
"MessageQuickMatchDescription": "Compila i dettagli dell'articolo vuoto e copri con il risultato della prima corrispondenza di '{0}'. Non sovrascrive i dettagli a meno che non sia abilitata l'impostazione del server \"Preferisci metadati corrispondenti\".",
|
"MessageQuickMatchDescription": "Compila i dettagli dell'articolo vuoto e copri con il risultato della prima corrispondenza di '{0}'. Non sovrascrive i dettagli a meno che non sia abilitata l'impostazione del server \"Preferisci metadati corrispondenti\".",
|
||||||
"MessageRemoveAllItemsWarning": "AVVERTIMENTO! Questa azione rimuoverà tutti gli elementi della libreria dal database, inclusi eventuali aggiornamenti o corrispondenze apportate. Questo non fa nulla ai tuoi file effettivi. Sei sicuro?",
|
|
||||||
"MessageRemoveChapter": "Rimuovi Capitolo",
|
"MessageRemoveChapter": "Rimuovi Capitolo",
|
||||||
"MessageRemoveEpisodes": "rimuovi {0} episodio(i)",
|
"MessageRemoveEpisodes": "rimuovi {0} episodio(i)",
|
||||||
"MessageRemoveFromPlayerQueue": "Rimuovi dalla coda di riproduzione",
|
"MessageRemoveFromPlayerQueue": "Rimuovi dalla coda di riproduzione",
|
||||||
@@ -671,8 +700,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Oggetto rimosso dalla Raccolta",
|
"ToastRemoveItemFromCollectionSuccess": "Oggetto rimosso dalla Raccolta",
|
||||||
"ToastRSSFeedCloseFailed": "Errore chiusura RSS feed",
|
"ToastRSSFeedCloseFailed": "Errore chiusura RSS feed",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS feed chiuso",
|
"ToastRSSFeedCloseSuccess": "RSS feed chiuso",
|
||||||
"ToastSendEbookToDeviceFailed": "Failed to Send Ebook to device",
|
"ToastSendEbookToDeviceFailed": "Impossibile inviare l'ebook al dispositivo",
|
||||||
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
|
"ToastSendEbookToDeviceSuccess": "Ebook inviato al dispositivo \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Aggiornaento Serie Fallito",
|
"ToastSeriesUpdateFailed": "Aggiornaento Serie Fallito",
|
||||||
"ToastSeriesUpdateSuccess": "Serie Aggornate",
|
"ToastSeriesUpdateSuccess": "Serie Aggornate",
|
||||||
"ToastSessionDeleteFailed": "Errore eliminazione sessione",
|
"ToastSessionDeleteFailed": "Errore eliminazione sessione",
|
||||||
|
|||||||
@@ -0,0 +1,714 @@
|
|||||||
|
{
|
||||||
|
"ButtonAdd": "Pridėti",
|
||||||
|
"ButtonAddChapters": "Pridėti skyrius",
|
||||||
|
"ButtonAddPodcasts": "Pridėti tinklalaides",
|
||||||
|
"ButtonAddYourFirstLibrary": "Pridėkite savo pirmąją biblioteką",
|
||||||
|
"ButtonApply": "Taikyti",
|
||||||
|
"ButtonApplyChapters": "Taikyti skyrius",
|
||||||
|
"ButtonAuthors": "Autoriai",
|
||||||
|
"ButtonBrowseForFolder": "Naršyti aplanko",
|
||||||
|
"ButtonCancel": "Atšaukti",
|
||||||
|
"ButtonCancelEncode": "Atšaukti kodavimą",
|
||||||
|
"ButtonChangeRootPassword": "Keisti root slaptažodį",
|
||||||
|
"ButtonCheckAndDownloadNewEpisodes": "Patikrinti ir parsiųsti naujus epizodus",
|
||||||
|
"ButtonChooseAFolder": "Pasirinkite aplanką",
|
||||||
|
"ButtonChooseFiles": "Pasirinkite failus",
|
||||||
|
"ButtonClearFilter": "Valyti filtrą",
|
||||||
|
"ButtonCloseFeed": "Uždaryti srautą",
|
||||||
|
"ButtonCollections": "Kolekcijos",
|
||||||
|
"ButtonConfigureScanner": "Konfigūruoti skenerį",
|
||||||
|
"ButtonCreate": "Kurti",
|
||||||
|
"ButtonCreateBackup": "Kurti atsarginę kopiją",
|
||||||
|
"ButtonDelete": "Ištrinti",
|
||||||
|
"ButtonDownloadQueue": "Parsisiuntimų eilė",
|
||||||
|
"ButtonEdit": "Redaguoti",
|
||||||
|
"ButtonEditChapters": "Redaguoti skyrius",
|
||||||
|
"ButtonEditPodcast": "Redaguoti tinklalaidę",
|
||||||
|
"ButtonForceReScan": "Priverstinai nuskaityti iš naujo",
|
||||||
|
"ButtonFullPath": "Visas kelias",
|
||||||
|
"ButtonHide": "Slėpti",
|
||||||
|
"ButtonHome": "Pradžia",
|
||||||
|
"ButtonIssues": "Problemos",
|
||||||
|
"ButtonLatest": "Naujausias",
|
||||||
|
"ButtonLibrary": "Biblioteka",
|
||||||
|
"ButtonLogout": "Atsijungti",
|
||||||
|
"ButtonLookup": "Ieškoti",
|
||||||
|
"ButtonManageTracks": "Tvarkyti takelius",
|
||||||
|
"ButtonMapChapterTitles": "Suderinti skyrių pavadinimus",
|
||||||
|
"ButtonMatchAllAuthors": "Pritaikyti visus autorius",
|
||||||
|
"ButtonMatchBooks": "Pritaikyti knygas",
|
||||||
|
"ButtonNevermind": "Nesvarbu",
|
||||||
|
"ButtonOk": "Ok",
|
||||||
|
"ButtonOpenFeed": "Atidaryti srautą",
|
||||||
|
"ButtonOpenManager": "Atidaryti tvarkyklę",
|
||||||
|
"ButtonPlay": "Groti",
|
||||||
|
"ButtonPlaying": "Grojama",
|
||||||
|
"ButtonPlaylists": "Grojaraščiai",
|
||||||
|
"ButtonPurgeAllCache": "Valyti visą saugyklą",
|
||||||
|
"ButtonPurgeItemsCache": "Valyti elementų saugyklą",
|
||||||
|
"ButtonPurgeMediaProgress": "Valyti medijos progresą",
|
||||||
|
"ButtonQueueAddItem": "Pridėti į eilę",
|
||||||
|
"ButtonQueueRemoveItem": "Pašalinti iš eilės",
|
||||||
|
"ButtonQuickMatch": "Greitas pritaikymas",
|
||||||
|
"ButtonRead": "Skaityti",
|
||||||
|
"ButtonRemove": "Pašalinti",
|
||||||
|
"ButtonRemoveAll": "Pašalinti viską",
|
||||||
|
"ButtonRemoveAllLibraryItems": "Pašalinti visus bibliotekos elementus",
|
||||||
|
"ButtonRemoveFromContinueListening": "Pašalinti iš Tęsti Klausimą",
|
||||||
|
"ButtonRemoveFromContinueReading": "Pašalinti iš Tęsti Skaitymą",
|
||||||
|
"ButtonRemoveSeriesFromContinueSeries": "Pašalinti seriją iš Tęsti Seriją",
|
||||||
|
"ButtonReScan": "Iš naujo nuskaityti",
|
||||||
|
"ButtonReset": "Atstatyti",
|
||||||
|
"ButtonRestore": "Atkurti",
|
||||||
|
"ButtonSave": "Išsaugoti",
|
||||||
|
"ButtonSaveAndClose": "Išsaugoti ir uždaryti",
|
||||||
|
"ButtonSaveTracklist": "Išsaugoti takelių sąrašą",
|
||||||
|
"ButtonScan": "Nuskaityti",
|
||||||
|
"ButtonScanLibrary": "Nuskaityti biblioteką",
|
||||||
|
"ButtonSearch": "Ieškoti",
|
||||||
|
"ButtonSelectFolderPath": "Pasirinkti aplanko kelią",
|
||||||
|
"ButtonSeries": "Serijos",
|
||||||
|
"ButtonSetChaptersFromTracks": "Nustatyti skyrius iš takelių",
|
||||||
|
"ButtonShiftTimes": "Perstumti laikus",
|
||||||
|
"ButtonShow": "Rodyti",
|
||||||
|
"ButtonStartM4BEncode": "Pradėti M4B kodavimą",
|
||||||
|
"ButtonStartMetadataEmbed": "Pradėti metaduomenų įterpimą",
|
||||||
|
"ButtonSubmit": "Pateikti",
|
||||||
|
"ButtonTest": "Testuoti",
|
||||||
|
"ButtonUpload": "Įkelti",
|
||||||
|
"ButtonUploadBackup": "Įkelti atsarginę kopiją",
|
||||||
|
"ButtonUploadCover": "Įkelti viršelį",
|
||||||
|
"ButtonUploadOPMLFile": "Įkelti OPML failą",
|
||||||
|
"ButtonUserDelete": "Ištrinti naudotoją {0}",
|
||||||
|
"ButtonUserEdit": "Redaguoti naudotoją {0}",
|
||||||
|
"ButtonViewAll": "Peržiūrėti visus",
|
||||||
|
"ButtonYes": "Taip",
|
||||||
|
"HeaderAccount": "Paskyra",
|
||||||
|
"HeaderAdvanced": "Papildomi",
|
||||||
|
"HeaderAppriseNotificationSettings": "Apprise pranešimo nustatymai",
|
||||||
|
"HeaderAudiobookTools": "Audioknygų failų valdymo įrankiai",
|
||||||
|
"HeaderAudioTracks": "Garso takeliai",
|
||||||
|
"HeaderBackups": "Atsarginės kopijos",
|
||||||
|
"HeaderChangePassword": "Pakeisti slaptažodį",
|
||||||
|
"HeaderChapters": "Skyriai",
|
||||||
|
"HeaderChooseAFolder": "Pasirinkti aplanką",
|
||||||
|
"HeaderCollection": "Kolekcija",
|
||||||
|
"HeaderCollectionItems": "Kolekcijos elementai",
|
||||||
|
"HeaderCover": "Viršelis",
|
||||||
|
"HeaderCurrentDownloads": "Dabartiniai parsisiuntimai",
|
||||||
|
"HeaderDetails": "Detalės",
|
||||||
|
"HeaderDownloadQueue": "Parsisiuntimo eilė",
|
||||||
|
"HeaderEbookFiles": "Eknygos failai",
|
||||||
|
"HeaderEmail": "El. paštas",
|
||||||
|
"HeaderEmailSettings": "El. pašto nustatymai",
|
||||||
|
"HeaderEpisodes": "Epizodai",
|
||||||
|
"HeaderEreaderDevices": "Elektroniniai skaitytuvai",
|
||||||
|
"HeaderEreaderSettings": "Elektroninių skaitytuvų nustatymai",
|
||||||
|
"HeaderFiles": "Failai",
|
||||||
|
"HeaderFindChapters": "Rasti skyrius",
|
||||||
|
"HeaderIgnoredFiles": "Ignoruojami failai",
|
||||||
|
"HeaderItemFiles": "Elemento failai",
|
||||||
|
"HeaderItemMetadataUtils": "Elemento metaduomenų įrankiai",
|
||||||
|
"HeaderLastListeningSession": "Paskutinė klausymosi sesija",
|
||||||
|
"HeaderLatestEpisodes": "Naujausi epizodai",
|
||||||
|
"HeaderLibraries": "Bibliotekos",
|
||||||
|
"HeaderLibraryFiles": "Bibliotekos failai",
|
||||||
|
"HeaderLibraryStats": "Bibliotekos statistika",
|
||||||
|
"HeaderListeningSessions": "Klausymosi sesijos",
|
||||||
|
"HeaderListeningStats": "Klausymosi statistika",
|
||||||
|
"HeaderLogin": "Prisijungti",
|
||||||
|
"HeaderLogs": "Žurnalai",
|
||||||
|
"HeaderManageGenres": "Tvarkyti žanrus",
|
||||||
|
"HeaderManageTags": "Tvarkyti žymas",
|
||||||
|
"HeaderMapDetails": "Susieti detales",
|
||||||
|
"HeaderMatch": "Atitaikyti",
|
||||||
|
"HeaderMetadataToEmbed": "Metaduomenys įterpimui",
|
||||||
|
"HeaderNewAccount": "Nauja paskyra",
|
||||||
|
"HeaderNewLibrary": "Nauja biblioteka",
|
||||||
|
"HeaderNotifications": "Pranešimai",
|
||||||
|
"HeaderOpenRSSFeed": "Atidaryti RSS srautą",
|
||||||
|
"HeaderOtherFiles": "Kiti failai",
|
||||||
|
"HeaderPermissions": "Leidimai",
|
||||||
|
"HeaderPlayerQueue": "Grotuvo eilė",
|
||||||
|
"HeaderPlaylist": "Grojaraštis",
|
||||||
|
"HeaderPlaylistItems": "Grojaraščio elementai",
|
||||||
|
"HeaderPodcastsToAdd": "Pridėti tinklalaides",
|
||||||
|
"HeaderPreviewCover": "Peržiūrėti viršelį",
|
||||||
|
"HeaderRemoveEpisode": "Pašalinti epizodą",
|
||||||
|
"HeaderRemoveEpisodes": "Pašalinti {0} epizodus",
|
||||||
|
"HeaderRSSFeedGeneral": "RSS informacija",
|
||||||
|
"HeaderRSSFeedIsOpen": "RSS srautas yra atidarytas",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
|
"HeaderSavedMediaProgress": "Išsaugota medijos pažanga",
|
||||||
|
"HeaderSchedule": "Tvarkaraštis",
|
||||||
|
"HeaderScheduleLibraryScans": "Nustatyti bibliotekų nuskaitymo tvarkaraštį",
|
||||||
|
"HeaderSession": "Sesija",
|
||||||
|
"HeaderSetBackupSchedule": "Nustatyti atsarginių kopijų tvarkaraštį",
|
||||||
|
"HeaderSettings": "Nustatymai",
|
||||||
|
"HeaderSettingsDisplay": "Rodymas",
|
||||||
|
"HeaderSettingsExperimental": "Eksperimentinės funkcijos",
|
||||||
|
"HeaderSettingsGeneral": "Bendra",
|
||||||
|
"HeaderSettingsScanner": "Skaitytuvas",
|
||||||
|
"HeaderSleepTimer": "Miego laikmatis",
|
||||||
|
"HeaderStatsLargestItems": "Didžiausi elementai",
|
||||||
|
"HeaderStatsLongestItems": "Ilgiausi elementai (val.)",
|
||||||
|
"HeaderStatsMinutesListeningChart": "Klausymo minutės (paskutinės 7 dienos)",
|
||||||
|
"HeaderStatsRecentSessions": "Naujausios sesijos",
|
||||||
|
"HeaderStatsTop10Authors": "Top 10 autorių",
|
||||||
|
"HeaderStatsTop5Genres": "Top 5 žanrai",
|
||||||
|
"HeaderTableOfContents": "Turinys",
|
||||||
|
"HeaderTools": "Įrankiai",
|
||||||
|
"HeaderUpdateAccount": "Atnaujinti paskyrą",
|
||||||
|
"HeaderUpdateAuthor": "Atnaujinti autorių",
|
||||||
|
"HeaderUpdateDetails": "Atnaujinti informaciją",
|
||||||
|
"HeaderUpdateLibrary": "Atnaujinti biblioteką",
|
||||||
|
"HeaderUsers": "Naudotojai",
|
||||||
|
"HeaderYourStats": "Jūsų statistika",
|
||||||
|
"LabelAbridged": "Santrauka",
|
||||||
|
"LabelAccountType": "Paskyros tipas",
|
||||||
|
"LabelAccountTypeAdmin": "Administratorius",
|
||||||
|
"LabelAccountTypeGuest": "Svečias",
|
||||||
|
"LabelAccountTypeUser": "Naudotojas",
|
||||||
|
"LabelActivity": "Veikla",
|
||||||
|
"LabelAdded": "Pridėta",
|
||||||
|
"LabelAddedAt": "Pridėta {0}",
|
||||||
|
"LabelAddToCollection": "Pridėti į kolekciją",
|
||||||
|
"LabelAddToCollectionBatch": "Pridėti {0} knygas į kolekciją",
|
||||||
|
"LabelAddToPlaylist": "Pridėti į grojaraštį",
|
||||||
|
"LabelAddToPlaylistBatch": "Pridėti {0} elementus į grojaraštį",
|
||||||
|
"LabelAll": "Visi",
|
||||||
|
"LabelAllUsers": "Visi naudotojai",
|
||||||
|
"LabelAlreadyInYourLibrary": "Jau yra jūsų bibliotekoje",
|
||||||
|
"LabelAppend": "Pridėti",
|
||||||
|
"LabelAuthor": "Autorius",
|
||||||
|
"LabelAuthorFirstLast": "Autorius (Vardas Pavardė)",
|
||||||
|
"LabelAuthorLastFirst": "Autorius (Pavardė, Vardas)",
|
||||||
|
"LabelAuthors": "Autoriai",
|
||||||
|
"LabelAutoDownloadEpisodes": "Automatiškai atsisiųsti epizodus",
|
||||||
|
"LabelBackToUser": "Grįžti į naudotoją",
|
||||||
|
"LabelBackupsEnableAutomaticBackups": "Įjungti automatinį atsarginių kopijų kūrimą",
|
||||||
|
"LabelBackupsEnableAutomaticBackupsHelp": "Atsarginės kopijos bus išsaugotos /metadata/backups aplanke",
|
||||||
|
"LabelBackupsMaxBackupSize": "Maksimalus atsarginių kopijų dydis (GB)",
|
||||||
|
"LabelBackupsMaxBackupSizeHelp": "Jei konfigūruotas dydis viršijamas, atsarginės kopijos nebus sukurtos, kad būtų išvengta klaidingų konfigūracijų.",
|
||||||
|
"LabelBackupsNumberToKeep": "Laikytinų atsarginių kopijų skaičius",
|
||||||
|
"LabelBackupsNumberToKeepHelp": "Tik viena atsarginė kopija bus pašalinta vienu metu, todėl jei jau turite daugiau atsarginių kopijų nei nurodyta, turite jas pašalinti rankiniu būdu.",
|
||||||
|
"LabelBitrate": "Bitų sparta",
|
||||||
|
"LabelBooks": "Knygos",
|
||||||
|
"LabelChangePassword": "Pakeisti slaptažodį",
|
||||||
|
"LabelChannels": "Kanalai",
|
||||||
|
"LabelChapters": "Skyriai",
|
||||||
|
"LabelChaptersFound": "rasti skyriai",
|
||||||
|
"LabelChapterTitle": "Skyriaus pavadinimas",
|
||||||
|
"LabelClosePlayer": "Uždaryti grotuvą",
|
||||||
|
"LabelCodec": "Kodekas",
|
||||||
|
"LabelCollapseSeries": "Suskleisti seriją",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
|
"LabelCollections": "Kolekcijos",
|
||||||
|
"LabelComplete": "Baigta",
|
||||||
|
"LabelConfirmPassword": "Patvirtinkite slaptažodį",
|
||||||
|
"LabelContinueListening": "Tęsti klausymąsi",
|
||||||
|
"LabelContinueReading": "Tęsti skaitymą",
|
||||||
|
"LabelContinueSeries": "Tęsti seriją",
|
||||||
|
"LabelCover": "Viršelis",
|
||||||
|
"LabelCoverImageURL": "Viršelio paveikslėlio URL",
|
||||||
|
"LabelCreatedAt": "Sukurta",
|
||||||
|
"LabelCronExpression": "Cron išraiška",
|
||||||
|
"LabelCurrent": "Dabartinė",
|
||||||
|
"LabelCurrently": "Šiuo metu:",
|
||||||
|
"LabelCustomCronExpression": "Nestandartinė Cron išraiška:",
|
||||||
|
"LabelDatetime": "Data ir laikas",
|
||||||
|
"LabelDescription": "Aprašymas",
|
||||||
|
"LabelDeselectAll": "Išvalyti pasirinktus",
|
||||||
|
"LabelDevice": "Įrenginys",
|
||||||
|
"LabelDeviceInfo": "Įrenginio informacija",
|
||||||
|
"LabelDirectory": "Katalogas",
|
||||||
|
"LabelDiscFromFilename": "Diskas pagal failo pavadinimą",
|
||||||
|
"LabelDiscFromMetadata": "Diskas pagal metaduomenis",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
|
"LabelDownload": "Atsisiųsti",
|
||||||
|
"LabelDownloadNEpisodes": "Atsisiųsti {0} epizodų",
|
||||||
|
"LabelDuration": "Trukmė",
|
||||||
|
"LabelDurationFound": "Rasta trukmė:",
|
||||||
|
"LabelEbook": "Elektroninė knyga",
|
||||||
|
"LabelEbooks": "Elektroninės knygos",
|
||||||
|
"LabelEdit": "Redaguoti",
|
||||||
|
"LabelEmail": "El. paštas",
|
||||||
|
"LabelEmailSettingsFromAddress": "Siuntėjo adresas",
|
||||||
|
"LabelEmailSettingsSecure": "Apsaugota",
|
||||||
|
"LabelEmailSettingsSecureHelp": "Jei ši reikšmė yra \"true\", ryšys naudos TLS protokolą. Jei \"false\", TLS bus naudojamas tik tada, jei serveris palaiko STARTTLS plėtinį. Daugumos atveju, jei jungiamasi prie 465 prievado, šią reikšmę turėtumėte nustatyti kaip \"true\". Jei jungiamasi prie 587 arba 25 prievado, turi būti nustatyta \"false\". (iš nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Testinis adresas",
|
||||||
|
"LabelEmbeddedCover": "Įterptas viršelis",
|
||||||
|
"LabelEnable": "Įjungti",
|
||||||
|
"LabelEnd": "Pabaiga",
|
||||||
|
"LabelEpisode": "Epizodas",
|
||||||
|
"LabelEpisodeTitle": "Epizodo pavadinimas",
|
||||||
|
"LabelEpisodeType": "Epizodo tipas",
|
||||||
|
"LabelExample": "Pavyzdys",
|
||||||
|
"LabelExplicit": "Suaugusiems",
|
||||||
|
"LabelFeedURL": "Srauto URL",
|
||||||
|
"LabelFile": "Failas",
|
||||||
|
"LabelFileBirthtime": "Failo kūrimo laikas",
|
||||||
|
"LabelFileModified": "Failo keitimo laikas",
|
||||||
|
"LabelFilename": "Failo pavadinimas",
|
||||||
|
"LabelFilterByUser": "Filtruoti pagal naudotoją",
|
||||||
|
"LabelFindEpisodes": "Rasti epizodus",
|
||||||
|
"LabelFinished": "Baigta",
|
||||||
|
"LabelFolder": "Aplankas",
|
||||||
|
"LabelFolders": "Aplankai",
|
||||||
|
"LabelFontScale": "Šrifto mastelis",
|
||||||
|
"LabelFormat": "Formatas",
|
||||||
|
"LabelGenre": "Žanras",
|
||||||
|
"LabelGenres": "Žanrai",
|
||||||
|
"LabelHardDeleteFile": "Galutinai ištrinti failą",
|
||||||
|
"LabelHasEbook": "Turi e-knygą",
|
||||||
|
"LabelHasSupplementaryEbook": "Turi papildomą e-knygą",
|
||||||
|
"LabelHost": "Serveris",
|
||||||
|
"LabelHour": "Valanda",
|
||||||
|
"LabelIcon": "Piktograma",
|
||||||
|
"LabelIncludeInTracklist": "Įtraukti į takelių sąrašą",
|
||||||
|
"LabelIncomplete": "Nebaigta",
|
||||||
|
"LabelInProgress": "Vyksta",
|
||||||
|
"LabelInterval": "Intervalas",
|
||||||
|
"LabelIntervalCustomDailyWeekly": "Pasirinktinis kasdieninės/savaitinės periodiškumas",
|
||||||
|
"LabelIntervalEvery12Hours": "Kas 12 valandų",
|
||||||
|
"LabelIntervalEvery15Minutes": "Kas 15 minučių",
|
||||||
|
"LabelIntervalEvery2Hours": "Kas 2 valandas",
|
||||||
|
"LabelIntervalEvery30Minutes": "Kas 30 minučių",
|
||||||
|
"LabelIntervalEvery6Hours": "Kas 6 valandas",
|
||||||
|
"LabelIntervalEveryDay": "Kasdien",
|
||||||
|
"LabelIntervalEveryHour": "Kiekvieną valandą",
|
||||||
|
"LabelInvalidParts": "Netinkamos dalys",
|
||||||
|
"LabelInvert": "Apversti",
|
||||||
|
"LabelItem": "Elementas",
|
||||||
|
"LabelLanguage": "Kalba",
|
||||||
|
"LabelLanguageDefaultServer": "Numatytoji serverio kalba",
|
||||||
|
"LabelLastBookAdded": "Paskutinė pridėta knyga",
|
||||||
|
"LabelLastBookUpdated": "Paskutinė atnaujinta knyga",
|
||||||
|
"LabelLastSeen": "Paskutinį kartą matyta",
|
||||||
|
"LabelLastTime": "Paskutinį kartą",
|
||||||
|
"LabelLastUpdate": "Paskutinė atnaujinimo data",
|
||||||
|
"LabelLayout": "Išdėstymas",
|
||||||
|
"LabelLayoutSinglePage": "Vieno puslapio",
|
||||||
|
"LabelLayoutSplitPage": "Padalinto puslapio",
|
||||||
|
"LabelLess": "Mažiau",
|
||||||
|
"LabelLibrariesAccessibleToUser": "Naudotojui pasiekiamos bibliotekos",
|
||||||
|
"LabelLibrary": "Biblioteka",
|
||||||
|
"LabelLibraryItem": "Bibliotekos elementas",
|
||||||
|
"LabelLibraryName": "Bibliotekos pavadinimas",
|
||||||
|
"LabelLimit": "Limitas",
|
||||||
|
"LabelLineSpacing": "Tarpas tarp eilučių",
|
||||||
|
"LabelListenAgain": "Klausytis iš naujo",
|
||||||
|
"LabelLogLevelDebug": "Debug",
|
||||||
|
"LabelLogLevelInfo": "Info",
|
||||||
|
"LabelLogLevelWarn": "Warn",
|
||||||
|
"LabelLookForNewEpisodesAfterDate": "Ieškoti naujų epizodų po šios datos",
|
||||||
|
"LabelMediaPlayer": "Grotuvas",
|
||||||
|
"LabelMediaType": "Medijos tipas",
|
||||||
|
"LabelMetadataProvider": "Metaduomenų tiekėjas",
|
||||||
|
"LabelMetaTag": "Meta žymė",
|
||||||
|
"LabelMetaTags": "Meta žymos",
|
||||||
|
"LabelMinute": "Minutė",
|
||||||
|
"LabelMissing": "Trūksta",
|
||||||
|
"LabelMissingParts": "Trūkstamos dalys",
|
||||||
|
"LabelMore": "Daugiau",
|
||||||
|
"LabelMoreInfo": "Daugiau informacijos",
|
||||||
|
"LabelName": "Pavadinimas",
|
||||||
|
"LabelNarrator": "Skaitytojas",
|
||||||
|
"LabelNarrators": "Skaitytojai",
|
||||||
|
"LabelNew": "Nauja",
|
||||||
|
"LabelNewestAuthors": "Naujausi autoriai",
|
||||||
|
"LabelNewestEpisodes": "Naujausi epizodai",
|
||||||
|
"LabelNewPassword": "Naujas slaptažodis",
|
||||||
|
"LabelNextBackupDate": "Kitos atsarginės kopijos data",
|
||||||
|
"LabelNextScheduledRun": "Kito planuoto vykdymo data",
|
||||||
|
"LabelNoEpisodesSelected": "Nepasirinkti jokie epizodai",
|
||||||
|
"LabelNotes": "Užrašai",
|
||||||
|
"LabelNotFinished": "Nebaigta",
|
||||||
|
"LabelNotificationAppriseURL": "Pranešimo (Apprise) URL",
|
||||||
|
"LabelNotificationAvailableVariables": "Galimi kintamieji",
|
||||||
|
"LabelNotificationBodyTemplate": "Turinio šablonas",
|
||||||
|
"LabelNotificationEvent": "Pranešimo įvykis",
|
||||||
|
"LabelNotificationsMaxFailedAttempts": "Maksimalus nesėkmingų bandymų skaičius",
|
||||||
|
"LabelNotificationsMaxFailedAttemptsHelp": "Pranešimai bus išjungti, jei nepavyks jų išsiųsti nurodytą kartų",
|
||||||
|
"LabelNotificationsMaxQueueSize": "Maksimalus pranešimų eilių dydis",
|
||||||
|
"LabelNotificationsMaxQueueSizeHelp": "Įvykiai yra apriboti vienu įvykiu per sekundę. Įvykiai bus ignoruojami, jei eilė yra maksimalaus dydžio. Tai apsaugo nuo pranešimų šlamšto.",
|
||||||
|
"LabelNotificationTitleTemplate": "Pavadinimo šablonas",
|
||||||
|
"LabelNotStarted": "Nepasileista",
|
||||||
|
"LabelNumberOfBooks": "Knygų skaičius",
|
||||||
|
"LabelNumberOfEpisodes": "Epizodų skaičius",
|
||||||
|
"LabelOpenRSSFeed": "Atidaryti RSS srautą",
|
||||||
|
"LabelOverwrite": "Perrašyti",
|
||||||
|
"LabelPassword": "Slaptažodis",
|
||||||
|
"LabelPath": "Kelias",
|
||||||
|
"LabelPermissionsAccessAllLibraries": "Gali pasiekti visas bibliotekas",
|
||||||
|
"LabelPermissionsAccessAllTags": "Gali pasiekti visas žymes",
|
||||||
|
"LabelPermissionsAccessExplicitContent": "Gali pasiekti turinį suaugusiems",
|
||||||
|
"LabelPermissionsDelete": "Gali trinti",
|
||||||
|
"LabelPermissionsDownload": "Gali atsisiųsti",
|
||||||
|
"LabelPermissionsUpdate": "Gali atnaujinti",
|
||||||
|
"LabelPermissionsUpload": "Gali įkelti",
|
||||||
|
"LabelPhotoPathURL": "Nuotraukos kelias/URL",
|
||||||
|
"LabelPlaylists": "Grojaraščiai",
|
||||||
|
"LabelPlayMethod": "Grojimo metodas",
|
||||||
|
"LabelPodcast": "Tinklalaidė",
|
||||||
|
"LabelPodcasts": "Tinklalaidės",
|
||||||
|
"LabelPodcastType": "Tinklalaidės tipas",
|
||||||
|
"LabelPort": "Prievadas",
|
||||||
|
"LabelPrefixesToIgnore": "Ignoruojami priešdėliai (didžiosios/mažosios nesvarbu)",
|
||||||
|
"LabelPreventIndexing": "Neleisti indeksuoti jūsų srauto „iTunes“ ir Google podcast kataloguose",
|
||||||
|
"LabelPrimaryEbook": "Pagrindinė e-knyga",
|
||||||
|
"LabelProgress": "Progresas",
|
||||||
|
"LabelProvider": "Tiekėjas",
|
||||||
|
"LabelPubDate": "Publikavimo data",
|
||||||
|
"LabelPublisher": "Leidėjas",
|
||||||
|
"LabelPublishYear": "Leidimo metai",
|
||||||
|
"LabelRead": "Skaityta",
|
||||||
|
"LabelReadAgain": "Skaityti dar kartą",
|
||||||
|
"LabelReadEbookWithoutProgress": "Skaityti e-knygą be pažangos saugojimo",
|
||||||
|
"LabelRecentlyAdded": "Neseniai pridėta",
|
||||||
|
"LabelRecentSeries": "Naujausios serijos",
|
||||||
|
"LabelRecommended": "Rekomenduojama",
|
||||||
|
"LabelRegion": "Regionas",
|
||||||
|
"LabelReleaseDate": "Išleidimo data",
|
||||||
|
"LabelRemoveCover": "Pašalinti viršelį",
|
||||||
|
"LabelRSSFeedCustomOwnerEmail": "Pasirinktinis savininko el. paštas",
|
||||||
|
"LabelRSSFeedCustomOwnerName": "Pasirinktinis savininko vardas",
|
||||||
|
"LabelRSSFeedOpen": "Atidarytas RSS srautas",
|
||||||
|
"LabelRSSFeedPreventIndexing": "Neleisti indeksuoti",
|
||||||
|
"LabelRSSFeedSlug": "RSS srauto identifikatorius",
|
||||||
|
"LabelRSSFeedURL": "RSS srauto URL",
|
||||||
|
"LabelSearchTerm": "Paieškos žodis",
|
||||||
|
"LabelSearchTitle": "Ieškoti pavadinimo",
|
||||||
|
"LabelSearchTitleOrASIN": "Ieškoti pavadinimo arba ASIN",
|
||||||
|
"LabelSeason": "Sezonas",
|
||||||
|
"LabelSelectAllEpisodes": "Pažymėti visus epizodus",
|
||||||
|
"LabelSelectEpisodesShowing": "Pažymėti {0} rodomus epizodus",
|
||||||
|
"LabelSendEbookToDevice": "Siųsti e-knygą į...",
|
||||||
|
"LabelSequence": "Seka",
|
||||||
|
"LabelSeries": "Serija",
|
||||||
|
"LabelSeriesName": "Serijos pavadinimas",
|
||||||
|
"LabelSeriesProgress": "Serijos progresas",
|
||||||
|
"LabelSetEbookAsPrimary": "Nustatyti kaip pagrindinę",
|
||||||
|
"LabelSetEbookAsSupplementary": "Nustatyti kaip papildomą",
|
||||||
|
"LabelSettingsAudiobooksOnly": "Tik garso knygos",
|
||||||
|
"LabelSettingsAudiobooksOnlyHelp": "Įjungus šią parinktį, e-knygų failai bus ignoruojami, nebent jie būtų audioknygų aplankuose, kurie tada būtų rodomi kaip papildomos e-knygos",
|
||||||
|
"LabelSettingsBookshelfViewHelp": "Knygų lentynos dizainas su medinėmis lentynomis",
|
||||||
|
"LabelSettingsChromecastSupport": "„Chromecast“ palaikymas",
|
||||||
|
"LabelSettingsDateFormat": "Datos formatas",
|
||||||
|
"LabelSettingsDisableWatcher": "Išjungti stebėtoją",
|
||||||
|
"LabelSettingsDisableWatcherForLibrary": "Išjungti aplankų stebėtoją bibliotekai",
|
||||||
|
"LabelSettingsDisableWatcherHelp": "Išjungia automatinį elementų pridėjimą/atnaujinimą, jei pastebėti failų pokyčiai. *Reikalingas serverio paleidimas iš naujo",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
|
"LabelSettingsExperimentalFeatures": "Eksperimentiniai funkcionalumai",
|
||||||
|
"LabelSettingsExperimentalFeaturesHelp": "Funkcijos, kurios yra kuriamos ir laukiami jūsų komentarai. Spustelėkite, kad atidarytumėte „GitHub“ diskusiją.",
|
||||||
|
"LabelSettingsFindCovers": "Rasti viršelius",
|
||||||
|
"LabelSettingsFindCoversHelp": "Jei jūsų audioknyga neturi įterpto viršelio arba viršelio paveikslėlio aplanko, skeneris bandys rasti viršelį.<br>Pastaba: Tai padidins skenavimo trukmę.",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Slėpti serijas, turinčias tik vieną knygą",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Serijos, turinčios tik vieną knygą, bus paslėptos nuo serijų puslapio ir pagrindinio puslapio lentynų.",
|
||||||
|
"LabelSettingsHomePageBookshelfView": "Naudoti pagrindinio puslapio knygų lentynų vaizdą",
|
||||||
|
"LabelSettingsLibraryBookshelfView": "Naudoti bibliotekos knygų lentynų vaizdą",
|
||||||
|
"LabelSettingsOverdriveMediaMarkers": "Naudoti Overdrive žymeklius skyriams",
|
||||||
|
"LabelSettingsOverdriveMediaMarkersHelp": "MP3 failai iš Overdrive turi įterptus skyrių laikus kaip papildomą metaduomenį. Įjungus šią funkciją, skyrių laikai bus automatiškai naudojami.",
|
||||||
|
"LabelSettingsParseSubtitles": "Analizuoti subtitrus",
|
||||||
|
"LabelSettingsParseSubtitlesHelp": "Išskleisti subtitrus iš audioknygos aplanko pavadinimų.<br>Subtitrai turi būti atskirti brūkšniu \"-\"<br>pavyzdžiui, \"Knygos pavadinimas - Čia yra subtitrai\" turi subtitrą \"Čia yra subtitrai\"",
|
||||||
|
"LabelSettingsPreferAudioMetadata": "Pirmenybė failo metaduomenis",
|
||||||
|
"LabelSettingsPreferAudioMetadataHelp": "Garso failo ID3 metaduomenys bus naudojami knygos informacijai (vietoj aplankų pavadinimų)",
|
||||||
|
"LabelSettingsPreferMatchedMetadata": "Pirmenybė atitaikytiems metaduomenis",
|
||||||
|
"LabelSettingsPreferMatchedMetadataHelp": "Atitaikyti duomenys pakeis elementų informaciją naudojant Greitą atitikimą. Pagal nutylėjimą Greitas atitaikymas užpildys tik trūkstamas detales.",
|
||||||
|
"LabelSettingsPreferOPFMetadata": "Pirmenybė OPF metaduomenis",
|
||||||
|
"LabelSettingsPreferOPFMetadataHelp": "OPF failo metaduomenys bus naudojami knygos informacijai (vietoj aplankų pavadinimų)",
|
||||||
|
"LabelSettingsSkipMatchingBooksWithASIN": "Praleisti knygas, kurios jau turi ASIN",
|
||||||
|
"LabelSettingsSkipMatchingBooksWithISBN": "Praleisti knygas, kurios jau turi ISBN",
|
||||||
|
"LabelSettingsSortingIgnorePrefixes": "Ignoruoti priešdėlius rūšiuojant",
|
||||||
|
"LabelSettingsSortingIgnorePrefixesHelp": "pvz., su priešdėliu \"the\" knygos pavadinimas \"The Book Title\" bus rūšiuojamas kaip \"Book Title, The\"",
|
||||||
|
"LabelSettingsSquareBookCovers": "Naudoti kvadratinius knygos viršelius",
|
||||||
|
"LabelSettingsSquareBookCoversHelp": "Naudoti kvadratinius viršelius vietoj standartinių 1.6:1 knygų viršelių",
|
||||||
|
"LabelSettingsStoreCoversWithItem": "Saugoti viršelius su elementu",
|
||||||
|
"LabelSettingsStoreCoversWithItemHelp": "Pagal nutylėjimą viršeliai saugomi /metadata/items aplanke, įjungus šią parinktį viršeliai bus saugomi jūsų bibliotekos elemento aplanke. Bus išsaugotas tik vienas „cover“ pavadinimo failas.",
|
||||||
|
"LabelSettingsStoreMetadataWithItem": "Saugoti metaduomenis su elementu",
|
||||||
|
"LabelSettingsStoreMetadataWithItemHelp": "Pagal nutylėjimą metaduomenų failai saugomi /metadata/items aplanke, įjungus šią parinktį metaduomenų failai bus saugomi jūsų bibliotekos elemento aplanke. Naudojamas .abs plėtinys.",
|
||||||
|
"LabelSettingsTimeFormat": "Laiko formatas",
|
||||||
|
"LabelShowAll": "Rodyti viską",
|
||||||
|
"LabelSize": "Dydis",
|
||||||
|
"LabelSleepTimer": "Miego laikmatis",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
|
"LabelStart": "Pradėti",
|
||||||
|
"LabelStarted": "Pradėta",
|
||||||
|
"LabelStartedAt": "Pradėta",
|
||||||
|
"LabelStartTime": "Pradžios laikas",
|
||||||
|
"LabelStatsAudioTracks": "Garsiniai takeliai",
|
||||||
|
"LabelStatsAuthors": "Autoriai",
|
||||||
|
"LabelStatsBestDay": "Geriausia diena",
|
||||||
|
"LabelStatsDailyAverage": "Vidutiniškai per dieną",
|
||||||
|
"LabelStatsDays": "Dienos",
|
||||||
|
"LabelStatsDaysListened": "Klausyta dienų",
|
||||||
|
"LabelStatsHours": "Valandos",
|
||||||
|
"LabelStatsInARow": "iš eilės",
|
||||||
|
"LabelStatsItemsFinished": "Baigti elementai",
|
||||||
|
"LabelStatsItemsInLibrary": "Elementai bibliotekoje",
|
||||||
|
"LabelStatsMinutes": "minutės",
|
||||||
|
"LabelStatsMinutesListening": "Klausyta minučių",
|
||||||
|
"LabelStatsOverallDays": "Iš viso dienų",
|
||||||
|
"LabelStatsOverallHours": "Iš viso valandų",
|
||||||
|
"LabelStatsWeekListening": "Savaitės klausymas",
|
||||||
|
"LabelSubtitle": "Subtitrai",
|
||||||
|
"LabelSupportedFileTypes": "Palaikomi failų tipai",
|
||||||
|
"LabelTag": "Žyma",
|
||||||
|
"LabelTags": "Žymos",
|
||||||
|
"LabelTagsAccessibleToUser": "Žymos, pasiekiamos vartotojui",
|
||||||
|
"LabelTagsNotAccessibleToUser": "Žymos, nepasiekiamos vartotojui",
|
||||||
|
"LabelTasks": "Vykdomos užduotys",
|
||||||
|
"LabelTheme": "Tema",
|
||||||
|
"LabelThemeDark": "Tamsi",
|
||||||
|
"LabelThemeLight": "Šviesi",
|
||||||
|
"LabelTimeBase": "Laiko pagrindas",
|
||||||
|
"LabelTimeListened": "Klausytas laikas",
|
||||||
|
"LabelTimeListenedToday": "Klausytas laikas šiandien",
|
||||||
|
"LabelTimeRemaining": "{0} likę",
|
||||||
|
"LabelTimeToShift": "Laiko perkėlimas sekundėmis",
|
||||||
|
"LabelTitle": "Pavadinimas",
|
||||||
|
"LabelToolsEmbedMetadata": "Įterpti metaduomenis",
|
||||||
|
"LabelToolsEmbedMetadataDescription": "Įterpti metaduomenis į garso failus, įskaitant viršelio paveikslu ir skyrius.",
|
||||||
|
"LabelToolsMakeM4b": "Sukurti M4B garso knygų failą",
|
||||||
|
"LabelToolsMakeM4bDescription": "Sukurti .M4B garso knygų failą su įterptais metaduomenimis, viršelio paveikslu ir skyriais.",
|
||||||
|
"LabelToolsSplitM4b": "Skaidyti M4B į MP3 failus",
|
||||||
|
"LabelToolsSplitM4bDescription": "Sukurti MP3 failus iš M4B su skyrių skaldymu ir įterptais metaduomenimis, viršelio paveikslu ir skyriais.",
|
||||||
|
"LabelTotalDuration": "Viso trukmė",
|
||||||
|
"LabelTotalTimeListened": "Iš viso klausyta laiko",
|
||||||
|
"LabelTrackFromFilename": "Takelis iš failo pavadinimo",
|
||||||
|
"LabelTrackFromMetadata": "Takelis iš metaduomenų",
|
||||||
|
"LabelTracks": "Takeliai",
|
||||||
|
"LabelTracksMultiTrack": "Keli takeliai",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
|
"LabelTracksSingleTrack": "Vienas takelis",
|
||||||
|
"LabelType": "Tipas",
|
||||||
|
"LabelUnabridged": "Neprikurptas",
|
||||||
|
"LabelUnknown": "Nežinoma",
|
||||||
|
"LabelUpdateCover": "Atnaujinti viršelį",
|
||||||
|
"LabelUpdateCoverHelp": "Leisti perrašyti esamus viršelius pasirinktoms knygoms, kai yra rasta atitikmenų",
|
||||||
|
"LabelUpdatedAt": "Atnaujinta",
|
||||||
|
"LabelUpdateDetails": "Atnaujinti duomenis",
|
||||||
|
"LabelUpdateDetailsHelp": "Leisti perrašyti esamus duomenis pasirinktoms knygoms, kai yra rasta atitikmenų",
|
||||||
|
"LabelUploaderDragAndDrop": "Tempkite ir paleiskite failus ar aplankus",
|
||||||
|
"LabelUploaderDropFiles": "Nutempti failus",
|
||||||
|
"LabelUseChapterTrack": "Naudoti skyrių takelį",
|
||||||
|
"LabelUseFullTrack": "Naudoti visą takelį",
|
||||||
|
"LabelUser": "Vartotojas",
|
||||||
|
"LabelUsername": "Vartotojo vardas",
|
||||||
|
"LabelValue": "Reikšmė",
|
||||||
|
"LabelVersion": "Versija",
|
||||||
|
"LabelViewBookmarks": "Peržiūrėti skirtukus",
|
||||||
|
"LabelViewChapters": "Peržiūrėti skyrius",
|
||||||
|
"LabelViewQueue": "Peržiūrėti grotuvo eilę",
|
||||||
|
"LabelVolume": "Garsumas",
|
||||||
|
"LabelWeekdaysToRun": "Dienos, kuriomis vykdyti",
|
||||||
|
"LabelYourAudiobookDuration": "Jūsų garso knygos trukmė",
|
||||||
|
"LabelYourBookmarks": "Jūsų skirtukai",
|
||||||
|
"LabelYourPlaylists": "Jūsų grojaraščiai",
|
||||||
|
"LabelYourProgress": "Jūsų pažanga",
|
||||||
|
"MessageAddToPlayerQueue": "Pridėti į grotuvo eilę",
|
||||||
|
"MessageAppriseDescription": "Norint naudoti šią funkciją, reikės turėti <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> veikiantį arba API, kuris tvarkys tas pačias užklausas.<br />Apprise API URL turėtų būti visi kelio takai iki pranešimo siuntimo, pvz., jei jūsų API pasiekiamas adresu <code>http://192.168.1.1:8337</code>, tada įveskite <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
|
"MessageBackupsDescription": "Atsarginės kopijos apima vartotojus, vartotojų pažangą, bibliotekos elemento informaciją, serverio nustatymus ir vaizdus, saugomus <code>/metadata/items</code> ir <code>/metadata/authors</code>. Atsarginės kopijos <strong>neįtraukia</strong> jokių failų, saugomų jūsų bibliotekos aplankuose.",
|
||||||
|
"MessageBatchQuickMatchDescription": "Greitas atitikmens rasti bandys pridėti trūkstamus viršelius ir metaduomenis pasirinktiems elementams. Įjunkite žemiau esančias parinktis, kad leistumėte Greitajam atitikmeniui perrašyti esamus viršelius ir/ar metaduomenis.",
|
||||||
|
"MessageBookshelfNoCollections": "Dar nepridėjote jokių kolekcijų",
|
||||||
|
"MessageBookshelfNoResultsForFilter": "Rezultatų pagal filtrą \"{0}: {1}\" nėra",
|
||||||
|
"MessageBookshelfNoRSSFeeds": "Nėra atvertų RSS srautų",
|
||||||
|
"MessageBookshelfNoSeries": "Neturite jokių serijų",
|
||||||
|
"MessageChapterEndIsAfter": "Skyriaus pabaiga yra po jūsų garso knygos pabaigos",
|
||||||
|
"MessageChapterErrorFirstNotZero": "Pirmasis skyrius turi prasidėti nuo 0",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Netinkamas pradžios laikas. Turi būti mažesnis nei garso knygos trukmė",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Netinkamas pradžios laikas. Turi būti didesnis arba lygus ankstesnio skyriaus pradžios laikui",
|
||||||
|
"MessageChapterStartIsAfter": "Skyriaus pradžia yra po jūsų garso knygos pabaigos",
|
||||||
|
"MessageCheckingCron": "Tikrinamas cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
|
"MessageConfirmDeleteBackup": "Ar tikrai norite ištrinti atsarginę kopiją, skirtą {0}?",
|
||||||
|
"MessageConfirmDeleteFile": "Tai ištrins failą iš jūsų failų sistemos. Ar tikrai?",
|
||||||
|
"MessageConfirmDeleteLibrary": "Ar tikrai norite visam laikui ištrinti biblioteką \"{0}\"?",
|
||||||
|
"MessageConfirmDeleteSession": "Ar tikrai norite ištrinti šią sesiją?",
|
||||||
|
"MessageConfirmForceReScan": "Ar tikrai norite priversti perskenavimą?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Ar tikrai norite pažymėti visus epizodus kaip užbaigtus?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Ar tikrai norite pažymėti visus epizodus kaip nebaigtus?",
|
||||||
|
"MessageConfirmMarkSeriesFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip užbaigtas?",
|
||||||
|
"MessageConfirmMarkSeriesNotFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip nebaigtas?",
|
||||||
|
"MessageConfirmRemoveAllChapters": "Ar tikrai norite pašalinti visus skyrius?",
|
||||||
|
"MessageConfirmRemoveCollection": "Ar tikrai norite pašalinti kolekciją \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveEpisode": "Ar tikrai norite pašalinti epizodą \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveEpisodes": "Ar tikrai norite pašalinti {0} epizodus?",
|
||||||
|
"MessageConfirmRemoveNarrator": "Ar tikrai norite pašalinti skaitytoją \"{0}\"?",
|
||||||
|
"MessageConfirmRemovePlaylist": "Ar tikrai norite pašalinti savo grojaraštį \"{0}\"?",
|
||||||
|
"MessageConfirmRenameGenre": "Ar tikrai norite pervadinti žanrą \"{0}\" į \"{1}\" visiems elementams?",
|
||||||
|
"MessageConfirmRenameGenreMergeNote": "Pastaba: šis žanras jau yra, todėl jie bus sujungti.",
|
||||||
|
"MessageConfirmRenameGenreWarning": "Įspėjimas! Panašus žanras jau yra \"{0}\".",
|
||||||
|
"MessageConfirmRenameTag": "Ar tikrai norite pervadinti žymą \"{0}\" į \"{1}\" visiems elementams?",
|
||||||
|
"MessageConfirmRenameTagMergeNote": "Pastaba: ši žyma jau egzistuoja, todėl jos bus sujungtos.",
|
||||||
|
"MessageConfirmRenameTagWarning": "Įspėjimas! Panaši žyma jau egzistuoja \"{0}\".",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Ar tikrai norite nusiųsti {0} el. knygą \"{1}\" į įrenginį \"{2}\"?",
|
||||||
|
"MessageDownloadingEpisode": "Epizodas atsisiunčiamas",
|
||||||
|
"MessageDragFilesIntoTrackOrder": "Surikiuokite takelius vilkdami failus",
|
||||||
|
"MessageEmbedFinished": "Įterpimas baigtas!",
|
||||||
|
"MessageEpisodesQueuedForDownload": "{0} epizodai laukia atsisiuntimo",
|
||||||
|
"MessageFeedURLWillBe": "Srauto URL bus {0}",
|
||||||
|
"MessageFetching": "Surenkama...",
|
||||||
|
"MessageForceReScanDescription": "skenuos visus failus lyg iš naujo. Garsinių failų ID3 žymos, OPF failai ir tekstiniai failai bus nuskenuoti kaip nauji.",
|
||||||
|
"MessageImportantNotice": "Svarbus pranešimas!",
|
||||||
|
"MessageInsertChapterBelow": "Įterpti skyrių žemiau",
|
||||||
|
"MessageItemsSelected": "Pasirinkti {0} elementai (-ų)",
|
||||||
|
"MessageItemsUpdated": "Atnaujinti {0} elementai (-ų)",
|
||||||
|
"MessageJoinUsOn": "Prisijunkite prie mūsų",
|
||||||
|
"MessageListeningSessionsInTheLastYear": "{0} klausymo sesijų per paskutinius metus",
|
||||||
|
"MessageLoading": "Kraunama...",
|
||||||
|
"MessageLoadingFolders": "Kraunami aplankai...",
|
||||||
|
"MessageM4BFailed": "M4B Nepavyko!",
|
||||||
|
"MessageM4BFinished": "M4B Baigta!",
|
||||||
|
"MessageMapChapterTitles": "Susieti skyriaus pavadinimus su jūsų esamais garso knygos skyriais, neredaguojant laiko žymų",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Pažymėti visus epizodus kaip užbaigtus",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Pažymėti visus epizodus kaip nebaigtus",
|
||||||
|
"MessageMarkAsFinished": "Pažymėti kaip užbaigtą",
|
||||||
|
"MessageMarkAsNotFinished": "Pažymėti kaip nebaigtą",
|
||||||
|
"MessageMatchBooksDescription": "bandys suderinti bibliotekos knygas su knyga iš pasirinkto paieškos tiekėjo ir užpildys tuščius duomenis ir viršelius. Neperrašo detalių.",
|
||||||
|
"MessageNoAudioTracks": "Nėra garso takelių",
|
||||||
|
"MessageNoAuthors": "Nėra autorių",
|
||||||
|
"MessageNoBackups": "Nėra atsarginių kopijų",
|
||||||
|
"MessageNoBookmarks": "Nėra žymų",
|
||||||
|
"MessageNoChapters": "Nėra skyrių",
|
||||||
|
"MessageNoCollections": "Nėra kolekcijų",
|
||||||
|
"MessageNoCoversFound": "Nerasta viršelių",
|
||||||
|
"MessageNoDescription": "Nėra aprašymo",
|
||||||
|
"MessageNoDownloadsInProgress": "Nėra vykstančių atsisiuntimų",
|
||||||
|
"MessageNoDownloadsQueued": "Nėra eilėje esančių atsisiuntimų",
|
||||||
|
"MessageNoEpisodeMatchesFound": "Nerasta epizodo atitikmenų",
|
||||||
|
"MessageNoEpisodes": "Nėra epizodų",
|
||||||
|
"MessageNoFoldersAvailable": "Nėra prieinamų aplankų",
|
||||||
|
"MessageNoGenres": "Nėra žanrų",
|
||||||
|
"MessageNoIssues": "Nėra problemų",
|
||||||
|
"MessageNoItems": "Nėra elementų",
|
||||||
|
"MessageNoItemsFound": "Elementų nerasta",
|
||||||
|
"MessageNoListeningSessions": "Klausymo sesijų nėra",
|
||||||
|
"MessageNoLogs": "Žurnalo įrašų nėra",
|
||||||
|
"MessageNoMediaProgress": "Nėra medijos pažangos",
|
||||||
|
"MessageNoNotifications": "Nėra pranešimų",
|
||||||
|
"MessageNoPodcastsFound": "Tinklalaidžių nerasta",
|
||||||
|
"MessageNoResults": "Rezultatų nėra",
|
||||||
|
"MessageNoSearchResultsFor": "Paieškos rezultatų nėra „{0}“",
|
||||||
|
"MessageNoSeries": "Serijų nėra",
|
||||||
|
"MessageNoTags": "Žymų nėra",
|
||||||
|
"MessageNoTasksRunning": "Nėra vykstančių užduočių",
|
||||||
|
"MessageNotYetImplemented": "Dar neįgyvendinta",
|
||||||
|
"MessageNoUpdateNecessary": "Atnaujinimai nereikalingi",
|
||||||
|
"MessageNoUpdatesWereNecessary": "Nereikalingi jokie atnaujinimai",
|
||||||
|
"MessageNoUserPlaylists": "Neturite grojaraščių",
|
||||||
|
"MessageOr": "arba",
|
||||||
|
"MessagePauseChapter": "Pristabdyti skyriaus grojimą",
|
||||||
|
"MessagePlayChapter": "Paklausyti skyriaus pradžios",
|
||||||
|
"MessagePlaylistCreateFromCollection": "Sukurti grojaraštį iš kolekcijos",
|
||||||
|
"MessagePodcastHasNoRSSFeedForMatching": "Tinklalidė neturi RSS srauto URL kuriuo būtų galima sulyginti",
|
||||||
|
"MessageQuickMatchDescription": "Užpildykite tuščius elementų duomenis ir viršelius su pirmuoju atitikimo rezultatu iš „{0}“. Neneperrašo detalių, nebent įgalintas serverio nustatymas „Pirmenybė atitaikytiems metaduomenis“.",
|
||||||
|
"MessageRemoveChapter": "Pašalinti skyrių",
|
||||||
|
"MessageRemoveEpisodes": "Pašalinti {0} epizodų (-ą)",
|
||||||
|
"MessageRemoveFromPlayerQueue": "Pašalinti iš grojaraščio",
|
||||||
|
"MessageRemoveUserWarning": "Ar tikrai norite visam laikui ištrinti naudotoją „{0}“?",
|
||||||
|
"MessageReportBugsAndContribute": "Praneškite apie klaidas, prašykite naujovių ir prisidėkite",
|
||||||
|
"MessageResetChaptersConfirm": "Ar tikrai norite atkurti skyrius ir atšaukti pakeitimus, kuriuos atlikote?",
|
||||||
|
"MessageRestoreBackupConfirm": "Ar tikrai norite atkurti atsarginę kopiją, sukurtą",
|
||||||
|
"MessageRestoreBackupWarning": "Atkurdami atsarginę kopiją perrašysite visą duomenų bazę, esančią /config ir viršelių vaizdus /metadata/items ir /metadata/authors.<br /><br />Atsarginės kopijos nekeičia jokių failų jūsų bibliotekos aplankuose. Jei esate įgalinę serverio nustatymus, kad viršelio meną ir metaduomenis saugotumėte savo bibliotekos aplankuose, šie neperrašomi ar atkuriami.<br /><br />Visi klientai, naudojantys jūsų serverį, bus automatiškai atnaujinti.",
|
||||||
|
"MessageSearchResultsFor": "Paieškos rezultatai „{0}“",
|
||||||
|
"MessageServerCouldNotBeReached": "Nepavyko pasiekti serverio",
|
||||||
|
"MessageSetChaptersFromTracksDescription": "Nustatyti skyrius, naudojant kiekvieną garso failą kaip skyrių ir skyriaus pavadinimą kaip garso failo pavadinimą",
|
||||||
|
"MessageStartPlaybackAtTime": "Paleisti klausymą „{0}“ nuo {1}?",
|
||||||
|
"MessageThinking": "Mąstau...",
|
||||||
|
"MessageUploaderItemFailed": "Įkelti nepavyko",
|
||||||
|
"MessageUploaderItemSuccess": "Sėkmingai įkelta!",
|
||||||
|
"MessageUploading": "Įkeliama...",
|
||||||
|
"MessageValidCronExpression": "Galiojanti cron išraiška",
|
||||||
|
"MessageWatcherIsDisabledGlobally": "Serverio nustatymuose stebėtojas išjungtas visuotinai",
|
||||||
|
"MessageXLibraryIsEmpty": "{0} biblioteka tuščia!",
|
||||||
|
"MessageYourAudiobookDurationIsLonger": "Jūsų garso knygos trukmė yra ilgesnė nei rasta trukmė",
|
||||||
|
"MessageYourAudiobookDurationIsShorter": "Jūsų garso knygos trukmė yra trumpesnė nei rasta trukmė",
|
||||||
|
"NoteChangeRootPassword": "Tik root vartotojas gali turėti tuščią slaptažodį",
|
||||||
|
"NoteChapterEditorTimes": "Pastaba: Pirmasis skyriaus pradžios laikas turi likti 0:00, o paskutinio skyriaus pradžios laikas negali viršyti šios garso knygos trukmės.",
|
||||||
|
"NoteFolderPicker": "Pastaba: jau susieti aplankai nebus rodomi",
|
||||||
|
"NoteFolderPickerDebian": "Pastaba: Aplanko pasirinkimo įrankis „Debian“ sistemoje nėra visiškai įgyvendintas. Turėtumėte tiesiogiai įvesti kelią į savo biblioteką.",
|
||||||
|
"NoteRSSFeedPodcastAppsHttps": "Įspėjimas: Dauguma tinklalaidžių programų reikalauja, kad RSS kanalo URL būtų naudojamas su HTTPS",
|
||||||
|
"NoteRSSFeedPodcastAppsPubDate": "Įspėjimas: Vienas ar daugiau jūsų epizodų neturi publikavimo datos. Kai kurios tinklalaidžių programos to reikalauja.",
|
||||||
|
"NoteUploaderFoldersWithMediaFiles": "Aplankai su medijos failais bus tvarkomi kaip atskiri bibliotekos elementai.",
|
||||||
|
"NoteUploaderOnlyAudioFiles": "Jei įkeliami tik garso failai, kiekvienas garso failas bus tvarkomas kaip atskira garso knyga.",
|
||||||
|
"NoteUploaderUnsupportedFiles": "Nepalaikomi failai yra ignoruojami. Pasirinkus ar atidarant aplanką, kiti failai, nesantys elementų aplankuose, yra ignoruojami.",
|
||||||
|
"PlaceholderNewCollection": "Naujas kolekcijos pavadinimas",
|
||||||
|
"PlaceholderNewFolderPath": "Naujas aplanko kelias",
|
||||||
|
"PlaceholderNewPlaylist": "Naujas grojaraščio pavadinimas",
|
||||||
|
"PlaceholderSearch": "Ieškoti..",
|
||||||
|
"PlaceholderSearchEpisode": "Ieškoti epizodo..",
|
||||||
|
"ToastAccountUpdateFailed": "Paskyros atnaujinimas nepavyko",
|
||||||
|
"ToastAccountUpdateSuccess": "Paskyra atnaujinta",
|
||||||
|
"ToastAuthorImageRemoveFailed": "Nepavyko pašalinti autoriaus paveiksliuko",
|
||||||
|
"ToastAuthorImageRemoveSuccess": "Autoriaus paveiksliukas pašalintas",
|
||||||
|
"ToastAuthorUpdateFailed": "Nepavyko atnaujinti autoriaus",
|
||||||
|
"ToastAuthorUpdateMerged": "Autorius sujungtas",
|
||||||
|
"ToastAuthorUpdateSuccess": "Autorius atnaujintas",
|
||||||
|
"ToastAuthorUpdateSuccessNoImageFound": "Autorius atnaujintas (paveiksliukas nerastas)",
|
||||||
|
"ToastBackupCreateFailed": "Atsarginės kopijos sukurti nepavyko",
|
||||||
|
"ToastBackupCreateSuccess": "Atsarginė kopija sukurta",
|
||||||
|
"ToastBackupDeleteFailed": "Atsarginės kopijos ištrinti nepavyko",
|
||||||
|
"ToastBackupDeleteSuccess": "Atsarginė kopija ištrinta",
|
||||||
|
"ToastBackupRestoreFailed": "Atsarginės kopijos atkurti nepavyko",
|
||||||
|
"ToastBackupUploadFailed": "Atsarginės kopijos įkelti nepavyko",
|
||||||
|
"ToastBackupUploadSuccess": "Atsarginė kopija įkelta",
|
||||||
|
"ToastBatchUpdateFailed": "Masinis atnaujinimas nepavyko",
|
||||||
|
"ToastBatchUpdateSuccess": "Masinis atnaujinimas sėkmingas",
|
||||||
|
"ToastBookmarkCreateFailed": "Žymos sukurti nepavyko",
|
||||||
|
"ToastBookmarkCreateSuccess": "Žyma pridėta",
|
||||||
|
"ToastBookmarkRemoveFailed": "Žymos pašalinti nepavyko",
|
||||||
|
"ToastBookmarkRemoveSuccess": "Žyma pašalinta",
|
||||||
|
"ToastBookmarkUpdateFailed": "Žymos atnaujinti nepavyko",
|
||||||
|
"ToastBookmarkUpdateSuccess": "Žyma atnaujinta",
|
||||||
|
"ToastChaptersHaveErrors": "Skyriai turi klaidų",
|
||||||
|
"ToastChaptersMustHaveTitles": "Skyriai turi turėti pavadinimus",
|
||||||
|
"ToastCollectionItemsRemoveFailed": "Elementų pašalinti iš kolekcijos nepavyko",
|
||||||
|
"ToastCollectionItemsRemoveSuccess": "Elementai pašalinti iš kolekcijos",
|
||||||
|
"ToastCollectionRemoveFailed": "Kolekcijos pašalinti nepavyko",
|
||||||
|
"ToastCollectionRemoveSuccess": "Kolekcija pašalinta",
|
||||||
|
"ToastCollectionUpdateFailed": "Kolekcijos atnaujinti nepavyko",
|
||||||
|
"ToastCollectionUpdateSuccess": "Kolekcija atnaujinta",
|
||||||
|
"ToastItemCoverUpdateFailed": "Elemento viršelio atnaujinti nepavyko",
|
||||||
|
"ToastItemCoverUpdateSuccess": "Elemento viršelis atnaujintas",
|
||||||
|
"ToastItemDetailsUpdateFailed": "Elemento detalių atnaujinti nepavyko",
|
||||||
|
"ToastItemDetailsUpdateSuccess": "Elemento detalės atnaujintos",
|
||||||
|
"ToastItemDetailsUpdateUnneeded": "Elemento detalės atnaujinimas nereikalingas",
|
||||||
|
"ToastItemMarkedAsFinishedFailed": "Pažymėti kaip Baigta nepavyko",
|
||||||
|
"ToastItemMarkedAsFinishedSuccess": "Elementas pažymėtas kaip Baigta",
|
||||||
|
"ToastItemMarkedAsNotFinishedFailed": "Pažymėti kaip Nebaigta nepavyko",
|
||||||
|
"ToastItemMarkedAsNotFinishedSuccess": "Elementas pažymėtas kaip Nebaigta",
|
||||||
|
"ToastLibraryCreateFailed": "Bibliotekos sukurti nepavyko",
|
||||||
|
"ToastLibraryCreateSuccess": "Biblioteka \"{0}\" sukurta",
|
||||||
|
"ToastLibraryDeleteFailed": "Bibliotekos ištrinti nepavyko",
|
||||||
|
"ToastLibraryDeleteSuccess": "Biblioteka ištrinta",
|
||||||
|
"ToastLibraryScanFailedToStart": "Nepavyko pradėti bibliotekos skenavimo",
|
||||||
|
"ToastLibraryScanStarted": "Bibliotekos skenavimas pradėtas",
|
||||||
|
"ToastLibraryUpdateFailed": "Bibliotekos atnaujinti nepavyko",
|
||||||
|
"ToastLibraryUpdateSuccess": "Biblioteka \"{0}\" atnaujinta",
|
||||||
|
"ToastPlaylistCreateFailed": "Grojaraščio sukurti nepavyko",
|
||||||
|
"ToastPlaylistCreateSuccess": "Grojaraštis sukurtas",
|
||||||
|
"ToastPlaylistRemoveFailed": "Grojaraščio pašalinti nepavyko",
|
||||||
|
"ToastPlaylistRemoveSuccess": "Grojaraštis pašalintas",
|
||||||
|
"ToastPlaylistUpdateFailed": "Grojaraščio atnaujinti nepavyko",
|
||||||
|
"ToastPlaylistUpdateSuccess": "Grojaraštis atnaujintas",
|
||||||
|
"ToastPodcastCreateFailed": "Tinklalaidės sukurti nepavyko",
|
||||||
|
"ToastPodcastCreateSuccess": "Tinklalaidė sėkmingai sukurta",
|
||||||
|
"ToastRemoveItemFromCollectionFailed": "Elemento pašalinti iš kolekcijos nepavyko",
|
||||||
|
"ToastRemoveItemFromCollectionSuccess": "Elementas pašalintas iš kolekcijos",
|
||||||
|
"ToastRSSFeedCloseFailed": "RSS srauto uždaryti nepavyko",
|
||||||
|
"ToastRSSFeedCloseSuccess": "RSS srautas uždarytas",
|
||||||
|
"ToastSendEbookToDeviceFailed": "Nepavyko nusiųsti e-knygos į įrenginį",
|
||||||
|
"ToastSendEbookToDeviceSuccess": "E-knyga išsiųsta į įrenginį \"{0}\"",
|
||||||
|
"ToastSeriesUpdateFailed": "Serijos atnaujinti nepavyko",
|
||||||
|
"ToastSeriesUpdateSuccess": "Serijos atnaujintos",
|
||||||
|
"ToastSessionDeleteFailed": "Sesijos ištrinti nepavyko",
|
||||||
|
"ToastSessionDeleteSuccess": "Sesija ištrinta",
|
||||||
|
"ToastSocketConnected": "Serveris prijungtas",
|
||||||
|
"ToastSocketDisconnected": "Severis atjungtas",
|
||||||
|
"ToastSocketFailedToConnect": "Nepavyko prisijungti prie serverio",
|
||||||
|
"ToastUserDeleteFailed": "Nepavyko ištrinti naudotojo",
|
||||||
|
"ToastUserDeleteSuccess": "Naudotojas ištrintas"
|
||||||
|
}
|
||||||
+31
-2
@@ -102,7 +102,8 @@
|
|||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Afleveringen",
|
"HeaderEpisodes": "Afleveringen",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Ereader Devices",
|
||||||
|
"HeaderEreaderSettings": "Ereader Settings",
|
||||||
"HeaderFiles": "Bestanden",
|
"HeaderFiles": "Bestanden",
|
||||||
"HeaderFindChapters": "Zoek hoofdstukken",
|
"HeaderFindChapters": "Zoek hoofdstukken",
|
||||||
"HeaderIgnoredFiles": "Genegeerde bestanden",
|
"HeaderIgnoredFiles": "Genegeerde bestanden",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Verwijder {0} afleveringen",
|
"HeaderRemoveEpisodes": "Verwijder {0} afleveringen",
|
||||||
"HeaderRSSFeedGeneral": "RSS-details",
|
"HeaderRSSFeedGeneral": "RSS-details",
|
||||||
"HeaderRSSFeedIsOpen": "RSS-feed is open",
|
"HeaderRSSFeedIsOpen": "RSS-feed is open",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Opgeslagen mediavoortgang",
|
"HeaderSavedMediaProgress": "Opgeslagen mediavoortgang",
|
||||||
"HeaderSchedule": "Schema",
|
"HeaderSchedule": "Schema",
|
||||||
"HeaderScheduleLibraryScans": "Schema automatische bibliotheekscans",
|
"HeaderScheduleLibraryScans": "Schema automatische bibliotheekscans",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Recente sessies",
|
"HeaderStatsRecentSessions": "Recente sessies",
|
||||||
"HeaderStatsTop10Authors": "Top 10 auteurs",
|
"HeaderStatsTop10Authors": "Top 10 auteurs",
|
||||||
"HeaderStatsTop5Genres": "Top 5 genres",
|
"HeaderStatsTop5Genres": "Top 5 genres",
|
||||||
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderTools": "Tools",
|
"HeaderTools": "Tools",
|
||||||
"HeaderUpdateAccount": "Account bijwerken",
|
"HeaderUpdateAccount": "Account bijwerken",
|
||||||
"HeaderUpdateAuthor": "Auteur bijwerken",
|
"HeaderUpdateAuthor": "Auteur bijwerken",
|
||||||
@@ -199,6 +202,7 @@
|
|||||||
"LabelClosePlayer": "Sluit speler",
|
"LabelClosePlayer": "Sluit speler",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Series inklappen",
|
"LabelCollapseSeries": "Series inklappen",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Collecties",
|
"LabelCollections": "Collecties",
|
||||||
"LabelComplete": "Compleet",
|
"LabelComplete": "Compleet",
|
||||||
"LabelConfirmPassword": "Bevestig wachtwoord",
|
"LabelConfirmPassword": "Bevestig wachtwoord",
|
||||||
@@ -220,7 +224,9 @@
|
|||||||
"LabelDirectory": "Map",
|
"LabelDirectory": "Map",
|
||||||
"LabelDiscFromFilename": "Schijf uit bestandsnaam",
|
"LabelDiscFromFilename": "Schijf uit bestandsnaam",
|
||||||
"LabelDiscFromMetadata": "Schijf uit metadata",
|
"LabelDiscFromMetadata": "Schijf uit metadata",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Download",
|
"LabelDownload": "Download",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Duur",
|
"LabelDuration": "Duur",
|
||||||
"LabelDurationFound": "Gevonden duur:",
|
"LabelDurationFound": "Gevonden duur:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Ebook",
|
||||||
@@ -230,6 +236,7 @@
|
|||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Test Address",
|
||||||
"LabelEmbeddedCover": "Ingesloten cover",
|
"LabelEmbeddedCover": "Ingesloten cover",
|
||||||
"LabelEnable": "Inschakelen",
|
"LabelEnable": "Inschakelen",
|
||||||
"LabelEnd": "Einde",
|
"LabelEnd": "Einde",
|
||||||
@@ -248,6 +255,7 @@
|
|||||||
"LabelFinished": "Voltooid",
|
"LabelFinished": "Voltooid",
|
||||||
"LabelFolder": "Map",
|
"LabelFolder": "Map",
|
||||||
"LabelFolders": "Mappen",
|
"LabelFolders": "Mappen",
|
||||||
|
"LabelFontScale": "Font scale",
|
||||||
"LabelFormat": "Format",
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Genre",
|
"LabelGenre": "Genre",
|
||||||
"LabelGenres": "Genres",
|
"LabelGenres": "Genres",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "Laatst gezien",
|
"LabelLastSeen": "Laatst gezien",
|
||||||
"LabelLastTime": "Laatste keer",
|
"LabelLastTime": "Laatste keer",
|
||||||
"LabelLastUpdate": "Laatste update",
|
"LabelLastUpdate": "Laatste update",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Single page",
|
||||||
|
"LabelLayoutSplitPage": "Split page",
|
||||||
"LabelLess": "Minder",
|
"LabelLess": "Minder",
|
||||||
"LabelLibrariesAccessibleToUser": "Voor gebruiker toegankelijke bibliotheken",
|
"LabelLibrariesAccessibleToUser": "Voor gebruiker toegankelijke bibliotheken",
|
||||||
"LabelLibrary": "Bibliotheek",
|
"LabelLibrary": "Bibliotheek",
|
||||||
"LabelLibraryItem": "Library Item",
|
"LabelLibraryItem": "Library Item",
|
||||||
"LabelLibraryName": "Library Name",
|
"LabelLibraryName": "Library Name",
|
||||||
"LabelLimit": "Limiet",
|
"LabelLimit": "Limiet",
|
||||||
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Luister opnieuw",
|
"LabelListenAgain": "Luister opnieuw",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "Nieuw wachtwoord",
|
"LabelNewPassword": "Nieuw wachtwoord",
|
||||||
"LabelNextBackupDate": "Volgende back-up datum",
|
"LabelNextBackupDate": "Volgende back-up datum",
|
||||||
"LabelNextScheduledRun": "Volgende geplande run",
|
"LabelNextScheduledRun": "Volgende geplande run",
|
||||||
|
"LabelNoEpisodesSelected": "No episodes selected",
|
||||||
"LabelNotes": "Notities",
|
"LabelNotes": "Notities",
|
||||||
"LabelNotFinished": "Niet Voltooid",
|
"LabelNotFinished": "Niet Voltooid",
|
||||||
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
"LabelNotificationAppriseURL": "Apprise URL(s)",
|
||||||
@@ -368,6 +381,8 @@
|
|||||||
"LabelSearchTitle": "Zoek titel",
|
"LabelSearchTitle": "Zoek titel",
|
||||||
"LabelSearchTitleOrASIN": "Zoek titel of ASIN",
|
"LabelSearchTitleOrASIN": "Zoek titel of ASIN",
|
||||||
"LabelSeason": "Seizoen",
|
"LabelSeason": "Seizoen",
|
||||||
|
"LabelSelectAllEpisodes": "Select all episodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSendEbookToDevice": "Send Ebook to...",
|
||||||
"LabelSequence": "Sequentie",
|
"LabelSequence": "Sequentie",
|
||||||
"LabelSeries": "Serie",
|
"LabelSeries": "Serie",
|
||||||
@@ -383,10 +398,15 @@
|
|||||||
"LabelSettingsDisableWatcher": "Watcher uitschakelen",
|
"LabelSettingsDisableWatcher": "Watcher uitschakelen",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Map-watcher voor bibliotheek uitschakelen",
|
"LabelSettingsDisableWatcherForLibrary": "Map-watcher voor bibliotheek uitschakelen",
|
||||||
"LabelSettingsDisableWatcherHelp": "Schakelt het automatisch toevoegen/bijwerken van onderdelen wanneer bestandswijzigingen gedetecteerd zijn uit. *Vereist herstart server",
|
"LabelSettingsDisableWatcherHelp": "Schakelt het automatisch toevoegen/bijwerken van onderdelen wanneer bestandswijzigingen gedetecteerd zijn uit. *Vereist herstart server",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Experimentele functies",
|
"LabelSettingsExperimentalFeatures": "Experimentele functies",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Functies in ontwikkeling die je feedback en testing kunnen gebruiken. Klik om de Github-discussie te openen.",
|
"LabelSettingsExperimentalFeaturesHelp": "Functies in ontwikkeling die je feedback en testing kunnen gebruiken. Klik om de Github-discussie te openen.",
|
||||||
"LabelSettingsFindCovers": "Zoek covers",
|
"LabelSettingsFindCovers": "Zoek covers",
|
||||||
"LabelSettingsFindCoversHelp": "Als je audioboek geen ingesloten cover of cover in de map heeft, zal de scanner proberen een cover te vinden.<br>Opmerking: Dit zal de scan-duur verlengen",
|
"LabelSettingsFindCoversHelp": "Als je audioboek geen ingesloten cover of cover in de map heeft, zal de scanner proberen een cover te vinden.<br>Opmerking: Dit zal de scan-duur verlengen",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Hide single book series",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Boekenplank-view voor homepagina",
|
"LabelSettingsHomePageBookshelfView": "Boekenplank-view voor homepagina",
|
||||||
"LabelSettingsLibraryBookshelfView": "Boekenplank-view voor bibliotheek",
|
"LabelSettingsLibraryBookshelfView": "Boekenplank-view voor bibliotheek",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Gebruik Overdrive media markers voor hoofdstukken",
|
"LabelSettingsOverdriveMediaMarkers": "Gebruik Overdrive media markers voor hoofdstukken",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Toon alle",
|
"LabelShowAll": "Toon alle",
|
||||||
"LabelSize": "Grootte",
|
"LabelSize": "Grootte",
|
||||||
"LabelSleepTimer": "Slaaptimer",
|
"LabelSleepTimer": "Slaaptimer",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Start",
|
"LabelStart": "Start",
|
||||||
"LabelStarted": "Gestart",
|
"LabelStarted": "Gestart",
|
||||||
"LabelStartedAt": "Gestart op",
|
"LabelStartedAt": "Gestart op",
|
||||||
@@ -439,6 +460,9 @@
|
|||||||
"LabelTagsAccessibleToUser": "Tags toegankelijk voor de gebruiker",
|
"LabelTagsAccessibleToUser": "Tags toegankelijk voor de gebruiker",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags niet toegankelijk voor de gebruiker",
|
"LabelTagsNotAccessibleToUser": "Tags niet toegankelijk voor de gebruiker",
|
||||||
"LabelTasks": "Lopende taken",
|
"LabelTasks": "Lopende taken",
|
||||||
|
"LabelTheme": "Theme",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeBase": "Tijdsbasis",
|
"LabelTimeBase": "Tijdsbasis",
|
||||||
"LabelTimeListened": "Tijd geluisterd",
|
"LabelTimeListened": "Tijd geluisterd",
|
||||||
"LabelTimeListenedToday": "Tijd geluisterd vandaag",
|
"LabelTimeListenedToday": "Tijd geluisterd vandaag",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Track vanuit metadata",
|
"LabelTrackFromMetadata": "Track vanuit metadata",
|
||||||
"LabelTracks": "Tracks",
|
"LabelTracks": "Tracks",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Type",
|
"LabelType": "Type",
|
||||||
"LabelUnabridged": "Onverkort",
|
"LabelUnabridged": "Onverkort",
|
||||||
@@ -497,11 +522,14 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "Ongeldig: starttijd moet be groter zijn dan of equal aan starttijd van vorig hoofdstuk",
|
"MessageChapterErrorStartLtPrev": "Ongeldig: starttijd moet be groter zijn dan of equal aan starttijd van vorig hoofdstuk",
|
||||||
"MessageChapterStartIsAfter": "Start van hoofdstuk is na het einde van je audioboek",
|
"MessageChapterStartIsAfter": "Start van hoofdstuk is na het einde van je audioboek",
|
||||||
"MessageCheckingCron": "Cron aan het checken...",
|
"MessageCheckingCron": "Cron aan het checken...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Weet je zeker dat je de backup voor {0} wil verwijderen?",
|
"MessageConfirmDeleteBackup": "Weet je zeker dat je de backup voor {0} wil verwijderen?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
||||||
"MessageConfirmDeleteLibrary": "Weet je zeker dat je de bibliotheek \"{0}\" permanent wil verwijderen?",
|
"MessageConfirmDeleteLibrary": "Weet je zeker dat je de bibliotheek \"{0}\" permanent wil verwijderen?",
|
||||||
"MessageConfirmDeleteSession": "Weet je zeker dat je deze sessie wil verwijderen?",
|
"MessageConfirmDeleteSession": "Weet je zeker dat je deze sessie wil verwijderen?",
|
||||||
"MessageConfirmForceReScan": "Weet je zeker dat je geforceerd opnieuw wil scannen?",
|
"MessageConfirmForceReScan": "Weet je zeker dat je geforceerd opnieuw wil scannen?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als voltooid?",
|
"MessageConfirmMarkSeriesFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als voltooid?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als niet voltooid?",
|
"MessageConfirmMarkSeriesNotFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als niet voltooid?",
|
||||||
"MessageConfirmRemoveAllChapters": "Weet je zeker dat je alle hoofdstukken wil verwijderen?",
|
"MessageConfirmRemoveAllChapters": "Weet je zeker dat je alle hoofdstukken wil verwijderen?",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "M4B mislukt!",
|
"MessageM4BFailed": "M4B mislukt!",
|
||||||
"MessageM4BFinished": "M4B voltooid!",
|
"MessageM4BFinished": "M4B voltooid!",
|
||||||
"MessageMapChapterTitles": "Map hoofdstuktitels naar je bestaande audioboekhoofdstukken zonder aanpassing van tijden",
|
"MessageMapChapterTitles": "Map hoofdstuktitels naar je bestaande audioboekhoofdstukken zonder aanpassing van tijden",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Mark all episodes finished",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished",
|
||||||
"MessageMarkAsFinished": "Markeer als Voltooid",
|
"MessageMarkAsFinished": "Markeer als Voltooid",
|
||||||
"MessageMarkAsNotFinished": "Markeer als Niet Voltooid",
|
"MessageMarkAsNotFinished": "Markeer als Niet Voltooid",
|
||||||
"MessageMatchBooksDescription": "zal proberen boeken in de bibliotheek te matchen met een boek uit de geselecteerde bron en lege details en coverafbeelding te vullen. Overschrijft details niet.",
|
"MessageMatchBooksDescription": "zal proberen boeken in de bibliotheek te matchen met een boek uit de geselecteerde bron en lege details en coverafbeelding te vullen. Overschrijft details niet.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Afspeellijst aanmaken vanuit collectie",
|
"MessagePlaylistCreateFromCollection": "Afspeellijst aanmaken vanuit collectie",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast heeft geen RSS-feed URL om te gebruiken voor matching",
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast heeft geen RSS-feed URL om te gebruiken voor matching",
|
||||||
"MessageQuickMatchDescription": "Vul lege onderdeeldetails & cover met eerste matchresultaat van '{0}'. Overschrijft geen details tenzij 'Prefereer gematchte metadata' serverinstelling is ingeschakeld.",
|
"MessageQuickMatchDescription": "Vul lege onderdeeldetails & cover met eerste matchresultaat van '{0}'. Overschrijft geen details tenzij 'Prefereer gematchte metadata' serverinstelling is ingeschakeld.",
|
||||||
"MessageRemoveAllItemsWarning": "WAARSCHUWING! Deze actie zal alle onderdelen in de bibliotheek verwijderen uit de database, inclusief enige bijwerkingen of matches die je hebt gemaakt. Dit doet niets met je onderliggende bestanden. Weet je het zeker?",
|
|
||||||
"MessageRemoveChapter": "Verwijder hoofdstuk",
|
"MessageRemoveChapter": "Verwijder hoofdstuk",
|
||||||
"MessageRemoveEpisodes": "Verwijder {0} aflevering(en)",
|
"MessageRemoveEpisodes": "Verwijder {0} aflevering(en)",
|
||||||
"MessageRemoveFromPlayerQueue": "Verwijder uit afspeelwachtrij",
|
"MessageRemoveFromPlayerQueue": "Verwijder uit afspeelwachtrij",
|
||||||
|
|||||||
+31
-2
@@ -102,7 +102,8 @@
|
|||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Email Settings",
|
||||||
"HeaderEpisodes": "Rozdziały",
|
"HeaderEpisodes": "Rozdziały",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Ereader Devices",
|
||||||
|
"HeaderEreaderSettings": "Ereader Settings",
|
||||||
"HeaderFiles": "Pliki",
|
"HeaderFiles": "Pliki",
|
||||||
"HeaderFindChapters": "Wyszukaj rozdziały",
|
"HeaderFindChapters": "Wyszukaj rozdziały",
|
||||||
"HeaderIgnoredFiles": "Zignoruj pliki",
|
"HeaderIgnoredFiles": "Zignoruj pliki",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Usuń {0} odcinków",
|
"HeaderRemoveEpisodes": "Usuń {0} odcinków",
|
||||||
"HeaderRSSFeedGeneral": "RSS Details",
|
"HeaderRSSFeedGeneral": "RSS Details",
|
||||||
"HeaderRSSFeedIsOpen": "Kanał RSS jest otwarty",
|
"HeaderRSSFeedIsOpen": "Kanał RSS jest otwarty",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Zapisany postęp",
|
"HeaderSavedMediaProgress": "Zapisany postęp",
|
||||||
"HeaderSchedule": "Harmonogram",
|
"HeaderSchedule": "Harmonogram",
|
||||||
"HeaderScheduleLibraryScans": "Zaplanuj automatyczne skanowanie biblioteki",
|
"HeaderScheduleLibraryScans": "Zaplanuj automatyczne skanowanie biblioteki",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Ostatnie sesje",
|
"HeaderStatsRecentSessions": "Ostatnie sesje",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Autorów",
|
"HeaderStatsTop10Authors": "Top 10 Autorów",
|
||||||
"HeaderStatsTop5Genres": "Top 5 Gatunków",
|
"HeaderStatsTop5Genres": "Top 5 Gatunków",
|
||||||
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderTools": "Narzędzia",
|
"HeaderTools": "Narzędzia",
|
||||||
"HeaderUpdateAccount": "Zaktualizuj konto",
|
"HeaderUpdateAccount": "Zaktualizuj konto",
|
||||||
"HeaderUpdateAuthor": "Zaktualizuj autorów",
|
"HeaderUpdateAuthor": "Zaktualizuj autorów",
|
||||||
@@ -199,6 +202,7 @@
|
|||||||
"LabelClosePlayer": "Zamknij odtwarzacz",
|
"LabelClosePlayer": "Zamknij odtwarzacz",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Codec",
|
||||||
"LabelCollapseSeries": "Podsumuj serię",
|
"LabelCollapseSeries": "Podsumuj serię",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Kolekcje",
|
"LabelCollections": "Kolekcje",
|
||||||
"LabelComplete": "Ukończone",
|
"LabelComplete": "Ukończone",
|
||||||
"LabelConfirmPassword": "Potwierdź hasło",
|
"LabelConfirmPassword": "Potwierdź hasło",
|
||||||
@@ -220,7 +224,9 @@
|
|||||||
"LabelDirectory": "Katalog",
|
"LabelDirectory": "Katalog",
|
||||||
"LabelDiscFromFilename": "Oznaczenie dysku z nazwy pliku",
|
"LabelDiscFromFilename": "Oznaczenie dysku z nazwy pliku",
|
||||||
"LabelDiscFromMetadata": "Oznaczenie dysku z metadanych",
|
"LabelDiscFromMetadata": "Oznaczenie dysku z metadanych",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Pobierz",
|
"LabelDownload": "Pobierz",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Czas trwania",
|
"LabelDuration": "Czas trwania",
|
||||||
"LabelDurationFound": "Znaleziona długość:",
|
"LabelDurationFound": "Znaleziona długość:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "Ebook",
|
||||||
@@ -230,6 +236,7 @@
|
|||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "From Address",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Secure",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "Test Address",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmbeddedCover": "Embedded Cover",
|
||||||
"LabelEnable": "Włącz",
|
"LabelEnable": "Włącz",
|
||||||
"LabelEnd": "Zakończ",
|
"LabelEnd": "Zakończ",
|
||||||
@@ -248,6 +255,7 @@
|
|||||||
"LabelFinished": "Zakończone",
|
"LabelFinished": "Zakończone",
|
||||||
"LabelFolder": "Folder",
|
"LabelFolder": "Folder",
|
||||||
"LabelFolders": "Foldery",
|
"LabelFolders": "Foldery",
|
||||||
|
"LabelFontScale": "Font scale",
|
||||||
"LabelFormat": "Format",
|
"LabelFormat": "Format",
|
||||||
"LabelGenre": "Gatunek",
|
"LabelGenre": "Gatunek",
|
||||||
"LabelGenres": "Gatunki",
|
"LabelGenres": "Gatunki",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "Ostatnio widziany",
|
"LabelLastSeen": "Ostatnio widziany",
|
||||||
"LabelLastTime": "Ostatni czas",
|
"LabelLastTime": "Ostatni czas",
|
||||||
"LabelLastUpdate": "Ostatnia aktualizacja",
|
"LabelLastUpdate": "Ostatnia aktualizacja",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Single page",
|
||||||
|
"LabelLayoutSplitPage": "Split page",
|
||||||
"LabelLess": "Mniej",
|
"LabelLess": "Mniej",
|
||||||
"LabelLibrariesAccessibleToUser": "Biblioteki dostępne dla użytkownika",
|
"LabelLibrariesAccessibleToUser": "Biblioteki dostępne dla użytkownika",
|
||||||
"LabelLibrary": "Biblioteka",
|
"LabelLibrary": "Biblioteka",
|
||||||
"LabelLibraryItem": "Element biblioteki",
|
"LabelLibraryItem": "Element biblioteki",
|
||||||
"LabelLibraryName": "Nazwa biblioteki",
|
"LabelLibraryName": "Nazwa biblioteki",
|
||||||
"LabelLimit": "Limit",
|
"LabelLimit": "Limit",
|
||||||
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Słuchaj ponownie",
|
"LabelListenAgain": "Słuchaj ponownie",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Informacja",
|
"LabelLogLevelInfo": "Informacja",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "Nowe hasło",
|
"LabelNewPassword": "Nowe hasło",
|
||||||
"LabelNextBackupDate": "Next backup date",
|
"LabelNextBackupDate": "Next backup date",
|
||||||
"LabelNextScheduledRun": "Next scheduled run",
|
"LabelNextScheduledRun": "Next scheduled run",
|
||||||
|
"LabelNoEpisodesSelected": "No episodes selected",
|
||||||
"LabelNotes": "Uwagi",
|
"LabelNotes": "Uwagi",
|
||||||
"LabelNotFinished": "Nieukończone",
|
"LabelNotFinished": "Nieukończone",
|
||||||
"LabelNotificationAppriseURL": "URLe Apprise",
|
"LabelNotificationAppriseURL": "URLe Apprise",
|
||||||
@@ -368,6 +381,8 @@
|
|||||||
"LabelSearchTitle": "Wyszukaj tytuł",
|
"LabelSearchTitle": "Wyszukaj tytuł",
|
||||||
"LabelSearchTitleOrASIN": "Szukaj tytuł lub ASIN",
|
"LabelSearchTitleOrASIN": "Szukaj tytuł lub ASIN",
|
||||||
"LabelSeason": "Sezon",
|
"LabelSeason": "Sezon",
|
||||||
|
"LabelSelectAllEpisodes": "Select all episodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSendEbookToDevice": "Send Ebook to...",
|
||||||
"LabelSequence": "Kolejność",
|
"LabelSequence": "Kolejność",
|
||||||
"LabelSeries": "Serie",
|
"LabelSeries": "Serie",
|
||||||
@@ -383,10 +398,15 @@
|
|||||||
"LabelSettingsDisableWatcher": "Wyłącz monitorowanie",
|
"LabelSettingsDisableWatcher": "Wyłącz monitorowanie",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Wyłącz monitorowanie folderów dla biblioteki",
|
"LabelSettingsDisableWatcherForLibrary": "Wyłącz monitorowanie folderów dla biblioteki",
|
||||||
"LabelSettingsDisableWatcherHelp": "Wyłącz automatyczne dodawanie/aktualizowanie elementów po wykryciu zmian w plikach. *Wymaga restartu serwera",
|
"LabelSettingsDisableWatcherHelp": "Wyłącz automatyczne dodawanie/aktualizowanie elementów po wykryciu zmian w plikach. *Wymaga restartu serwera",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Funkcje eksperymentalne",
|
"LabelSettingsExperimentalFeatures": "Funkcje eksperymentalne",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Funkcje w trakcie rozwoju, które mogą zyskanć na Twojej opinii i pomocy w testowaniu. Kliknij, aby otworzyć dyskusję na githubie.",
|
"LabelSettingsExperimentalFeaturesHelp": "Funkcje w trakcie rozwoju, które mogą zyskanć na Twojej opinii i pomocy w testowaniu. Kliknij, aby otworzyć dyskusję na githubie.",
|
||||||
"LabelSettingsFindCovers": "Szukanie okładek",
|
"LabelSettingsFindCovers": "Szukanie okładek",
|
||||||
"LabelSettingsFindCoversHelp": "Jeśli audiobook nie posiada zintegrowanej okładki albo w folderze nie zostanie znaleziony plik okładki, skaner podejmie próbę pobrania okładki z sieci. <br>Uwaga: może to wydłuzyć proces skanowania",
|
"LabelSettingsFindCoversHelp": "Jeśli audiobook nie posiada zintegrowanej okładki albo w folderze nie zostanie znaleziony plik okładki, skaner podejmie próbę pobrania okładki z sieci. <br>Uwaga: może to wydłuzyć proces skanowania",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Hide single book series",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Widok półki z książkami na stronie głównej",
|
"LabelSettingsHomePageBookshelfView": "Widok półki z książkami na stronie głównej",
|
||||||
"LabelSettingsLibraryBookshelfView": "Widok półki z książkami na stronie biblioteki",
|
"LabelSettingsLibraryBookshelfView": "Widok półki z książkami na stronie biblioteki",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Użyj markerów Overdrive Media Markers dla rozdziałów",
|
"LabelSettingsOverdriveMediaMarkers": "Użyj markerów Overdrive Media Markers dla rozdziałów",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Pokaż wszystko",
|
"LabelShowAll": "Pokaż wszystko",
|
||||||
"LabelSize": "Rozmiar",
|
"LabelSize": "Rozmiar",
|
||||||
"LabelSleepTimer": "Wyłącznik czasowy",
|
"LabelSleepTimer": "Wyłącznik czasowy",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Rozpocznij",
|
"LabelStart": "Rozpocznij",
|
||||||
"LabelStarted": "Rozpoczęty",
|
"LabelStarted": "Rozpoczęty",
|
||||||
"LabelStartedAt": "Rozpoczęto",
|
"LabelStartedAt": "Rozpoczęto",
|
||||||
@@ -439,6 +460,9 @@
|
|||||||
"LabelTagsAccessibleToUser": "Tagi dostępne dla użytkownika",
|
"LabelTagsAccessibleToUser": "Tagi dostępne dla użytkownika",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
||||||
"LabelTasks": "Tasks Running",
|
"LabelTasks": "Tasks Running",
|
||||||
|
"LabelTheme": "Theme",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
"LabelTimeBase": "Time Base",
|
"LabelTimeBase": "Time Base",
|
||||||
"LabelTimeListened": "Czas odtwarzania",
|
"LabelTimeListened": "Czas odtwarzania",
|
||||||
"LabelTimeListenedToday": "Czas odtwarzania dzisiaj",
|
"LabelTimeListenedToday": "Czas odtwarzania dzisiaj",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Ścieżka z metadanych",
|
"LabelTrackFromMetadata": "Ścieżka z metadanych",
|
||||||
"LabelTracks": "Tracks",
|
"LabelTracks": "Tracks",
|
||||||
"LabelTracksMultiTrack": "Multi-track",
|
"LabelTracksMultiTrack": "Multi-track",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Single-track",
|
"LabelTracksSingleTrack": "Single-track",
|
||||||
"LabelType": "Typ",
|
"LabelType": "Typ",
|
||||||
"LabelUnabridged": "Unabridged",
|
"LabelUnabridged": "Unabridged",
|
||||||
@@ -497,11 +522,14 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
"MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time",
|
||||||
"MessageChapterStartIsAfter": "Początek rozdziału następuje po zakończeniu audiobooka",
|
"MessageChapterStartIsAfter": "Początek rozdziału następuje po zakończeniu audiobooka",
|
||||||
"MessageCheckingCron": "Sprawdzanie cron...",
|
"MessageCheckingCron": "Sprawdzanie cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Czy na pewno chcesz usunąć kopię zapasową dla {0}?",
|
"MessageConfirmDeleteBackup": "Czy na pewno chcesz usunąć kopię zapasową dla {0}?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
||||||
"MessageConfirmDeleteLibrary": "Czy na pewno chcesz trwale usunąć bibliotekę \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Czy na pewno chcesz trwale usunąć bibliotekę \"{0}\"?",
|
||||||
"MessageConfirmDeleteSession": "Czy na pewno chcesz usunąć tę sesję?",
|
"MessageConfirmDeleteSession": "Czy na pewno chcesz usunąć tę sesję?",
|
||||||
"MessageConfirmForceReScan": "Czy na pewno chcesz wymusić ponowne skanowanie?",
|
"MessageConfirmForceReScan": "Czy na pewno chcesz wymusić ponowne skanowanie?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "Tworzenie pliku M4B nie powiodło się",
|
"MessageM4BFailed": "Tworzenie pliku M4B nie powiodło się",
|
||||||
"MessageM4BFinished": "Tworzenie pliku M4B zakończyło się!",
|
"MessageM4BFinished": "Tworzenie pliku M4B zakończyło się!",
|
||||||
"MessageMapChapterTitles": "Mapowanie tytułów rozdziałów do istniejących rozdziałów audiobooka bez dostosowywania znaczników czasu",
|
"MessageMapChapterTitles": "Mapowanie tytułów rozdziałów do istniejących rozdziałów audiobooka bez dostosowywania znaczników czasu",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Mark all episodes finished",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished",
|
||||||
"MessageMarkAsFinished": "Oznacz jako ukończone",
|
"MessageMarkAsFinished": "Oznacz jako ukończone",
|
||||||
"MessageMarkAsNotFinished": "Oznacz jako nieukończone",
|
"MessageMarkAsNotFinished": "Oznacz jako nieukończone",
|
||||||
"MessageMatchBooksDescription": "spróbuje dopasować książki w bibliotece bez plików audio, korzystając z wybranego dostawcy wyszukiwania i wypełnić puste szczegóły i okładki. Nie nadpisuje informacji.",
|
"MessageMatchBooksDescription": "spróbuje dopasować książki w bibliotece bez plików audio, korzystając z wybranego dostawcy wyszukiwania i wypełnić puste szczegóły i okładki. Nie nadpisuje informacji.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nie ma adresu url kanału RSS, który mógłby zostać użyty do dopasowania",
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nie ma adresu url kanału RSS, który mógłby zostać użyty do dopasowania",
|
||||||
"MessageQuickMatchDescription": "Wypełnij puste informacje i okładkę pierwszym wynikiem dopasowania z '{0}'. Nie nadpisuje szczegółów, chyba że włączone jest ustawienie serwera 'Preferuj dopasowane metadane'.",
|
"MessageQuickMatchDescription": "Wypełnij puste informacje i okładkę pierwszym wynikiem dopasowania z '{0}'. Nie nadpisuje szczegółów, chyba że włączone jest ustawienie serwera 'Preferuj dopasowane metadane'.",
|
||||||
"MessageRemoveAllItemsWarning": "UWAGA! Ta akcja usunie wszystkie elementy biblioteki z bazy danych, w tym wszystkie aktualizacje lub dopasowania, które zostały wykonane. Pliki pozostaną niezmienione. Czy jesteś pewien?",
|
|
||||||
"MessageRemoveChapter": "Usuń rozdział",
|
"MessageRemoveChapter": "Usuń rozdział",
|
||||||
"MessageRemoveEpisodes": "Usuń {0} odcinków",
|
"MessageRemoveEpisodes": "Usuń {0} odcinków",
|
||||||
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
"MessageRemoveFromPlayerQueue": "Remove from player queue",
|
||||||
|
|||||||
+74
-45
@@ -55,7 +55,7 @@
|
|||||||
"ButtonRemoveAll": "Удалить всё",
|
"ButtonRemoveAll": "Удалить всё",
|
||||||
"ButtonRemoveAllLibraryItems": "Удалить все элементы библиотеки",
|
"ButtonRemoveAllLibraryItems": "Удалить все элементы библиотеки",
|
||||||
"ButtonRemoveFromContinueListening": "Удалить из Продолжить слушать",
|
"ButtonRemoveFromContinueListening": "Удалить из Продолжить слушать",
|
||||||
"ButtonRemoveFromContinueReading": "Remove from Continue Reading",
|
"ButtonRemoveFromContinueReading": "Удалить из Продолжить читать",
|
||||||
"ButtonRemoveSeriesFromContinueSeries": "Удалить серию из Продолжить серию",
|
"ButtonRemoveSeriesFromContinueSeries": "Удалить серию из Продолжить серию",
|
||||||
"ButtonReScan": "Пересканировать",
|
"ButtonReScan": "Пересканировать",
|
||||||
"ButtonReset": "Сбросить",
|
"ButtonReset": "Сбросить",
|
||||||
@@ -98,11 +98,12 @@
|
|||||||
"HeaderCurrentDownloads": "Текущие закачки",
|
"HeaderCurrentDownloads": "Текущие закачки",
|
||||||
"HeaderDetails": "Подробности",
|
"HeaderDetails": "Подробности",
|
||||||
"HeaderDownloadQueue": "Очередь скачивания",
|
"HeaderDownloadQueue": "Очередь скачивания",
|
||||||
"HeaderEbookFiles": "Ebook Files",
|
"HeaderEbookFiles": "Файлы e-книг",
|
||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "Email",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "Настройки Email",
|
||||||
"HeaderEpisodes": "Эпизоды",
|
"HeaderEpisodes": "Эпизоды",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Устройства E-книга",
|
||||||
|
"HeaderEreaderSettings": "Ereader Settings",
|
||||||
"HeaderFiles": "Файлы",
|
"HeaderFiles": "Файлы",
|
||||||
"HeaderFindChapters": "Найти главы",
|
"HeaderFindChapters": "Найти главы",
|
||||||
"HeaderIgnoredFiles": "Игнорируемые Файлы",
|
"HeaderIgnoredFiles": "Игнорируемые Файлы",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "Удалить {0} эпизодов",
|
"HeaderRemoveEpisodes": "Удалить {0} эпизодов",
|
||||||
"HeaderRSSFeedGeneral": "Сведения о RSS",
|
"HeaderRSSFeedGeneral": "Сведения о RSS",
|
||||||
"HeaderRSSFeedIsOpen": "RSS-канал открыт",
|
"HeaderRSSFeedIsOpen": "RSS-канал открыт",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "Прогресс медиа сохранен",
|
"HeaderSavedMediaProgress": "Прогресс медиа сохранен",
|
||||||
"HeaderSchedule": "Планировщик",
|
"HeaderSchedule": "Планировщик",
|
||||||
"HeaderScheduleLibraryScans": "Планировщик автоматического сканирования библиотеки",
|
"HeaderScheduleLibraryScans": "Планировщик автоматического сканирования библиотеки",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "Последние сеансы",
|
"HeaderStatsRecentSessions": "Последние сеансы",
|
||||||
"HeaderStatsTop10Authors": "Топ 10 авторов",
|
"HeaderStatsTop10Authors": "Топ 10 авторов",
|
||||||
"HeaderStatsTop5Genres": "Топ 5 жанров",
|
"HeaderStatsTop5Genres": "Топ 5 жанров",
|
||||||
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderTools": "Инструменты",
|
"HeaderTools": "Инструменты",
|
||||||
"HeaderUpdateAccount": "Обновить учетную запись",
|
"HeaderUpdateAccount": "Обновить учетную запись",
|
||||||
"HeaderUpdateAuthor": "Обновить автора",
|
"HeaderUpdateAuthor": "Обновить автора",
|
||||||
@@ -167,7 +170,7 @@
|
|||||||
"LabelAccountTypeGuest": "Гость",
|
"LabelAccountTypeGuest": "Гость",
|
||||||
"LabelAccountTypeUser": "Пользователь",
|
"LabelAccountTypeUser": "Пользователь",
|
||||||
"LabelActivity": "Активность",
|
"LabelActivity": "Активность",
|
||||||
"LabelAdded": "Added",
|
"LabelAdded": "Добавили",
|
||||||
"LabelAddedAt": "Дата добавления",
|
"LabelAddedAt": "Дата добавления",
|
||||||
"LabelAddToCollection": "Добавить в коллекцию",
|
"LabelAddToCollection": "Добавить в коллекцию",
|
||||||
"LabelAddToCollectionBatch": "Добавить {0} книг в коллекцию",
|
"LabelAddToCollectionBatch": "Добавить {0} книг в коллекцию",
|
||||||
@@ -189,21 +192,22 @@
|
|||||||
"LabelBackupsMaxBackupSizeHelp": "В качестве защиты процесс бэкапирования будет завершаться ошибкой, если будет превышен настроенный размер.",
|
"LabelBackupsMaxBackupSizeHelp": "В качестве защиты процесс бэкапирования будет завершаться ошибкой, если будет превышен настроенный размер.",
|
||||||
"LabelBackupsNumberToKeep": "Сохранять бэкапов",
|
"LabelBackupsNumberToKeep": "Сохранять бэкапов",
|
||||||
"LabelBackupsNumberToKeepHelp": "За один раз только 1 бэкап будет удален, так что если у вас будет больше бэкапов, то их нужно удалить вручную.",
|
"LabelBackupsNumberToKeepHelp": "За один раз только 1 бэкап будет удален, так что если у вас будет больше бэкапов, то их нужно удалить вручную.",
|
||||||
"LabelBitrate": "Bitrate",
|
"LabelBitrate": "Битрейт",
|
||||||
"LabelBooks": "Книги",
|
"LabelBooks": "Книги",
|
||||||
"LabelChangePassword": "Изменить пароль",
|
"LabelChangePassword": "Изменить пароль",
|
||||||
"LabelChannels": "Channels",
|
"LabelChannels": "Каналы",
|
||||||
"LabelChapters": "Chapters",
|
"LabelChapters": "Главы",
|
||||||
"LabelChaptersFound": "глав найдено",
|
"LabelChaptersFound": "глав найдено",
|
||||||
"LabelChapterTitle": "Название главы",
|
"LabelChapterTitle": "Название главы",
|
||||||
"LabelClosePlayer": "Закрыть проигрыватель",
|
"LabelClosePlayer": "Закрыть проигрыватель",
|
||||||
"LabelCodec": "Codec",
|
"LabelCodec": "Кодек",
|
||||||
"LabelCollapseSeries": "Свернуть серии",
|
"LabelCollapseSeries": "Свернуть серии",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "Коллекции",
|
"LabelCollections": "Коллекции",
|
||||||
"LabelComplete": "Завершить",
|
"LabelComplete": "Завершить",
|
||||||
"LabelConfirmPassword": "Подтвердить пароль",
|
"LabelConfirmPassword": "Подтвердить пароль",
|
||||||
"LabelContinueListening": "Продолжить слушать",
|
"LabelContinueListening": "Продолжить слушать",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Продолжить читать",
|
||||||
"LabelContinueSeries": "Продолжить серию",
|
"LabelContinueSeries": "Продолжить серию",
|
||||||
"LabelCover": "Обложка",
|
"LabelCover": "Обложка",
|
||||||
"LabelCoverImageURL": "URL изображения обложки",
|
"LabelCoverImageURL": "URL изображения обложки",
|
||||||
@@ -220,17 +224,20 @@
|
|||||||
"LabelDirectory": "Каталог",
|
"LabelDirectory": "Каталог",
|
||||||
"LabelDiscFromFilename": "Диск из Имени файла",
|
"LabelDiscFromFilename": "Диск из Имени файла",
|
||||||
"LabelDiscFromMetadata": "Диск из Метаданных",
|
"LabelDiscFromMetadata": "Диск из Метаданных",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "Скачать",
|
"LabelDownload": "Скачать",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "Длина",
|
"LabelDuration": "Длина",
|
||||||
"LabelDurationFound": "Найденная длина:",
|
"LabelDurationFound": "Найденная длина:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "E-книга",
|
||||||
"LabelEbooks": "Ebooks",
|
"LabelEbooks": "E-книги",
|
||||||
"LabelEdit": "Редактировать",
|
"LabelEdit": "Редактировать",
|
||||||
"LabelEmail": "Email",
|
"LabelEmail": "Email",
|
||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "Адрес От",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "Безопасность",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "Если значение истинно, то соединение будет использовать TLS при подключении к серверу. Если значение ложно, то TLS будет использован, если сервер поддерживает расширение STARTTLS. В большинстве случаев установите это значение в истину, если вы подключаетесь к порту 465. Для порта 587 или 25 оставьте значение ложным. (из nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmbeddedCover": "Embedded Cover",
|
"LabelEmailSettingsTestAddress": "Тестовый адрес",
|
||||||
|
"LabelEmbeddedCover": "Встроенная обложка",
|
||||||
"LabelEnable": "Включить",
|
"LabelEnable": "Включить",
|
||||||
"LabelEnd": "Конец",
|
"LabelEnd": "Конец",
|
||||||
"LabelEpisode": "Эпизод",
|
"LabelEpisode": "Эпизод",
|
||||||
@@ -248,13 +255,14 @@
|
|||||||
"LabelFinished": "Закончен",
|
"LabelFinished": "Закончен",
|
||||||
"LabelFolder": "Папка",
|
"LabelFolder": "Папка",
|
||||||
"LabelFolders": "Папки",
|
"LabelFolders": "Папки",
|
||||||
"LabelFormat": "Format",
|
"LabelFontScale": "Font scale",
|
||||||
|
"LabelFormat": "Формат",
|
||||||
"LabelGenre": "Жанр",
|
"LabelGenre": "Жанр",
|
||||||
"LabelGenres": "Жанры",
|
"LabelGenres": "Жанры",
|
||||||
"LabelHardDeleteFile": "Жесткое удаление файла",
|
"LabelHardDeleteFile": "Жесткое удаление файла",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "Есть e-книга",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "Есть дополнительная e-книга",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Хост",
|
||||||
"LabelHour": "Часы",
|
"LabelHour": "Часы",
|
||||||
"LabelIcon": "Иконка",
|
"LabelIcon": "Иконка",
|
||||||
"LabelIncludeInTracklist": "Включать в список воспроизведения",
|
"LabelIncludeInTracklist": "Включать в список воспроизведения",
|
||||||
@@ -270,21 +278,25 @@
|
|||||||
"LabelIntervalEveryDay": "Каждый день",
|
"LabelIntervalEveryDay": "Каждый день",
|
||||||
"LabelIntervalEveryHour": "Каждый час",
|
"LabelIntervalEveryHour": "Каждый час",
|
||||||
"LabelInvalidParts": "Неверные части",
|
"LabelInvalidParts": "Неверные части",
|
||||||
"LabelInvert": "Invert",
|
"LabelInvert": "Инвертировать",
|
||||||
"LabelItem": "Элемент",
|
"LabelItem": "Элемент",
|
||||||
"LabelLanguage": "Язык",
|
"LabelLanguage": "Язык",
|
||||||
"LabelLanguageDefaultServer": "Язык сервера по умолчанию",
|
"LabelLanguageDefaultServer": "Язык сервера по умолчанию",
|
||||||
"LabelLastBookAdded": "Last Book Added",
|
"LabelLastBookAdded": "Последняя книга добавлена",
|
||||||
"LabelLastBookUpdated": "Last Book Updated",
|
"LabelLastBookUpdated": "Последняя книга обновлена",
|
||||||
"LabelLastSeen": "Последнее сканирование",
|
"LabelLastSeen": "Последнее сканирование",
|
||||||
"LabelLastTime": "Последний по времени",
|
"LabelLastTime": "Последний по времени",
|
||||||
"LabelLastUpdate": "Последний обновленный",
|
"LabelLastUpdate": "Последний обновленный",
|
||||||
|
"LabelLayout": "Layout",
|
||||||
|
"LabelLayoutSinglePage": "Single page",
|
||||||
|
"LabelLayoutSplitPage": "Split page",
|
||||||
"LabelLess": "Менее",
|
"LabelLess": "Менее",
|
||||||
"LabelLibrariesAccessibleToUser": "Библиотеки доступные для пользователя",
|
"LabelLibrariesAccessibleToUser": "Библиотеки доступные для пользователя",
|
||||||
"LabelLibrary": "Библиотека",
|
"LabelLibrary": "Библиотека",
|
||||||
"LabelLibraryItem": "Элемент библиотеки",
|
"LabelLibraryItem": "Элемент библиотеки",
|
||||||
"LabelLibraryName": "Имя библиотеки",
|
"LabelLibraryName": "Имя библиотеки",
|
||||||
"LabelLimit": "Лимит",
|
"LabelLimit": "Лимит",
|
||||||
|
"LabelLineSpacing": "Line spacing",
|
||||||
"LabelListenAgain": "Послушать снова",
|
"LabelListenAgain": "Послушать снова",
|
||||||
"LabelLogLevelDebug": "Debug",
|
"LabelLogLevelDebug": "Debug",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
@@ -294,12 +306,12 @@
|
|||||||
"LabelMediaType": "Тип медиа",
|
"LabelMediaType": "Тип медиа",
|
||||||
"LabelMetadataProvider": "Провайдер",
|
"LabelMetadataProvider": "Провайдер",
|
||||||
"LabelMetaTag": "Мета тег",
|
"LabelMetaTag": "Мета тег",
|
||||||
"LabelMetaTags": "Meta Tags",
|
"LabelMetaTags": "Мета теги",
|
||||||
"LabelMinute": "Минуты",
|
"LabelMinute": "Минуты",
|
||||||
"LabelMissing": "Потеряно",
|
"LabelMissing": "Потеряно",
|
||||||
"LabelMissingParts": "Потерянные части",
|
"LabelMissingParts": "Потерянные части",
|
||||||
"LabelMore": "Еще",
|
"LabelMore": "Еще",
|
||||||
"LabelMoreInfo": "More Info",
|
"LabelMoreInfo": "Больше информации",
|
||||||
"LabelName": "Имя",
|
"LabelName": "Имя",
|
||||||
"LabelNarrator": "Читает",
|
"LabelNarrator": "Читает",
|
||||||
"LabelNarrators": "Чтецы",
|
"LabelNarrators": "Чтецы",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "Новый пароль",
|
"LabelNewPassword": "Новый пароль",
|
||||||
"LabelNextBackupDate": "Следующая дата бэкапирования",
|
"LabelNextBackupDate": "Следующая дата бэкапирования",
|
||||||
"LabelNextScheduledRun": "Следущий запланированный запуск",
|
"LabelNextScheduledRun": "Следущий запланированный запуск",
|
||||||
|
"LabelNoEpisodesSelected": "No episodes selected",
|
||||||
"LabelNotes": "Заметки",
|
"LabelNotes": "Заметки",
|
||||||
"LabelNotFinished": "Не завершено",
|
"LabelNotFinished": "Не завершено",
|
||||||
"LabelNotificationAppriseURL": "URL(ы) для извещений",
|
"LabelNotificationAppriseURL": "URL(ы) для извещений",
|
||||||
@@ -340,18 +353,18 @@
|
|||||||
"LabelPodcast": "Подкаст",
|
"LabelPodcast": "Подкаст",
|
||||||
"LabelPodcasts": "Подкасты",
|
"LabelPodcasts": "Подкасты",
|
||||||
"LabelPodcastType": "Тип подкаста",
|
"LabelPodcastType": "Тип подкаста",
|
||||||
"LabelPort": "Port",
|
"LabelPort": "Порт",
|
||||||
"LabelPrefixesToIgnore": "Игнорируемые префиксы (без учета регистра)",
|
"LabelPrefixesToIgnore": "Игнорируемые префиксы (без учета регистра)",
|
||||||
"LabelPreventIndexing": "Запретить индексацию фида каталогами подкастов iTunes и Google",
|
"LabelPreventIndexing": "Запретить индексацию фида каталогами подкастов iTunes и Google",
|
||||||
"LabelPrimaryEbook": "Primary ebook",
|
"LabelPrimaryEbook": "Основная e-книга",
|
||||||
"LabelProgress": "Прогресс",
|
"LabelProgress": "Прогресс",
|
||||||
"LabelProvider": "Провайдер",
|
"LabelProvider": "Провайдер",
|
||||||
"LabelPubDate": "Дата публикации",
|
"LabelPubDate": "Дата публикации",
|
||||||
"LabelPublisher": "Издатель",
|
"LabelPublisher": "Издатель",
|
||||||
"LabelPublishYear": "Год публикации",
|
"LabelPublishYear": "Год публикации",
|
||||||
"LabelRead": "Read",
|
"LabelRead": "Читать",
|
||||||
"LabelReadAgain": "Read Again",
|
"LabelReadAgain": "Читать снова",
|
||||||
"LabelReadEbookWithoutProgress": "Read ebook without keeping progress",
|
"LabelReadEbookWithoutProgress": "Читать e-книгу без сохранения прогресса",
|
||||||
"LabelRecentlyAdded": "Недавно добавленные",
|
"LabelRecentlyAdded": "Недавно добавленные",
|
||||||
"LabelRecentSeries": "Последние серии",
|
"LabelRecentSeries": "Последние серии",
|
||||||
"LabelRecommended": "Рекомендованное",
|
"LabelRecommended": "Рекомендованное",
|
||||||
@@ -368,25 +381,32 @@
|
|||||||
"LabelSearchTitle": "Поиск по названию",
|
"LabelSearchTitle": "Поиск по названию",
|
||||||
"LabelSearchTitleOrASIN": "Поиск по названию или ASIN",
|
"LabelSearchTitleOrASIN": "Поиск по названию или ASIN",
|
||||||
"LabelSeason": "Сезон",
|
"LabelSeason": "Сезон",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSelectAllEpisodes": "Select all episodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||||
|
"LabelSendEbookToDevice": "Отправить e-книгу в...",
|
||||||
"LabelSequence": "Последовательность",
|
"LabelSequence": "Последовательность",
|
||||||
"LabelSeries": "Серия",
|
"LabelSeries": "Серия",
|
||||||
"LabelSeriesName": "Имя серии",
|
"LabelSeriesName": "Имя серии",
|
||||||
"LabelSeriesProgress": "Прогресс серии",
|
"LabelSeriesProgress": "Прогресс серии",
|
||||||
"LabelSetEbookAsPrimary": "Set as primary",
|
"LabelSetEbookAsPrimary": "Установить как основную",
|
||||||
"LabelSetEbookAsSupplementary": "Set as supplementary",
|
"LabelSetEbookAsSupplementary": "Установить как дополнительную",
|
||||||
"LabelSettingsAudiobooksOnly": "Audiobooks only",
|
"LabelSettingsAudiobooksOnly": "Только аудиокниги",
|
||||||
"LabelSettingsAudiobooksOnlyHelp": "Enabling this setting will ignore ebook files unless they are inside an audiobook folder in which case they will be set as supplementary ebooks",
|
"LabelSettingsAudiobooksOnlyHelp": "Если включить эту настройку, файлы электронных книг будут игнорироваться, за исключением случаев, когда они находятся в папке с аудиокнигами, в этом случае они будут рассматриваться как дополнительные электронные книги.",
|
||||||
"LabelSettingsBookshelfViewHelp": "Конструкция с деревянными полками",
|
"LabelSettingsBookshelfViewHelp": "Конструкция с деревянными полками",
|
||||||
"LabelSettingsChromecastSupport": "Поддержка Chromecast",
|
"LabelSettingsChromecastSupport": "Поддержка Chromecast",
|
||||||
"LabelSettingsDateFormat": "Формат даты",
|
"LabelSettingsDateFormat": "Формат даты",
|
||||||
"LabelSettingsDisableWatcher": "Отключить отслеживание",
|
"LabelSettingsDisableWatcher": "Отключить отслеживание",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "Отключить отслеживание для библиотеки",
|
"LabelSettingsDisableWatcherForLibrary": "Отключить отслеживание для библиотеки",
|
||||||
"LabelSettingsDisableWatcherHelp": "Отключает автоматическое добавление/обновление элементов, когда обнаружено изменение файлов. *Требуется перезапуск сервера",
|
"LabelSettingsDisableWatcherHelp": "Отключает автоматическое добавление/обновление элементов, когда обнаружено изменение файлов. *Требуется перезапуск сервера",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "Экспериментальные функции",
|
"LabelSettingsExperimentalFeatures": "Экспериментальные функции",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "Функционал в разработке на который Вы могли бы дать отзыв или помочь в тестировании. Нажмите для открытия обсуждения на github.",
|
"LabelSettingsExperimentalFeaturesHelp": "Функционал в разработке на который Вы могли бы дать отзыв или помочь в тестировании. Нажмите для открытия обсуждения на github.",
|
||||||
"LabelSettingsFindCovers": "Найти обложки",
|
"LabelSettingsFindCovers": "Найти обложки",
|
||||||
"LabelSettingsFindCoversHelp": "Если у Ваших аудиокниг нет встроенной обложки или файла обложки в папке книги, то сканер попробует найти обложку.<br>Примечание: Это увеличит время сканирования",
|
"LabelSettingsFindCoversHelp": "Если у Ваших аудиокниг нет встроенной обложки или файла обложки в папке книги, то сканер попробует найти обложку.<br>Примечание: Это увеличит время сканирования",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Hide single book series",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
|
||||||
"LabelSettingsHomePageBookshelfView": "Вид книжной полки на Домашней странице",
|
"LabelSettingsHomePageBookshelfView": "Вид книжной полки на Домашней странице",
|
||||||
"LabelSettingsLibraryBookshelfView": "Вид книжной полки в Библиотеке",
|
"LabelSettingsLibraryBookshelfView": "Вид книжной полки в Библиотеке",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "Overdrive Media Markers для глав",
|
"LabelSettingsOverdriveMediaMarkers": "Overdrive Media Markers для глав",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "Показать все",
|
"LabelShowAll": "Показать все",
|
||||||
"LabelSize": "Размер",
|
"LabelSize": "Размер",
|
||||||
"LabelSleepTimer": "Таймер сна",
|
"LabelSleepTimer": "Таймер сна",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "Начало",
|
"LabelStart": "Начало",
|
||||||
"LabelStarted": "Начат",
|
"LabelStarted": "Начат",
|
||||||
"LabelStartedAt": "Начато В",
|
"LabelStartedAt": "Начато В",
|
||||||
@@ -431,15 +452,18 @@
|
|||||||
"LabelStatsMinutesListening": "Минут прослушано",
|
"LabelStatsMinutesListening": "Минут прослушано",
|
||||||
"LabelStatsOverallDays": "Всего дней",
|
"LabelStatsOverallDays": "Всего дней",
|
||||||
"LabelStatsOverallHours": "Всего часов",
|
"LabelStatsOverallHours": "Всего часов",
|
||||||
"LabelStatsWeekListening": "Недель прослушано",
|
"LabelStatsWeekListening": "Прослушано за неделю",
|
||||||
"LabelSubtitle": "Подзаголовок",
|
"LabelSubtitle": "Подзаголовок",
|
||||||
"LabelSupportedFileTypes": "Поддерживаемые типы файлов",
|
"LabelSupportedFileTypes": "Поддерживаемые типы файлов",
|
||||||
"LabelTag": "Тег",
|
"LabelTag": "Тег",
|
||||||
"LabelTags": "Теги",
|
"LabelTags": "Теги",
|
||||||
"LabelTagsAccessibleToUser": "Теги доступные для пользователя",
|
"LabelTagsAccessibleToUser": "Теги доступные для пользователя",
|
||||||
"LabelTagsNotAccessibleToUser": "Tags not Accessible to User",
|
"LabelTagsNotAccessibleToUser": "Теги не доступные для пользователя",
|
||||||
"LabelTasks": "Запущенные задачи",
|
"LabelTasks": "Запущенные задачи",
|
||||||
"LabelTimeBase": "Time Base",
|
"LabelTheme": "Theme",
|
||||||
|
"LabelThemeDark": "Dark",
|
||||||
|
"LabelThemeLight": "Light",
|
||||||
|
"LabelTimeBase": "Временная база",
|
||||||
"LabelTimeListened": "Время прослушивания",
|
"LabelTimeListened": "Время прослушивания",
|
||||||
"LabelTimeListenedToday": "Время прослушивания сегодня",
|
"LabelTimeListenedToday": "Время прослушивания сегодня",
|
||||||
"LabelTimeRemaining": "{0} осталось",
|
"LabelTimeRemaining": "{0} осталось",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "Трек из Метаданных",
|
"LabelTrackFromMetadata": "Трек из Метаданных",
|
||||||
"LabelTracks": "Треков",
|
"LabelTracks": "Треков",
|
||||||
"LabelTracksMultiTrack": "Мультитрек",
|
"LabelTracksMultiTrack": "Мультитрек",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "Один трек",
|
"LabelTracksSingleTrack": "Один трек",
|
||||||
"LabelType": "Тип",
|
"LabelType": "Тип",
|
||||||
"LabelUnabridged": "Полное издание",
|
"LabelUnabridged": "Полное издание",
|
||||||
@@ -497,18 +522,21 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "Неверное время начала, должно быть больше или равно времени начала предыдущей главы",
|
"MessageChapterErrorStartLtPrev": "Неверное время начала, должно быть больше или равно времени начала предыдущей главы",
|
||||||
"MessageChapterStartIsAfter": "Глава начинается после окончания аудиокниги",
|
"MessageChapterStartIsAfter": "Глава начинается после окончания аудиокниги",
|
||||||
"MessageCheckingCron": "Проверка cron...",
|
"MessageCheckingCron": "Проверка cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "Это удалит файл из Вашей файловой системы. Вы уверены?",
|
||||||
"MessageConfirmDeleteLibrary": "Вы уверены, что хотите навсегда удалить библиотеку \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Вы уверены, что хотите навсегда удалить библиотеку \"{0}\"?",
|
||||||
"MessageConfirmDeleteSession": "Вы уверены, что хотите удалить этот сеанс?",
|
"MessageConfirmDeleteSession": "Вы уверены, что хотите удалить этот сеанс?",
|
||||||
"MessageConfirmForceReScan": "Вы уверены, что хотите принудительно выполнить повторное сканирование?",
|
"MessageConfirmForceReScan": "Вы уверены, что хотите принудительно выполнить повторное сканирование?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как законченные?",
|
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как законченные?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как незаконченные?",
|
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как незаконченные?",
|
||||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
"MessageConfirmRemoveAllChapters": "Вы уверены, что хотите удалить все главы?",
|
||||||
"MessageConfirmRemoveCollection": "Вы уверены, что хотите удалить коллекцию \"{0}\"?",
|
"MessageConfirmRemoveCollection": "Вы уверены, что хотите удалить коллекцию \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "Вы уверены, что хотите удалить эпизод \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "Вы уверены, что хотите удалить эпизод \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisodes": "Вы уверены, что хотите удалить {0} эпизодов?",
|
"MessageConfirmRemoveEpisodes": "Вы уверены, что хотите удалить {0} эпизодов?",
|
||||||
"MessageConfirmRemoveNarrator": "Are you sure you want to remove narrator \"{0}\"?",
|
"MessageConfirmRemoveNarrator": "Вы уверены, что хотите удалить чтеца \"{0}\"?",
|
||||||
"MessageConfirmRemovePlaylist": "Вы уверены, что хотите удалить плейлист \"{0}\"?",
|
"MessageConfirmRemovePlaylist": "Вы уверены, что хотите удалить плейлист \"{0}\"?",
|
||||||
"MessageConfirmRenameGenre": "Вы уверены, что хотите переименовать жанр \"{0}\" в \"{1}\" для всех элементов?",
|
"MessageConfirmRenameGenre": "Вы уверены, что хотите переименовать жанр \"{0}\" в \"{1}\" для всех элементов?",
|
||||||
"MessageConfirmRenameGenreMergeNote": "Примечание: Этот жанр уже существует, поэтому они будут объединены.",
|
"MessageConfirmRenameGenreMergeNote": "Примечание: Этот жанр уже существует, поэтому они будут объединены.",
|
||||||
@@ -516,7 +544,7 @@
|
|||||||
"MessageConfirmRenameTag": "Вы уверены, что хотите переименовать тег \"{0}\" в \"{1}\" для всех элементов?",
|
"MessageConfirmRenameTag": "Вы уверены, что хотите переименовать тег \"{0}\" в \"{1}\" для всех элементов?",
|
||||||
"MessageConfirmRenameTagMergeNote": "Примечание: Этот тег уже существует, поэтому они будут объединены.",
|
"MessageConfirmRenameTagMergeNote": "Примечание: Этот тег уже существует, поэтому они будут объединены.",
|
||||||
"MessageConfirmRenameTagWarning": "Предупреждение! Похожий тег с другими начальными буквами уже существует \"{0}\".",
|
"MessageConfirmRenameTagWarning": "Предупреждение! Похожий тег с другими начальными буквами уже существует \"{0}\".",
|
||||||
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "Вы уверены, что хотите отправить {0} e-книгу \"{1}\" на устройство \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "Эпизод скачивается",
|
"MessageDownloadingEpisode": "Эпизод скачивается",
|
||||||
"MessageDragFilesIntoTrackOrder": "Перетащите файлы для исправления порядка треков",
|
"MessageDragFilesIntoTrackOrder": "Перетащите файлы для исправления порядка треков",
|
||||||
"MessageEmbedFinished": "Встраивание завершено!",
|
"MessageEmbedFinished": "Встраивание завершено!",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "M4B Ошибка!",
|
"MessageM4BFailed": "M4B Ошибка!",
|
||||||
"MessageM4BFinished": "M4B Завершено!",
|
"MessageM4BFinished": "M4B Завершено!",
|
||||||
"MessageMapChapterTitles": "Сопоставление названий глав с существующими главами аудиокниги без корректировки временных меток",
|
"MessageMapChapterTitles": "Сопоставление названий глав с существующими главами аудиокниги без корректировки временных меток",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Mark all episodes finished",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished",
|
||||||
"MessageMarkAsFinished": "Отметить, как завершенную",
|
"MessageMarkAsFinished": "Отметить, как завершенную",
|
||||||
"MessageMarkAsNotFinished": "Отметить, как не завершенную",
|
"MessageMarkAsNotFinished": "Отметить, как не завершенную",
|
||||||
"MessageMatchBooksDescription": "попытается сопоставить книги в библиотеке с книгой из выбранного поставщика поиска и заполнить пустые детали и обложку. Не перезаписывает сведения.",
|
"MessageMatchBooksDescription": "попытается сопоставить книги в библиотеке с книгой из выбранного поставщика поиска и заполнить пустые детали и обложку. Не перезаписывает сведения.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Создать плейлист из коллекции",
|
"MessagePlaylistCreateFromCollection": "Создать плейлист из коллекции",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Подкаст не имеет URL-адреса RSS-канала, который можно использовать для поиска",
|
"MessagePodcastHasNoRSSFeedForMatching": "Подкаст не имеет URL-адреса RSS-канала, который можно использовать для поиска",
|
||||||
"MessageQuickMatchDescription": "Заполняет пустые детали элемента и обложку первым результатом поиска из «{0}». Не перезаписывает сведения, если не включен параметр сервера 'Предпочитать метаданные поиска'.",
|
"MessageQuickMatchDescription": "Заполняет пустые детали элемента и обложку первым результатом поиска из «{0}». Не перезаписывает сведения, если не включен параметр сервера 'Предпочитать метаданные поиска'.",
|
||||||
"MessageRemoveAllItemsWarning": "ПРЕДУПРЕЖДЕНИЕ! Это действие удалит все элементы библиотеки из базы данных, включая все сделанные обновления или совпадения. Ничего не произойдет с вашими фактическими файлами. Уверены?",
|
|
||||||
"MessageRemoveChapter": "Удалить главу",
|
"MessageRemoveChapter": "Удалить главу",
|
||||||
"MessageRemoveEpisodes": "Удалить {0} эпизод(ов)",
|
"MessageRemoveEpisodes": "Удалить {0} эпизод(ов)",
|
||||||
"MessageRemoveFromPlayerQueue": "Удалить из очереди воспроизведения",
|
"MessageRemoveFromPlayerQueue": "Удалить из очереди воспроизведения",
|
||||||
@@ -671,8 +700,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "Элемент удален из коллекции",
|
"ToastRemoveItemFromCollectionSuccess": "Элемент удален из коллекции",
|
||||||
"ToastRSSFeedCloseFailed": "Не удалось закрыть RSS-канал",
|
"ToastRSSFeedCloseFailed": "Не удалось закрыть RSS-канал",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS-канал закрыт",
|
"ToastRSSFeedCloseSuccess": "RSS-канал закрыт",
|
||||||
"ToastSendEbookToDeviceFailed": "Failed to Send Ebook to device",
|
"ToastSendEbookToDeviceFailed": "Не удалось отправить e-книгу на устройство",
|
||||||
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
|
"ToastSendEbookToDeviceSuccess": "E-книга отправлена на устройство \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
|
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
|
||||||
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
|
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
|
||||||
"ToastSessionDeleteFailed": "Не удалось удалить сеанс",
|
"ToastSessionDeleteFailed": "Не удалось удалить сеанс",
|
||||||
|
|||||||
+61
-32
@@ -55,7 +55,7 @@
|
|||||||
"ButtonRemoveAll": "移除所有",
|
"ButtonRemoveAll": "移除所有",
|
||||||
"ButtonRemoveAllLibraryItems": "移除所有媒体库项目",
|
"ButtonRemoveAllLibraryItems": "移除所有媒体库项目",
|
||||||
"ButtonRemoveFromContinueListening": "从继续收听中删除",
|
"ButtonRemoveFromContinueListening": "从继续收听中删除",
|
||||||
"ButtonRemoveFromContinueReading": "Remove from Continue Reading",
|
"ButtonRemoveFromContinueReading": "从继续阅读中删除",
|
||||||
"ButtonRemoveSeriesFromContinueSeries": "从继续收听系列中删除",
|
"ButtonRemoveSeriesFromContinueSeries": "从继续收听系列中删除",
|
||||||
"ButtonReScan": "重新扫描",
|
"ButtonReScan": "重新扫描",
|
||||||
"ButtonReset": "重置",
|
"ButtonReset": "重置",
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
"ButtonStartM4BEncode": "开始 M4B 编码",
|
"ButtonStartM4BEncode": "开始 M4B 编码",
|
||||||
"ButtonStartMetadataEmbed": "开始嵌入元数据",
|
"ButtonStartMetadataEmbed": "开始嵌入元数据",
|
||||||
"ButtonSubmit": "提交",
|
"ButtonSubmit": "提交",
|
||||||
"ButtonTest": "Test",
|
"ButtonTest": "测试",
|
||||||
"ButtonUpload": "上传",
|
"ButtonUpload": "上传",
|
||||||
"ButtonUploadBackup": "上传备份",
|
"ButtonUploadBackup": "上传备份",
|
||||||
"ButtonUploadCover": "上传封面",
|
"ButtonUploadCover": "上传封面",
|
||||||
@@ -98,11 +98,12 @@
|
|||||||
"HeaderCurrentDownloads": "当前下载",
|
"HeaderCurrentDownloads": "当前下载",
|
||||||
"HeaderDetails": "详情",
|
"HeaderDetails": "详情",
|
||||||
"HeaderDownloadQueue": "下载队列",
|
"HeaderDownloadQueue": "下载队列",
|
||||||
"HeaderEbookFiles": "Ebook Files",
|
"HeaderEbookFiles": "电子书文件",
|
||||||
"HeaderEmail": "Email",
|
"HeaderEmail": "邮箱",
|
||||||
"HeaderEmailSettings": "Email Settings",
|
"HeaderEmailSettings": "邮箱设置",
|
||||||
"HeaderEpisodes": "剧集",
|
"HeaderEpisodes": "剧集",
|
||||||
"HeaderEReaderDevices": "E-Reader Devices",
|
"HeaderEreaderDevices": "Ereader 设备",
|
||||||
|
"HeaderEreaderSettings": "Ereader 设置",
|
||||||
"HeaderFiles": "文件",
|
"HeaderFiles": "文件",
|
||||||
"HeaderFindChapters": "查找章节",
|
"HeaderFindChapters": "查找章节",
|
||||||
"HeaderIgnoredFiles": "忽略的文件",
|
"HeaderIgnoredFiles": "忽略的文件",
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
"HeaderRemoveEpisodes": "移除 {0} 剧集",
|
"HeaderRemoveEpisodes": "移除 {0} 剧集",
|
||||||
"HeaderRSSFeedGeneral": "RSS 详细信息",
|
"HeaderRSSFeedGeneral": "RSS 详细信息",
|
||||||
"HeaderRSSFeedIsOpen": "RSS 源已打开",
|
"HeaderRSSFeedIsOpen": "RSS 源已打开",
|
||||||
|
"HeaderRSSFeeds": "RSS Feeds",
|
||||||
"HeaderSavedMediaProgress": "保存媒体进度",
|
"HeaderSavedMediaProgress": "保存媒体进度",
|
||||||
"HeaderSchedule": "计划任务",
|
"HeaderSchedule": "计划任务",
|
||||||
"HeaderScheduleLibraryScans": "自动扫描媒体库",
|
"HeaderScheduleLibraryScans": "自动扫描媒体库",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderStatsRecentSessions": "历史会话",
|
"HeaderStatsRecentSessions": "历史会话",
|
||||||
"HeaderStatsTop10Authors": "前 10 位作者",
|
"HeaderStatsTop10Authors": "前 10 位作者",
|
||||||
"HeaderStatsTop5Genres": "前 5 种流派",
|
"HeaderStatsTop5Genres": "前 5 种流派",
|
||||||
|
"HeaderTableOfContents": "目录",
|
||||||
"HeaderTools": "工具",
|
"HeaderTools": "工具",
|
||||||
"HeaderUpdateAccount": "更新帐户",
|
"HeaderUpdateAccount": "更新帐户",
|
||||||
"HeaderUpdateAuthor": "更新作者",
|
"HeaderUpdateAuthor": "更新作者",
|
||||||
@@ -199,11 +202,12 @@
|
|||||||
"LabelClosePlayer": "关闭播放器",
|
"LabelClosePlayer": "关闭播放器",
|
||||||
"LabelCodec": "编解码",
|
"LabelCodec": "编解码",
|
||||||
"LabelCollapseSeries": "折叠系列",
|
"LabelCollapseSeries": "折叠系列",
|
||||||
|
"LabelCollection": "Collection",
|
||||||
"LabelCollections": "收藏",
|
"LabelCollections": "收藏",
|
||||||
"LabelComplete": "已完成",
|
"LabelComplete": "已完成",
|
||||||
"LabelConfirmPassword": "确认密码",
|
"LabelConfirmPassword": "确认密码",
|
||||||
"LabelContinueListening": "继续收听",
|
"LabelContinueListening": "继续收听",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "继续阅读",
|
||||||
"LabelContinueSeries": "继续收听系列",
|
"LabelContinueSeries": "继续收听系列",
|
||||||
"LabelCover": "封面",
|
"LabelCover": "封面",
|
||||||
"LabelCoverImageURL": "封面图像 URL",
|
"LabelCoverImageURL": "封面图像 URL",
|
||||||
@@ -220,16 +224,19 @@
|
|||||||
"LabelDirectory": "目录",
|
"LabelDirectory": "目录",
|
||||||
"LabelDiscFromFilename": "从文件名获取光盘",
|
"LabelDiscFromFilename": "从文件名获取光盘",
|
||||||
"LabelDiscFromMetadata": "从元数据获取光盘",
|
"LabelDiscFromMetadata": "从元数据获取光盘",
|
||||||
|
"LabelDiscover": "Discover",
|
||||||
"LabelDownload": "下载",
|
"LabelDownload": "下载",
|
||||||
|
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||||
"LabelDuration": "持续时间",
|
"LabelDuration": "持续时间",
|
||||||
"LabelDurationFound": "找到持续时间:",
|
"LabelDurationFound": "找到持续时间:",
|
||||||
"LabelEbook": "Ebook",
|
"LabelEbook": "电子书",
|
||||||
"LabelEbooks": "Ebooks",
|
"LabelEbooks": "电子书",
|
||||||
"LabelEdit": "编辑",
|
"LabelEdit": "编辑",
|
||||||
"LabelEmail": "Email",
|
"LabelEmail": "邮箱",
|
||||||
"LabelEmailSettingsFromAddress": "From Address",
|
"LabelEmailSettingsFromAddress": "发件人地址",
|
||||||
"LabelEmailSettingsSecure": "Secure",
|
"LabelEmailSettingsSecure": "安全",
|
||||||
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "如果选是, 则连接将在连接到服务器时使用TLS. 如果选否, 则若服务器支持STARTTLS扩展, 则使用TLS. 在大多数情况下, 如果连接到端口465, 请将该值设置为是. 对于端口587或25, 请保持为否. (来自nodemailer.com/smtp/#authentication)",
|
||||||
|
"LabelEmailSettingsTestAddress": "测试地址",
|
||||||
"LabelEmbeddedCover": "嵌入封面",
|
"LabelEmbeddedCover": "嵌入封面",
|
||||||
"LabelEnable": "启用",
|
"LabelEnable": "启用",
|
||||||
"LabelEnd": "结束",
|
"LabelEnd": "结束",
|
||||||
@@ -248,13 +255,14 @@
|
|||||||
"LabelFinished": "已听完",
|
"LabelFinished": "已听完",
|
||||||
"LabelFolder": "文件夹",
|
"LabelFolder": "文件夹",
|
||||||
"LabelFolders": "文件夹",
|
"LabelFolders": "文件夹",
|
||||||
|
"LabelFontScale": "字体比例",
|
||||||
"LabelFormat": "编码格式",
|
"LabelFormat": "编码格式",
|
||||||
"LabelGenre": "流派",
|
"LabelGenre": "流派",
|
||||||
"LabelGenres": "流派",
|
"LabelGenres": "流派",
|
||||||
"LabelHardDeleteFile": "完全删除文件",
|
"LabelHardDeleteFile": "完全删除文件",
|
||||||
"LabelHasEbook": "Has ebook",
|
"LabelHasEbook": "有电子书",
|
||||||
"LabelHasSupplementaryEbook": "Has supplementary ebook",
|
"LabelHasSupplementaryEbook": "有补充电子书",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "主机",
|
||||||
"LabelHour": "小时",
|
"LabelHour": "小时",
|
||||||
"LabelIcon": "图标",
|
"LabelIcon": "图标",
|
||||||
"LabelIncludeInTracklist": "包含在音轨列表中",
|
"LabelIncludeInTracklist": "包含在音轨列表中",
|
||||||
@@ -279,12 +287,16 @@
|
|||||||
"LabelLastSeen": "上次查看时间",
|
"LabelLastSeen": "上次查看时间",
|
||||||
"LabelLastTime": "最近一次",
|
"LabelLastTime": "最近一次",
|
||||||
"LabelLastUpdate": "最近更新",
|
"LabelLastUpdate": "最近更新",
|
||||||
|
"LabelLayout": "布局",
|
||||||
|
"LabelLayoutSinglePage": "单页",
|
||||||
|
"LabelLayoutSplitPage": "分页",
|
||||||
"LabelLess": "较少",
|
"LabelLess": "较少",
|
||||||
"LabelLibrariesAccessibleToUser": "用户可访问的媒体库",
|
"LabelLibrariesAccessibleToUser": "用户可访问的媒体库",
|
||||||
"LabelLibrary": "媒体库",
|
"LabelLibrary": "媒体库",
|
||||||
"LabelLibraryItem": "媒体库项目",
|
"LabelLibraryItem": "媒体库项目",
|
||||||
"LabelLibraryName": "媒体库名称",
|
"LabelLibraryName": "媒体库名称",
|
||||||
"LabelLimit": "限制",
|
"LabelLimit": "限制",
|
||||||
|
"LabelLineSpacing": "行间距",
|
||||||
"LabelListenAgain": "再次收听",
|
"LabelListenAgain": "再次收听",
|
||||||
"LabelLogLevelDebug": "调试",
|
"LabelLogLevelDebug": "调试",
|
||||||
"LabelLogLevelInfo": "信息",
|
"LabelLogLevelInfo": "信息",
|
||||||
@@ -309,6 +321,7 @@
|
|||||||
"LabelNewPassword": "新密码",
|
"LabelNewPassword": "新密码",
|
||||||
"LabelNextBackupDate": "下次备份日期",
|
"LabelNextBackupDate": "下次备份日期",
|
||||||
"LabelNextScheduledRun": "下次任务运行",
|
"LabelNextScheduledRun": "下次任务运行",
|
||||||
|
"LabelNoEpisodesSelected": "No episodes selected",
|
||||||
"LabelNotes": "注释",
|
"LabelNotes": "注释",
|
||||||
"LabelNotFinished": "未听完",
|
"LabelNotFinished": "未听完",
|
||||||
"LabelNotificationAppriseURL": "通知 URL(s)",
|
"LabelNotificationAppriseURL": "通知 URL(s)",
|
||||||
@@ -340,18 +353,18 @@
|
|||||||
"LabelPodcast": "播客",
|
"LabelPodcast": "播客",
|
||||||
"LabelPodcasts": "播客",
|
"LabelPodcasts": "播客",
|
||||||
"LabelPodcastType": "播客类型",
|
"LabelPodcastType": "播客类型",
|
||||||
"LabelPort": "Port",
|
"LabelPort": "端口",
|
||||||
"LabelPrefixesToIgnore": "忽略的前缀 (不区分大小写)",
|
"LabelPrefixesToIgnore": "忽略的前缀 (不区分大小写)",
|
||||||
"LabelPreventIndexing": "防止 iTunes 和 Google 播客目录对你的源进行索引",
|
"LabelPreventIndexing": "防止 iTunes 和 Google 播客目录对你的源进行索引",
|
||||||
"LabelPrimaryEbook": "Primary ebook",
|
"LabelPrimaryEbook": "主电子书",
|
||||||
"LabelProgress": "进度",
|
"LabelProgress": "进度",
|
||||||
"LabelProvider": "供应商",
|
"LabelProvider": "供应商",
|
||||||
"LabelPubDate": "出版日期",
|
"LabelPubDate": "出版日期",
|
||||||
"LabelPublisher": "出版商",
|
"LabelPublisher": "出版商",
|
||||||
"LabelPublishYear": "发布年份",
|
"LabelPublishYear": "发布年份",
|
||||||
"LabelRead": "Read",
|
"LabelRead": "阅读",
|
||||||
"LabelReadAgain": "Read Again",
|
"LabelReadAgain": "再次阅读",
|
||||||
"LabelReadEbookWithoutProgress": "Read ebook without keeping progress",
|
"LabelReadEbookWithoutProgress": "阅读电子书而不保存进度",
|
||||||
"LabelRecentlyAdded": "最近添加",
|
"LabelRecentlyAdded": "最近添加",
|
||||||
"LabelRecentSeries": "最近添加系列",
|
"LabelRecentSeries": "最近添加系列",
|
||||||
"LabelRecommended": "推荐内容",
|
"LabelRecommended": "推荐内容",
|
||||||
@@ -368,25 +381,32 @@
|
|||||||
"LabelSearchTitle": "搜索标题",
|
"LabelSearchTitle": "搜索标题",
|
||||||
"LabelSearchTitleOrASIN": "搜索标题或 ASIN",
|
"LabelSearchTitleOrASIN": "搜索标题或 ASIN",
|
||||||
"LabelSeason": "季",
|
"LabelSeason": "季",
|
||||||
"LabelSendEbookToDevice": "Send Ebook to...",
|
"LabelSelectAllEpisodes": "Select all episodes",
|
||||||
|
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||||
|
"LabelSendEbookToDevice": "发送电子书到...",
|
||||||
"LabelSequence": "序列",
|
"LabelSequence": "序列",
|
||||||
"LabelSeries": "系列",
|
"LabelSeries": "系列",
|
||||||
"LabelSeriesName": "系列名称",
|
"LabelSeriesName": "系列名称",
|
||||||
"LabelSeriesProgress": "系列进度",
|
"LabelSeriesProgress": "系列进度",
|
||||||
"LabelSetEbookAsPrimary": "Set as primary",
|
"LabelSetEbookAsPrimary": "设置为主",
|
||||||
"LabelSetEbookAsSupplementary": "Set as supplementary",
|
"LabelSetEbookAsSupplementary": "设置为补充",
|
||||||
"LabelSettingsAudiobooksOnly": "Audiobooks only",
|
"LabelSettingsAudiobooksOnly": "只有有声读物",
|
||||||
"LabelSettingsAudiobooksOnlyHelp": "Enabling this setting will ignore ebook files unless they are inside an audiobook folder in which case they will be set as supplementary ebooks",
|
"LabelSettingsAudiobooksOnlyHelp": "启用此设置将忽略电子书文件, 除非它们位于有声读物文件夹中, 在这种情况下, 它们将被设置为补充电子书",
|
||||||
"LabelSettingsBookshelfViewHelp": "带有木架子的拟物化设计",
|
"LabelSettingsBookshelfViewHelp": "带有木架子的拟物化设计",
|
||||||
"LabelSettingsChromecastSupport": "Chromecast 支持",
|
"LabelSettingsChromecastSupport": "Chromecast 支持",
|
||||||
"LabelSettingsDateFormat": "日期格式",
|
"LabelSettingsDateFormat": "日期格式",
|
||||||
"LabelSettingsDisableWatcher": "禁用监视程序",
|
"LabelSettingsDisableWatcher": "禁用监视程序",
|
||||||
"LabelSettingsDisableWatcherForLibrary": "禁用媒体库的文件夹监视程序",
|
"LabelSettingsDisableWatcherForLibrary": "禁用媒体库的文件夹监视程序",
|
||||||
"LabelSettingsDisableWatcherHelp": "检测到文件更改时禁用自动添加和更新项目. *需要重启服务器",
|
"LabelSettingsDisableWatcherHelp": "检测到文件更改时禁用自动添加和更新项目. *需要重启服务器",
|
||||||
|
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||||
|
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||||
|
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||||
"LabelSettingsExperimentalFeatures": "实验功能",
|
"LabelSettingsExperimentalFeatures": "实验功能",
|
||||||
"LabelSettingsExperimentalFeaturesHelp": "开发中的功能需要你的反馈并帮助测试. 点击打开 github 讨论.",
|
"LabelSettingsExperimentalFeaturesHelp": "开发中的功能需要你的反馈并帮助测试. 点击打开 github 讨论.",
|
||||||
"LabelSettingsFindCovers": "查找封面",
|
"LabelSettingsFindCovers": "查找封面",
|
||||||
"LabelSettingsFindCoversHelp": "如果你的有声读物在文件夹中没有嵌入封面或封面图像, 扫描将尝试查找封面.<br>注意: 这将延长扫描时间",
|
"LabelSettingsFindCoversHelp": "如果你的有声读物在文件夹中没有嵌入封面或封面图像, 扫描将尝试查找封面.<br>注意: 这将延长扫描时间",
|
||||||
|
"LabelSettingsHideSingleBookSeries": "Hide single book series",
|
||||||
|
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
|
||||||
"LabelSettingsHomePageBookshelfView": "首页使用书架视图",
|
"LabelSettingsHomePageBookshelfView": "首页使用书架视图",
|
||||||
"LabelSettingsLibraryBookshelfView": "媒体库使用书架视图",
|
"LabelSettingsLibraryBookshelfView": "媒体库使用书架视图",
|
||||||
"LabelSettingsOverdriveMediaMarkers": "对章节使用 Overdrive 媒体标记",
|
"LabelSettingsOverdriveMediaMarkers": "对章节使用 Overdrive 媒体标记",
|
||||||
@@ -413,6 +433,7 @@
|
|||||||
"LabelShowAll": "全部显示",
|
"LabelShowAll": "全部显示",
|
||||||
"LabelSize": "文件大小",
|
"LabelSize": "文件大小",
|
||||||
"LabelSleepTimer": "睡眠定时",
|
"LabelSleepTimer": "睡眠定时",
|
||||||
|
"LabelSlug": "Slug",
|
||||||
"LabelStart": "开始",
|
"LabelStart": "开始",
|
||||||
"LabelStarted": "开始于",
|
"LabelStarted": "开始于",
|
||||||
"LabelStartedAt": "从这开始",
|
"LabelStartedAt": "从这开始",
|
||||||
@@ -439,6 +460,9 @@
|
|||||||
"LabelTagsAccessibleToUser": "用户可访问的标签",
|
"LabelTagsAccessibleToUser": "用户可访问的标签",
|
||||||
"LabelTagsNotAccessibleToUser": "用户无法访问标签",
|
"LabelTagsNotAccessibleToUser": "用户无法访问标签",
|
||||||
"LabelTasks": "正在运行的任务",
|
"LabelTasks": "正在运行的任务",
|
||||||
|
"LabelTheme": "主题",
|
||||||
|
"LabelThemeDark": "黑暗",
|
||||||
|
"LabelThemeLight": "明亮",
|
||||||
"LabelTimeBase": "时间基准",
|
"LabelTimeBase": "时间基准",
|
||||||
"LabelTimeListened": "收听时间",
|
"LabelTimeListened": "收听时间",
|
||||||
"LabelTimeListenedToday": "今日收听的时间",
|
"LabelTimeListenedToday": "今日收听的时间",
|
||||||
@@ -457,6 +481,7 @@
|
|||||||
"LabelTrackFromMetadata": "从源数据获取音轨",
|
"LabelTrackFromMetadata": "从源数据获取音轨",
|
||||||
"LabelTracks": "音轨",
|
"LabelTracks": "音轨",
|
||||||
"LabelTracksMultiTrack": "多轨",
|
"LabelTracksMultiTrack": "多轨",
|
||||||
|
"LabelTracksNone": "No tracks",
|
||||||
"LabelTracksSingleTrack": "单轨",
|
"LabelTracksSingleTrack": "单轨",
|
||||||
"LabelType": "类型",
|
"LabelType": "类型",
|
||||||
"LabelUnabridged": "未删节",
|
"LabelUnabridged": "未删节",
|
||||||
@@ -497,18 +522,21 @@
|
|||||||
"MessageChapterErrorStartLtPrev": "无效的开始时间, 必须大于或等于上一章节的开始时间",
|
"MessageChapterErrorStartLtPrev": "无效的开始时间, 必须大于或等于上一章节的开始时间",
|
||||||
"MessageChapterStartIsAfter": "章节开始是在有声读物结束之后",
|
"MessageChapterStartIsAfter": "章节开始是在有声读物结束之后",
|
||||||
"MessageCheckingCron": "检查计划任务...",
|
"MessageCheckingCron": "检查计划任务...",
|
||||||
|
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
|
||||||
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
||||||
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
|
"MessageConfirmDeleteFile": "这将从文件系统中删除该文件. 你确定吗?",
|
||||||
"MessageConfirmDeleteLibrary": "你确定要永久删除媒体库 \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "你确定要永久删除媒体库 \"{0}\"?",
|
||||||
"MessageConfirmDeleteSession": "你确定要删除此会话吗?",
|
"MessageConfirmDeleteSession": "你确定要删除此会话吗?",
|
||||||
"MessageConfirmForceReScan": "你确定要强制重新扫描吗?",
|
"MessageConfirmForceReScan": "你确定要强制重新扫描吗?",
|
||||||
|
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
|
||||||
|
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||||
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
|
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
|
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
|
||||||
"MessageConfirmRemoveAllChapters": "你确定要移除所有章节吗?",
|
"MessageConfirmRemoveAllChapters": "你确定要移除所有章节吗?",
|
||||||
"MessageConfirmRemoveCollection": "您确定要移除收藏 \"{0}\"?",
|
"MessageConfirmRemoveCollection": "您确定要移除收藏 \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "您确定要移除剧集 \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "您确定要移除剧集 \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
|
"MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
|
||||||
"MessageConfirmRemoveNarrator": "Are you sure you want to remove narrator \"{0}\"?",
|
"MessageConfirmRemoveNarrator": "你确定要删除演播者 \"{0}\"?",
|
||||||
"MessageConfirmRemovePlaylist": "你确定要移除播放列表 \"{0}\"?",
|
"MessageConfirmRemovePlaylist": "你确定要移除播放列表 \"{0}\"?",
|
||||||
"MessageConfirmRenameGenre": "你确定要将所有项目流派 \"{0}\" 重命名到 \"{1}\"?",
|
"MessageConfirmRenameGenre": "你确定要将所有项目流派 \"{0}\" 重命名到 \"{1}\"?",
|
||||||
"MessageConfirmRenameGenreMergeNote": "注意: 该流派已经存在, 因此它们将被合并.",
|
"MessageConfirmRenameGenreMergeNote": "注意: 该流派已经存在, 因此它们将被合并.",
|
||||||
@@ -516,7 +544,7 @@
|
|||||||
"MessageConfirmRenameTag": "你确定要将所有项目标签 \"{0}\" 重命名到 \"{1}\"?",
|
"MessageConfirmRenameTag": "你确定要将所有项目标签 \"{0}\" 重命名到 \"{1}\"?",
|
||||||
"MessageConfirmRenameTagMergeNote": "注意: 该标签已经存在, 因此它们将被合并.",
|
"MessageConfirmRenameTagMergeNote": "注意: 该标签已经存在, 因此它们将被合并.",
|
||||||
"MessageConfirmRenameTagWarning": "警告! 已经存在有大小写不同的类似标签 \"{0}\".",
|
"MessageConfirmRenameTagWarning": "警告! 已经存在有大小写不同的类似标签 \"{0}\".",
|
||||||
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "你确定要发送 {0} 电子书 \"{1}\" 到设备 \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "正在下载剧集",
|
"MessageDownloadingEpisode": "正在下载剧集",
|
||||||
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
||||||
"MessageEmbedFinished": "嵌入完成!",
|
"MessageEmbedFinished": "嵌入完成!",
|
||||||
@@ -535,6 +563,8 @@
|
|||||||
"MessageM4BFailed": "M4B 失败!",
|
"MessageM4BFailed": "M4B 失败!",
|
||||||
"MessageM4BFinished": "M4B 完成!",
|
"MessageM4BFinished": "M4B 完成!",
|
||||||
"MessageMapChapterTitles": "将章节标题映射到现有的有声读物章节, 无需调整时间戳",
|
"MessageMapChapterTitles": "将章节标题映射到现有的有声读物章节, 无需调整时间戳",
|
||||||
|
"MessageMarkAllEpisodesFinished": "Mark all episodes finished",
|
||||||
|
"MessageMarkAllEpisodesNotFinished": "Mark all episodes not finished",
|
||||||
"MessageMarkAsFinished": "标记为已听完",
|
"MessageMarkAsFinished": "标记为已听完",
|
||||||
"MessageMarkAsNotFinished": "标记为未听完",
|
"MessageMarkAsNotFinished": "标记为未听完",
|
||||||
"MessageMatchBooksDescription": "尝试将媒体库中的图书与所选搜索提供商的图书进行匹配, 并填写空白的详细信息和封面. 不覆盖详细信息.",
|
"MessageMatchBooksDescription": "尝试将媒体库中的图书与所选搜索提供商的图书进行匹配, 并填写空白的详细信息和封面. 不覆盖详细信息.",
|
||||||
@@ -575,7 +605,6 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "从收藏中创建播放列表",
|
"MessagePlaylistCreateFromCollection": "从收藏中创建播放列表",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "播客没有可用于匹配 RSS 源的 url",
|
"MessagePodcastHasNoRSSFeedForMatching": "播客没有可用于匹配 RSS 源的 url",
|
||||||
"MessageQuickMatchDescription": "使用来自 '{0}' 的第一个匹配结果填充空白详细信息和封面. 除非启用 '首选匹配元数据' 服务器设置, 否则不会覆盖详细信息.",
|
"MessageQuickMatchDescription": "使用来自 '{0}' 的第一个匹配结果填充空白详细信息和封面. 除非启用 '首选匹配元数据' 服务器设置, 否则不会覆盖详细信息.",
|
||||||
"MessageRemoveAllItemsWarning": "警告! 此操作将从数据库中删除所有的媒体库项, 包括您所做的任何更新或匹配. 这不会对实际文件产生任何影响. 你确定吗?",
|
|
||||||
"MessageRemoveChapter": "移除章节",
|
"MessageRemoveChapter": "移除章节",
|
||||||
"MessageRemoveEpisodes": "移除 {0} 剧集",
|
"MessageRemoveEpisodes": "移除 {0} 剧集",
|
||||||
"MessageRemoveFromPlayerQueue": "从播放队列中移除",
|
"MessageRemoveFromPlayerQueue": "从播放队列中移除",
|
||||||
@@ -671,8 +700,8 @@
|
|||||||
"ToastRemoveItemFromCollectionSuccess": "项目已从收藏中删除",
|
"ToastRemoveItemFromCollectionSuccess": "项目已从收藏中删除",
|
||||||
"ToastRSSFeedCloseFailed": "关闭 RSS 源失败",
|
"ToastRSSFeedCloseFailed": "关闭 RSS 源失败",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS 源已关闭",
|
"ToastRSSFeedCloseSuccess": "RSS 源已关闭",
|
||||||
"ToastSendEbookToDeviceFailed": "Failed to send Ebook to device",
|
"ToastSendEbookToDeviceFailed": "发送电子书到设备失败",
|
||||||
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
|
"ToastSendEbookToDeviceSuccess": "电子书已经发送到设备 \"{0}\"",
|
||||||
"ToastSeriesUpdateFailed": "更新系列失败",
|
"ToastSeriesUpdateFailed": "更新系列失败",
|
||||||
"ToastSeriesUpdateSuccess": "系列已更新",
|
"ToastSeriesUpdateSuccess": "系列已更新",
|
||||||
"ToastSessionDeleteFailed": "删除会话失败",
|
"ToastSessionDeleteFailed": "删除会话失败",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ services:
|
|||||||
- 13378:80
|
- 13378:80
|
||||||
volumes:
|
volumes:
|
||||||
- ./audiobooks:/audiobooks
|
- ./audiobooks:/audiobooks
|
||||||
|
- ./podcasts:/podcasts
|
||||||
- ./metadata:/metadata
|
- ./metadata:/metadata
|
||||||
- ./config:/config
|
- ./config:/config
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
/*
|
|
||||||
This is an example of a fully expanded book library item
|
|
||||||
*/
|
|
||||||
|
|
||||||
const LibraryItem = require('../server/objects/LibraryItem')
|
|
||||||
|
|
||||||
new LibraryItem({
|
|
||||||
id: 'li_abai123wir',
|
|
||||||
ino: "55450570412017066",
|
|
||||||
libraryId: 'lib_1239p1d8',
|
|
||||||
folderId: 'fol_192ab8901',
|
|
||||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule',
|
|
||||||
relPath: '/Terry Goodkind/Sword of Truth/1 - Wizards First Rule',
|
|
||||||
mtimeMs: 1646784672127,
|
|
||||||
ctimeMs: 1646784672127,
|
|
||||||
birthtimeMs: 1646784672127,
|
|
||||||
addedAt: 1646784672127,
|
|
||||||
updatedAt: 1646784672127,
|
|
||||||
lastScan: 1646784672127,
|
|
||||||
scanVersion: 1.72,
|
|
||||||
isMissing: false,
|
|
||||||
isInvalid: false,
|
|
||||||
mediaType: 'book',
|
|
||||||
media: { // Book.js
|
|
||||||
coverPath: '/metadata/items/li_abai123wir/cover.webp',
|
|
||||||
tags: ['favorites'],
|
|
||||||
lastCoverSearch: null,
|
|
||||||
lastCoverSearchQuery: null,
|
|
||||||
metadata: { // BookMetadata.js
|
|
||||||
title: 'Wizards First Rule',
|
|
||||||
subtitle: null,
|
|
||||||
authors: [
|
|
||||||
{
|
|
||||||
id: 'au_42908lkajsfdk',
|
|
||||||
name: 'Terry Goodkind'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
narrators: ['Sam Tsoutsouvas'],
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
id: 'se_902384lansf',
|
|
||||||
name: 'Sword of Truth',
|
|
||||||
sequence: 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
genres: ['Fantasy', 'Adventure'],
|
|
||||||
publishedYear: '1994',
|
|
||||||
publishedDate: '1994-01-01',
|
|
||||||
publisher: 'Brilliance Audio',
|
|
||||||
description: 'In the aftermath of the brutal murder of his father, a mysterious woman...',
|
|
||||||
isbn: '289374092834',
|
|
||||||
asin: '19023819203',
|
|
||||||
language: 'english',
|
|
||||||
explicit: false
|
|
||||||
},
|
|
||||||
audioFiles: [
|
|
||||||
{ // AudioFile.js
|
|
||||||
ino: "55450570412017066",
|
|
||||||
index: 1,
|
|
||||||
metadata: { // FileMetadata.js
|
|
||||||
filename: 'audiofile.mp3',
|
|
||||||
ext: '.mp3',
|
|
||||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/CD01/audiofile.mp3',
|
|
||||||
relPath: '/CD01/audiofile.mp3',
|
|
||||||
mtimeMs: 1646784672127,
|
|
||||||
ctimeMs: 1646784672127,
|
|
||||||
birthtimeMs: 1646784672127,
|
|
||||||
size: 1197449516
|
|
||||||
},
|
|
||||||
trackNumFromMeta: 1,
|
|
||||||
discNumFromMeta: null,
|
|
||||||
trackNumFromFilename: null,
|
|
||||||
discNumFromFilename: 1,
|
|
||||||
manuallyVerified: false,
|
|
||||||
exclude: false,
|
|
||||||
invalid: false,
|
|
||||||
format: "MP2/3 (MPEG audio layer 2/3)",
|
|
||||||
duration: 2342342,
|
|
||||||
bitRate: 324234,
|
|
||||||
language: null,
|
|
||||||
codec: 'mp3',
|
|
||||||
timeBase: "1/14112000",
|
|
||||||
channels: 1,
|
|
||||||
channelLayout: "mono",
|
|
||||||
chapters: [],
|
|
||||||
embeddedCoverArt: 'jpeg', // Video stream codec ['mjpeg', 'jpeg', 'png'] or null
|
|
||||||
metaTags: { // AudioMetaTags.js
|
|
||||||
tagAlbum: '',
|
|
||||||
tagArtist: '',
|
|
||||||
tagGenre: '',
|
|
||||||
tagTitle: '',
|
|
||||||
tagSeries: '',
|
|
||||||
tagSeriesPart: '',
|
|
||||||
tagTrack: '',
|
|
||||||
tagDisc: '',
|
|
||||||
tagSubtitle: '',
|
|
||||||
tagAlbumArtist: '',
|
|
||||||
tagDate: '',
|
|
||||||
tagComposer: '',
|
|
||||||
tagPublisher: '',
|
|
||||||
tagComment: '',
|
|
||||||
tagDescription: '',
|
|
||||||
tagEncoder: '',
|
|
||||||
tagEncodedBy: '',
|
|
||||||
tagIsbn: '',
|
|
||||||
tagLanguage: '',
|
|
||||||
tagASIN: ''
|
|
||||||
},
|
|
||||||
addedAt: 1646784672127,
|
|
||||||
updatedAt: 1646784672127
|
|
||||||
}
|
|
||||||
],
|
|
||||||
chapters: [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
title: 'Chapter 01',
|
|
||||||
start: 0,
|
|
||||||
end: 2467.753
|
|
||||||
}
|
|
||||||
],
|
|
||||||
missingParts: [4, 10], // Array of missing parts in tracklist
|
|
||||||
ebookFile: { // EBookFile.js
|
|
||||||
ino: "55450570412017066",
|
|
||||||
metadata: { // FileMetadata.js
|
|
||||||
filename: 'ebookfile.mobi',
|
|
||||||
ext: '.mobi',
|
|
||||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/ebookfile.mobi',
|
|
||||||
relPath: '/ebookfile.mobi',
|
|
||||||
mtimeMs: 1646784672127,
|
|
||||||
ctimeMs: 1646784672127,
|
|
||||||
birthtimeMs: 1646784672127,
|
|
||||||
size: 1197449516
|
|
||||||
},
|
|
||||||
ebookFormat: 'mobi',
|
|
||||||
addedAt: 1646784672127,
|
|
||||||
updatedAt: 1646784672127
|
|
||||||
}
|
|
||||||
},
|
|
||||||
libraryFiles: [
|
|
||||||
{ // LibraryFile.js
|
|
||||||
ino: "55450570412017066",
|
|
||||||
metadata: { // FileMetadata.js
|
|
||||||
filename: 'cover.png',
|
|
||||||
ext: '.png',
|
|
||||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/subfolder/cover.png',
|
|
||||||
relPath: '/subfolder/cover.png',
|
|
||||||
mtimeMs: 1646784672127,
|
|
||||||
ctimeMs: 1646784672127,
|
|
||||||
birthtimeMs: 1646784672127,
|
|
||||||
size: 1197449516
|
|
||||||
},
|
|
||||||
addedAt: 1646784672127,
|
|
||||||
updatedAt: 1646784672127
|
|
||||||
},
|
|
||||||
{ // LibraryFile.js
|
|
||||||
ino: "55450570412017066",
|
|
||||||
metadata: { // FileMetadata.js
|
|
||||||
filename: 'cover.png',
|
|
||||||
ext: '.mobi',
|
|
||||||
path: '/audiobooks/Terry Goodkind/Sword of Truth/1 - Wizards First Rule/ebookfile.mobi',
|
|
||||||
relPath: '/ebookfile.mobi',
|
|
||||||
mtimeMs: 1646784672127,
|
|
||||||
ctimeMs: 1646784672127,
|
|
||||||
birthtimeMs: 1646784672127,
|
|
||||||
size: 1197449516
|
|
||||||
},
|
|
||||||
addedAt: 1646784672127,
|
|
||||||
updatedAt: 1646784672127
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
This is an example of a fully expanded podcast library item (under construction)
|
|
||||||
*/
|
|
||||||
|
|
||||||
const LibraryItem = require('../server/objects/LibraryItem')
|
|
||||||
|
|
||||||
new LibraryItem({
|
|
||||||
id: 'li_abai123wir',
|
|
||||||
ino: "55450570412017066",
|
|
||||||
libraryId: 'lib_1239p1d8',
|
|
||||||
folderId: 'fol_192ab8901',
|
|
||||||
path: '/podcasts/Great Podcast Name',
|
|
||||||
relPath: '/Great Podcast Name',
|
|
||||||
mtimeMs: 1646784672127,
|
|
||||||
ctimeMs: 1646784672127,
|
|
||||||
birthtimeMs: 1646784672127,
|
|
||||||
addedAt: 1646784672127,
|
|
||||||
updatedAt: 1646784672127,
|
|
||||||
lastScan: 1646784672127,
|
|
||||||
scanVersion: 1.72,
|
|
||||||
isMissing: false,
|
|
||||||
isInvalid: false,
|
|
||||||
mediaType: 'podcast',
|
|
||||||
media: { // Podcast.js
|
|
||||||
coverPath: '/metadata/items/li_abai123wir/cover.webp',
|
|
||||||
tags: ['favorites'],
|
|
||||||
lastCoverSearch: null,
|
|
||||||
lastCoverSearchQuery: null,
|
|
||||||
metadata: { // PodcastMetadata.js
|
|
||||||
title: 'Great Podcast Name',
|
|
||||||
artist: 'Some Artist Name',
|
|
||||||
genres: ['Fantasy', 'Adventure'],
|
|
||||||
publishedDate: '1994-01-01',
|
|
||||||
description: 'In the aftermath of the brutal murder of his father, a mysterious woman...',
|
|
||||||
feedUrl: '',
|
|
||||||
itunesPageUrl: '',
|
|
||||||
itunesId: '',
|
|
||||||
itunesArtistId: '',
|
|
||||||
explicit: false
|
|
||||||
},
|
|
||||||
episodes: [
|
|
||||||
{ // PodcastEpisode.js
|
|
||||||
id: 'ep_289374asf0a98',
|
|
||||||
index: 1,
|
|
||||||
// TODO: podcast episode data and PodcastEpisodeMetadata
|
|
||||||
addedAt: 1646784672127,
|
|
||||||
updatedAt: 1646784672127
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
libraryFiles: [
|
|
||||||
{ // LibraryFile.js
|
|
||||||
ino: "55450570412017066",
|
|
||||||
metadata: { // FileMetadata.js
|
|
||||||
filename: 'cover.png',
|
|
||||||
ext: '.png',
|
|
||||||
path: '/podcasts/Great Podcast Name/cover.png',
|
|
||||||
relPath: '/cover.png',
|
|
||||||
mtimeMs: 1646784672127,
|
|
||||||
ctimeMs: 1646784672127,
|
|
||||||
birthtimeMs: 1646784672127,
|
|
||||||
size: 1197449516
|
|
||||||
},
|
|
||||||
addedAt: 1646784672127,
|
|
||||||
updatedAt: 1646784672127
|
|
||||||
},
|
|
||||||
{ // LibraryFile.js
|
|
||||||
ino: "55450570412017066",
|
|
||||||
metadata: { // FileMetadata.js
|
|
||||||
filename: 'episode_1.mp3',
|
|
||||||
ext: '.mp3',
|
|
||||||
path: '/podcasts/Great Podcast Name/episode_1.mp3',
|
|
||||||
relPath: '/episode_1.mp3',
|
|
||||||
mtimeMs: 1646784672127,
|
|
||||||
ctimeMs: 1646784672127,
|
|
||||||
birthtimeMs: 1646784672127,
|
|
||||||
size: 1197449516
|
|
||||||
},
|
|
||||||
addedAt: 1646784672127,
|
|
||||||
updatedAt: 1646784672127
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
Generated
+2362
-18
File diff suppressed because it is too large
Load Diff
+4
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.2.23",
|
"version": "2.3.4",
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"pkg": {
|
"pkg": {
|
||||||
"assets": [
|
"assets": [
|
||||||
"client/dist/**/*",
|
"client/dist/**/*",
|
||||||
"server/Db.js"
|
"node_modules/sqlite3/lib/binding/**/*.node"
|
||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"prod.js",
|
"prod.js",
|
||||||
@@ -36,7 +36,9 @@
|
|||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"node-tone": "^1.0.1",
|
"node-tone": "^1.0.1",
|
||||||
"nodemailer": "^6.9.2",
|
"nodemailer": "^6.9.2",
|
||||||
|
"sequelize": "^6.32.1",
|
||||||
"socket.io": "^4.5.4",
|
"socket.io": "^4.5.4",
|
||||||
|
"sqlite3": "^5.1.6",
|
||||||
"xml2js": "^0.5.0"
|
"xml2js": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ Audiobookshelf is a self-hosted audiobook and podcast server.
|
|||||||
* Merge your audio files into a single m4b
|
* Merge your audio files into a single m4b
|
||||||
* Embed metadata and cover image into your audio files (using [Tone](https://github.com/sandreas/tone))
|
* Embed metadata and cover image into your audio files (using [Tone](https://github.com/sandreas/tone))
|
||||||
* Basic ebook support and ereader
|
* Basic ebook support and ereader
|
||||||
|
* Epub, pdf, cbr, cbz
|
||||||
|
* Send ebook to device (i.e. Kindle)
|
||||||
|
* Open RSS feeds for podcasts and audiobooks
|
||||||
|
|
||||||
Is there a feature you are looking for? [Suggest it](https://github.com/advplyr/audiobookshelf/issues/new/choose)
|
Is there a feature you are looking for? [Suggest it](https://github.com/advplyr/audiobookshelf/issues/new/choose)
|
||||||
|
|
||||||
|
|||||||
+40
-45
@@ -2,21 +2,10 @@ const bcrypt = require('./libs/bcryptjs')
|
|||||||
const jwt = require('./libs/jsonwebtoken')
|
const jwt = require('./libs/jsonwebtoken')
|
||||||
const requestIp = require('./libs/requestIp')
|
const requestIp = require('./libs/requestIp')
|
||||||
const Logger = require('./Logger')
|
const Logger = require('./Logger')
|
||||||
|
const Database = require('./Database')
|
||||||
|
|
||||||
class Auth {
|
class Auth {
|
||||||
constructor(db) {
|
constructor() { }
|
||||||
this.db = db
|
|
||||||
|
|
||||||
this.user = null
|
|
||||||
}
|
|
||||||
|
|
||||||
get username() {
|
|
||||||
return this.user ? this.user.username : 'nobody'
|
|
||||||
}
|
|
||||||
|
|
||||||
get users() {
|
|
||||||
return this.db.users
|
|
||||||
}
|
|
||||||
|
|
||||||
cors(req, res, next) {
|
cors(req, res, next) {
|
||||||
res.header('Access-Control-Allow-Origin', '*')
|
res.header('Access-Control-Allow-Origin', '*')
|
||||||
@@ -35,20 +24,21 @@ class Auth {
|
|||||||
async initTokenSecret() {
|
async initTokenSecret() {
|
||||||
if (process.env.TOKEN_SECRET) { // User can supply their own token secret
|
if (process.env.TOKEN_SECRET) { // User can supply their own token secret
|
||||||
Logger.debug(`[Auth] Setting token secret - using user passed in TOKEN_SECRET env var`)
|
Logger.debug(`[Auth] Setting token secret - using user passed in TOKEN_SECRET env var`)
|
||||||
this.db.serverSettings.tokenSecret = process.env.TOKEN_SECRET
|
Database.serverSettings.tokenSecret = process.env.TOKEN_SECRET
|
||||||
} else {
|
} else {
|
||||||
Logger.debug(`[Auth] Setting token secret - using random bytes`)
|
Logger.debug(`[Auth] Setting token secret - using random bytes`)
|
||||||
this.db.serverSettings.tokenSecret = require('crypto').randomBytes(256).toString('base64')
|
Database.serverSettings.tokenSecret = require('crypto').randomBytes(256).toString('base64')
|
||||||
}
|
}
|
||||||
await this.db.updateServerSettings()
|
await Database.updateServerSettings()
|
||||||
|
|
||||||
// New token secret creation added in v2.1.0 so generate new API tokens for each user
|
// New token secret creation added in v2.1.0 so generate new API tokens for each user
|
||||||
if (this.db.users.length) {
|
const users = await Database.userModel.getOldUsers()
|
||||||
for (const user of this.db.users) {
|
if (users.length) {
|
||||||
|
for (const user of users) {
|
||||||
user.token = await this.generateAccessToken({ userId: user.id, username: user.username })
|
user.token = await this.generateAccessToken({ userId: user.id, username: user.username })
|
||||||
Logger.warn(`[Auth] User ${user.username} api token has been updated using new token secret`)
|
Logger.warn(`[Auth] User ${user.username} api token has been updated using new token secret`)
|
||||||
}
|
}
|
||||||
await this.db.updateEntities('user', this.db.users)
|
await Database.updateBulkUsers(users)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +58,7 @@ class Auth {
|
|||||||
return res.sendStatus(401)
|
return res.sendStatus(401)
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = await this.verifyToken(token)
|
const user = await this.verifyToken(token)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
Logger.error('Verify Token User Not Found', token)
|
Logger.error('Verify Token User Not Found', token)
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
@@ -95,7 +85,7 @@ class Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateAccessToken(payload) {
|
generateAccessToken(payload) {
|
||||||
return jwt.sign(payload, global.ServerSettings.tokenSecret);
|
return jwt.sign(payload, Database.serverSettings.tokenSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticateUser(token) {
|
authenticateUser(token) {
|
||||||
@@ -104,23 +94,34 @@ class Auth {
|
|||||||
|
|
||||||
verifyToken(token) {
|
verifyToken(token) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
jwt.verify(token, global.ServerSettings.tokenSecret, (err, payload) => {
|
jwt.verify(token, Database.serverSettings.tokenSecret, async (err, payload) => {
|
||||||
if (!payload || err) {
|
if (!payload || err) {
|
||||||
Logger.error('JWT Verify Token Failed', err)
|
Logger.error('JWT Verify Token Failed', err)
|
||||||
return resolve(null)
|
return resolve(null)
|
||||||
}
|
}
|
||||||
const user = this.users.find(u => u.id === payload.userId && u.username === payload.username)
|
|
||||||
resolve(user || null)
|
const user = await Database.userModel.getUserByIdOrOldId(payload.userId)
|
||||||
|
if (user && user.username === payload.username) {
|
||||||
|
resolve(user)
|
||||||
|
} else {
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserLoginResponsePayload(user) {
|
/**
|
||||||
|
* Payload returned to a user after successful login
|
||||||
|
* @param {oldUser} user
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
async getUserLoginResponsePayload(user) {
|
||||||
|
const libraryIds = await Database.libraryModel.getAllLibraryIds()
|
||||||
return {
|
return {
|
||||||
user: user.toJSONForBrowser(),
|
user: user.toJSONForBrowser(),
|
||||||
userDefaultLibraryId: user.getDefaultLibraryId(this.db.libraries),
|
userDefaultLibraryId: user.getDefaultLibraryId(libraryIds),
|
||||||
serverSettings: this.db.serverSettings.toJSONForBrowser(),
|
serverSettings: Database.serverSettings.toJSONForBrowser(),
|
||||||
ereaderDevices: this.db.emailSettings.getEReaderDevices(user),
|
ereaderDevices: Database.emailSettings.getEReaderDevices(user),
|
||||||
Source: global.Source
|
Source: global.Source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +131,7 @@ class Auth {
|
|||||||
const username = (req.body.username || '').toLowerCase()
|
const username = (req.body.username || '').toLowerCase()
|
||||||
const password = req.body.password || ''
|
const password = req.body.password || ''
|
||||||
|
|
||||||
const user = this.users.find(u => u.username.toLowerCase() === username)
|
const user = await Database.userModel.getUserByUsername(username)
|
||||||
|
|
||||||
if (!user?.isActive) {
|
if (!user?.isActive) {
|
||||||
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
|
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
|
||||||
@@ -142,12 +143,13 @@ class Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check passwordless root user
|
// Check passwordless root user
|
||||||
if (user.id === 'root' && (!user.pash || user.pash === '')) {
|
if (user.type === 'root' && (!user.pash || user.pash === '')) {
|
||||||
if (password) {
|
if (password) {
|
||||||
return res.status(401).send('Invalid root password (hint: there is none)')
|
return res.status(401).send('Invalid root password (hint: there is none)')
|
||||||
} else {
|
} else {
|
||||||
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`)
|
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`)
|
||||||
return res.json(this.getUserLoginResponsePayload(user))
|
const userLoginResponsePayload = await this.getUserLoginResponsePayload(user)
|
||||||
|
return res.json(userLoginResponsePayload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +157,8 @@ class Auth {
|
|||||||
const compare = await bcrypt.compare(password, user.pash)
|
const compare = await bcrypt.compare(password, user.pash)
|
||||||
if (compare) {
|
if (compare) {
|
||||||
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`)
|
Logger.info(`[Auth] ${user.username} logged in from ${ipAddress}`)
|
||||||
res.json(this.getUserLoginResponsePayload(user))
|
const userLoginResponsePayload = await this.getUserLoginResponsePayload(user)
|
||||||
|
res.json(userLoginResponsePayload)
|
||||||
} else {
|
} else {
|
||||||
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
|
Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
|
||||||
if (req.rateLimit.remaining <= 2) {
|
if (req.rateLimit.remaining <= 2) {
|
||||||
@@ -166,15 +169,6 @@ class Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not in use now
|
|
||||||
lockUser(user) {
|
|
||||||
user.isLocked = true
|
|
||||||
return this.db.updateEntity('user', user).catch((error) => {
|
|
||||||
Logger.error('[Auth] Failed to lock user', user.username, error)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
comparePassword(password, user) {
|
comparePassword(password, user) {
|
||||||
if (user.type === 'root' && !password && !user.pash) return true
|
if (user.type === 'root' && !password && !user.pash) return true
|
||||||
if (!password || !user.pash) return false
|
if (!password || !user.pash) return false
|
||||||
@@ -184,7 +178,7 @@ class Auth {
|
|||||||
async userChangePassword(req, res) {
|
async userChangePassword(req, res) {
|
||||||
var { password, newPassword } = req.body
|
var { password, newPassword } = req.body
|
||||||
newPassword = newPassword || ''
|
newPassword = newPassword || ''
|
||||||
var matchingUser = this.users.find(u => u.id === req.user.id)
|
const matchingUser = await Database.userModel.getUserById(req.user.id)
|
||||||
|
|
||||||
// Only root can have an empty password
|
// Only root can have an empty password
|
||||||
if (matchingUser.type !== 'root' && !newPassword) {
|
if (matchingUser.type !== 'root' && !newPassword) {
|
||||||
@@ -193,14 +187,14 @@ class Auth {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var compare = await this.comparePassword(password, matchingUser)
|
const compare = await this.comparePassword(password, matchingUser)
|
||||||
if (!compare) {
|
if (!compare) {
|
||||||
return res.json({
|
return res.json({
|
||||||
error: 'Invalid password'
|
error: 'Invalid password'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var pw = ''
|
let pw = ''
|
||||||
if (newPassword) {
|
if (newPassword) {
|
||||||
pw = await this.hashPass(newPassword)
|
pw = await this.hashPass(newPassword)
|
||||||
if (!pw) {
|
if (!pw) {
|
||||||
@@ -211,7 +205,8 @@ class Auth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
matchingUser.pash = pw
|
matchingUser.pash = pw
|
||||||
var success = await this.db.updateEntity('user', matchingUser)
|
|
||||||
|
const success = await Database.updateUser(matchingUser)
|
||||||
if (success) {
|
if (success) {
|
||||||
res.json({
|
res.json({
|
||||||
success: true
|
success: true
|
||||||
|
|||||||
@@ -0,0 +1,694 @@
|
|||||||
|
const Path = require('path')
|
||||||
|
const { Sequelize } = require('sequelize')
|
||||||
|
|
||||||
|
const packageJson = require('../package.json')
|
||||||
|
const fs = require('./libs/fsExtra')
|
||||||
|
const Logger = require('./Logger')
|
||||||
|
|
||||||
|
const dbMigration = require('./utils/migrations/dbMigration')
|
||||||
|
const Auth = require('./Auth')
|
||||||
|
|
||||||
|
class Database {
|
||||||
|
constructor() {
|
||||||
|
this.sequelize = null
|
||||||
|
this.dbPath = null
|
||||||
|
this.isNew = false // New absdatabase.sqlite created
|
||||||
|
this.hasRootUser = false // Used to show initialization page in web ui
|
||||||
|
|
||||||
|
this.settings = []
|
||||||
|
|
||||||
|
// Cached library filter data
|
||||||
|
this.libraryFilterData = {}
|
||||||
|
|
||||||
|
/** @type {import('./objects/settings/ServerSettings')} */
|
||||||
|
this.serverSettings = null
|
||||||
|
/** @type {import('./objects/settings/NotificationSettings')} */
|
||||||
|
this.notificationSettings = null
|
||||||
|
/** @type {import('./objects/settings/EmailSettings')} */
|
||||||
|
this.emailSettings = null
|
||||||
|
}
|
||||||
|
|
||||||
|
get models() {
|
||||||
|
return this.sequelize?.models || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/User')} */
|
||||||
|
get userModel() {
|
||||||
|
return this.models.user
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/Library')} */
|
||||||
|
get libraryModel() {
|
||||||
|
return this.models.library
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/LibraryFolder')} */
|
||||||
|
get libraryFolderModel() {
|
||||||
|
return this.models.libraryFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/Author')} */
|
||||||
|
get authorModel() {
|
||||||
|
return this.models.author
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/Series')} */
|
||||||
|
get seriesModel() {
|
||||||
|
return this.models.series
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/Book')} */
|
||||||
|
get bookModel() {
|
||||||
|
return this.models.book
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/BookSeries')} */
|
||||||
|
get bookSeriesModel() {
|
||||||
|
return this.models.bookSeries
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/BookAuthor')} */
|
||||||
|
get bookAuthorModel() {
|
||||||
|
return this.models.bookAuthor
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/Podcast')} */
|
||||||
|
get podcastModel() {
|
||||||
|
return this.models.podcast
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/PodcastEpisode')} */
|
||||||
|
get podcastEpisodeModel() {
|
||||||
|
return this.models.podcastEpisode
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/LibraryItem')} */
|
||||||
|
get libraryItemModel() {
|
||||||
|
return this.models.libraryItem
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/PodcastEpisode')} */
|
||||||
|
get podcastEpisodeModel() {
|
||||||
|
return this.models.podcastEpisode
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/MediaProgress')} */
|
||||||
|
get mediaProgressModel() {
|
||||||
|
return this.models.mediaProgress
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/Collection')} */
|
||||||
|
get collectionModel() {
|
||||||
|
return this.models.collection
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/CollectionBook')} */
|
||||||
|
get collectionBookModel() {
|
||||||
|
return this.models.collectionBook
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/Playlist')} */
|
||||||
|
get playlistModel() {
|
||||||
|
return this.models.playlist
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/PlaylistMediaItem')} */
|
||||||
|
get playlistMediaItemModel() {
|
||||||
|
return this.models.playlistMediaItem
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/Feed')} */
|
||||||
|
get feedModel() {
|
||||||
|
return this.models.feed
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {typeof import('./models/Feed')} */
|
||||||
|
get feedEpisodeModel() {
|
||||||
|
return this.models.feedEpisode
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if db file exists
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
async checkHasDb() {
|
||||||
|
if (!await fs.pathExists(this.dbPath)) {
|
||||||
|
Logger.info(`[Database] absdatabase.sqlite not found at ${this.dbPath}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to db, build models and run migrations
|
||||||
|
* @param {boolean} [force=false] Used for testing, drops & re-creates all tables
|
||||||
|
*/
|
||||||
|
async init(force = false) {
|
||||||
|
this.dbPath = Path.join(global.ConfigPath, 'absdatabase.sqlite')
|
||||||
|
|
||||||
|
// First check if this is a new database
|
||||||
|
this.isNew = !(await this.checkHasDb()) || force
|
||||||
|
|
||||||
|
if (!await this.connect()) {
|
||||||
|
throw new Error('Database connection failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.buildModels(force)
|
||||||
|
Logger.info(`[Database] Db initialized with models:`, Object.keys(this.sequelize.models).join(', '))
|
||||||
|
|
||||||
|
await this.cleanDatabase()
|
||||||
|
await this.loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to db
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
async connect() {
|
||||||
|
Logger.info(`[Database] Initializing db at "${this.dbPath}"`)
|
||||||
|
this.sequelize = new Sequelize({
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: this.dbPath,
|
||||||
|
logging: false,
|
||||||
|
transactionType: 'IMMEDIATE'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Helper function
|
||||||
|
this.sequelize.uppercaseFirst = str => str ? `${str[0].toUpperCase()}${str.substr(1)}` : ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.sequelize.authenticate()
|
||||||
|
Logger.info(`[Database] Db connection was successful`)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`[Database] Failed to connect to db`, error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from db
|
||||||
|
*/
|
||||||
|
async disconnect() {
|
||||||
|
Logger.info(`[Database] Disconnecting sqlite db`)
|
||||||
|
await this.sequelize.close()
|
||||||
|
this.sequelize = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnect to db and init
|
||||||
|
*/
|
||||||
|
async reconnect() {
|
||||||
|
Logger.info(`[Database] Reconnecting sqlite db`)
|
||||||
|
await this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
buildModels(force = false) {
|
||||||
|
require('./models/User').init(this.sequelize)
|
||||||
|
require('./models/Library').init(this.sequelize)
|
||||||
|
require('./models/LibraryFolder').init(this.sequelize)
|
||||||
|
require('./models/Book').init(this.sequelize)
|
||||||
|
require('./models/Podcast').init(this.sequelize)
|
||||||
|
require('./models/PodcastEpisode').init(this.sequelize)
|
||||||
|
require('./models/LibraryItem').init(this.sequelize)
|
||||||
|
require('./models/MediaProgress').init(this.sequelize)
|
||||||
|
require('./models/Series').init(this.sequelize)
|
||||||
|
require('./models/BookSeries').init(this.sequelize)
|
||||||
|
require('./models/Author').init(this.sequelize)
|
||||||
|
require('./models/BookAuthor').init(this.sequelize)
|
||||||
|
require('./models/Collection').init(this.sequelize)
|
||||||
|
require('./models/CollectionBook').init(this.sequelize)
|
||||||
|
require('./models/Playlist').init(this.sequelize)
|
||||||
|
require('./models/PlaylistMediaItem').init(this.sequelize)
|
||||||
|
require('./models/Device').init(this.sequelize)
|
||||||
|
require('./models/PlaybackSession').init(this.sequelize)
|
||||||
|
require('./models/Feed').init(this.sequelize)
|
||||||
|
require('./models/FeedEpisode').init(this.sequelize)
|
||||||
|
require('./models/Setting').init(this.sequelize)
|
||||||
|
|
||||||
|
return this.sequelize.sync({ force, alter: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two server versions
|
||||||
|
* @param {string} v1
|
||||||
|
* @param {string} v2
|
||||||
|
* @returns {-1|0|1} 1 if v1 > v2
|
||||||
|
*/
|
||||||
|
compareVersions(v1, v2) {
|
||||||
|
if (!v1 || !v2) return 0
|
||||||
|
return v1.localeCompare(v2, undefined, { numeric: true, sensitivity: "case", caseFirst: "upper" })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if migration to sqlite db is necessary & runs migration.
|
||||||
|
*
|
||||||
|
* Check if version was upgraded and run any version specific migrations.
|
||||||
|
*
|
||||||
|
* Loads most of the data from the database. This is a temporary solution.
|
||||||
|
*/
|
||||||
|
async loadData() {
|
||||||
|
if (this.isNew && await dbMigration.checkShouldMigrate()) {
|
||||||
|
Logger.info(`[Database] New database was created and old database was detected - migrating old to new`)
|
||||||
|
await dbMigration.migrate(this.models)
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsData = await this.models.setting.getOldSettings()
|
||||||
|
this.settings = settingsData.settings
|
||||||
|
this.emailSettings = settingsData.emailSettings
|
||||||
|
this.serverSettings = settingsData.serverSettings
|
||||||
|
this.notificationSettings = settingsData.notificationSettings
|
||||||
|
global.ServerSettings = this.serverSettings.toJSON()
|
||||||
|
|
||||||
|
// Version specific migrations
|
||||||
|
if (this.serverSettings.version === '2.3.0' && this.compareVersions(packageJson.version, '2.3.0') == 1) {
|
||||||
|
await dbMigration.migrationPatch(this)
|
||||||
|
}
|
||||||
|
if (['2.3.0', '2.3.1', '2.3.2', '2.3.3'].includes(this.serverSettings.version) && this.compareVersions(packageJson.version, '2.3.3') >= 0) {
|
||||||
|
await dbMigration.migrationPatch2(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set if root user has been created
|
||||||
|
this.hasRootUser = await this.models.user.getHasRootUser()
|
||||||
|
|
||||||
|
if (packageJson.version !== this.serverSettings.version) {
|
||||||
|
Logger.info(`[Database] Server upgrade detected from ${this.serverSettings.version} to ${packageJson.version}`)
|
||||||
|
this.serverSettings.version = packageJson.version
|
||||||
|
await this.updateServerSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create root user
|
||||||
|
* @param {string} username
|
||||||
|
* @param {string} pash
|
||||||
|
* @param {Auth} auth
|
||||||
|
* @returns {boolean} true if created
|
||||||
|
*/
|
||||||
|
async createRootUser(username, pash, auth) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await this.models.user.createRootUser(username, pash, auth)
|
||||||
|
this.hasRootUser = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
updateServerSettings() {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
global.ServerSettings = this.serverSettings.toJSON()
|
||||||
|
return this.updateSetting(this.serverSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSetting(settings) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.setting.updateSettingObj(settings.toJSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
async createUser(oldUser) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await this.models.user.createFromOld(oldUser)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUser(oldUser) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.user.updateFromOld(oldUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBulkUsers(oldUsers) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return Promise.all(oldUsers.map(u => this.updateUser(u)))
|
||||||
|
}
|
||||||
|
|
||||||
|
removeUser(userId) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.user.removeById(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
upsertMediaProgress(oldMediaProgress) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.mediaProgress.upsertFromOld(oldMediaProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMediaProgress(mediaProgressId) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.mediaProgress.removeById(mediaProgressId)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBulkBooks(oldBooks) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return Promise.all(oldBooks.map(oldBook => this.models.book.saveFromOld(oldBook)))
|
||||||
|
}
|
||||||
|
|
||||||
|
createLibrary(oldLibrary) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.library.createFromOld(oldLibrary)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLibrary(oldLibrary) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.library.updateFromOld(oldLibrary)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeLibrary(libraryId) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.library.removeById(libraryId)
|
||||||
|
}
|
||||||
|
|
||||||
|
createBulkCollectionBooks(collectionBooks) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.collectionBook.bulkCreate(collectionBooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlaylistMediaItem(playlistMediaItem) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.playlistMediaItem.create(playlistMediaItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
createBulkPlaylistMediaItems(playlistMediaItems) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.playlistMediaItem.bulkCreate(playlistMediaItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createLibraryItem(oldLibraryItem) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await oldLibraryItem.saveMetadata()
|
||||||
|
await this.models.libraryItem.fullCreateFromOld(oldLibraryItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateLibraryItem(oldLibraryItem) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await oldLibraryItem.saveMetadata()
|
||||||
|
return this.models.libraryItem.fullUpdateFromOld(oldLibraryItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeLibraryItem(libraryItemId) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await this.models.libraryItem.removeById(libraryItemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createFeed(oldFeed) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await this.models.feed.fullCreateFromOld(oldFeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFeed(oldFeed) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.feed.fullUpdateFromOld(oldFeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeFeed(feedId) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
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 createAuthor(oldAuthor) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await this.models.author.createFromOld(oldAuthor)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createBulkAuthors(oldAuthors) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await this.models.author.createBulkFromOld(oldAuthors)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAuthor(oldAuthor) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.author.updateFromOld(oldAuthor)
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeAuthor(authorId) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await this.models.author.removeById(authorId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createBulkBookAuthors(bookAuthors) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
await this.models.bookAuthor.bulkCreate(bookAuthors)
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeBulkBookAuthors(authorId = null, bookId = null) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
if (!authorId && !bookId) return
|
||||||
|
await this.models.bookAuthor.removeByIds(authorId, bookId)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlaybackSessions(where = null) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.playbackSession.getOldPlaybackSessions(where)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlaybackSession(sessionId) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.playbackSession.getById(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlaybackSession(oldSession) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.playbackSession.createFromOld(oldSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePlaybackSession(oldSession) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.playbackSession.updateFromOld(oldSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
removePlaybackSession(sessionId) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.playbackSession.removeById(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeviceByDeviceId(deviceId) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.device.getOldDeviceByDeviceId(deviceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDevice(oldDevice) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.device.updateFromOld(oldDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
createDevice(oldDevice) {
|
||||||
|
if (!this.sequelize) return false
|
||||||
|
return this.models.device.createFromOld(oldDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceTagInFilterData(oldTag, newTag) {
|
||||||
|
for (const libraryId in this.libraryFilterData) {
|
||||||
|
const indexOf = this.libraryFilterData[libraryId].tags.findIndex(n => n === oldTag)
|
||||||
|
if (indexOf >= 0) {
|
||||||
|
this.libraryFilterData[libraryId].tags.splice(indexOf, 1, newTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTagFromFilterData(tag) {
|
||||||
|
for (const libraryId in this.libraryFilterData) {
|
||||||
|
this.libraryFilterData[libraryId].tags = this.libraryFilterData[libraryId].tags.filter(t => t !== tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addTagsToFilterData(libraryId, tags) {
|
||||||
|
if (!this.libraryFilterData[libraryId] || !tags?.length) return
|
||||||
|
tags.forEach((t) => {
|
||||||
|
if (!this.libraryFilterData[libraryId].tags.includes(t)) {
|
||||||
|
this.libraryFilterData[libraryId].tags.push(t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceGenreInFilterData(oldGenre, newGenre) {
|
||||||
|
for (const libraryId in this.libraryFilterData) {
|
||||||
|
const indexOf = this.libraryFilterData[libraryId].genres.findIndex(n => n === oldGenre)
|
||||||
|
if (indexOf >= 0) {
|
||||||
|
this.libraryFilterData[libraryId].genres.splice(indexOf, 1, newGenre)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeGenreFromFilterData(genre) {
|
||||||
|
for (const libraryId in this.libraryFilterData) {
|
||||||
|
this.libraryFilterData[libraryId].genres = this.libraryFilterData[libraryId].genres.filter(g => g !== genre)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addGenresToFilterData(libraryId, genres) {
|
||||||
|
if (!this.libraryFilterData[libraryId] || !genres?.length) return
|
||||||
|
genres.forEach((g) => {
|
||||||
|
if (!this.libraryFilterData[libraryId].genres.includes(g)) {
|
||||||
|
this.libraryFilterData[libraryId].genres.push(g)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceNarratorInFilterData(oldNarrator, newNarrator) {
|
||||||
|
for (const libraryId in this.libraryFilterData) {
|
||||||
|
const indexOf = this.libraryFilterData[libraryId].narrators.findIndex(n => n === oldNarrator)
|
||||||
|
if (indexOf >= 0) {
|
||||||
|
this.libraryFilterData[libraryId].narrators.splice(indexOf, 1, newNarrator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeNarratorFromFilterData(narrator) {
|
||||||
|
for (const libraryId in this.libraryFilterData) {
|
||||||
|
this.libraryFilterData[libraryId].narrators = this.libraryFilterData[libraryId].narrators.filter(n => n !== narrator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addNarratorsToFilterData(libraryId, narrators) {
|
||||||
|
if (!this.libraryFilterData[libraryId] || !narrators?.length) return
|
||||||
|
narrators.forEach((n) => {
|
||||||
|
if (!this.libraryFilterData[libraryId].narrators.includes(n)) {
|
||||||
|
this.libraryFilterData[libraryId].narrators.push(n)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSeriesFromFilterData(libraryId, seriesId) {
|
||||||
|
if (!this.libraryFilterData[libraryId]) return
|
||||||
|
this.libraryFilterData[libraryId].series = this.libraryFilterData[libraryId].series.filter(se => se.id !== seriesId)
|
||||||
|
}
|
||||||
|
|
||||||
|
addSeriesToFilterData(libraryId, seriesName, seriesId) {
|
||||||
|
if (!this.libraryFilterData[libraryId]) return
|
||||||
|
// Check if series is already added
|
||||||
|
if (this.libraryFilterData[libraryId].series.some(se => se.id === seriesId)) return
|
||||||
|
this.libraryFilterData[libraryId].series.push({
|
||||||
|
id: seriesId,
|
||||||
|
name: seriesName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAuthorFromFilterData(libraryId, authorId) {
|
||||||
|
if (!this.libraryFilterData[libraryId]) return
|
||||||
|
this.libraryFilterData[libraryId].authors = this.libraryFilterData[libraryId].authors.filter(au => au.id !== authorId)
|
||||||
|
}
|
||||||
|
|
||||||
|
addAuthorToFilterData(libraryId, authorName, authorId) {
|
||||||
|
if (!this.libraryFilterData[libraryId]) return
|
||||||
|
// Check if author is already added
|
||||||
|
if (this.libraryFilterData[libraryId].authors.some(au => au.id === authorId)) return
|
||||||
|
this.libraryFilterData[libraryId].authors.push({
|
||||||
|
id: authorId,
|
||||||
|
name: authorName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addPublisherToFilterData(libraryId, publisher) {
|
||||||
|
if (!this.libraryFilterData[libraryId] || !publisher || this.libraryFilterData[libraryId].publishers.includes(publisher)) return
|
||||||
|
this.libraryFilterData[libraryId].publishers.push(publisher)
|
||||||
|
}
|
||||||
|
|
||||||
|
addLanguageToFilterData(libraryId, language) {
|
||||||
|
if (!this.libraryFilterData[libraryId] || !language || this.libraryFilterData[libraryId].languages.includes(language)) return
|
||||||
|
this.libraryFilterData[libraryId].languages.push(language)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when updating items to make sure author id exists
|
||||||
|
* If library filter data is set then use that for check
|
||||||
|
* otherwise lookup in db
|
||||||
|
* @param {string} libraryId
|
||||||
|
* @param {string} authorId
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async checkAuthorExists(libraryId, authorId) {
|
||||||
|
if (!this.libraryFilterData[libraryId]) {
|
||||||
|
return this.authorModel.checkExistsById(authorId)
|
||||||
|
}
|
||||||
|
return this.libraryFilterData[libraryId].authors.some(au => au.id === authorId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when updating items to make sure series id exists
|
||||||
|
* If library filter data is set then use that for check
|
||||||
|
* otherwise lookup in db
|
||||||
|
* @param {string} libraryId
|
||||||
|
* @param {string} seriesId
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async checkSeriesExists(libraryId, seriesId) {
|
||||||
|
if (!this.libraryFilterData[libraryId]) {
|
||||||
|
return this.seriesModel.checkExistsById(seriesId)
|
||||||
|
}
|
||||||
|
return this.libraryFilterData[libraryId].series.some(se => se.id === seriesId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset numIssues for library
|
||||||
|
* @param {string} libraryId
|
||||||
|
*/
|
||||||
|
async resetLibraryIssuesFilterData(libraryId) {
|
||||||
|
if (!this.libraryFilterData[libraryId]) return // Do nothing if filter data is not set
|
||||||
|
|
||||||
|
this.libraryFilterData[libraryId].numIssues = await this.libraryItemModel.count({
|
||||||
|
where: {
|
||||||
|
libraryId,
|
||||||
|
[Sequelize.Op.or]: [
|
||||||
|
{
|
||||||
|
isMissing: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isInvalid: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean invalid records in database
|
||||||
|
* Series should have atleast one Book
|
||||||
|
* Book and Podcast must have an associated LibraryItem
|
||||||
|
*/
|
||||||
|
async cleanDatabase() {
|
||||||
|
// Remove invalid Podcast records
|
||||||
|
const podcastsWithNoLibraryItem = await this.podcastModel.findAll({
|
||||||
|
where: Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM libraryItems li WHERE li.mediaId = podcast.id)`), 0)
|
||||||
|
})
|
||||||
|
for (const podcast of podcastsWithNoLibraryItem) {
|
||||||
|
Logger.warn(`Found podcast "${podcast.title}" with no libraryItem - removing it`)
|
||||||
|
await podcast.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove invalid Book records
|
||||||
|
const booksWithNoLibraryItem = await this.bookModel.findAll({
|
||||||
|
where: Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM libraryItems li WHERE li.mediaId = book.id)`), 0)
|
||||||
|
})
|
||||||
|
for (const book of booksWithNoLibraryItem) {
|
||||||
|
Logger.warn(`Found book "${book.title}" with no libraryItem - removing it`)
|
||||||
|
await book.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove empty series
|
||||||
|
const emptySeries = await this.seriesModel.findAll({
|
||||||
|
where: Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)`), 0)
|
||||||
|
})
|
||||||
|
for (const series of emptySeries) {
|
||||||
|
Logger.warn(`Found series "${series.name}" with no books - removing it`)
|
||||||
|
await series.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Database()
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user