mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-05 02:02:44 +02:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52a3bc224a | |||
| 54d67e5216 | |||
| b55d8250cc | |||
| 3a1e9abd68 | |||
| c5ba40a178 | |||
| f0c6dccadb | |||
| e701d1ab6a | |||
| e10c8093c9 | |||
| e81b3461b2 | |||
| 9345cb3934 | |||
| eb36a0b3dd | |||
| 7e442ecb3d | |||
| f07c5eb725 | |||
| a486be92cb | |||
| 4d84060036 | |||
| fc503691fe | |||
| c80dd43a3e | |||
| a4a62e0c18 | |||
| 2f98cb9b6d | |||
| 91dc6eebb0 | |||
| d72e0a4418 | |||
| 2c8ebd43cc | |||
| 9f561aa296 | |||
| 930bacd45d | |||
| ef2d736b20 | |||
| f3a453be20 | |||
| 45c97a778d | |||
| 6ebc64f73b | |||
| 52807d0d49 | |||
| a5e18e99bc | |||
| f545b3e745 | |||
| e0877803e3 | |||
| 4916887c8d | |||
| 20eb573897 |
@@ -16,6 +16,7 @@ RUN apk update && \
|
|||||||
tzdata \
|
tzdata \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
make \
|
make \
|
||||||
|
gcompat \
|
||||||
python3 \
|
python3 \
|
||||||
g++ \
|
g++ \
|
||||||
tini
|
tini
|
||||||
|
|||||||
@@ -6,10 +6,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="px-8 py-6 w-full rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-y-scroll" style="max-height: 80vh">
|
<div class="px-8 py-6 w-full rounded-lg bg-bg shadow-lg border border-black-300 relative overflow-y-scroll" style="max-height: 80vh">
|
||||||
<p class="text-xl font-bold pb-4">
|
<template v-for="release in releasesToShow">
|
||||||
Changelog <a :href="currentTagUrl" target="_blank" class="hover:underline">v{{ currentVersionNumber }}</a> ({{ currentVersionPubDate }})
|
<div :key="release.name">
|
||||||
</p>
|
<p class="text-xl font-bold pb-4">
|
||||||
<div class="custom-text" v-html="compiledMarkedown" />
|
Changelog <a :href="`https://github.com/advplyr/audiobookshelf/releases/tag/${release.name}`" target="_blank" class="hover:underline">{{ release.name }}</a> ({{ $formatDate(release.pubdate, dateFormat) }})
|
||||||
|
</p>
|
||||||
|
<div class="custom-text" v-html="getChangelog(release)" />
|
||||||
|
</div>
|
||||||
|
<div v-if="release !== releasesToShow[releasesToShow.length - 1]" class="border-b border-black-300 my-8" />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
</template>
|
</template>
|
||||||
@@ -37,24 +42,15 @@ export default {
|
|||||||
dateFormat() {
|
dateFormat() {
|
||||||
return this.$store.state.serverSettings.dateFormat
|
return this.$store.state.serverSettings.dateFormat
|
||||||
},
|
},
|
||||||
changelog() {
|
releasesToShow() {
|
||||||
return this.versionData?.currentVersionChangelog || 'No Changelog Available'
|
return this.versionData?.releasesToShow || []
|
||||||
},
|
}
|
||||||
compiledMarkedown() {
|
},
|
||||||
return marked.parse(this.changelog, { gfm: true, breaks: true })
|
methods: {
|
||||||
},
|
getChangelog(release) {
|
||||||
currentVersionPubDate() {
|
return marked.parse(release.changelog || 'No Changelog Available', { gfm: true, breaks: true })
|
||||||
if (!this.versionData?.currentVersionPubDate) return 'Unknown release date'
|
|
||||||
return `${this.$formatDate(this.versionData.currentVersionPubDate, this.dateFormat)}`
|
|
||||||
},
|
|
||||||
currentTagUrl() {
|
|
||||||
return this.versionData?.currentTagUrl
|
|
||||||
},
|
|
||||||
currentVersionNumber() {
|
|
||||||
return this.$config.version
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {},
|
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -132,6 +132,8 @@ export default {
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const twoColumnWidth = 210
|
||||||
|
|
||||||
ctx.globalAlpha = 1
|
ctx.globalAlpha = 1
|
||||||
ctx.textBaseline = 'middle'
|
ctx.textBaseline = 'middle'
|
||||||
|
|
||||||
@@ -150,12 +152,12 @@ export default {
|
|||||||
|
|
||||||
// Top text
|
// Top text
|
||||||
addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28)
|
addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28)
|
||||||
addText(`${this.year} YEAR IN REVIEW`, '18px', 'bold', 'white', '1px', 65, 51)
|
addText(`${this.year} ${this.$strings.StatsYearInReview}`, '18px', 'bold', 'white', '1px', 65, 51,)
|
||||||
|
|
||||||
// Top left box
|
// Top left box
|
||||||
createRoundedRect(50, 100, 340, 160)
|
createRoundedRect(50, 100, 340, 160)
|
||||||
addText(this.yearStats.numBooksFinished, '64px', 'bold', 'white', '0px', 160, 165)
|
addText(this.yearStats.numBooksFinished, '64px', 'bold', 'white', '0px', 160, 165)
|
||||||
addText('books finished', '28px', 'normal', tanColor, '0px', 160, 210)
|
addText(this.$strings.StatsBooksFinished, '28px', 'normal', tanColor, '0px', 160, 210, twoColumnWidth)
|
||||||
const readIconPath = new Path2D()
|
const readIconPath = new Path2D()
|
||||||
readIconPath.addPath(new Path2D('M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z'), { a: 2, d: 2, e: 100, f: 160 })
|
readIconPath.addPath(new Path2D('M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z'), { a: 2, d: 2, e: 100, f: 160 })
|
||||||
ctx.fillStyle = '#ffffff'
|
ctx.fillStyle = '#ffffff'
|
||||||
@@ -164,40 +166,40 @@ export default {
|
|||||||
// Box top right
|
// Box top right
|
||||||
createRoundedRect(410, 100, 340, 160)
|
createRoundedRect(410, 100, 340, 160)
|
||||||
addText(this.$elapsedPrettyExtended(this.yearStats.totalListeningTime, true, false), '40px', 'bold', 'white', '0px', 500, 165)
|
addText(this.$elapsedPrettyExtended(this.yearStats.totalListeningTime, true, false), '40px', 'bold', 'white', '0px', 500, 165)
|
||||||
addText('spent listening', '28px', 'normal', tanColor, '0px', 500, 205)
|
addText(this.$strings.StatsSpentListening, '28px', 'normal', tanColor, '0px', 500, 205, twoColumnWidth)
|
||||||
addIcon('watch_later', 'white', '52px', 440, 180)
|
addIcon('watch_later', 'white', '52px', 440, 180)
|
||||||
|
|
||||||
// Box bottom left
|
// Box bottom left
|
||||||
createRoundedRect(50, 280, 340, 160)
|
createRoundedRect(50, 280, 340, 160)
|
||||||
addText(this.yearStats.totalListeningSessions, '64px', 'bold', 'white', '0px', 160, 345)
|
addText(this.yearStats.totalListeningSessions, '64px', 'bold', 'white', '0px', 160, 345)
|
||||||
addText('sessions', '28px', 'normal', tanColor, '1px', 160, 390)
|
addText(this.$strings.StatsSessions, '28px', 'normal', tanColor, '1px', 160, 390, twoColumnWidth)
|
||||||
addIcon('headphones', 'white', '52px', 95, 360)
|
addIcon('headphones', 'white', '52px', 95, 360)
|
||||||
|
|
||||||
// Box bottom right
|
// Box bottom right
|
||||||
createRoundedRect(410, 280, 340, 160)
|
createRoundedRect(410, 280, 340, 160)
|
||||||
addText(this.yearStats.numBooksListened, '64px', 'bold', 'white', '0px', 500, 345)
|
addText(this.yearStats.numBooksListened, '64px', 'bold', 'white', '0px', 500, 345)
|
||||||
addText('books listened to', '28px', 'normal', tanColor, '0px', 500, 390)
|
addText(this.$strings.StatsBooksListenedTo, '28px', 'normal', tanColor, '0px', 500, 390, twoColumnWidth)
|
||||||
addIcon('local_library', 'white', '52px', 440, 360)
|
addIcon('local_library', 'white', '52px', 440, 360)
|
||||||
|
|
||||||
if (!this.variant) {
|
if (!this.variant) {
|
||||||
// Text stats
|
// Text stats
|
||||||
const topNarrator = this.yearStats.mostListenedNarrator
|
const topNarrator = this.yearStats.mostListenedNarrator
|
||||||
if (topNarrator) {
|
if (topNarrator) {
|
||||||
addText('TOP NARRATOR', '24px', 'normal', tanColor, '1px', 70, 520)
|
addText(this.$strings.StatsTopNarrator, '24px', 'normal', tanColor, '1px', 70, 520, 330)
|
||||||
addText(topNarrator.name, '36px', 'bolder', 'white', '0px', 70, 564, 330)
|
addText(topNarrator.name, '36px', 'bolder', 'white', '0px', 70, 564, 330)
|
||||||
addText(this.$elapsedPrettyExtended(topNarrator.time, true, false), '24px', 'lighter', 'white', '1px', 70, 599)
|
addText(this.$elapsedPrettyExtended(topNarrator.time, true, false), '24px', 'lighter', 'white', '1px', 70, 599)
|
||||||
}
|
}
|
||||||
|
|
||||||
const topGenre = this.yearStats.topGenres[0]
|
const topGenre = this.yearStats.topGenres[0]
|
||||||
if (topGenre) {
|
if (topGenre) {
|
||||||
addText('TOP GENRE', '24px', 'normal', tanColor, '1px', 430, 520)
|
addText(this.$strings.StatsTopGenre, '24px', 'normal', tanColor, '1px', 430, 520, 330)
|
||||||
addText(topGenre.genre, '36px', 'bolder', 'white', '0px', 430, 564, 330)
|
addText(topGenre.genre, '36px', 'bolder', 'white', '0px', 430, 564, 330)
|
||||||
addText(this.$elapsedPrettyExtended(topGenre.time, true, false), '24px', 'lighter', 'white', '1px', 430, 599)
|
addText(this.$elapsedPrettyExtended(topGenre.time, true, false), '24px', 'lighter', 'white', '1px', 430, 599)
|
||||||
}
|
}
|
||||||
|
|
||||||
const topAuthor = this.yearStats.topAuthors[0]
|
const topAuthor = this.yearStats.topAuthors[0]
|
||||||
if (topAuthor) {
|
if (topAuthor) {
|
||||||
addText('TOP AUTHOR', '24px', 'normal', tanColor, '1px', 70, 670)
|
addText(this.$strings.StatsTopAuthor, '24px', 'normal', tanColor, '1px', 70, 670, 330)
|
||||||
addText(topAuthor.name, '36px', 'bolder', 'white', '0px', 70, 714, 330)
|
addText(topAuthor.name, '36px', 'bolder', 'white', '0px', 70, 714, 330)
|
||||||
addText(this.$elapsedPrettyExtended(topAuthor.time, true, false), '24px', 'lighter', 'white', '1px', 70, 749)
|
addText(this.$elapsedPrettyExtended(topAuthor.time, true, false), '24px', 'lighter', 'white', '1px', 70, 749)
|
||||||
}
|
}
|
||||||
@@ -205,7 +207,7 @@ export default {
|
|||||||
if (this.yearStats.mostListenedMonth?.time) {
|
if (this.yearStats.mostListenedMonth?.time) {
|
||||||
const jsdate = new Date(this.year, this.yearStats.mostListenedMonth.month, 1)
|
const jsdate = new Date(this.year, this.yearStats.mostListenedMonth.month, 1)
|
||||||
const monthName = this.$formatJsDate(jsdate, 'LLLL')
|
const monthName = this.$formatJsDate(jsdate, 'LLLL')
|
||||||
addText('TOP MONTH', '24px', 'normal', tanColor, '1px', 430, 670)
|
addText(this.$strings.StatsTopMonth, '24px', 'normal', tanColor, '1px', 430, 670, 330)
|
||||||
addText(monthName, '36px', 'bolder', 'white', '0px', 430, 714, 330)
|
addText(monthName, '36px', 'bolder', 'white', '0px', 430, 714, 330)
|
||||||
addText(this.$elapsedPrettyExtended(this.yearStats.mostListenedMonth.time, true, false), '24px', 'lighter', 'white', '1px', 430, 749)
|
addText(this.$elapsedPrettyExtended(this.yearStats.mostListenedMonth.time, true, false), '24px', 'lighter', 'white', '1px', 430, 749)
|
||||||
}
|
}
|
||||||
@@ -214,7 +216,7 @@ export default {
|
|||||||
finishedBookCoverImgs = Object.values(finishedBookCoverImgs)
|
finishedBookCoverImgs = Object.values(finishedBookCoverImgs)
|
||||||
if (finishedBookCoverImgs.length > 0) {
|
if (finishedBookCoverImgs.length > 0) {
|
||||||
ctx.textAlign = 'center'
|
ctx.textAlign = 'center'
|
||||||
addText('Some books finished this year...', '28px', 'normal', tanColor, '0px', canvas.width / 2, 530)
|
addText(this.$strings.StatsBooksFinishedThisYear, '28px', 'normal', tanColor, '0px', canvas.width / 2, 530)
|
||||||
|
|
||||||
for (let i = 0; i < Math.min(5, finishedBookCoverImgs.length); i++) {
|
for (let i = 0; i < Math.min(5, finishedBookCoverImgs.length); i++) {
|
||||||
let imgToAdd = finishedBookCoverImgs[i]
|
let imgToAdd = finishedBookCoverImgs[i]
|
||||||
@@ -224,14 +226,14 @@ export default {
|
|||||||
} else if (this.variant === 2) {
|
} else if (this.variant === 2) {
|
||||||
// Text stats
|
// Text stats
|
||||||
if (this.yearStats.topAuthors.length) {
|
if (this.yearStats.topAuthors.length) {
|
||||||
addText('TOP AUTHORS', '24px', 'normal', tanColor, '1px', 70, 524)
|
addText(this.$strings.StatsTopAuthors, '24px', 'normal', tanColor, '1px', 70, 524)
|
||||||
for (let i = 0; i < this.yearStats.topAuthors.length; i++) {
|
for (let i = 0; i < this.yearStats.topAuthors.length; i++) {
|
||||||
addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 584 + i * 60, 330)
|
addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 584 + i * 60, 330)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.yearStats.topGenres.length) {
|
if (this.yearStats.topGenres.length) {
|
||||||
addText('TOP GENRES', '24px', 'normal', tanColor, '1px', 430, 524)
|
addText(this.$strings.StatsTopGenres, '24px', 'normal', tanColor, '1px', 430, 524)
|
||||||
for (let i = 0; i < this.yearStats.topGenres.length; i++) {
|
for (let i = 0; i < this.yearStats.topGenres.length; i++) {
|
||||||
addText(this.yearStats.topGenres[i].genre, '36px', 'bolder', 'white', '0px', 430, 584 + i * 60, 330)
|
addText(this.yearStats.topGenres[i].genre, '36px', 'bolder', 'white', '0px', 430, 584 + i * 60, 330)
|
||||||
}
|
}
|
||||||
@@ -263,7 +265,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.$toast.error('Cannot share natively on this device')
|
this.$toast.error(this.$strings.ToastErrorCannotShare)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ export default {
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const threeColumnTextWidth = 200
|
||||||
|
|
||||||
ctx.globalAlpha = 1
|
ctx.globalAlpha = 1
|
||||||
ctx.textBaseline = 'middle'
|
ctx.textBaseline = 'middle'
|
||||||
|
|
||||||
@@ -141,33 +143,33 @@ export default {
|
|||||||
|
|
||||||
// Top text
|
// Top text
|
||||||
addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28)
|
addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28)
|
||||||
addText(`${this.year} YEAR IN REVIEW`, '18px', 'bold', 'white', '1px', 65, 51)
|
addText(`${this.year} ${this.$strings.StatsYearInReview}`, '18px', 'bold', 'white', '1px', 65, 51)
|
||||||
|
|
||||||
// Top left box
|
// Top left box
|
||||||
createRoundedRect(40, 100, 230, 100)
|
createRoundedRect(40, 100, 230, 100)
|
||||||
ctx.textAlign = 'center'
|
ctx.textAlign = 'center'
|
||||||
addText(this.yearStats.numBooksAdded, '48px', 'bold', 'white', '0px', 155, 140)
|
addText(this.yearStats.numBooksAdded, '48px', 'bold', 'white', '0px', 155, 140)
|
||||||
addText('books added', '18px', 'normal', tanColor, '0px', 155, 170)
|
addText(this.$strings.StatsBooksAdded, '18px', 'normal', tanColor, '0px', 155, 170, threeColumnTextWidth)
|
||||||
|
|
||||||
// Box top right
|
// Box top right
|
||||||
createRoundedRect(285, 100, 230, 100)
|
createRoundedRect(285, 100, 230, 100)
|
||||||
addText(this.yearStats.numAuthorsAdded, '48px', 'bold', 'white', '0px', 400, 140)
|
addText(this.yearStats.numAuthorsAdded, '48px', 'bold', 'white', '0px', 400, 140)
|
||||||
addText('authors added', '18px', 'normal', tanColor, '0px', 400, 170)
|
addText(this.$strings.StatsAuthorsAdded, '18px', 'normal', tanColor, '0px', 400, 170, threeColumnTextWidth)
|
||||||
|
|
||||||
// Box bottom left
|
// Box bottom left
|
||||||
createRoundedRect(530, 100, 230, 100)
|
createRoundedRect(530, 100, 230, 100)
|
||||||
addText(this.yearStats.numListeningSessions, '48px', 'bold', 'white', '0px', 645, 140)
|
addText(this.yearStats.numListeningSessions, '48px', 'bold', 'white', '0px', 645, 140)
|
||||||
addText('sessions', '18px', 'normal', tanColor, '1px', 645, 170)
|
addText(this.$strings.StatsSessions, '18px', 'normal', tanColor, '1px', 645, 170, threeColumnTextWidth)
|
||||||
|
|
||||||
// Text stats
|
// Text stats
|
||||||
if (this.yearStats.totalBooksAddedSize) {
|
if (this.yearStats.totalBooksAddedSize) {
|
||||||
addText('Your book collection grew to...', '24px', 'normal', tanColor, '0px', canvas.width / 2, 260)
|
addText(this.$strings.StatsCollectionGrewTo, '24px', 'normal', tanColor, '0px', canvas.width / 2, 260)
|
||||||
addText(this.$bytesPretty(this.yearStats.totalBooksSize), '36px', 'bolder', 'white', '0px', canvas.width / 2, 300)
|
addText(this.$bytesPretty(this.yearStats.totalBooksSize), '36px', 'bolder', 'white', '0px', canvas.width / 2, 300)
|
||||||
addText('+' + this.$bytesPretty(this.yearStats.totalBooksAddedSize), '20px', 'lighter', 'white', '0px', canvas.width / 2, 330)
|
addText('+' + this.$bytesPretty(this.yearStats.totalBooksAddedSize), '20px', 'lighter', 'white', '0px', canvas.width / 2, 330)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.yearStats.totalBooksAddedDuration) {
|
if (this.yearStats.totalBooksAddedDuration) {
|
||||||
addText('With a total duration of...', '24px', 'normal', tanColor, '0px', canvas.width / 2, 400)
|
addText(this.$strings.StatsTotalDuration, '24px', 'normal', tanColor, '0px', canvas.width / 2, 400)
|
||||||
addText(this.$elapsedPrettyExtended(this.yearStats.totalBooksDuration, true, false), '36px', 'bolder', 'white', '0px', canvas.width / 2, 440)
|
addText(this.$elapsedPrettyExtended(this.yearStats.totalBooksDuration, true, false), '36px', 'bolder', 'white', '0px', canvas.width / 2, 440)
|
||||||
addText('+' + this.$elapsedPrettyExtended(this.yearStats.totalBooksAddedDuration, true, false), '20px', 'lighter', 'white', '0px', canvas.width / 2, 470)
|
addText('+' + this.$elapsedPrettyExtended(this.yearStats.totalBooksAddedDuration, true, false), '20px', 'lighter', 'white', '0px', canvas.width / 2, 470)
|
||||||
}
|
}
|
||||||
@@ -176,7 +178,7 @@ export default {
|
|||||||
// Bottom images
|
// Bottom images
|
||||||
imgsToAdd = Object.values(imgsToAdd)
|
imgsToAdd = Object.values(imgsToAdd)
|
||||||
if (imgsToAdd.length > 0) {
|
if (imgsToAdd.length > 0) {
|
||||||
addText('Some additions include...', '24px', 'normal', tanColor, '0px', canvas.width / 2, 540)
|
addText(this.$strings.StatsBooksAdditional, '24px', 'normal', tanColor, '0px', canvas.width / 2, 540)
|
||||||
|
|
||||||
for (let i = 0; i < Math.min(5, imgsToAdd.length); i++) {
|
for (let i = 0; i < Math.min(5, imgsToAdd.length); i++) {
|
||||||
let imgToAdd = imgsToAdd[i]
|
let imgToAdd = imgsToAdd[i]
|
||||||
@@ -187,14 +189,14 @@ export default {
|
|||||||
// Text stats
|
// Text stats
|
||||||
ctx.textAlign = 'left'
|
ctx.textAlign = 'left'
|
||||||
if (this.yearStats.topAuthors.length) {
|
if (this.yearStats.topAuthors.length) {
|
||||||
addText('TOP AUTHORS', '24px', 'normal', tanColor, '1px', 70, 549)
|
addText(this.$strings.StatsTopAuthors, '24px', 'normal', tanColor, '1px', 70, 549, 330)
|
||||||
for (let i = 0; i < this.yearStats.topAuthors.length; i++) {
|
for (let i = 0; i < this.yearStats.topAuthors.length; i++) {
|
||||||
addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 609 + i * 60, 330)
|
addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 609 + i * 60, 330)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.yearStats.topNarrators.length) {
|
if (this.yearStats.topNarrators.length) {
|
||||||
addText('TOP NARRATORS', '24px', 'normal', tanColor, '1px', 430, 549)
|
addText(this.$strings.StatsTopNarrators, '24px', 'normal', tanColor, '1px', 430, 549)
|
||||||
for (let i = 0; i < this.yearStats.topNarrators.length; i++) {
|
for (let i = 0; i < this.yearStats.topNarrators.length; i++) {
|
||||||
addText(this.yearStats.topNarrators[i].name, '36px', 'bolder', 'white', '0px', 430, 609 + i * 60, 330)
|
addText(this.yearStats.topNarrators[i].name, '36px', 'bolder', 'white', '0px', 430, 609 + i * 60, 330)
|
||||||
}
|
}
|
||||||
@@ -203,14 +205,14 @@ export default {
|
|||||||
// Text stats
|
// Text stats
|
||||||
ctx.textAlign = 'left'
|
ctx.textAlign = 'left'
|
||||||
if (this.yearStats.topAuthors.length) {
|
if (this.yearStats.topAuthors.length) {
|
||||||
addText('TOP AUTHORS', '24px', 'normal', tanColor, '1px', 70, 549)
|
addText(this.$strings.StatsTopAuthors, '24px', 'normal', tanColor, '1px', 70, 549, 330)
|
||||||
for (let i = 0; i < this.yearStats.topAuthors.length; i++) {
|
for (let i = 0; i < this.yearStats.topAuthors.length; i++) {
|
||||||
addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 609 + i * 60, 330)
|
addText(this.yearStats.topAuthors[i].name, '36px', 'bolder', 'white', '0px', 70, 609 + i * 60, 330)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.yearStats.topGenres.length) {
|
if (this.yearStats.topGenres.length) {
|
||||||
addText('TOP GENRES', '24px', 'normal', tanColor, '1px', 430, 549)
|
addText(this.$strings.StatsTopGenres, '24px', 'normal', tanColor, '1px', 430, 549)
|
||||||
for (let i = 0; i < this.yearStats.topGenres.length; i++) {
|
for (let i = 0; i < this.yearStats.topGenres.length; i++) {
|
||||||
addText(this.yearStats.topGenres[i].genre, '36px', 'bolder', 'white', '0px', 430, 609 + i * 60, 330)
|
addText(this.yearStats.topGenres[i].genre, '36px', 'bolder', 'white', '0px', 430, 609 + i * 60, 330)
|
||||||
}
|
}
|
||||||
@@ -239,7 +241,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.$toast.error('Cannot share natively on this device')
|
this.$toast.error(this.$strings.ToastErrorCannotShare)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ export default {
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const twoColumnWidth = 180
|
||||||
|
|
||||||
ctx.globalAlpha = 1
|
ctx.globalAlpha = 1
|
||||||
ctx.textBaseline = 'middle'
|
ctx.textBaseline = 'middle'
|
||||||
|
|
||||||
@@ -131,12 +133,12 @@ export default {
|
|||||||
|
|
||||||
// Top text
|
// Top text
|
||||||
addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28)
|
addText('audiobookshelf', '28px', 'normal', tanColor, '0px', 65, 28)
|
||||||
addText(`${this.year} YEAR IN REVIEW`, '18px', 'bold', 'white', '1px', 65, 51)
|
addText(`${this.year} ${this.$strings.StatsYearInReview}`, '18px', 'bold', 'white', '1px', 65, 51)
|
||||||
|
|
||||||
// Top left box
|
// Top left box
|
||||||
createRoundedRect(15, 75, 280, 110)
|
createRoundedRect(15, 75, 280, 110)
|
||||||
addText(this.yearStats.numBooksFinished, '48px', 'bold', 'white', '0px', 105, 120)
|
addText(this.yearStats.numBooksFinished, '48px', 'bold', 'white', '0px', 105, 120)
|
||||||
addText('books finished', '20px', 'normal', tanColor, '0px', 105, 155)
|
addText(this.$strings.StatsBooksFinished, '20px', 'normal', tanColor, '0px', 105, 155, twoColumnWidth)
|
||||||
const readIconPath = new Path2D()
|
const readIconPath = new Path2D()
|
||||||
readIconPath.addPath(new Path2D('M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z'), { a: 1.5, d: 1.5, e: 55, f: 115 })
|
readIconPath.addPath(new Path2D('M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z'), { a: 1.5, d: 1.5, e: 55, f: 115 })
|
||||||
ctx.fillStyle = '#ffffff'
|
ctx.fillStyle = '#ffffff'
|
||||||
@@ -144,7 +146,7 @@ export default {
|
|||||||
|
|
||||||
createRoundedRect(305, 75, 280, 110)
|
createRoundedRect(305, 75, 280, 110)
|
||||||
addText(this.yearStats.numBooksListened, '48px', 'bold', 'white', '0px', 400, 120)
|
addText(this.yearStats.numBooksListened, '48px', 'bold', 'white', '0px', 400, 120)
|
||||||
addText('books listened to', '20px', 'normal', tanColor, '0px', 400, 155)
|
addText(this.$strings.StatsBooksListenedTo, '20px', 'normal', tanColor, '0px', 400, 155, twoColumnWidth)
|
||||||
addIcon('local_library', 'white', '42px', 345, 130)
|
addIcon('local_library', 'white', '42px', 345, 130)
|
||||||
|
|
||||||
this.canvas = canvas
|
this.canvas = canvas
|
||||||
@@ -169,7 +171,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.$toast.error('Cannot share natively on this device')
|
this.$toast.error(this.$strings.ToastErrorCannotShare)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.12.0",
|
"version": "2.12.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.12.0",
|
"version": "2.12.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.12.0",
|
"version": "2.12.3",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
+46
-33
@@ -11,6 +11,7 @@ function parseSemver(ver) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
name: ver,
|
||||||
total,
|
total,
|
||||||
version: groups[2],
|
version: groups[2],
|
||||||
major: Number(groups[3]),
|
major: Number(groups[3]),
|
||||||
@@ -24,49 +25,61 @@ function parseSemver(ver) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getReleases() {
|
||||||
|
return axios
|
||||||
|
.get(`https://api.github.com/repos/advplyr/audiobookshelf/releases`)
|
||||||
|
.then((res) => {
|
||||||
|
return res.data
|
||||||
|
.map((release) => {
|
||||||
|
const tagName = release.tag_name
|
||||||
|
const verObj = parseSemver(tagName)
|
||||||
|
if (verObj) {
|
||||||
|
verObj.pubdate = new Date(release.published_at)
|
||||||
|
verObj.changelog = release.body
|
||||||
|
return verObj
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
.filter((verObj) => verObj)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to get releases', error)
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const currentVersion = packagejson.version
|
export const currentVersion = packagejson.version
|
||||||
|
|
||||||
export async function checkForUpdate() {
|
export async function checkForUpdate() {
|
||||||
if (!packagejson.version) {
|
if (!packagejson.version) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
var currVerObj = parseSemver('v' + packagejson.version)
|
|
||||||
if (!currVerObj) {
|
|
||||||
console.error('Invalid version', packagejson.version)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
var largestVer = null
|
|
||||||
await axios.get(`https://api.github.com/repos/advplyr/audiobookshelf/releases`).then((res) => {
|
|
||||||
var releases = res.data
|
|
||||||
if (releases && releases.length) {
|
|
||||||
releases.forEach((release) => {
|
|
||||||
var tagName = release.tag_name
|
|
||||||
var verObj = parseSemver(tagName)
|
|
||||||
if (verObj) {
|
|
||||||
if (!largestVer || largestVer.total < verObj.total) {
|
|
||||||
largestVer = verObj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (verObj.version == currVerObj.version) {
|
const releases = await getReleases()
|
||||||
currVerObj.pubdate = new Date(release.published_at)
|
if (!releases.length) {
|
||||||
currVerObj.changelog = release.body
|
console.error('No releases found')
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (!largestVer) {
|
|
||||||
console.error('No valid version tags to compare with')
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentVersion = releases.find((release) => release.version == packagejson.version)
|
||||||
|
if (!currentVersion) {
|
||||||
|
console.error('Current version not found in releases')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestVersion = releases[0]
|
||||||
|
const currentVersionMinor = currentVersion.minor
|
||||||
|
const currentVersionMajor = currentVersion.major
|
||||||
|
// Show all releases with the same minor version and lower or equal total version
|
||||||
|
const releasesToShow = releases.filter((release) => {
|
||||||
|
return release.major == currentVersionMajor && release.minor == currentVersionMinor && release.total <= currentVersion.total
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasUpdate: largestVer.total > currVerObj.total,
|
hasUpdate: latestVersion.total > currentVersion.total,
|
||||||
latestVersion: largestVer.version,
|
latestVersion: latestVersion.version,
|
||||||
githubTagUrl: `https://github.com/advplyr/audiobookshelf/releases/tag/v${largestVer.version}`,
|
githubTagUrl: `https://github.com/advplyr/audiobookshelf/releases/tag/v${latestVersion.version}`,
|
||||||
currentVersion: currVerObj.version,
|
currentVersion: currentVersion.version,
|
||||||
currentTagUrl: `https://github.com/advplyr/audiobookshelf/releases/tag/v${currVerObj.version}`,
|
releasesToShow
|
||||||
currentVersionPubDate: currVerObj.pubdate,
|
|
||||||
currentVersionChangelog: currVerObj.changelog
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-19
@@ -32,33 +32,33 @@ export const state = () => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getServerSetting: state => key => {
|
getServerSetting: (state) => (key) => {
|
||||||
if (!state.serverSettings) return null
|
if (!state.serverSettings) return null
|
||||||
return state.serverSettings[key]
|
return state.serverSettings[key]
|
||||||
},
|
},
|
||||||
getLibraryItemIdStreaming: state => {
|
getLibraryItemIdStreaming: (state) => {
|
||||||
return state.streamLibraryItem?.id || null
|
return state.streamLibraryItem?.id || null
|
||||||
},
|
},
|
||||||
getIsStreamingFromDifferentLibrary: (state, getters, rootState) => {
|
getIsStreamingFromDifferentLibrary: (state, getters, rootState) => {
|
||||||
if (!state.streamLibraryItem) return false
|
if (!state.streamLibraryItem) return false
|
||||||
return state.streamLibraryItem.libraryId !== rootState.libraries.currentLibraryId
|
return state.streamLibraryItem.libraryId !== rootState.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
getIsMediaStreaming: state => (libraryItemId, episodeId) => {
|
getIsMediaStreaming: (state) => (libraryItemId, episodeId) => {
|
||||||
if (!state.streamLibraryItem) return null
|
if (!state.streamLibraryItem) return null
|
||||||
if (!episodeId) return state.streamLibraryItem.id == libraryItemId
|
if (!episodeId) return state.streamLibraryItem.id == libraryItemId
|
||||||
return state.streamLibraryItem.id == libraryItemId && state.streamEpisodeId == episodeId
|
return state.streamLibraryItem.id == libraryItemId && state.streamEpisodeId == episodeId
|
||||||
},
|
},
|
||||||
getIsMediaQueued: state => (libraryItemId, episodeId) => {
|
getIsMediaQueued: (state) => (libraryItemId, episodeId) => {
|
||||||
return state.playerQueueItems.some(i => {
|
return state.playerQueueItems.some((i) => {
|
||||||
if (!episodeId) return i.libraryItemId === libraryItemId
|
if (!episodeId) return i.libraryItemId === libraryItemId
|
||||||
return i.libraryItemId === libraryItemId && i.episodeId === episodeId
|
return i.libraryItemId === libraryItemId && i.episodeId === episodeId
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getBookshelfView: state => {
|
getBookshelfView: (state) => {
|
||||||
if (!state.serverSettings || isNaN(state.serverSettings.bookshelfView)) return Constants.BookshelfView.STANDARD
|
if (!state.serverSettings || isNaN(state.serverSettings.bookshelfView)) return Constants.BookshelfView.STANDARD
|
||||||
return state.serverSettings.bookshelfView
|
return state.serverSettings.bookshelfView
|
||||||
},
|
},
|
||||||
getHomeBookshelfView: state => {
|
getHomeBookshelfView: (state) => {
|
||||||
if (!state.serverSettings || isNaN(state.serverSettings.homeBookshelfView)) return Constants.BookshelfView.STANDARD
|
if (!state.serverSettings || isNaN(state.serverSettings.homeBookshelfView)) return Constants.BookshelfView.STANDARD
|
||||||
return state.serverSettings.homeBookshelfView
|
return state.serverSettings.homeBookshelfView
|
||||||
}
|
}
|
||||||
@@ -69,17 +69,20 @@ export const actions = {
|
|||||||
const updatePayload = {
|
const updatePayload = {
|
||||||
...payload
|
...payload
|
||||||
}
|
}
|
||||||
return this.$axios.$patch('/api/settings', updatePayload).then((result) => {
|
return this.$axios
|
||||||
if (result.success) {
|
.$patch('/api/settings', updatePayload)
|
||||||
commit('setServerSettings', result.serverSettings)
|
.then((result) => {
|
||||||
return true
|
if (result.success) {
|
||||||
} else {
|
commit('setServerSettings', result.serverSettings)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to update server settings', error)
|
||||||
return false
|
return false
|
||||||
}
|
})
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Failed to update server settings', error)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
checkForUpdate({ commit }) {
|
checkForUpdate({ commit }) {
|
||||||
const VERSION_CHECK_BUFF = 1000 * 60 * 5 // 5 minutes
|
const VERSION_CHECK_BUFF = 1000 * 60 * 5 // 5 minutes
|
||||||
@@ -96,7 +99,7 @@ export const actions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var shouldCheckForUpdate = Date.now() - Number(lastVerCheck) > VERSION_CHECK_BUFF
|
var shouldCheckForUpdate = Date.now() - Number(lastVerCheck) > VERSION_CHECK_BUFF
|
||||||
if (!shouldCheckForUpdate && savedVersionData && savedVersionData.version !== currentVersion) {
|
if (!shouldCheckForUpdate && savedVersionData && (savedVersionData.version !== currentVersion || !savedVersionData.releasesToShow)) {
|
||||||
// Version mismatch between saved data so check for update anyway
|
// Version mismatch between saved data so check for update anyway
|
||||||
shouldCheckForUpdate = true
|
shouldCheckForUpdate = true
|
||||||
}
|
}
|
||||||
@@ -180,7 +183,7 @@ export const mutations = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
addItemToQueue(state, item) {
|
addItemToQueue(state, item) {
|
||||||
const exists = state.playerQueueItems.some(i => {
|
const exists = state.playerQueueItems.some((i) => {
|
||||||
if (!i.episodeId) return i.libraryItemId === item.libraryItemId
|
if (!i.episodeId) return i.libraryItemId === item.libraryItemId
|
||||||
return i.libraryItemId === item.libraryItemId && i.episodeId === item.episodeId
|
return i.libraryItemId === item.libraryItemId && i.episodeId === item.episodeId
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -229,7 +229,7 @@
|
|||||||
"LabelBackupLocation": "Backup-Ort",
|
"LabelBackupLocation": "Backup-Ort",
|
||||||
"LabelBackupsEnableAutomaticBackups": "Automatische Sicherung aktivieren",
|
"LabelBackupsEnableAutomaticBackups": "Automatische Sicherung aktivieren",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "Backups werden in /metadata/backups gespeichert",
|
"LabelBackupsEnableAutomaticBackupsHelp": "Backups werden in /metadata/backups gespeichert",
|
||||||
"LabelBackupsMaxBackupSize": "Maximale Sicherungsgröße (in GB)(0 gleich ohne Begrenzung)",
|
"LabelBackupsMaxBackupSize": "Maximale Sicherungsgröße (in GB) (0 gleich ohne Begrenzung)",
|
||||||
"LabelBackupsMaxBackupSizeHelp": "Zum Schutz vor Fehlkonfigurationen schlagen Sicherungen fehl, wenn sie die konfigurierte Größe überschreiten.",
|
"LabelBackupsMaxBackupSizeHelp": "Zum Schutz vor Fehlkonfigurationen schlagen Sicherungen fehl, wenn sie die konfigurierte Größe überschreiten.",
|
||||||
"LabelBackupsNumberToKeep": "Anzahl der aufzubewahrenden Sicherungen",
|
"LabelBackupsNumberToKeep": "Anzahl der aufzubewahrenden Sicherungen",
|
||||||
"LabelBackupsNumberToKeepHelp": "Es wird immer nur 1 Sicherung auf einmal entfernt. Wenn du bereits mehrere Sicherungen als die definierte max. Anzahl hast, solltest du diese manuell entfernen.",
|
"LabelBackupsNumberToKeepHelp": "Es wird immer nur 1 Sicherung auf einmal entfernt. Wenn du bereits mehrere Sicherungen als die definierte max. Anzahl hast, solltest du diese manuell entfernen.",
|
||||||
|
|||||||
@@ -771,6 +771,24 @@
|
|||||||
"PlaceholderNewPlaylist": "New playlist name",
|
"PlaceholderNewPlaylist": "New playlist name",
|
||||||
"PlaceholderSearch": "Search..",
|
"PlaceholderSearch": "Search..",
|
||||||
"PlaceholderSearchEpisode": "Search episode..",
|
"PlaceholderSearchEpisode": "Search episode..",
|
||||||
|
"StatsAuthorsAdded": "authors added",
|
||||||
|
"StatsBooksAdded": "books added",
|
||||||
|
"StatsBooksAdditional": "Some additions include…",
|
||||||
|
"StatsBooksFinished": "books finished",
|
||||||
|
"StatsBooksFinishedThisYear": "Some books finished this year…",
|
||||||
|
"StatsBooksListenedTo": "books listened to",
|
||||||
|
"StatsCollectionGrewTo": "Your book collection grew to…",
|
||||||
|
"StatsSessions": "sessions",
|
||||||
|
"StatsSpentListening": "spent listening",
|
||||||
|
"StatsTopAuthor": "TOP AUTHOR",
|
||||||
|
"StatsTopAuthors": "TOP AUTHORS",
|
||||||
|
"StatsTopGenre": "TOP GENRE",
|
||||||
|
"StatsTopGenres": "TOP GENRES",
|
||||||
|
"StatsTopMonth": "TOP MONTH",
|
||||||
|
"StatsTopNarrator": "TOP NARRATOR",
|
||||||
|
"StatsTopNarrators": "TOP NARRATORS",
|
||||||
|
"StatsTotalDuration": "With a total duration of…",
|
||||||
|
"StatsYearInReview": "YEAR IN REVIEW",
|
||||||
"ToastAccountUpdateFailed": "Failed to update account",
|
"ToastAccountUpdateFailed": "Failed to update account",
|
||||||
"ToastAccountUpdateSuccess": "Account updated",
|
"ToastAccountUpdateSuccess": "Account updated",
|
||||||
"ToastAuthorImageRemoveFailed": "Failed to remove image",
|
"ToastAuthorImageRemoveFailed": "Failed to remove image",
|
||||||
@@ -806,6 +824,7 @@
|
|||||||
"ToastCollectionUpdateSuccess": "Collection updated",
|
"ToastCollectionUpdateSuccess": "Collection updated",
|
||||||
"ToastDeleteFileFailed": "Failed to delete file",
|
"ToastDeleteFileFailed": "Failed to delete file",
|
||||||
"ToastDeleteFileSuccess": "File deleted",
|
"ToastDeleteFileSuccess": "File deleted",
|
||||||
|
"ToastErrorCannotShare": "Cannot share natively on this device",
|
||||||
"ToastFailedToLoadData": "Failed to load data",
|
"ToastFailedToLoadData": "Failed to load data",
|
||||||
"ToastItemCoverUpdateFailed": "Failed to update item cover",
|
"ToastItemCoverUpdateFailed": "Failed to update item cover",
|
||||||
"ToastItemCoverUpdateSuccess": "Item cover updated",
|
"ToastItemCoverUpdateSuccess": "Item cover updated",
|
||||||
|
|||||||
+94
-1
@@ -88,6 +88,7 @@
|
|||||||
"ButtonShow": "Näytä",
|
"ButtonShow": "Näytä",
|
||||||
"ButtonStartM4BEncode": "Aloita M4B enkoodaus",
|
"ButtonStartM4BEncode": "Aloita M4B enkoodaus",
|
||||||
"ButtonStartMetadataEmbed": "Aloita metadatan embed",
|
"ButtonStartMetadataEmbed": "Aloita metadatan embed",
|
||||||
|
"ButtonStats": "Tilastot",
|
||||||
"ButtonSubmit": "Lähetä",
|
"ButtonSubmit": "Lähetä",
|
||||||
"ButtonTest": "Testi",
|
"ButtonTest": "Testi",
|
||||||
"ButtonUpload": "Lähetä palvelimelle",
|
"ButtonUpload": "Lähetä palvelimelle",
|
||||||
@@ -120,66 +121,158 @@
|
|||||||
"HeaderDetails": "Yksityiskohdat",
|
"HeaderDetails": "Yksityiskohdat",
|
||||||
"HeaderDownloadQueue": "Latausjono",
|
"HeaderDownloadQueue": "Latausjono",
|
||||||
"HeaderEbookFiles": "E-kirjatiedostot",
|
"HeaderEbookFiles": "E-kirjatiedostot",
|
||||||
|
"HeaderEmail": "Sähköposti",
|
||||||
|
"HeaderEmailSettings": "Sähköpostiasetukset",
|
||||||
"HeaderEpisodes": "Jaksot",
|
"HeaderEpisodes": "Jaksot",
|
||||||
|
"HeaderEreaderDevices": "E-lukijalaitteet",
|
||||||
"HeaderEreaderSettings": "E-lukijan asetukset",
|
"HeaderEreaderSettings": "E-lukijan asetukset",
|
||||||
|
"HeaderFiles": "Tiedostot",
|
||||||
|
"HeaderIgnoredFiles": "Ohitetut tiedostot",
|
||||||
"HeaderLatestEpisodes": "Viimeisimmät jaksot",
|
"HeaderLatestEpisodes": "Viimeisimmät jaksot",
|
||||||
"HeaderLibraries": "Kirjastot",
|
"HeaderLibraries": "Kirjastot",
|
||||||
|
"HeaderLibraryFiles": "Kirjaston tiedostot",
|
||||||
|
"HeaderLibraryStats": "Kirjaston tilastot",
|
||||||
|
"HeaderListeningStats": "Kuuntelutilastot",
|
||||||
|
"HeaderLogs": "Lokit",
|
||||||
|
"HeaderNewAccount": "Uusi tili",
|
||||||
|
"HeaderNewLibrary": "Uusi kirjasto",
|
||||||
|
"HeaderNotifications": "Ilmoitukset",
|
||||||
"HeaderOpenRSSFeed": "Avaa RSS-syöte",
|
"HeaderOpenRSSFeed": "Avaa RSS-syöte",
|
||||||
|
"HeaderOtherFiles": "Muut tiedostot",
|
||||||
|
"HeaderPermissions": "Käyttöoikeudet",
|
||||||
"HeaderPlaylist": "Soittolista",
|
"HeaderPlaylist": "Soittolista",
|
||||||
|
"HeaderPlaylistItems": "Soittolistan kohteet",
|
||||||
"HeaderRSSFeedGeneral": "RSS yksityiskohdat",
|
"HeaderRSSFeedGeneral": "RSS yksityiskohdat",
|
||||||
"HeaderRSSFeedIsOpen": "RSS syöte on avoinna",
|
"HeaderRSSFeedIsOpen": "RSS syöte on avoinna",
|
||||||
|
"HeaderRemoveEpisode": "Poista jakso",
|
||||||
|
"HeaderRemoveEpisodes": "Poista {0} jaksoa",
|
||||||
|
"HeaderSchedule": "Ajoita",
|
||||||
|
"HeaderScheduleLibraryScans": "Ajoita automaattiset kirjastoskannaukset",
|
||||||
|
"HeaderSetBackupSchedule": "Aseta varmuuskopiointiaikataulu",
|
||||||
"HeaderSettings": "Asetukset",
|
"HeaderSettings": "Asetukset",
|
||||||
|
"HeaderSettingsExperimental": "Kokeelliset ominaisuudet",
|
||||||
"HeaderSleepTimer": "Uniajastin",
|
"HeaderSleepTimer": "Uniajastin",
|
||||||
"HeaderStatsMinutesListeningChart": "Kuunteluminuutit (viim. 7 pv)",
|
"HeaderStatsMinutesListeningChart": "Kuunteluminuutit (viim. 7 pv)",
|
||||||
"HeaderStatsRecentSessions": "Viimeaikaiset istunnot",
|
"HeaderStatsRecentSessions": "Viimeaikaiset istunnot",
|
||||||
"HeaderTableOfContents": "Sisällysluettelo",
|
"HeaderTableOfContents": "Sisällysluettelo",
|
||||||
|
"HeaderTools": "Työkalut",
|
||||||
|
"HeaderUsers": "Käyttäjät",
|
||||||
"HeaderYourStats": "Tilastosi",
|
"HeaderYourStats": "Tilastosi",
|
||||||
|
"LabelAccountType": "Tilin tyyppi",
|
||||||
|
"LabelAccountTypeGuest": "Vieras",
|
||||||
|
"LabelAccountTypeUser": "Käyttäjä",
|
||||||
|
"LabelActivity": "Toiminta",
|
||||||
|
"LabelAddToCollection": "Lisää kokoelmaan",
|
||||||
|
"LabelAddToCollectionBatch": "Lisää {0} kirjaa kokoelmaan",
|
||||||
"LabelAddToPlaylist": "Lisää soittolistaan",
|
"LabelAddToPlaylist": "Lisää soittolistaan",
|
||||||
|
"LabelAddToPlaylistBatch": "Lisää {0} kohdetta soittolistaan",
|
||||||
"LabelAdded": "Lisätty",
|
"LabelAdded": "Lisätty",
|
||||||
"LabelAddedAt": "Lisätty listalle",
|
"LabelAddedAt": "Lisätty listalle",
|
||||||
"LabelAll": "Kaikki",
|
"LabelAll": "Kaikki",
|
||||||
|
"LabelAllUsers": "Kaikki käyttäjät",
|
||||||
|
"LabelAllUsersExcludingGuests": "Kaikki käyttäjät vieraita lukuun ottamatta",
|
||||||
|
"LabelAllUsersIncludingGuests": "Kaikki käyttäjät mukaan lukien vieraat",
|
||||||
"LabelAuthor": "Tekijä",
|
"LabelAuthor": "Tekijä",
|
||||||
"LabelAuthorFirstLast": "Tekijä (Etunimi Sukunimi)",
|
"LabelAuthorFirstLast": "Tekijä (Etunimi Sukunimi)",
|
||||||
"LabelAuthorLastFirst": "Tekijä (Sukunimi, Etunimi)",
|
"LabelAuthorLastFirst": "Tekijä (Sukunimi, Etunimi)",
|
||||||
"LabelAuthors": "Tekijät",
|
"LabelAuthors": "Tekijät",
|
||||||
"LabelAutoDownloadEpisodes": "Lataa jaksot automaattisesti",
|
"LabelAutoDownloadEpisodes": "Lataa jaksot automaattisesti",
|
||||||
|
"LabelBackupsEnableAutomaticBackups": "Ota automaattinen varmuuskopiointi käyttöön",
|
||||||
|
"LabelBackupsEnableAutomaticBackupsHelp": "Varmuuskopiot tallennettu kansioon /metadata/backups",
|
||||||
|
"LabelBackupsMaxBackupSize": "Varmuuskopion enimmäiskoko (Gt) (0 rajaton)",
|
||||||
|
"LabelBackupsNumberToKeep": "Säilytettävien varmuuskopioiden määrä",
|
||||||
"LabelBooks": "Kirjat",
|
"LabelBooks": "Kirjat",
|
||||||
|
"LabelButtonText": "Painikkeen teksti",
|
||||||
|
"LabelChangePassword": "Vaihda salasana",
|
||||||
"LabelChapters": "Luvut",
|
"LabelChapters": "Luvut",
|
||||||
|
"LabelClickForMoreInfo": "Napsauta saadaksesi lisätietoja",
|
||||||
"LabelClosePlayer": "Sulje soitin",
|
"LabelClosePlayer": "Sulje soitin",
|
||||||
|
"LabelCodec": "Koodekki",
|
||||||
"LabelCollapseSeries": "Pienennä sarja",
|
"LabelCollapseSeries": "Pienennä sarja",
|
||||||
|
"LabelCollection": "Kokoelma",
|
||||||
|
"LabelCollections": "Kokoelmat",
|
||||||
"LabelComplete": "Valmis",
|
"LabelComplete": "Valmis",
|
||||||
|
"LabelConfirmPassword": "Vahvista salasana",
|
||||||
"LabelContinueListening": "Jatka kuuntelua",
|
"LabelContinueListening": "Jatka kuuntelua",
|
||||||
"LabelContinueReading": "Jatka lukemista",
|
"LabelContinueReading": "Jatka lukemista",
|
||||||
"LabelContinueSeries": "Jatka sarjoja",
|
"LabelContinueSeries": "Jatka sarjoja",
|
||||||
|
"LabelCover": "Kansikuva",
|
||||||
|
"LabelCoverImageURL": "Kansikuvan URL-osoite",
|
||||||
|
"LabelCurrent": "Nykyinen",
|
||||||
"LabelDescription": "Kuvaus",
|
"LabelDescription": "Kuvaus",
|
||||||
|
"LabelDevice": "Laite",
|
||||||
|
"LabelDeviceInfo": "Laitteen tiedot",
|
||||||
"LabelDownload": "Lataa",
|
"LabelDownload": "Lataa",
|
||||||
|
"LabelDownloadNEpisodes": "Lataa {0} jaksoa",
|
||||||
"LabelDuration": "Kesto",
|
"LabelDuration": "Kesto",
|
||||||
"LabelEbook": "E-kirja",
|
"LabelEbook": "E-kirja",
|
||||||
"LabelEbooks": "E-kirjat",
|
"LabelEbooks": "E-kirjat",
|
||||||
|
"LabelEdit": "Muokkaa",
|
||||||
|
"LabelEmail": "Sähköposti",
|
||||||
"LabelEnable": "Ota käyttöön",
|
"LabelEnable": "Ota käyttöön",
|
||||||
|
"LabelEndOfChapter": "Luvun loppu",
|
||||||
|
"LabelEpisode": "Jakso",
|
||||||
"LabelFile": "Tiedosto",
|
"LabelFile": "Tiedosto",
|
||||||
"LabelFileBirthtime": "Tiedoston syntymäaika",
|
"LabelFileBirthtime": "Tiedoston syntymäaika",
|
||||||
"LabelFileModified": "Muutettu tiedosto",
|
"LabelFileModified": "Muutettu tiedosto",
|
||||||
"LabelFilename": "Tiedostonimi",
|
"LabelFilename": "Tiedostonimi",
|
||||||
"LabelFolder": "Kansio",
|
"LabelFolder": "Kansio",
|
||||||
|
"LabelInProgress": "Kesken",
|
||||||
|
"LabelIncomplete": "Keskeneräinen",
|
||||||
"LabelLanguage": "Kieli",
|
"LabelLanguage": "Kieli",
|
||||||
|
"LabelListenAgain": "Kuuntele uudelleen",
|
||||||
|
"LabelMediaType": "Mediatyyppi",
|
||||||
"LabelMore": "Lisää",
|
"LabelMore": "Lisää",
|
||||||
|
"LabelMoreInfo": "Lisätietoja",
|
||||||
|
"LabelName": "Nimi",
|
||||||
"LabelNarrator": "Lukija",
|
"LabelNarrator": "Lukija",
|
||||||
"LabelNarrators": "Lukijat",
|
"LabelNarrators": "Lukijat",
|
||||||
"LabelNewestAuthors": "Uusimmat kirjailijat",
|
"LabelNewestAuthors": "Uusimmat kirjailijat",
|
||||||
"LabelNewestEpisodes": "Uusimmat jaksot",
|
"LabelNewestEpisodes": "Uusimmat jaksot",
|
||||||
"LabelPassword": "Salasana",
|
"LabelPassword": "Salasana",
|
||||||
"LabelPath": "Polku",
|
"LabelPath": "Polku",
|
||||||
|
"LabelPodcast": "Podcast",
|
||||||
|
"LabelPodcasts": "Podcastit",
|
||||||
|
"LabelPublishYear": "Julkaisuvuosi",
|
||||||
|
"LabelRSSFeedPreventIndexing": "Estä indeksointi",
|
||||||
"LabelRead": "Lue",
|
"LabelRead": "Lue",
|
||||||
"LabelReadAgain": "Lue uudelleen",
|
"LabelReadAgain": "Lue uudelleen",
|
||||||
|
"LabelRecentSeries": "Viimeisimmät sarjat",
|
||||||
|
"LabelRecentlyAdded": "Viimeeksi lisätyt",
|
||||||
"LabelSeason": "Kausi",
|
"LabelSeason": "Kausi",
|
||||||
|
"LabelSetEbookAsPrimary": "Aseta ensisijaiseksi",
|
||||||
|
"LabelSetEbookAsSupplementary": "Aseta täydentäväksi",
|
||||||
"LabelShowAll": "Näytä kaikki",
|
"LabelShowAll": "Näytä kaikki",
|
||||||
"LabelSize": "Koko",
|
"LabelSize": "Koko",
|
||||||
"LabelSleepTimer": "Uniajastin",
|
"LabelSleepTimer": "Uniajastin",
|
||||||
|
"LabelStatsDailyAverage": "Päivittäinen keskiarvo",
|
||||||
|
"LabelStatsInARow": "peräjälkeen",
|
||||||
|
"LabelStatsMinutes": "minuuttia",
|
||||||
"LabelTheme": "Teema",
|
"LabelTheme": "Teema",
|
||||||
"LabelThemeDark": "Tumma",
|
"LabelThemeDark": "Tumma",
|
||||||
"LabelThemeLight": "Kirkas",
|
"LabelThemeLight": "Kirkas",
|
||||||
|
"LabelTimeRemaining": "{0} jäljellä",
|
||||||
|
"LabelType": "Tyyppi",
|
||||||
"LabelUser": "Käyttäjä",
|
"LabelUser": "Käyttäjä",
|
||||||
"LabelUsername": "Käyttäjätunnus",
|
"LabelUsername": "Käyttäjätunnus",
|
||||||
"MessageDownloadingEpisode": "Ladataan jaksoa"
|
"LabelYourBookmarks": "Kirjanmerkkisi",
|
||||||
|
"LabelYourProgress": "Edistymisesi",
|
||||||
|
"MessageDownloadingEpisode": "Ladataan jaksoa",
|
||||||
|
"MessageEpisodesQueuedForDownload": "{0} jaksoa on latausjonossa",
|
||||||
|
"MessageFetching": "Haetaan...",
|
||||||
|
"MessageLoading": "Ladataan...",
|
||||||
|
"MessageMarkAsFinished": "Merkitse valmiiksi",
|
||||||
|
"MessageNoBookmarks": "Ei kirjanmerkkejä",
|
||||||
|
"MessageNoItems": "Ei kohteita",
|
||||||
|
"MessageNoItemsFound": "Kohteita ei löytynyt",
|
||||||
|
"MessageNoPodcastsFound": "Podcasteja ei löytynyt",
|
||||||
|
"MessageNoUserPlaylists": "Sinulla ei ole soittolistoja",
|
||||||
|
"MessageReportBugsAndContribute": "Ilmoita virheistä, toivo ominaisuuksia ja osallistu",
|
||||||
|
"ToastBookmarkCreateFailed": "Kirjanmerkin luominen epäonnistui",
|
||||||
|
"ToastBookmarkRemoveFailed": "Kirjanmerkin poistaminen epäonnistui",
|
||||||
|
"ToastBookmarkUpdateFailed": "Kirjanmerkin päivittäminen epäonnistui",
|
||||||
|
"ToastItemMarkedAsFinishedFailed": "Valmiiksi merkitseminen epäonnistui",
|
||||||
|
"ToastPlaylistCreateFailed": "Soittolistan luominen epäonnistui",
|
||||||
|
"ToastPodcastCreateFailed": "Podcastin luominen epäonnistui",
|
||||||
|
"ToastPodcastCreateSuccess": "Podcastin luominen onnistui"
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-5
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Ajouter",
|
"ButtonAdd": "Ajouter",
|
||||||
"ButtonAddChapters": "Ajouter le chapitre",
|
"ButtonAddChapters": "Ajouter des chapitres",
|
||||||
"ButtonAddDevice": "Ajouter un appareil",
|
"ButtonAddDevice": "Ajouter un appareil",
|
||||||
"ButtonAddLibrary": "Ajouter une bibliothèque",
|
"ButtonAddLibrary": "Ajouter une bibliothèque",
|
||||||
"ButtonAddPodcasts": "Ajouter des podcasts",
|
"ButtonAddPodcasts": "Ajouter des podcasts",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"ButtonJumpForward": "Avancer",
|
"ButtonJumpForward": "Avancer",
|
||||||
"ButtonLatest": "Dernière version",
|
"ButtonLatest": "Dernière version",
|
||||||
"ButtonLibrary": "Bibliothèque",
|
"ButtonLibrary": "Bibliothèque",
|
||||||
"ButtonLogout": "Me déconnecter",
|
"ButtonLogout": "Déconnexion",
|
||||||
"ButtonLookup": "Chercher",
|
"ButtonLookup": "Chercher",
|
||||||
"ButtonManageTracks": "Gérer les pistes",
|
"ButtonManageTracks": "Gérer les pistes",
|
||||||
"ButtonMapChapterTitles": "Correspondance des titres de chapitres",
|
"ButtonMapChapterTitles": "Correspondance des titres de chapitres",
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
"ButtonPurgeItemsCache": "Purger le cache des éléments",
|
"ButtonPurgeItemsCache": "Purger le cache des éléments",
|
||||||
"ButtonQueueAddItem": "Ajouter à la liste de lecture",
|
"ButtonQueueAddItem": "Ajouter à la liste de lecture",
|
||||||
"ButtonQueueRemoveItem": "Supprimer de la liste de lecture",
|
"ButtonQueueRemoveItem": "Supprimer de la liste de lecture",
|
||||||
|
"ButtonQuickEmbedMetadata": "Ajouter rapidement des métadonnées",
|
||||||
"ButtonQuickMatch": "Recherche rapide",
|
"ButtonQuickMatch": "Recherche rapide",
|
||||||
"ButtonReScan": "Nouvelle analyse",
|
"ButtonReScan": "Nouvelle analyse",
|
||||||
"ButtonRead": "Lire",
|
"ButtonRead": "Lire",
|
||||||
@@ -88,6 +89,7 @@
|
|||||||
"ButtonShow": "Afficher",
|
"ButtonShow": "Afficher",
|
||||||
"ButtonStartM4BEncode": "Démarrer l’encodage M4B",
|
"ButtonStartM4BEncode": "Démarrer l’encodage M4B",
|
||||||
"ButtonStartMetadataEmbed": "Démarrer les Métadonnées intégrées",
|
"ButtonStartMetadataEmbed": "Démarrer les Métadonnées intégrées",
|
||||||
|
"ButtonStats": "Statistiques",
|
||||||
"ButtonSubmit": "Soumettre",
|
"ButtonSubmit": "Soumettre",
|
||||||
"ButtonTest": "Test",
|
"ButtonTest": "Test",
|
||||||
"ButtonUpload": "Téléverser",
|
"ButtonUpload": "Téléverser",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderPasswordAuthentication": "Authentification par mot de passe",
|
"HeaderPasswordAuthentication": "Authentification par mot de passe",
|
||||||
"HeaderPermissions": "Permissions",
|
"HeaderPermissions": "Permissions",
|
||||||
"HeaderPlayerQueue": "Liste d’écoute",
|
"HeaderPlayerQueue": "Liste d’écoute",
|
||||||
|
"HeaderPlayerSettings": "Paramètres du lecteur",
|
||||||
"HeaderPlaylist": "Liste de lecture",
|
"HeaderPlaylist": "Liste de lecture",
|
||||||
"HeaderPlaylistItems": "Éléments de la liste de lecture",
|
"HeaderPlaylistItems": "Éléments de la liste de lecture",
|
||||||
"HeaderPodcastsToAdd": "Podcasts à ajouter",
|
"HeaderPodcastsToAdd": "Podcasts à ajouter",
|
||||||
@@ -226,7 +229,7 @@
|
|||||||
"LabelBackupLocation": "Emplacement de la sauvegarde",
|
"LabelBackupLocation": "Emplacement de la sauvegarde",
|
||||||
"LabelBackupsEnableAutomaticBackups": "Activer les sauvegardes automatiques",
|
"LabelBackupsEnableAutomaticBackups": "Activer les sauvegardes automatiques",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "Sauvegardes enregistrées dans /metadata/backups",
|
"LabelBackupsEnableAutomaticBackupsHelp": "Sauvegardes enregistrées dans /metadata/backups",
|
||||||
"LabelBackupsMaxBackupSize": "Taille maximale de la sauvegarde (en Go)",
|
"LabelBackupsMaxBackupSize": "Taille maximale de la sauvegarde (en Go) (0 pour illimité)",
|
||||||
"LabelBackupsMaxBackupSizeHelp": "Afin de prévenir les mauvaises configuration, la sauvegarde échouera si elle excède la taille limite.",
|
"LabelBackupsMaxBackupSizeHelp": "Afin de prévenir les mauvaises configuration, la sauvegarde échouera si elle excède la taille limite.",
|
||||||
"LabelBackupsNumberToKeep": "Nombre de sauvegardes à conserver",
|
"LabelBackupsNumberToKeep": "Nombre de sauvegardes à conserver",
|
||||||
"LabelBackupsNumberToKeepHelp": "Seule une sauvegarde sera supprimée à la fois. Si vous avez déjà plus de sauvegardes à effacer, vous devez les supprimer manuellement.",
|
"LabelBackupsNumberToKeepHelp": "Seule une sauvegarde sera supprimée à la fois. Si vous avez déjà plus de sauvegardes à effacer, vous devez les supprimer manuellement.",
|
||||||
@@ -282,20 +285,23 @@
|
|||||||
"LabelEmail": "Courriel",
|
"LabelEmail": "Courriel",
|
||||||
"LabelEmailSettingsFromAddress": "Expéditeur",
|
"LabelEmailSettingsFromAddress": "Expéditeur",
|
||||||
"LabelEmailSettingsRejectUnauthorized": "Rejeter les certificats non autorisés",
|
"LabelEmailSettingsRejectUnauthorized": "Rejeter les certificats non autorisés",
|
||||||
"LabelEmailSettingsRejectUnauthorizedHelp": "Désactiver la validation du certificat SSL peut exposer votre connexion à des risques de sécurité, tels que des attaques de type « man-in-the-middle ». Ne désactivez cette option que si vous en comprenez les implications et si vous faites confiance au serveur de messagerie auquel vous vous connectez.",
|
"LabelEmailSettingsRejectUnauthorizedHelp": "Désactiver la validation du certificat SSL peut exposer votre connexion à des risques de sécurité, tels que des attaques de type « Attaque de l’homme du milieu ». Ne désactivez cette option que si vous en comprenez les implications et si vous faites confiance au serveur de messagerie auquel vous vous connectez.",
|
||||||
"LabelEmailSettingsSecure": "Sécurisé",
|
"LabelEmailSettingsSecure": "Sécurisé",
|
||||||
"LabelEmailSettingsSecureHelp": "Si vous activez cette option, TLS sera utiliser lors de la connexion au serveur. Sinon, TLS est utilisé uniquement si le serveur supporte l’extension STARTTLS. Dans la plupart des cas, activez l’option, vous vous connecterai sur le port 465. Pour le port 587 ou 25, désactiver l’option. (source : nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "Si vous activez cette option, TLS sera utiliser lors de la connexion au serveur. Sinon, TLS est utilisé uniquement si le serveur supporte l’extension STARTTLS. Dans la plupart des cas, activez l’option, vous vous connecterai sur le port 465. Pour le port 587 ou 25, désactiver l’option. (source : nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmailSettingsTestAddress": "Adresse de test",
|
"LabelEmailSettingsTestAddress": "Adresse de test",
|
||||||
"LabelEmbeddedCover": "Couverture du livre intégrée",
|
"LabelEmbeddedCover": "Couverture du livre intégrée",
|
||||||
"LabelEnable": "Activer",
|
"LabelEnable": "Activer",
|
||||||
"LabelEnd": "Fin",
|
"LabelEnd": "Fin",
|
||||||
|
"LabelEndOfChapter": "Fin du chapitre",
|
||||||
"LabelEpisode": "Épisode",
|
"LabelEpisode": "Épisode",
|
||||||
"LabelEpisodeTitle": "Titre de l’épisode",
|
"LabelEpisodeTitle": "Titre de l’épisode",
|
||||||
"LabelEpisodeType": "Type de l’épisode",
|
"LabelEpisodeType": "Type de l’épisode",
|
||||||
"LabelExample": "Exemple",
|
"LabelExample": "Exemple",
|
||||||
|
"LabelExpandSeries": "Développer la série",
|
||||||
"LabelExplicit": "Restriction",
|
"LabelExplicit": "Restriction",
|
||||||
"LabelExplicitChecked": "Explicite (vérifié)",
|
"LabelExplicitChecked": "Explicite (vérifié)",
|
||||||
"LabelExplicitUnchecked": "Non explicite (non vérifié)",
|
"LabelExplicitUnchecked": "Non explicite (non vérifié)",
|
||||||
|
"LabelExportOPML": "Exporter OPML",
|
||||||
"LabelFeedURL": "URL du flux",
|
"LabelFeedURL": "URL du flux",
|
||||||
"LabelFetchingMetadata": "Récupération des métadonnées",
|
"LabelFetchingMetadata": "Récupération des métadonnées",
|
||||||
"LabelFile": "Fichier",
|
"LabelFile": "Fichier",
|
||||||
@@ -319,6 +325,7 @@
|
|||||||
"LabelHardDeleteFile": "Suppression du fichier",
|
"LabelHardDeleteFile": "Suppression du fichier",
|
||||||
"LabelHasEbook": "A un livre numérique",
|
"LabelHasEbook": "A un livre numérique",
|
||||||
"LabelHasSupplementaryEbook": "A un livre numérique supplémentaire",
|
"LabelHasSupplementaryEbook": "A un livre numérique supplémentaire",
|
||||||
|
"LabelHideSubtitles": "Masquer les sous-titres",
|
||||||
"LabelHighestPriority": "Priorité la plus élevée",
|
"LabelHighestPriority": "Priorité la plus élevée",
|
||||||
"LabelHost": "Hôte",
|
"LabelHost": "Hôte",
|
||||||
"LabelHour": "Heure",
|
"LabelHour": "Heure",
|
||||||
@@ -446,6 +453,8 @@
|
|||||||
"LabelRSSFeedPreventIndexing": "Empêcher l’indexation",
|
"LabelRSSFeedPreventIndexing": "Empêcher l’indexation",
|
||||||
"LabelRSSFeedSlug": "Balise URL du flux RSS",
|
"LabelRSSFeedSlug": "Balise URL du flux RSS",
|
||||||
"LabelRSSFeedURL": "Adresse du flux RSS",
|
"LabelRSSFeedURL": "Adresse du flux RSS",
|
||||||
|
"LabelRandomly": "Au hasard",
|
||||||
|
"LabelReAddSeriesToContinueListening": "Ajouter à nouveau la série pour continuer à l’écouter",
|
||||||
"LabelRead": "Lire",
|
"LabelRead": "Lire",
|
||||||
"LabelReadAgain": "Lire à nouveau",
|
"LabelReadAgain": "Lire à nouveau",
|
||||||
"LabelReadEbookWithoutProgress": "Lire le livre numérique sans sauvegarder la progression",
|
"LabelReadEbookWithoutProgress": "Lire le livre numérique sans sauvegarder la progression",
|
||||||
@@ -516,6 +525,7 @@
|
|||||||
"LabelShareURL": "Partager l’URL",
|
"LabelShareURL": "Partager l’URL",
|
||||||
"LabelShowAll": "Tout afficher",
|
"LabelShowAll": "Tout afficher",
|
||||||
"LabelShowSeconds": "Afficher les seondes",
|
"LabelShowSeconds": "Afficher les seondes",
|
||||||
|
"LabelShowSubtitles": "Afficher les sous-titres",
|
||||||
"LabelSize": "Taille",
|
"LabelSize": "Taille",
|
||||||
"LabelSleepTimer": "Minuterie de mise en veille",
|
"LabelSleepTimer": "Minuterie de mise en veille",
|
||||||
"LabelSlug": "Balise",
|
"LabelSlug": "Balise",
|
||||||
@@ -553,6 +563,10 @@
|
|||||||
"LabelThemeDark": "Sombre",
|
"LabelThemeDark": "Sombre",
|
||||||
"LabelThemeLight": "Clair",
|
"LabelThemeLight": "Clair",
|
||||||
"LabelTimeBase": "Base de temps",
|
"LabelTimeBase": "Base de temps",
|
||||||
|
"LabelTimeDurationXHours": "{0} heures",
|
||||||
|
"LabelTimeDurationXMinutes": "{0} minutes",
|
||||||
|
"LabelTimeDurationXSeconds": "{0} secondes",
|
||||||
|
"LabelTimeInMinutes": "Temps en minutes",
|
||||||
"LabelTimeListened": "Temps d’écoute",
|
"LabelTimeListened": "Temps d’écoute",
|
||||||
"LabelTimeListenedToday": "Nombres d’écoutes aujourd’hui",
|
"LabelTimeListenedToday": "Nombres d’écoutes aujourd’hui",
|
||||||
"LabelTimeRemaining": "{0} restantes",
|
"LabelTimeRemaining": "{0} restantes",
|
||||||
@@ -592,14 +606,17 @@
|
|||||||
"LabelVersion": "Version",
|
"LabelVersion": "Version",
|
||||||
"LabelViewBookmarks": "Afficher les favoris",
|
"LabelViewBookmarks": "Afficher les favoris",
|
||||||
"LabelViewChapters": "Afficher les chapitres",
|
"LabelViewChapters": "Afficher les chapitres",
|
||||||
|
"LabelViewPlayerSettings": "Afficher les paramètres du lecteur",
|
||||||
"LabelViewQueue": "Afficher la liste de lecture",
|
"LabelViewQueue": "Afficher la liste de lecture",
|
||||||
"LabelVolume": "Volume",
|
"LabelVolume": "Volume",
|
||||||
"LabelWeekdaysToRun": "Jours de la semaine à exécuter",
|
"LabelWeekdaysToRun": "Jours de la semaine à exécuter",
|
||||||
|
"LabelXBooks": "{0} livres",
|
||||||
|
"LabelXItems": "{0} éléments",
|
||||||
"LabelYearReviewHide": "Masquer le bilan de l’année",
|
"LabelYearReviewHide": "Masquer le bilan de l’année",
|
||||||
"LabelYearReviewShow": "Afficher le bilan de l’année",
|
"LabelYearReviewShow": "Afficher le bilan de l’année",
|
||||||
"LabelYourAudiobookDuration": "Durée de vos livres audios",
|
"LabelYourAudiobookDuration": "Durée de vos livres audios",
|
||||||
"LabelYourBookmarks": "Vos favoris",
|
"LabelYourBookmarks": "Vos favoris",
|
||||||
"LabelYourPlaylists": "Vos listes de lecture",
|
"LabelYourPlaylists": "Mes listes de lecture",
|
||||||
"LabelYourProgress": "Votre progression",
|
"LabelYourProgress": "Votre progression",
|
||||||
"MessageAddToPlayerQueue": "Ajouter en file d’attente",
|
"MessageAddToPlayerQueue": "Ajouter en file d’attente",
|
||||||
"MessageAppriseDescription": "Nécessite une instance d’<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes.<br />L’URL de l’API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Nécessite une instance d’<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes.<br />L’URL de l’API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
@@ -652,6 +669,7 @@
|
|||||||
"MessageConfirmSendEbookToDevice": "Êtes-vous sûr de vouloir envoyer {0} livre numérique « {1} » à l'appareil « {2} » ?",
|
"MessageConfirmSendEbookToDevice": "Êtes-vous sûr de vouloir envoyer {0} livre numérique « {1} » à l'appareil « {2} » ?",
|
||||||
"MessageDownloadingEpisode": "Téléchargement de l’épisode",
|
"MessageDownloadingEpisode": "Téléchargement de l’épisode",
|
||||||
"MessageDragFilesIntoTrackOrder": "Faites glisser les fichiers dans l’ordre correct des pistes",
|
"MessageDragFilesIntoTrackOrder": "Faites glisser les fichiers dans l’ordre correct des pistes",
|
||||||
|
"MessageEmbedFailed": "Échec de l’intégration !",
|
||||||
"MessageEmbedFinished": "Intégration terminée !",
|
"MessageEmbedFinished": "Intégration terminée !",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
|
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
|
||||||
"MessageEreaderDevices": "Pour garantir l’envoie des livres électroniques, il se peut que vous deviez ajouter l’adresse électronique ci-dessus en tant qu’expéditeur valide pour chaque appareil répertorié ci-dessous.",
|
"MessageEreaderDevices": "Pour garantir l’envoie des livres électroniques, il se peut que vous deviez ajouter l’adresse électronique ci-dessus en tant qu’expéditeur valide pour chaque appareil répertorié ci-dessous.",
|
||||||
@@ -706,6 +724,7 @@
|
|||||||
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n’était nécessaire",
|
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n’était nécessaire",
|
||||||
"MessageNoUserPlaylists": "Vous n’avez aucune liste de lecture",
|
"MessageNoUserPlaylists": "Vous n’avez aucune liste de lecture",
|
||||||
"MessageNotYetImplemented": "Non implémenté",
|
"MessageNotYetImplemented": "Non implémenté",
|
||||||
|
"MessageOpmlPreviewNote": "Remarque : Il s’agit d’un aperçu du fichier OPML analysé. Le titre réel du podcast provient du flux RSS.",
|
||||||
"MessageOr": "ou",
|
"MessageOr": "ou",
|
||||||
"MessagePauseChapter": "Suspendre la lecture du chapitre",
|
"MessagePauseChapter": "Suspendre la lecture du chapitre",
|
||||||
"MessagePlayChapter": "Écouter depuis le début du chapitre",
|
"MessagePlayChapter": "Écouter depuis le début du chapitre",
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
"ButtonOk": "确定",
|
"ButtonOk": "确定",
|
||||||
"ButtonOpenFeed": "打开源",
|
"ButtonOpenFeed": "打开源",
|
||||||
"ButtonOpenManager": "打开管理器",
|
"ButtonOpenManager": "打开管理器",
|
||||||
"ButtonPause": "Pause",
|
"ButtonPause": "暂停",
|
||||||
"ButtonPlay": "播放",
|
"ButtonPlay": "播放",
|
||||||
"ButtonPlaying": "正在播放",
|
"ButtonPlaying": "正在播放",
|
||||||
"ButtonPlaylists": "播放列表",
|
"ButtonPlaylists": "播放列表",
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
"ButtonPurgeItemsCache": "清理项目缓存",
|
"ButtonPurgeItemsCache": "清理项目缓存",
|
||||||
"ButtonQueueAddItem": "添加到队列",
|
"ButtonQueueAddItem": "添加到队列",
|
||||||
"ButtonQueueRemoveItem": "从队列中移除",
|
"ButtonQueueRemoveItem": "从队列中移除",
|
||||||
|
"ButtonQuickEmbedMetadata": "快速嵌入元数据",
|
||||||
"ButtonQuickMatch": "快速匹配",
|
"ButtonQuickMatch": "快速匹配",
|
||||||
"ButtonReScan": "重新扫描",
|
"ButtonReScan": "重新扫描",
|
||||||
"ButtonRead": "读取",
|
"ButtonRead": "读取",
|
||||||
@@ -88,6 +89,7 @@
|
|||||||
"ButtonShow": "显示",
|
"ButtonShow": "显示",
|
||||||
"ButtonStartM4BEncode": "开始 M4B 编码",
|
"ButtonStartM4BEncode": "开始 M4B 编码",
|
||||||
"ButtonStartMetadataEmbed": "开始嵌入元数据",
|
"ButtonStartMetadataEmbed": "开始嵌入元数据",
|
||||||
|
"ButtonStats": "统计数据",
|
||||||
"ButtonSubmit": "提交",
|
"ButtonSubmit": "提交",
|
||||||
"ButtonTest": "测试",
|
"ButtonTest": "测试",
|
||||||
"ButtonUpload": "上传",
|
"ButtonUpload": "上传",
|
||||||
@@ -154,6 +156,7 @@
|
|||||||
"HeaderPasswordAuthentication": "密码认证",
|
"HeaderPasswordAuthentication": "密码认证",
|
||||||
"HeaderPermissions": "权限",
|
"HeaderPermissions": "权限",
|
||||||
"HeaderPlayerQueue": "播放队列",
|
"HeaderPlayerQueue": "播放队列",
|
||||||
|
"HeaderPlayerSettings": "播放器设置",
|
||||||
"HeaderPlaylist": "播放列表",
|
"HeaderPlaylist": "播放列表",
|
||||||
"HeaderPlaylistItems": "播放列表项目",
|
"HeaderPlaylistItems": "播放列表项目",
|
||||||
"HeaderPodcastsToAdd": "要添加的播客",
|
"HeaderPodcastsToAdd": "要添加的播客",
|
||||||
@@ -226,7 +229,7 @@
|
|||||||
"LabelBackupLocation": "备份位置",
|
"LabelBackupLocation": "备份位置",
|
||||||
"LabelBackupsEnableAutomaticBackups": "启用自动备份",
|
"LabelBackupsEnableAutomaticBackups": "启用自动备份",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "备份保存到 /metadata/backups",
|
"LabelBackupsEnableAutomaticBackupsHelp": "备份保存到 /metadata/backups",
|
||||||
"LabelBackupsMaxBackupSize": "最大备份大小 (GB)",
|
"LabelBackupsMaxBackupSize": "最大备份大小 (GB) (0 为无限制)",
|
||||||
"LabelBackupsMaxBackupSizeHelp": "为了防止错误配置, 如果备份超过配置的大小, 备份将失败.",
|
"LabelBackupsMaxBackupSizeHelp": "为了防止错误配置, 如果备份超过配置的大小, 备份将失败.",
|
||||||
"LabelBackupsNumberToKeep": "要保留的备份个数",
|
"LabelBackupsNumberToKeep": "要保留的备份个数",
|
||||||
"LabelBackupsNumberToKeepHelp": "一次只能删除一个备份, 因此如果你已经有超过此数量的备份, 则应手动删除它们.",
|
"LabelBackupsNumberToKeepHelp": "一次只能删除一个备份, 因此如果你已经有超过此数量的备份, 则应手动删除它们.",
|
||||||
@@ -258,6 +261,7 @@
|
|||||||
"LabelCurrently": "当前:",
|
"LabelCurrently": "当前:",
|
||||||
"LabelCustomCronExpression": "自定义计划任务表达式:",
|
"LabelCustomCronExpression": "自定义计划任务表达式:",
|
||||||
"LabelDatetime": "日期时间",
|
"LabelDatetime": "日期时间",
|
||||||
|
"LabelDays": "天",
|
||||||
"LabelDeleteFromFileSystemCheckbox": "从文件系统删除 (取消选中仅从数据库中删除)",
|
"LabelDeleteFromFileSystemCheckbox": "从文件系统删除 (取消选中仅从数据库中删除)",
|
||||||
"LabelDescription": "描述",
|
"LabelDescription": "描述",
|
||||||
"LabelDeselectAll": "全部取消选择",
|
"LabelDeselectAll": "全部取消选择",
|
||||||
@@ -288,13 +292,16 @@
|
|||||||
"LabelEmbeddedCover": "嵌入封面",
|
"LabelEmbeddedCover": "嵌入封面",
|
||||||
"LabelEnable": "启用",
|
"LabelEnable": "启用",
|
||||||
"LabelEnd": "结束",
|
"LabelEnd": "结束",
|
||||||
|
"LabelEndOfChapter": "章节结束",
|
||||||
"LabelEpisode": "剧集",
|
"LabelEpisode": "剧集",
|
||||||
"LabelEpisodeTitle": "剧集标题",
|
"LabelEpisodeTitle": "剧集标题",
|
||||||
"LabelEpisodeType": "剧集类型",
|
"LabelEpisodeType": "剧集类型",
|
||||||
"LabelExample": "示例",
|
"LabelExample": "示例",
|
||||||
|
"LabelExpandSeries": "展开系列",
|
||||||
"LabelExplicit": "信息准确",
|
"LabelExplicit": "信息准确",
|
||||||
"LabelExplicitChecked": "明确(已选中)",
|
"LabelExplicitChecked": "明确(已选中)",
|
||||||
"LabelExplicitUnchecked": "不明确 (未选中)",
|
"LabelExplicitUnchecked": "不明确 (未选中)",
|
||||||
|
"LabelExportOPML": "导出 OPML",
|
||||||
"LabelFeedURL": "源 URL",
|
"LabelFeedURL": "源 URL",
|
||||||
"LabelFetchingMetadata": "正在获取元数据",
|
"LabelFetchingMetadata": "正在获取元数据",
|
||||||
"LabelFile": "文件",
|
"LabelFile": "文件",
|
||||||
@@ -318,9 +325,11 @@
|
|||||||
"LabelHardDeleteFile": "完全删除文件",
|
"LabelHardDeleteFile": "完全删除文件",
|
||||||
"LabelHasEbook": "有电子书",
|
"LabelHasEbook": "有电子书",
|
||||||
"LabelHasSupplementaryEbook": "有补充电子书",
|
"LabelHasSupplementaryEbook": "有补充电子书",
|
||||||
|
"LabelHideSubtitles": "隐藏标题",
|
||||||
"LabelHighestPriority": "最高优先级",
|
"LabelHighestPriority": "最高优先级",
|
||||||
"LabelHost": "主机",
|
"LabelHost": "主机",
|
||||||
"LabelHour": "小时",
|
"LabelHour": "小时",
|
||||||
|
"LabelHours": "小时",
|
||||||
"LabelIcon": "图标",
|
"LabelIcon": "图标",
|
||||||
"LabelImageURLFromTheWeb": "来自 Web 图像的 URL",
|
"LabelImageURLFromTheWeb": "来自 Web 图像的 URL",
|
||||||
"LabelInProgress": "正在听",
|
"LabelInProgress": "正在听",
|
||||||
@@ -337,6 +346,8 @@
|
|||||||
"LabelIntervalEveryHour": "每小时",
|
"LabelIntervalEveryHour": "每小时",
|
||||||
"LabelInvert": "倒转",
|
"LabelInvert": "倒转",
|
||||||
"LabelItem": "项目",
|
"LabelItem": "项目",
|
||||||
|
"LabelJumpBackwardAmount": "向后跳转时间",
|
||||||
|
"LabelJumpForwardAmount": "向前跳转时间",
|
||||||
"LabelLanguage": "语言",
|
"LabelLanguage": "语言",
|
||||||
"LabelLanguageDefaultServer": "默认服务器语言",
|
"LabelLanguageDefaultServer": "默认服务器语言",
|
||||||
"LabelLanguages": "语言",
|
"LabelLanguages": "语言",
|
||||||
@@ -371,6 +382,7 @@
|
|||||||
"LabelMetadataOrderOfPrecedenceDescription": "较高优先级的元数据源将覆盖较低优先级的元数据源",
|
"LabelMetadataOrderOfPrecedenceDescription": "较高优先级的元数据源将覆盖较低优先级的元数据源",
|
||||||
"LabelMetadataProvider": "元数据提供者",
|
"LabelMetadataProvider": "元数据提供者",
|
||||||
"LabelMinute": "分钟",
|
"LabelMinute": "分钟",
|
||||||
|
"LabelMinutes": "分钟",
|
||||||
"LabelMissing": "丢失",
|
"LabelMissing": "丢失",
|
||||||
"LabelMissingEbook": "没有电子书",
|
"LabelMissingEbook": "没有电子书",
|
||||||
"LabelMissingSupplementaryEbook": "没有补充电子书",
|
"LabelMissingSupplementaryEbook": "没有补充电子书",
|
||||||
@@ -410,6 +422,7 @@
|
|||||||
"LabelOverwrite": "覆盖",
|
"LabelOverwrite": "覆盖",
|
||||||
"LabelPassword": "密码",
|
"LabelPassword": "密码",
|
||||||
"LabelPath": "路径",
|
"LabelPath": "路径",
|
||||||
|
"LabelPermanent": "永久的",
|
||||||
"LabelPermissionsAccessAllLibraries": "可以访问所有媒体库",
|
"LabelPermissionsAccessAllLibraries": "可以访问所有媒体库",
|
||||||
"LabelPermissionsAccessAllTags": "可以访问所有标签",
|
"LabelPermissionsAccessAllTags": "可以访问所有标签",
|
||||||
"LabelPermissionsAccessExplicitContent": "可以访问显式内容",
|
"LabelPermissionsAccessExplicitContent": "可以访问显式内容",
|
||||||
@@ -442,6 +455,8 @@
|
|||||||
"LabelRSSFeedPreventIndexing": "防止索引",
|
"LabelRSSFeedPreventIndexing": "防止索引",
|
||||||
"LabelRSSFeedSlug": "RSS 源段",
|
"LabelRSSFeedSlug": "RSS 源段",
|
||||||
"LabelRSSFeedURL": "RSS 源 URL",
|
"LabelRSSFeedURL": "RSS 源 URL",
|
||||||
|
"LabelRandomly": "随机",
|
||||||
|
"LabelReAddSeriesToContinueListening": "重新添加系列以继续收听",
|
||||||
"LabelRead": "阅读",
|
"LabelRead": "阅读",
|
||||||
"LabelReadAgain": "再次阅读",
|
"LabelReadAgain": "再次阅读",
|
||||||
"LabelReadEbookWithoutProgress": "阅读电子书而不保存进度",
|
"LabelReadEbookWithoutProgress": "阅读电子书而不保存进度",
|
||||||
@@ -507,8 +522,12 @@
|
|||||||
"LabelSettingsStoreMetadataWithItem": "存储项目元数据",
|
"LabelSettingsStoreMetadataWithItem": "存储项目元数据",
|
||||||
"LabelSettingsStoreMetadataWithItemHelp": "默认情况下元数据文件存储在/metadata/items文件夹中, 启用此设置将存储元数据在你媒体项目文件夹中",
|
"LabelSettingsStoreMetadataWithItemHelp": "默认情况下元数据文件存储在/metadata/items文件夹中, 启用此设置将存储元数据在你媒体项目文件夹中",
|
||||||
"LabelSettingsTimeFormat": "时间格式",
|
"LabelSettingsTimeFormat": "时间格式",
|
||||||
|
"LabelShare": "分享",
|
||||||
|
"LabelShareOpen": "打开分享",
|
||||||
|
"LabelShareURL": "分享 URL",
|
||||||
"LabelShowAll": "全部显示",
|
"LabelShowAll": "全部显示",
|
||||||
"LabelShowSeconds": "显示秒数",
|
"LabelShowSeconds": "显示秒数",
|
||||||
|
"LabelShowSubtitles": "显示标题",
|
||||||
"LabelSize": "文件大小",
|
"LabelSize": "文件大小",
|
||||||
"LabelSleepTimer": "睡眠定时",
|
"LabelSleepTimer": "睡眠定时",
|
||||||
"LabelSlug": "Slug",
|
"LabelSlug": "Slug",
|
||||||
@@ -546,6 +565,10 @@
|
|||||||
"LabelThemeDark": "黑暗",
|
"LabelThemeDark": "黑暗",
|
||||||
"LabelThemeLight": "明亮",
|
"LabelThemeLight": "明亮",
|
||||||
"LabelTimeBase": "时间基准",
|
"LabelTimeBase": "时间基准",
|
||||||
|
"LabelTimeDurationXHours": "{0} 小时",
|
||||||
|
"LabelTimeDurationXMinutes": "{0} 分钟",
|
||||||
|
"LabelTimeDurationXSeconds": "{0} 秒",
|
||||||
|
"LabelTimeInMinutes": "时间 (分钟)",
|
||||||
"LabelTimeListened": "收听时间",
|
"LabelTimeListened": "收听时间",
|
||||||
"LabelTimeListenedToday": "今日收听的时间",
|
"LabelTimeListenedToday": "今日收听的时间",
|
||||||
"LabelTimeRemaining": "剩余 {0}",
|
"LabelTimeRemaining": "剩余 {0}",
|
||||||
@@ -585,9 +608,12 @@
|
|||||||
"LabelVersion": "版本",
|
"LabelVersion": "版本",
|
||||||
"LabelViewBookmarks": "查看书签",
|
"LabelViewBookmarks": "查看书签",
|
||||||
"LabelViewChapters": "查看章节",
|
"LabelViewChapters": "查看章节",
|
||||||
|
"LabelViewPlayerSettings": "查看播放器设置",
|
||||||
"LabelViewQueue": "查看播放列表",
|
"LabelViewQueue": "查看播放列表",
|
||||||
"LabelVolume": "音量",
|
"LabelVolume": "音量",
|
||||||
"LabelWeekdaysToRun": "工作日运行",
|
"LabelWeekdaysToRun": "工作日运行",
|
||||||
|
"LabelXBooks": "{0} 本书",
|
||||||
|
"LabelXItems": "{0} 项目",
|
||||||
"LabelYearReviewHide": "隐藏年度回顾",
|
"LabelYearReviewHide": "隐藏年度回顾",
|
||||||
"LabelYearReviewShow": "查看年度回顾",
|
"LabelYearReviewShow": "查看年度回顾",
|
||||||
"LabelYourAudiobookDuration": "你的有声读物持续时间",
|
"LabelYourAudiobookDuration": "你的有声读物持续时间",
|
||||||
@@ -598,6 +624,7 @@
|
|||||||
"MessageAppriseDescription": "要使用此功能,你需要运行一个 <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> 实例或一个可以处理这些相同请求的 API. <br />Apprise API Url 应该是发送通知的完整 URL 路径, 例如: 如果你的 API 实例运行在 <code>http://192.168.1.1:8337</code>, 那么你可以输入 <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "要使用此功能,你需要运行一个 <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> 实例或一个可以处理这些相同请求的 API. <br />Apprise API Url 应该是发送通知的完整 URL 路径, 例如: 如果你的 API 实例运行在 <code>http://192.168.1.1:8337</code>, 那么你可以输入 <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageBackupsDescription": "备份包括用户, 用户进度, 媒体库项目详细信息, 服务器设置和图像, 存储在 <code>/metadata/items</code> & <code>/metadata/authors</code>. 备份不包括存储在你的媒体库文件夹中的任何文件.",
|
"MessageBackupsDescription": "备份包括用户, 用户进度, 媒体库项目详细信息, 服务器设置和图像, 存储在 <code>/metadata/items</code> & <code>/metadata/authors</code>. 备份不包括存储在你的媒体库文件夹中的任何文件.",
|
||||||
"MessageBackupsLocationEditNote": "注意: 更新备份位置不会移动或修改现有备份",
|
"MessageBackupsLocationEditNote": "注意: 更新备份位置不会移动或修改现有备份",
|
||||||
|
"MessageBackupsLocationNoEditNote": "注意: 备份位置是通过环境变量设置的, 不能在此处更改.",
|
||||||
"MessageBackupsLocationPathEmpty": "备份位置路径不能为空",
|
"MessageBackupsLocationPathEmpty": "备份位置路径不能为空",
|
||||||
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
|
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
|
||||||
"MessageBookshelfNoCollections": "你尚未进行任何收藏",
|
"MessageBookshelfNoCollections": "你尚未进行任何收藏",
|
||||||
@@ -644,6 +671,7 @@
|
|||||||
"MessageConfirmSendEbookToDevice": "你确定要发送 {0} 电子书 \"{1}\" 到设备 \"{2}\"?",
|
"MessageConfirmSendEbookToDevice": "你确定要发送 {0} 电子书 \"{1}\" 到设备 \"{2}\"?",
|
||||||
"MessageDownloadingEpisode": "正在下载剧集",
|
"MessageDownloadingEpisode": "正在下载剧集",
|
||||||
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
"MessageDragFilesIntoTrackOrder": "将文件拖动到正确的音轨顺序",
|
||||||
|
"MessageEmbedFailed": "嵌入失败!",
|
||||||
"MessageEmbedFinished": "嵌入完成!",
|
"MessageEmbedFinished": "嵌入完成!",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} 个剧集排队等待下载",
|
"MessageEpisodesQueuedForDownload": "{0} 个剧集排队等待下载",
|
||||||
"MessageEreaderDevices": "为了确保电子书的送达, 你可能需要将上述电子邮件地址添加为下列每台设备的有效发件人.",
|
"MessageEreaderDevices": "为了确保电子书的送达, 你可能需要将上述电子邮件地址添加为下列每台设备的有效发件人.",
|
||||||
@@ -698,6 +726,7 @@
|
|||||||
"MessageNoUpdatesWereNecessary": "无需更新",
|
"MessageNoUpdatesWereNecessary": "无需更新",
|
||||||
"MessageNoUserPlaylists": "你没有播放列表",
|
"MessageNoUserPlaylists": "你没有播放列表",
|
||||||
"MessageNotYetImplemented": "尚未实施",
|
"MessageNotYetImplemented": "尚未实施",
|
||||||
|
"MessageOpmlPreviewNote": "注意: 这是解析的OPML文件的预览. 实际的播客标题将从 RSS 提要中获取.",
|
||||||
"MessageOr": "或",
|
"MessageOr": "或",
|
||||||
"MessagePauseChapter": "暂停章节播放",
|
"MessagePauseChapter": "暂停章节播放",
|
||||||
"MessagePlayChapter": "开始章节播放",
|
"MessagePlayChapter": "开始章节播放",
|
||||||
@@ -716,6 +745,9 @@
|
|||||||
"MessageSelected": "{0} 已选择",
|
"MessageSelected": "{0} 已选择",
|
||||||
"MessageServerCouldNotBeReached": "无法访问服务器",
|
"MessageServerCouldNotBeReached": "无法访问服务器",
|
||||||
"MessageSetChaptersFromTracksDescription": "把每个音频文件设置为章节并将章节标题设置为音频文件名",
|
"MessageSetChaptersFromTracksDescription": "把每个音频文件设置为章节并将章节标题设置为音频文件名",
|
||||||
|
"MessageShareExpirationWillBe": "到期日期为 <strong>{0}</strong>",
|
||||||
|
"MessageShareExpiresIn": "到期时间 {0}",
|
||||||
|
"MessageShareURLWillBe": "分享网址是 <strong>{0}</strong>",
|
||||||
"MessageStartPlaybackAtTime": "开始播放 \"{0}\" 在 {1}?",
|
"MessageStartPlaybackAtTime": "开始播放 \"{0}\" 在 {1}?",
|
||||||
"MessageThinking": "正在查找...",
|
"MessageThinking": "正在查找...",
|
||||||
"MessageUploaderItemFailed": "上传失败",
|
"MessageUploaderItemFailed": "上传失败",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.12.0",
|
"version": "2.12.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.12.0",
|
"version": "2.12.3",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.12.0",
|
"version": "2.12.3",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
+4
-5
@@ -207,7 +207,6 @@ class Database {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.sequelize.authenticate()
|
await this.sequelize.authenticate()
|
||||||
await this.loadExtensions([process.env.SQLEAN_UNICODE_PATH])
|
|
||||||
Logger.info(`[Database] Db connection was successful`)
|
Logger.info(`[Database] Db connection was successful`)
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -217,7 +216,7 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* TODO: Temporarily disabled
|
||||||
* @param {string[]} extensions paths to extension binaries
|
* @param {string[]} extensions paths to extension binaries
|
||||||
*/
|
*/
|
||||||
async loadExtensions(extensions) {
|
async loadExtensions(extensions) {
|
||||||
@@ -827,7 +826,7 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* TODO: Temporarily unused
|
||||||
* @param {string} value
|
* @param {string} value
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
@@ -836,7 +835,7 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* TODO: Temporarily unused
|
||||||
* @param {string} query
|
* @param {string} query
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
@@ -855,7 +854,7 @@ class Database {
|
|||||||
*/
|
*/
|
||||||
matchExpression(column, normalizedQuery) {
|
matchExpression(column, normalizedQuery) {
|
||||||
const normalizedPattern = this.sequelize.escape(`%${normalizedQuery}%`)
|
const normalizedPattern = this.sequelize.escape(`%${normalizedQuery}%`)
|
||||||
const normalizedColumn = this.normalize(column)
|
const normalizedColumn = column
|
||||||
return `${normalizedColumn} LIKE ${normalizedPattern}`
|
return `${normalizedColumn} LIKE ${normalizedPattern}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-7
@@ -41,7 +41,6 @@ const LibraryScanner = require('./scanner/LibraryScanner')
|
|||||||
//Import the main Passport and Express-Session library
|
//Import the main Passport and Express-Session library
|
||||||
const passport = require('passport')
|
const passport = require('passport')
|
||||||
const expressSession = require('express-session')
|
const expressSession = require('express-session')
|
||||||
const MemoryStore = require('./libs/memorystore')(expressSession)
|
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
constructor(SOURCE, PORT, HOST, CONFIG_PATH, METADATA_PATH, ROUTER_BASE_PATH) {
|
constructor(SOURCE, PORT, HOST, CONFIG_PATH, METADATA_PATH, ROUTER_BASE_PATH) {
|
||||||
@@ -219,12 +218,7 @@ class Server {
|
|||||||
cookie: {
|
cookie: {
|
||||||
// also send the cookie if were are not on https (not every use has https)
|
// also send the cookie if were are not on https (not every use has https)
|
||||||
secure: false
|
secure: false
|
||||||
},
|
}
|
||||||
store: new MemoryStore({
|
|
||||||
checkPeriod: 86400000, // prune expired entries every 24h
|
|
||||||
ttl: 86400000, // 24h
|
|
||||||
max: 1000
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
// init passport.js
|
// init passport.js
|
||||||
|
|||||||
@@ -113,21 +113,21 @@ class LibraryItemController {
|
|||||||
Logger.warn('User attempted to download without permission', req.user)
|
Logger.warn('User attempted to download without permission', req.user)
|
||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
}
|
}
|
||||||
|
const libraryItemPath = req.libraryItem.path
|
||||||
|
const itemTitle = req.libraryItem.media.metadata.title
|
||||||
|
|
||||||
// If library item is a single file in root dir then no need to zip
|
// If library item is a single file in root dir then no need to zip
|
||||||
if (req.libraryItem.isFile) {
|
if (req.libraryItem.isFile) {
|
||||||
// Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
|
// Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
|
||||||
const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(req.libraryItem.path))
|
const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(libraryItemPath))
|
||||||
if (audioMimeType) {
|
if (audioMimeType) {
|
||||||
res.setHeader('Content-Type', audioMimeType)
|
res.setHeader('Content-Type', audioMimeType)
|
||||||
}
|
}
|
||||||
|
Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
|
||||||
res.download(req.libraryItem.path, req.libraryItem.relPath)
|
res.download(libraryItemPath, req.libraryItem.relPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const libraryItemPath = req.libraryItem.path
|
|
||||||
const itemTitle = req.libraryItem.media.metadata.title
|
|
||||||
Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
|
Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${itemTitle}" at "${libraryItemPath}"`)
|
||||||
const filename = `${itemTitle}.zip`
|
const filename = `${itemTitle}.zip`
|
||||||
zipHelpers.zipDirectoryPipe(libraryItemPath, filename, res)
|
zipHelpers.zipDirectoryPipe(libraryItemPath, filename, res)
|
||||||
@@ -715,7 +715,7 @@ class LibraryItemController {
|
|||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.info(`[LibraryItemController] User "${req.user.username}" requested file download at "${libraryFile.metadata.path}"`)
|
Logger.info(`[LibraryItemController] User "${req.user.username}" requested download for item "${req.libraryItem.media.metadata.title}" file at "${libraryFile.metadata.path}"`)
|
||||||
|
|
||||||
if (global.XAccel) {
|
if (global.XAccel) {
|
||||||
const encodedURI = encodeUriPath(global.XAccel + libraryFile.metadata.path)
|
const encodedURI = encodeUriPath(global.XAccel + libraryFile.metadata.path)
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ const Logger = require('../Logger')
|
|||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
|
|
||||||
class ApiCacheManager {
|
class ApiCacheManager {
|
||||||
|
defaultCacheOptions = { max: 1000, maxSize: 10 * 1000 * 1000, sizeCalculation: (item) => item.body.length + JSON.stringify(item.headers).length }
|
||||||
defaultCacheOptions = { max: 1000, maxSize: 10 * 1000 * 1000, sizeCalculation: item => (item.body.length + JSON.stringify(item.headers).length) }
|
|
||||||
defaultTtlOptions = { ttl: 30 * 60 * 1000 }
|
defaultTtlOptions = { ttl: 30 * 60 * 1000 }
|
||||||
|
|
||||||
constructor(cache = new LRUCache(this.defaultCacheOptions), ttlOptions = this.defaultTtlOptions) {
|
constructor(cache = new LRUCache(this.defaultCacheOptions), ttlOptions = this.defaultTtlOptions) {
|
||||||
@@ -14,7 +13,7 @@ class ApiCacheManager {
|
|||||||
|
|
||||||
init(database = Database) {
|
init(database = Database) {
|
||||||
let hooks = ['afterCreate', 'afterUpdate', 'afterDestroy', 'afterBulkCreate', 'afterBulkUpdate', 'afterBulkDestroy', 'afterUpsert']
|
let hooks = ['afterCreate', 'afterUpdate', 'afterDestroy', 'afterBulkCreate', 'afterBulkUpdate', 'afterBulkDestroy', 'afterUpsert']
|
||||||
hooks.forEach(hook => database.sequelize.addHook(hook, (model) => this.clear(model, hook)))
|
hooks.forEach((hook) => database.sequelize.addHook(hook, (model) => this.clear(model, hook)))
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(model, hook) {
|
clear(model, hook) {
|
||||||
@@ -33,7 +32,16 @@ class ApiCacheManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get middleware() {
|
get middleware() {
|
||||||
|
/**
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
|
if (req.query.sort === 'random') {
|
||||||
|
Logger.debug(`[ApiCacheManager] Skipping cache for random sort`)
|
||||||
|
return next()
|
||||||
|
}
|
||||||
const key = { user: req.user.username, url: req.url }
|
const key = { user: req.user.username, url: req.url }
|
||||||
const stringifiedKey = JSON.stringify(key)
|
const stringifiedKey = JSON.stringify(key)
|
||||||
Logger.debug(`[ApiCacheManager] count: ${this.cache.size} size: ${this.cache.calculatedSize}`)
|
Logger.debug(`[ApiCacheManager] count: ${this.cache.size} size: ${this.cache.calculatedSize}`)
|
||||||
|
|||||||
@@ -263,8 +263,9 @@ module.exports.sqlean = sqlean // for testing
|
|||||||
class BinaryManager {
|
class BinaryManager {
|
||||||
defaultRequiredBinaries = [
|
defaultRequiredBinaries = [
|
||||||
new Binary('ffmpeg', 'executable', 'FFMPEG_PATH', ['5.1'], ffbinaries), // ffmpeg executable
|
new Binary('ffmpeg', 'executable', 'FFMPEG_PATH', ['5.1'], ffbinaries), // ffmpeg executable
|
||||||
new Binary('ffprobe', 'executable', 'FFPROBE_PATH', ['5.1'], ffbinaries), // ffprobe executable
|
new Binary('ffprobe', 'executable', 'FFPROBE_PATH', ['5.1'], ffbinaries) // ffprobe executable
|
||||||
new Binary('unicode', 'library', 'SQLEAN_UNICODE_PATH', ['0.24.2'], sqlean) // sqlean unicode extension
|
// TODO: Temporarily disabled due to db corruption issues
|
||||||
|
// new Binary('unicode', 'library', 'SQLEAN_UNICODE_PATH', ['0.24.2'], sqlean) // sqlean unicode extension
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor(requiredBinaries = this.defaultRequiredBinaries) {
|
constructor(requiredBinaries = this.defaultRequiredBinaries) {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
responseType: 'stream',
|
responseType: 'stream',
|
||||||
headers: {
|
headers: {
|
||||||
'User-Agent': 'audiobookshelf (+https://audiobookshelf.org; like iTMS)'
|
'User-Agent': 'audiobookshelf (+https://audiobookshelf.org)'
|
||||||
},
|
},
|
||||||
timeout: 30000
|
timeout: 30000
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const { AudioMimeType } = require('./constants')
|
|||||||
*/
|
*/
|
||||||
const filePathToPOSIX = (path) => {
|
const filePathToPOSIX = (path) => {
|
||||||
if (!global.isWin || !path) return path
|
if (!global.isWin || !path) return path
|
||||||
return path.replace(/\\/g, '/')
|
return path.startsWith('\\\\') ? '\\\\' + path.slice(2).replace(/\\/g, '/') : path.replace(/\\/g, '/')
|
||||||
}
|
}
|
||||||
module.exports.filePathToPOSIX = filePathToPOSIX
|
module.exports.filePathToPOSIX = filePathToPOSIX
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ async function recurseFiles(path, relPathToReplace = null) {
|
|||||||
extensions: true,
|
extensions: true,
|
||||||
deep: true,
|
deep: true,
|
||||||
realPath: true,
|
realPath: true,
|
||||||
normalizePath: true
|
normalizePath: false
|
||||||
}
|
}
|
||||||
let list = await rra.list(path, options)
|
let list = await rra.list(path, options)
|
||||||
if (list.error) {
|
if (list.error) {
|
||||||
@@ -186,6 +186,8 @@ async function recurseFiles(path, relPathToReplace = null) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.fullname = filePathToPOSIX(item.fullname)
|
||||||
|
item.path = filePathToPOSIX(item.path)
|
||||||
const relpath = item.fullname.replace(relPathToReplace, '')
|
const relpath = item.fullname.replace(relPathToReplace, '')
|
||||||
let reldirname = Path.dirname(relpath)
|
let reldirname = Path.dirname(relpath)
|
||||||
if (reldirname === '.') reldirname = ''
|
if (reldirname === '.') reldirname = ''
|
||||||
|
|||||||
@@ -975,7 +975,7 @@ module.exports = {
|
|||||||
async search(oldUser, oldLibrary, query, limit, offset) {
|
async search(oldUser, oldLibrary, query, limit, offset) {
|
||||||
const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(oldUser)
|
const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(oldUser)
|
||||||
|
|
||||||
const normalizedQuery = await Database.getNormalizedQuery(query)
|
const normalizedQuery = query
|
||||||
|
|
||||||
const matchTitle = Database.matchExpression('title', normalizedQuery)
|
const matchTitle = Database.matchExpression('title', normalizedQuery)
|
||||||
const matchSubtitle = Database.matchExpression('subtitle', normalizedQuery)
|
const matchSubtitle = Database.matchExpression('subtitle', normalizedQuery)
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ module.exports = {
|
|||||||
async search(oldUser, oldLibrary, query, limit, offset) {
|
async search(oldUser, oldLibrary, query, limit, offset) {
|
||||||
const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(oldUser)
|
const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(oldUser)
|
||||||
|
|
||||||
const normalizedQuery = await Database.getNormalizedQuery(query)
|
const normalizedQuery = query
|
||||||
const matchTitle = Database.matchExpression('title', normalizedQuery)
|
const matchTitle = Database.matchExpression('title', normalizedQuery)
|
||||||
const matchAuthor = Database.matchExpression('author', normalizedQuery)
|
const matchAuthor = Database.matchExpression('author', normalizedQuery)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe('ApiCacheManager', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cache = { get: sinon.stub(), set: sinon.spy() }
|
cache = { get: sinon.stub(), set: sinon.spy() }
|
||||||
req = { user: { username: 'testUser' }, url: '/test-url' }
|
req = { user: { username: 'testUser' }, url: '/test-url', query: {} }
|
||||||
res = { send: sinon.spy(), getHeaders: sinon.stub(), statusCode: 200, status: sinon.spy(), set: sinon.spy() }
|
res = { send: sinon.spy(), getHeaders: sinon.stub(), statusCode: 200, status: sinon.spy(), set: sinon.spy() }
|
||||||
next = sinon.spy()
|
next = sinon.spy()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user