Merge remote-tracking branch 'remotes/upstream/master'

This commit is contained in:
Toni Barth 2024-06-06 17:33:27 +02:00
commit fbed92dacf
68 changed files with 6264 additions and 1208 deletions

View file

@ -11,7 +11,7 @@
</div> </div>
</div> </div>
<div v-else-if="loaded && !shelves.length && search" class="w-full h-40 flex items-center justify-center"> <div v-else-if="loaded && !shelves.length && search" class="w-full h-40 flex items-center justify-center">
<p class="text-center text-xl py-4">No results for query</p> <p class="text-center text-xl py-4">{{ $strings.MessageBookshelfNoResultsForQuery }}</p>
</div> </div>
<!-- Alternate plain view --> <!-- Alternate plain view -->
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24"> <div v-else-if="isAlternativeBookshelfView" class="w-full mb-24">

View file

@ -37,12 +37,12 @@
<span class="material-icons text-2xl">arrow_left</span> <span class="material-icons text-2xl">arrow_left</span>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="font-normal block truncate">Back</span> <span class="font-normal block truncate">{{ $strings.ButtonBack }}</span>
</div> </div>
</li> </li>
<li v-if="!sublistItems.length" class="text-gray-400 select-none relative px-2" role="option"> <li v-if="!sublistItems.length" class="text-gray-400 select-none relative px-2" role="option">
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<span class="font-normal block truncate py-2">No {{ sublist }}</span> <span class="font-normal block truncate py-2">{{ $getString('LabelLibraryFilterSublistEmpty', [selectedSublistText]) }}</span>
</div> </div>
</li> </li>
<template v-for="item in sublistItems"> <template v-for="item in sublistItems">
@ -106,31 +106,37 @@ export default {
}, },
{ {
text: this.$strings.LabelGenre, text: this.$strings.LabelGenre,
textPlural: this.$strings.LabelGenres,
value: 'genres', value: 'genres',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelTag, text: this.$strings.LabelTag,
textPlural: this.$strings.LabelTags,
value: 'tags', value: 'tags',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelAuthor, text: this.$strings.LabelAuthor,
textPlural: this.$strings.LabelAuthors,
value: 'authors', value: 'authors',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelNarrator, text: this.$strings.LabelNarrator,
textPlural: this.$strings.LabelNarrators,
value: 'narrators', value: 'narrators',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelPublisher, text: this.$strings.LabelPublisher,
textPlural: this.$strings.LabelPublishers,
value: 'publishers', value: 'publishers',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelLanguage, text: this.$strings.LabelLanguage,
textPlural: this.$strings.LabelLanguages,
value: 'languages', value: 'languages',
sublist: true sublist: true
}, },
@ -149,36 +155,43 @@ export default {
}, },
{ {
text: this.$strings.LabelGenre, text: this.$strings.LabelGenre,
textPlural: this.$strings.LabelGenres,
value: 'genres', value: 'genres',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelTag, text: this.$strings.LabelTag,
textPlural: this.$strings.LabelTags,
value: 'tags', value: 'tags',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelSeries, text: this.$strings.LabelSeries,
textPlural: this.$strings.LabelSeries,
value: 'series', value: 'series',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelAuthor, text: this.$strings.LabelAuthor,
textPlural: this.$strings.LabelAuthors,
value: 'authors', value: 'authors',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelNarrator, text: this.$strings.LabelNarrator,
textPlural: this.$strings.LabelNarrators,
value: 'narrators', value: 'narrators',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelPublisher, text: this.$strings.LabelPublisher,
textPlural: this.$strings.LabelPublishers,
value: 'publishers', value: 'publishers',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelLanguage, text: this.$strings.LabelLanguage,
textPlural: this.$strings.LabelLanguages,
value: 'languages', value: 'languages',
sublist: true sublist: true
}, },
@ -227,16 +240,19 @@ export default {
}, },
{ {
text: this.$strings.LabelGenre, text: this.$strings.LabelGenre,
textPlural: this.$strings.LabelGenres,
value: 'genres', value: 'genres',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelTag, text: this.$strings.LabelTag,
textPlural: this.$strings.LabelTags,
value: 'tags', value: 'tags',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelLanguage, text: this.$strings.LabelLanguage,
textPlural: this.$strings.LabelLanguages,
value: 'languages', value: 'languages',
sublist: true sublist: true
}, },
@ -255,11 +271,13 @@ export default {
}, },
{ {
text: this.$strings.LabelGenre, text: this.$strings.LabelGenre,
textPlural: this.$strings.LabelGenres,
value: 'genres', value: 'genres',
sublist: true sublist: true
}, },
{ {
text: this.$strings.LabelTag, text: this.$strings.LabelTag,
textPlural: this.$strings.LabelTags,
value: 'tags', value: 'tags',
sublist: true sublist: true
}, },
@ -279,6 +297,13 @@ export default {
selectedItemSublist() { selectedItemSublist() {
return this.selected?.includes('.') ? this.selected.split('.')[0] : null return this.selected?.includes('.') ? this.selected.split('.')[0] : null
}, },
selectedSublistText() {
if (!this.sublist) {
return ''
}
const sublistItem = this.selectItems.find((i) => i.value === this.sublist)
return sublistItem?.textPlural || sublistItem?.text || ''
},
selectedText() { selectedText() {
if (!this.selected) return '' if (!this.selected) return ''
const parts = this.selected.split('.') const parts = this.selected.split('.')
@ -505,4 +530,4 @@ export default {
.libraryFilterMenu { .libraryFilterMenu {
max-height: calc(100vh - 125px); max-height: calc(100vh - 125px);
} }
</style> </style>

View file

@ -53,7 +53,7 @@
<p class="font-mono text-sm hidden sm:block text-gray-100 pointer-events-auto">&nbsp;/&nbsp;{{ progressPercent }}%</p> <p class="font-mono text-sm hidden sm:block text-gray-100 pointer-events-auto">&nbsp;/&nbsp;{{ progressPercent }}%</p>
<div class="flex-grow" /> <div class="flex-grow" />
<p class="text-xs sm:text-sm text-gray-300 pt-0.5"> <p class="text-xs sm:text-sm text-gray-300 pt-0.5">
{{ currentChapterName }} <span v-if="useChapterTrack" class="text-xs text-gray-400">&nbsp;({{ currentChapterIndex + 1 }} of {{ chapters.length }})</span> {{ currentChapterName }} <span v-if="useChapterTrack" class="text-xs text-gray-400">&nbsp;({{ $getString('LabelPlayerChapterNumberMarker', [currentChapterIndex + 1, chapters.length]) }})</span>
</p> </p>
<div class="flex-grow" /> <div class="flex-grow" />
<p class="font-mono text-xxs sm:text-sm text-gray-100 pointer-events-auto">{{ timeRemainingPretty }}</p> <p class="font-mono text-xxs sm:text-sm text-gray-100 pointer-events-auto">{{ timeRemainingPretty }}</p>

View file

@ -3,7 +3,7 @@
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" /> <div class="absolute top-0 left-0 right-0 w-full h-36 bg-gradient-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
<div ref="content" class="relative text-white" :style="{ height: modalHeight, width: modalWidth }" v-click-outside="clickedOutside"> <div ref="content" class="relative text-white" :style="{ height: modalHeight, width: modalWidth }" v-click-outside="clickedOutside">
<div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300"> <div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300">
<p class="text-lg mb-6 mt-2 px-1" v-html="message" /> <p id="confirm-prompt-message" class="text-lg mb-6 mt-2 px-1" v-html="message" />
<ui-checkbox v-if="checkboxLabel" v-model="checkboxValue" checkbox-bg="bg" :label="checkboxLabel" label-class="pl-2 text-base" class="mb-6 px-1" /> <ui-checkbox v-if="checkboxLabel" v-model="checkboxValue" checkbox-bg="bg" :label="checkboxLabel" label-class="pl-2 text-base" class="mb-6 px-1" />
@ -131,4 +131,14 @@ export default {
} }
} }
} }
</script> </script>
<style>
#confirm-prompt-message code {
font-size: 1rem;
border-radius: 6px;
background-color: rgb(82, 82, 82);
color: white;
padding: 2px 4px;
}
</style>

View file

@ -179,7 +179,7 @@ export default {
ebookLocation: this.page, ebookLocation: this.page,
ebookProgress: Math.max(0, Math.min(1, (Number(this.page) - 1) / Number(this.numPages))) ebookProgress: Math.max(0, Math.min(1, (Number(this.page) - 1) / Number(this.numPages)))
} }
this.$axios.$patch(`/api/me/progress/${this.libraryItemId}`, payload).catch((error) => { this.$axios.$patch(`/api/me/progress/${this.libraryItemId}`, payload, { progress: false }).catch((error) => {
console.error('ComicReader.updateProgress failed:', error) console.error('ComicReader.updateProgress failed:', error)
}) })
}, },
@ -386,4 +386,4 @@ export default {
.pagemenu { .pagemenu {
max-height: calc(100% - 48px); max-height: calc(100% - 48px);
} }
</style> </style>

View file

@ -195,7 +195,7 @@ export default {
*/ */
updateProgress(payload) { updateProgress(payload) {
if (!this.keepProgress) return if (!this.keepProgress) return
this.$axios.$patch(`/api/me/progress/${this.libraryItemId}`, payload).catch((error) => { this.$axios.$patch(`/api/me/progress/${this.libraryItemId}`, payload, { progress: false }).catch((error) => {
console.error('EpubReader.updateProgress failed:', error) console.error('EpubReader.updateProgress failed:', error)
}) })
}, },

View file

@ -23,13 +23,10 @@
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<div :style="{ width: pdfWidth + 'px', height: pdfHeight + 'px' }" class="overflow-auto"> <div :style="{ width: pdfWidth + 'px', height: pdfHeight + 'px' }" class="overflow-auto">
<div v-if="loadedRatio > 0 && loadedRatio < 1" style="background-color: green; color: white; text-align: center" :style="{ width: loadedRatio * 100 + '%' }">{{ Math.floor(loadedRatio * 100) }}%</div> <div v-if="loadedRatio > 0 && loadedRatio < 1" style="background-color: green; color: white; text-align: center" :style="{ width: loadedRatio * 100 + '%' }">{{ Math.floor(loadedRatio * 100) }}%</div>
<pdf ref="pdf" class="m-auto z-10 border border-black border-opacity-20 shadow-md" :src="pdfDocInitParams" :page="page" :rotate="rotate" @progress="progressEvt" @error="error" @num-pages="numPagesLoaded" @link-clicked="page = $event" @loaded="loadedEvt"></pdf> <pdf v-if="pdfDocInitParams" ref="pdf" class="m-auto z-10 border border-black border-opacity-20 shadow-md" :src="pdfDocInitParams" :page="page" :rotate="rotate" @progress="progressEvt" @error="error" @num-pages="numPagesLoaded" @link-clicked="page = $event" @loaded="loadedEvt"></pdf>
</div> </div>
</div> </div>
</div> </div>
<!-- <div class="text-center py-2 text-lg">
<p>{{ page }} / {{ numPages }}</p>
</div> -->
</div> </div>
</template> </template>
@ -57,7 +54,8 @@ export default {
rotate: 0, rotate: 0,
loadedRatio: 0, loadedRatio: 0,
page: 1, page: 1,
numPages: 0 numPages: 0,
pdfDocInitParams: null
} }
}, },
computed: { computed: {
@ -108,14 +106,6 @@ export default {
return `/api/items/${this.libraryItemId}/ebook/${this.fileId}` return `/api/items/${this.libraryItemId}/ebook/${this.fileId}`
} }
return `/api/items/${this.libraryItemId}/ebook` return `/api/items/${this.libraryItemId}/ebook`
},
pdfDocInitParams() {
return {
url: this.ebookUrl,
httpHeaders: {
Authorization: `Bearer ${this.userToken}`
}
}
} }
}, },
methods: { methods: {
@ -136,7 +126,7 @@ export default {
ebookLocation: this.page, ebookLocation: this.page,
ebookProgress: Math.max(0, Math.min(1, (Number(this.page) - 1) / Number(this.numPages))) ebookProgress: Math.max(0, Math.min(1, (Number(this.page) - 1) / Number(this.numPages)))
} }
this.$axios.$patch(`/api/me/progress/${this.libraryItemId}`, payload).catch((error) => { this.$axios.$patch(`/api/me/progress/${this.libraryItemId}`, payload, { progress: false }).catch((error) => {
console.error('EpubReader.updateProgress failed:', error) console.error('EpubReader.updateProgress failed:', error)
}) })
}, },
@ -149,6 +139,7 @@ export default {
this.loadedRatio = progress this.loadedRatio = progress
}, },
numPagesLoaded(e) { numPagesLoaded(e) {
if (!e) return
this.numPages = e this.numPages = e
}, },
prev() { prev() {
@ -167,15 +158,25 @@ export default {
resize() { resize() {
this.windowWidth = window.innerWidth this.windowWidth = window.innerWidth
this.windowHeight = window.innerHeight this.windowHeight = window.innerHeight
},
init() {
this.pdfDocInitParams = {
url: this.ebookUrl,
httpHeaders: {
Authorization: `Bearer ${this.userToken}`
}
}
} }
}, },
mounted() { mounted() {
this.windowWidth = window.innerWidth this.windowWidth = window.innerWidth
this.windowHeight = window.innerHeight this.windowHeight = window.innerHeight
window.addEventListener('resize', this.resize) window.addEventListener('resize', this.resize)
this.init()
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('resize', this.resize) window.removeEventListener('resize', this.resize)
} }
} }
</script> </script>

View file

@ -21,7 +21,7 @@
</tr> </tr>
</table> </table>
<div v-else-if="!processing" class="text-center py-8"> <div v-else-if="!processing" class="text-center py-8">
<p class="text-lg">No custom metadata providers</p> <p class="text-lg">{{ $strings.LabelNoCustomMetadataProviders }}</p>
</div> </div>
<div v-if="processing" class="absolute inset-0 h-full flex items-center justify-center bg-black/40 rounded-md"> <div v-if="processing" class="absolute inset-0 h-full flex items-center justify-center bg-black/40 rounded-md">

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@
<div class="w-full border border-white/10 rounded-xl p-4 my-4 bg-primary/25"> <div class="w-full border border-white/10 rounded-xl p-4 my-4 bg-primary/25">
<div class="flex items-center"> <div class="flex items-center">
<ui-checkbox v-model="showCustomLoginMessage" checkbox-bg="bg" /> <ui-checkbox v-model="showCustomLoginMessage" checkbox-bg="bg" />
<p class="text-lg pl-4">Custom Message on Login</p> <p class="text-lg pl-4">{{ $strings.HeaderCustomMessageOnLogin }}</p>
</div> </div>
<transition name="slide"> <transition name="slide">
<div v-if="showCustomLoginMessage" class="w-full pt-4"> <div v-if="showCustomLoginMessage" class="w-full pt-4">

View file

@ -20,13 +20,30 @@
</div> </div>
<div class="flex items-center mb-2 py-3"> <div class="flex items-center mb-2 py-3">
<ui-toggle-switch labeledBy="email-settings-secure" v-model="newSettings.secure" :disabled="savingSettings" /> <div class="w-full md:w-1/2 px-1">
<ui-tooltip :text="$strings.LabelEmailSettingsSecureHelp"> <!-- secure toggle -->
<div class="pl-4 flex items-center"> <div class="flex items-center">
<span id="email-settings-secure">{{ $strings.LabelEmailSettingsSecure }}</span> <ui-toggle-switch labeledBy="email-settings-secure" v-model="newSettings.secure" :disabled="savingSettings" />
<span class="material-icons text-lg pl-1">info_outlined</span> <ui-tooltip :text="$strings.LabelEmailSettingsSecureHelp">
<div class="pl-4 flex items-center">
<span id="email-settings-secure">{{ $strings.LabelEmailSettingsSecure }}</span>
<span class="material-icons text-lg pl-1">info_outlined</span>
</div>
</ui-tooltip>
</div> </div>
</ui-tooltip> </div>
<div class="w-full md:w-1/2 px-1">
<!-- reject unauthorized toggle -->
<div class="flex items-center">
<ui-toggle-switch labeledBy="email-settings-reject-unauthorized" v-model="newSettings.rejectUnauthorized" :disabled="savingSettings" />
<ui-tooltip :text="$strings.LabelEmailSettingsRejectUnauthorizedHelp">
<div class="pl-4 flex items-center">
<span id="email-settings-reject-unauthorized">{{ $strings.LabelEmailSettingsRejectUnauthorized }}</span>
<span class="material-icons text-lg pl-1">info_outlined</span>
</div>
</ui-tooltip>
</div>
</div>
</div> </div>
<div class="flex items-center -mx-1 mb-2"> <div class="flex items-center -mx-1 mb-2">
@ -119,6 +136,7 @@ export default {
host: null, host: null,
port: 465, port: 465,
secure: true, secure: true,
rejectUnauthorized: true,
user: null, user: null,
pass: null, pass: null,
testAddress: null, testAddress: null,
@ -257,6 +275,7 @@ export default {
host: this.newSettings.host, host: this.newSettings.host,
port: this.newSettings.port, port: this.newSettings.port,
secure: this.newSettings.secure, secure: this.newSettings.secure,
rejectUnauthorized: this.newSettings.rejectUnauthorized,
user: this.newSettings.user, user: this.newSettings.user,
pass: this.newSettings.pass, pass: this.newSettings.pass,
testAddress: this.newSettings.testAddress, testAddress: this.newSettings.testAddress,

View file

@ -368,7 +368,8 @@ export default {
}, },
purgeItemsCache() { purgeItemsCache() {
const payload = { const payload = {
message: `<span class="text-warning text-base">Warning! This will delete the entire folder at /metadata/cache/items.</span><br />Are you sure you want to purge items cache?`, // message: `This will delete the entire folder at <code>/metadata/cache/items</code>.<br />Are you sure you want to purge items cache?`,
message: this.$strings.MessageConfirmPurgeItemsCache,
callback: (confirmed) => { callback: (confirmed) => {
if (confirmed) { if (confirmed) {
this.sendPurgeItemsCache() this.sendPurgeItemsCache()

View file

@ -29,4 +29,4 @@ export default class AudioTrack {
return this.contentUrl + `?token=${this.userToken}` return this.contentUrl + `?token=${this.userToken}`
} }
} }

View file

@ -1,13 +1,22 @@
function getMediaInfoFromTrack(libraryItem, castImage, track) { function getMediaInfoFromTrack(libraryItem, castImage, track) {
// https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.media.AudiobookChapterMediaMetadata let metadata = null
var metadata = new chrome.cast.media.AudiobookChapterMediaMetadata() if (libraryItem.mediaType === 'podcast') {
metadata.bookTitle = libraryItem.media.metadata.title metadata = new chrome.cast.media.MusicTrackMediaMetadata()
metadata.chapterNumber = track.index metadata.albumArtist = libraryItem.media.metadata.author
metadata.chapterTitle = track.title metadata.artist = libraryItem.media.metadata.author
metadata.images = [castImage] metadata.title = track.title
metadata.title = track.title metadata.albumName = libraryItem.media.metadata.title
metadata.subtitle = libraryItem.media.metadata.title metadata.images = [castImage]
} else {
// https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.media.AudiobookChapterMediaMetadata
metadata = new chrome.cast.media.AudiobookChapterMediaMetadata()
metadata.bookTitle = libraryItem.media.metadata.title
metadata.chapterNumber = track.index
metadata.chapterTitle = track.title
metadata.images = [castImage]
metadata.title = track.title
metadata.subtitle = libraryItem.media.metadata.title
}
var trackurl = track.fullContentUrl var trackurl = track.fullContentUrl
var mimeType = track.mimeType var mimeType = track.mimeType
@ -20,17 +29,25 @@ function getMediaInfoFromTrack(libraryItem, castImage, track) {
function buildCastMediaInfo(libraryItem, coverUrl, tracks) { function buildCastMediaInfo(libraryItem, coverUrl, tracks) {
const castImage = new chrome.cast.Image(coverUrl) const castImage = new chrome.cast.Image(coverUrl)
return tracks.map(t => getMediaInfoFromTrack(libraryItem, castImage, t)) return tracks.map((t) => getMediaInfoFromTrack(libraryItem, castImage, t))
} }
function buildCastQueueRequest(libraryItem, coverUrl, tracks, startTime) { function buildCastQueueRequest(libraryItem, coverUrl, tracks, startTime) {
var mediaInfoItems = buildCastMediaInfo(libraryItem, coverUrl, tracks) var mediaInfoItems = buildCastMediaInfo(libraryItem, coverUrl, tracks)
var containerMetadata = new chrome.cast.media.AudiobookContainerMetadata() let containerMetadata = null
containerMetadata.authors = libraryItem.media.metadata.authors.map(a => a.name) let queueType = chrome.cast.media.QueueType.AUDIOBOOK
containerMetadata.narrators = libraryItem.media.metadata.narrators || [] if (libraryItem.mediaType === 'podcast') {
containerMetadata.publisher = libraryItem.media.metadata.publisher || undefined queueType = chrome.cast.media.QueueType.PODCAST_SERIES
containerMetadata.title = libraryItem.media.metadata.title containerMetadata = new chrome.cast.media.ContainerMetadata(chrome.cast.media.ContainerType.GENERIC_CONTAINER)
containerMetadata.title = libraryItem.media.metadata.title
} else {
containerMetadata = new chrome.cast.media.AudiobookContainerMetadata()
containerMetadata.authors = libraryItem.media.metadata.authors?.map((a) => a.name)
containerMetadata.narrators = libraryItem.media.metadata.narrators || []
containerMetadata.publisher = libraryItem.media.metadata.publisher || undefined
containerMetadata.title = libraryItem.media.metadata.title
}
var mediaQueueItems = mediaInfoItems.map((mi) => { var mediaQueueItems = mediaInfoItems.map((mi) => {
var queueItem = new chrome.cast.media.QueueItem(mi) var queueItem = new chrome.cast.media.QueueItem(mi)
@ -38,23 +55,25 @@ function buildCastQueueRequest(libraryItem, coverUrl, tracks, startTime) {
}) })
// Find track to start playback and calculate track start offset // Find track to start playback and calculate track start offset
var track = tracks.find(at => at.startOffset <= startTime && at.startOffset + at.duration > startTime) var track = tracks.find((at) => at.startOffset <= startTime && at.startOffset + at.duration > startTime)
var trackStartIndex = track ? track.index - 1 : 0 var trackStartIndex = track ? track.index - 1 : 0
var trackStartTime = Math.floor(track ? startTime - track.startOffset : 0) var trackStartTime = Math.floor(track ? startTime - track.startOffset : 0)
var queueData = new chrome.cast.media.QueueData(libraryItem.id, libraryItem.media.metadata.title, '', false, mediaQueueItems, trackStartIndex, trackStartTime) var queueData = new chrome.cast.media.QueueData(libraryItem.id, libraryItem.media.metadata.title, '', false, mediaQueueItems, trackStartIndex, trackStartTime)
queueData.containerMetadata = containerMetadata queueData.containerMetadata = containerMetadata
queueData.queueType = chrome.cast.media.QueueType.AUDIOBOOK queueData.queueType = queueType
return queueData return queueData
} }
function castLoadMedia(castSession, request) { function castLoadMedia(castSession, request) {
return new Promise((resolve) => { return new Promise((resolve) => {
castSession.loadMedia(request) castSession.loadMedia(request).then(
.then(() => resolve(true), (reason) => { () => resolve(true),
(reason) => {
console.error('Load media failed', reason) console.error('Load media failed', reason)
resolve(false) resolve(false)
}) }
)
}) })
} }
@ -69,7 +88,4 @@ function buildCastLoadRequest(libraryItem, coverUrl, tracks, startTime, autoplay
return request return request
} }
export { export { buildCastLoadRequest, castLoadMedia }
buildCastLoadRequest,
castLoadMedia
}

View file

@ -9,6 +9,7 @@
"ButtonApply": "Приложи", "ButtonApply": "Приложи",
"ButtonApplyChapters": "Приложи Глави", "ButtonApplyChapters": "Приложи Глави",
"ButtonAuthors": "Автори", "ButtonAuthors": "Автори",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Прегледай за папка", "ButtonBrowseForFolder": "Прегледай за папка",
"ButtonCancel": "Откажи", "ButtonCancel": "Откажи",
"ButtonCancelEncode": "Откажи закодирането", "ButtonCancelEncode": "Откажи закодирането",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Предишна Глава", "ButtonPreviousChapter": "Предишна Глава",
"ButtonPurgeAllCache": "Изчисти Всички Кешове", "ButtonPurgeAllCache": "Изчисти Всички Кешове",
"ButtonPurgeItemsCache": "Изчисти Кеша на Елементи", "ButtonPurgeItemsCache": "Изчисти Кеша на Елементи",
"ButtonPurgeMediaProgress": "Изчисти Прогреса на Медията",
"ButtonQueueAddItem": "Добави към опашката", "ButtonQueueAddItem": "Добави към опашката",
"ButtonQueueRemoveItem": "Премахни от опашката", "ButtonQueueRemoveItem": "Премахни от опашката",
"ButtonQuickMatch": "Бързо Съпоставяне", "ButtonQuickMatch": "Бързо Съпоставяне",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Елементи на Колекция", "HeaderCollectionItems": "Елементи на Колекция",
"HeaderCover": "Корица", "HeaderCover": "Корица",
"HeaderCurrentDownloads": "Текущи Сваляния", "HeaderCurrentDownloads": "Текущи Сваляния",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Потребителски Доставчици на Метаданни", "HeaderCustomMetadataProviders": "Потребителски Доставчици на Метаданни",
"HeaderDetails": "Детайли", "HeaderDetails": "Детайли",
"HeaderDownloadQueue": "Опашка за Сваляне", "HeaderDownloadQueue": "Опашка за Сваляне",
@ -279,6 +280,8 @@
"LabelEdit": "Редакция", "LabelEdit": "Редакция",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "От Адрес", "LabelEmailSettingsFromAddress": "От Адрес",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Сигурна", "LabelEmailSettingsSecure": "Сигурна",
"LabelEmailSettingsSecureHelp": "Ако е вярно възката ще изполва TLS когате се свързва със сървъра. Ако не е то TLS ще се използва ако сървъра поддържа разширението STARTTLS. В повечето случаи задайте тази стойност на истина ако се свързвате към порт 465. За порт 587 или 25 оставете я на лъжа. (от nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Ако е вярно възката ще изполва TLS когате се свързва със сървъра. Ако не е то TLS ще се използва ако сървъра поддържа разширението STARTTLS. В повечето случаи задайте тази стойност на истина ако се свързвате към порт 465. За порт 587 или 25 оставете я на лъжа. (от nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Тестов Адрес", "LabelEmailSettingsTestAddress": "Тестов Адрес",
@ -335,6 +338,7 @@
"LabelItem": "Елемент", "LabelItem": "Елемент",
"LabelLanguage": "Език", "LabelLanguage": "Език",
"LabelLanguageDefaultServer": "Език по подразбиране на сървъра", "LabelLanguageDefaultServer": "Език по подразбиране на сървъра",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Последно Добавена Книга", "LabelLastBookAdded": "Последно Добавена Книга",
"LabelLastBookUpdated": "Последно Обновена Книга", "LabelLastBookUpdated": "Последно Обновена Книга",
"LabelLastSeen": "Последно Видян", "LabelLastSeen": "Последно Видян",
@ -346,6 +350,7 @@
"LabelLess": "По-малко", "LabelLess": "По-малко",
"LabelLibrariesAccessibleToUser": "Библиотеки Достъпни за Потребителя", "LabelLibrariesAccessibleToUser": "Библиотеки Достъпни за Потребителя",
"LabelLibrary": "Библиотека", "LabelLibrary": "Библиотека",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Елемент на Библиотека", "LabelLibraryItem": "Елемент на Библиотека",
"LabelLibraryName": "Име на Библиотека", "LabelLibraryName": "Име на Библиотека",
"LabelLimit": "Лимит", "LabelLimit": "Лимит",
@ -381,6 +386,7 @@
"LabelNewPassword": "Нова Парола", "LabelNewPassword": "Нова Парола",
"LabelNextBackupDate": "Следваща Дата на Архивиране", "LabelNextBackupDate": "Следваща Дата на Архивиране",
"LabelNextScheduledRun": "Следващо Планирано Изпълнение", "LabelNextScheduledRun": "Следващо Планирано Изпълнение",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Няма избрани епизоди", "LabelNoEpisodesSelected": "Няма избрани епизоди",
"LabelNotes": "Бележки", "LabelNotes": "Бележки",
"LabelNotFinished": "Не е завършено", "LabelNotFinished": "Не е завършено",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Може да качва", "LabelPermissionsUpload": "Може да качва",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Път/URL на Снимка", "LabelPhotoPathURL": "Път/URL на Снимка",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Плейлисти", "LabelPlaylists": "Плейлисти",
"LabelPlayMethod": "Метод на Пускане", "LabelPlayMethod": "Метод на Пускане",
"LabelPodcast": "Подкаст", "LabelPodcast": "Подкаст",
@ -426,6 +433,7 @@
"LabelProvider": "Доставчик", "LabelProvider": "Доставчик",
"LabelPubDate": "Дата на Издаване", "LabelPubDate": "Дата на Издаване",
"LabelPublisher": "Издател", "LabelPublisher": "Издател",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Година на Издаване", "LabelPublishYear": "Година на Издаване",
"LabelRead": "Прочети", "LabelRead": "Прочети",
"LabelReadAgain": "Прочети Отново", "LabelReadAgain": "Прочети Отново",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Бързото Съпоставяне ще опита да добави липсващи корици и метаданни за избраните елементи. Активирайте опциите по-долу, за да позволите на Бързото съпоставяне да презапише съществуващите корици и/или метаданни.", "MessageBatchQuickMatchDescription": "Бързото Съпоставяне ще опита да добави липсващи корици и метаданни за избраните елементи. Активирайте опциите по-долу, за да позволите на Бързото съпоставяне да презапише съществуващите корици и/или метаданни.",
"MessageBookshelfNoCollections": "Все още нямате създадени колекции", "MessageBookshelfNoCollections": "Все още нямате създадени колекции",
"MessageBookshelfNoResultsForFilter": "Няма резултат за филтер \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Няма резултат за филтер \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Няма отворени RSS feed-ове", "MessageBookshelfNoRSSFeeds": "Няма отворени RSS feed-ове",
"MessageBookshelfNoSeries": "Нямаш сеЗЙ", "MessageBookshelfNoSeries": "Нямаш сеЗЙ",
"MessageChapterEndIsAfter": "Краят на главата е след края на вашата аудиокнига", "MessageChapterEndIsAfter": "Краят на главата е след края на вашата аудиокнига",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Сигурни ли сте, че искате да маркирате всички книги в тази серия като завършени?", "MessageConfirmMarkSeriesFinished": "Сигурни ли сте, че искате да маркирате всички книги в тази серия като завършени?",
"MessageConfirmMarkSeriesNotFinished": "Сигурни ли сте, че искате да маркирате всички книги в тази серия като незавършени?", "MessageConfirmMarkSeriesNotFinished": "Сигурни ли сте, че искате да маркирате всички книги в тази серия като незавършени?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Внимание! Бързото вграждане няма да архивира вашите аудио файлове. Уверете се, че имате резервно копие на вашите аудио файлове. <br><br>Искате ли да продължите?", "MessageConfirmQuickEmbed": "Внимание! Бързото вграждане няма да архивира вашите аудио файлове. Уверете се, че имате резервно копие на вашите аудио файлове. <br><br>Искате ли да продължите?",
"MessageConfirmRemoveAllChapters": "Сигурни ли сте, че искате да премахнете всички глави?", "MessageConfirmRemoveAllChapters": "Сигурни ли сте, че искате да премахнете всички глави?",
"MessageConfirmRemoveAuthor": "Сигурни ли сте, че искате да премахнете автор \"{0}\"?", "MessageConfirmRemoveAuthor": "Сигурни ли сте, че искате да премахнете автор \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "প্রয়োগ করুন", "ButtonApply": "প্রয়োগ করুন",
"ButtonApplyChapters": "অধ্যায় প্রয়োগ করুন", "ButtonApplyChapters": "অধ্যায় প্রয়োগ করুন",
"ButtonAuthors": "লেখক", "ButtonAuthors": "লেখক",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "ফোল্ডারের জন্য ব্রাউজ করুন", "ButtonBrowseForFolder": "ফোল্ডারের জন্য ব্রাউজ করুন",
"ButtonCancel": "বাতিল করুন", "ButtonCancel": "বাতিল করুন",
"ButtonCancelEncode": "এনকোড বাতিল করুন", "ButtonCancelEncode": "এনকোড বাতিল করুন",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "আগের অধ্যায়", "ButtonPreviousChapter": "আগের অধ্যায়",
"ButtonPurgeAllCache": "সমস্ত ক্যাশে পরিষ্কার করুন", "ButtonPurgeAllCache": "সমস্ত ক্যাশে পরিষ্কার করুন",
"ButtonPurgeItemsCache": "আইটেম ক্যাশে পরিষ্কার করুন", "ButtonPurgeItemsCache": "আইটেম ক্যাশে পরিষ্কার করুন",
"ButtonPurgeMediaProgress": "মিডিয়া ক্যাশে পরিষ্কার করুন",
"ButtonQueueAddItem": "সারিতে যোগ করুন", "ButtonQueueAddItem": "সারিতে যোগ করুন",
"ButtonQueueRemoveItem": "সারি থেকে মুছে ফেলুন", "ButtonQueueRemoveItem": "সারি থেকে মুছে ফেলুন",
"ButtonQuickMatch": "দ্রুত ম্যাচ", "ButtonQuickMatch": "দ্রুত ম্যাচ",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "সংগ্রহ আইটেম", "HeaderCollectionItems": "সংগ্রহ আইটেম",
"HeaderCover": "কভার", "HeaderCover": "কভার",
"HeaderCurrentDownloads": "বর্তমান ডাউনলোডগুলি", "HeaderCurrentDownloads": "বর্তমান ডাউনলোডগুলি",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "কাস্টম মেটাডেটা প্রদানকারী", "HeaderCustomMetadataProviders": "কাস্টম মেটাডেটা প্রদানকারী",
"HeaderDetails": "বিস্তারিত", "HeaderDetails": "বিস্তারিত",
"HeaderDownloadQueue": "ডাউনলোড সারি", "HeaderDownloadQueue": "ডাউনলোড সারি",
@ -279,6 +280,8 @@
"LabelEdit": "সম্পাদনা করুন", "LabelEdit": "সম্পাদনা করুন",
"LabelEmail": "ইমেইল", "LabelEmail": "ইমেইল",
"LabelEmailSettingsFromAddress": "ঠিকানা থেকে", "LabelEmailSettingsFromAddress": "ঠিকানা থেকে",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "নিরাপদ", "LabelEmailSettingsSecure": "নিরাপদ",
"LabelEmailSettingsSecureHelp": "যদি সত্য হয় সার্ভারের সাথে সংযোগ করার সময় সংযোগটি TLS ব্যবহার করবে। মিথ্যা হলে TLS ব্যবহার করা হবে যদি সার্ভার STARTTLS এক্সটেনশন সমর্থন করে। বেশিরভাগ ক্ষেত্রে এই মানটিকে সত্য হিসাবে সেট করুন যদি আপনি পোর্ট 465-এর সাথে সংযোগ করছেন। পোর্ট 587 বা পোর্টের জন্য 25 এটি মিথ্যা রাখুন। (nodemailer.com/smtp/#authentication থেকে)", "LabelEmailSettingsSecureHelp": "যদি সত্য হয় সার্ভারের সাথে সংযোগ করার সময় সংযোগটি TLS ব্যবহার করবে। মিথ্যা হলে TLS ব্যবহার করা হবে যদি সার্ভার STARTTLS এক্সটেনশন সমর্থন করে। বেশিরভাগ ক্ষেত্রে এই মানটিকে সত্য হিসাবে সেট করুন যদি আপনি পোর্ট 465-এর সাথে সংযোগ করছেন। পোর্ট 587 বা পোর্টের জন্য 25 এটি মিথ্যা রাখুন। (nodemailer.com/smtp/#authentication থেকে)",
"LabelEmailSettingsTestAddress": "পরীক্ষার ঠিকানা", "LabelEmailSettingsTestAddress": "পরীক্ষার ঠিকানা",
@ -335,6 +338,7 @@
"LabelItem": "আইটেম", "LabelItem": "আইটেম",
"LabelLanguage": "ভাষা", "LabelLanguage": "ভাষা",
"LabelLanguageDefaultServer": "সার্ভারের ডিফল্ট ভাষা", "LabelLanguageDefaultServer": "সার্ভারের ডিফল্ট ভাষা",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "শেষ বই যোগ করা হয়েছে", "LabelLastBookAdded": "শেষ বই যোগ করা হয়েছে",
"LabelLastBookUpdated": "শেষ বই আপডেট করা হয়েছে", "LabelLastBookUpdated": "শেষ বই আপডেট করা হয়েছে",
"LabelLastSeen": "শেষ দেখা", "LabelLastSeen": "শেষ দেখা",
@ -346,6 +350,7 @@
"LabelLess": "কম", "LabelLess": "কম",
"LabelLibrariesAccessibleToUser": "ব্যবহারকারীর কাছে অ্যাক্সেসযোগ্য লাইব্রেরি", "LabelLibrariesAccessibleToUser": "ব্যবহারকারীর কাছে অ্যাক্সেসযোগ্য লাইব্রেরি",
"LabelLibrary": "লাইব্রেরি", "LabelLibrary": "লাইব্রেরি",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "লাইব্রেরি আইটেম", "LabelLibraryItem": "লাইব্রেরি আইটেম",
"LabelLibraryName": "লাইব্রেরির নাম", "LabelLibraryName": "লাইব্রেরির নাম",
"LabelLimit": "সীমা", "LabelLimit": "সীমা",
@ -381,6 +386,7 @@
"LabelNewPassword": "নতুন পাসওয়ার্ড", "LabelNewPassword": "নতুন পাসওয়ার্ড",
"LabelNextBackupDate": "পরবর্তী ব্যাকআপ তারিখ", "LabelNextBackupDate": "পরবর্তী ব্যাকআপ তারিখ",
"LabelNextScheduledRun": "পরবর্তী নির্ধারিত দৌড়", "LabelNextScheduledRun": "পরবর্তী নির্ধারিত দৌড়",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "কোন পর্ব নির্বাচন করা হয়নি", "LabelNoEpisodesSelected": "কোন পর্ব নির্বাচন করা হয়নি",
"LabelNotes": "নোটস", "LabelNotes": "নোটস",
"LabelNotFinished": "সমাপ্ত হয়নি", "LabelNotFinished": "সমাপ্ত হয়নি",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "আপলোড করতে পারবে", "LabelPermissionsUpload": "আপলোড করতে পারবে",
"LabelPersonalYearReview": "আপনার বছরের পর্যালোচনা ({0})", "LabelPersonalYearReview": "আপনার বছরের পর্যালোচনা ({0})",
"LabelPhotoPathURL": "ছবি পথ/ইউআরএল", "LabelPhotoPathURL": "ছবি পথ/ইউআরএল",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "প্লেলিস্ট", "LabelPlaylists": "প্লেলিস্ট",
"LabelPlayMethod": "প্লে পদ্ধতি", "LabelPlayMethod": "প্লে পদ্ধতি",
"LabelPodcast": "পডকাস্ট", "LabelPodcast": "পডকাস্ট",
@ -426,6 +433,7 @@
"LabelProvider": "প্রদানকারী", "LabelProvider": "প্রদানকারী",
"LabelPubDate": "প্রকাশের তারিখ", "LabelPubDate": "প্রকাশের তারিখ",
"LabelPublisher": "প্রকাশক", "LabelPublisher": "প্রকাশক",
"LabelPublishers": "Publishers",
"LabelPublishYear": "প্রকাশের বছর", "LabelPublishYear": "প্রকাশের বছর",
"LabelRead": "পড়ুন", "LabelRead": "পড়ুন",
"LabelReadAgain": "আবার পড়ুন", "LabelReadAgain": "আবার পড়ুন",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "কুইক ম্যাচ নির্বাচিত আইটেমগুলির জন্য অনুপস্থিত কভার এবং মেটাডেটা যোগ করার চেষ্টা করবে। বিদ্যমান কভার এবং/অথবা মেটাডেটা ওভাররাইট করার জন্য দ্রুত ম্যাচকে অনুমতি দিতে নীচের বিকল্পগুলি সক্ষম করুন।", "MessageBatchQuickMatchDescription": "কুইক ম্যাচ নির্বাচিত আইটেমগুলির জন্য অনুপস্থিত কভার এবং মেটাডেটা যোগ করার চেষ্টা করবে। বিদ্যমান কভার এবং/অথবা মেটাডেটা ওভাররাইট করার জন্য দ্রুত ম্যাচকে অনুমতি দিতে নীচের বিকল্পগুলি সক্ষম করুন।",
"MessageBookshelfNoCollections": "আপনি এখনও কোনো সংগ্রহ করেননি", "MessageBookshelfNoCollections": "আপনি এখনও কোনো সংগ্রহ করেননি",
"MessageBookshelfNoResultsForFilter": "ফিল্টার \"{0}: {1}\" এর জন্য কোন ফলাফল নেই", "MessageBookshelfNoResultsForFilter": "ফিল্টার \"{0}: {1}\" এর জন্য কোন ফলাফল নেই",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "কোনও RSS ফিড খোলা নেই", "MessageBookshelfNoRSSFeeds": "কোনও RSS ফিড খোলা নেই",
"MessageBookshelfNoSeries": "আপনার কোনো সিরিজ নেই", "MessageBookshelfNoSeries": "আপনার কোনো সিরিজ নেই",
"MessageChapterEndIsAfter": "অধ্যায়ের সমাপ্তি আপনার অডিওবুকের শেষে", "MessageChapterEndIsAfter": "অধ্যায়ের সমাপ্তি আপনার অডিওবুকের শেষে",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে সমাপ্ত হিসাবে চিহ্নিত করতে চান?", "MessageConfirmMarkSeriesFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে সমাপ্ত হিসাবে চিহ্নিত করতে চান?",
"MessageConfirmMarkSeriesNotFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে শেষ হয়নি বলে চিহ্নিত করতে চান?", "MessageConfirmMarkSeriesNotFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে শেষ হয়নি বলে চিহ্নিত করতে চান?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "সতর্কতা! দ্রুত এম্বেড আপনার অডিও ফাইলের ব্যাকআপ করবে না। নিশ্চিত করুন যে আপনার অডিও ফাইলগুলির একটি ব্যাকআপ আছে। <br><br>আপনি কি চালিয়ে যেতে চান?", "MessageConfirmQuickEmbed": "সতর্কতা! দ্রুত এম্বেড আপনার অডিও ফাইলের ব্যাকআপ করবে না। নিশ্চিত করুন যে আপনার অডিও ফাইলগুলির একটি ব্যাকআপ আছে। <br><br>আপনি কি চালিয়ে যেতে চান?",
"MessageConfirmRemoveAllChapters": "আপনি কি নিশ্চিত যে আপনি সমস্ত অধ্যায় সরাতে চান?", "MessageConfirmRemoveAllChapters": "আপনি কি নিশ্চিত যে আপনি সমস্ত অধ্যায় সরাতে চান?",
"MessageConfirmRemoveAuthor": "আপনি কি নিশ্চিত যে আপনি লেখক \"{0}\" অপসারণ করতে চান?", "MessageConfirmRemoveAuthor": "আপনি কি নিশ্চিত যে আপনি লেখক \"{0}\" অপসারণ করতে চান?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Aplikovat", "ButtonApply": "Aplikovat",
"ButtonApplyChapters": "Aplikovat kapitoly", "ButtonApplyChapters": "Aplikovat kapitoly",
"ButtonAuthors": "Autoři", "ButtonAuthors": "Autoři",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Vyhledat složku", "ButtonBrowseForFolder": "Vyhledat složku",
"ButtonCancel": "Zrušit", "ButtonCancel": "Zrušit",
"ButtonCancelEncode": "Zrušit kódování", "ButtonCancelEncode": "Zrušit kódování",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "Vyčistit veškerou mezipaměť", "ButtonPurgeAllCache": "Vyčistit veškerou mezipaměť",
"ButtonPurgeItemsCache": "Vyčistit mezipaměť položek", "ButtonPurgeItemsCache": "Vyčistit mezipaměť položek",
"ButtonPurgeMediaProgress": "Vyčistit průběh médií",
"ButtonQueueAddItem": "Přidat do fronty", "ButtonQueueAddItem": "Přidat do fronty",
"ButtonQueueRemoveItem": "Odstranit z fronty", "ButtonQueueRemoveItem": "Odstranit z fronty",
"ButtonQuickMatch": "Rychlé přiřazení", "ButtonQuickMatch": "Rychlé přiřazení",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Položky kolekce", "HeaderCollectionItems": "Položky kolekce",
"HeaderCover": "Obálka", "HeaderCover": "Obálka",
"HeaderCurrentDownloads": "Aktuální stahování", "HeaderCurrentDownloads": "Aktuální stahování",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Podrobnosti", "HeaderDetails": "Podrobnosti",
"HeaderDownloadQueue": "Fronta stahování", "HeaderDownloadQueue": "Fronta stahování",
@ -279,6 +280,8 @@
"LabelEdit": "Upravit", "LabelEdit": "Upravit",
"LabelEmail": "E-mail", "LabelEmail": "E-mail",
"LabelEmailSettingsFromAddress": "Z adresy", "LabelEmailSettingsFromAddress": "Z adresy",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Zabezpečené", "LabelEmailSettingsSecure": "Zabezpečené",
"LabelEmailSettingsSecureHelp": "Pokud je true, připojení bude při připojování k serveru používat TLS. Pokud je false, použije se protokol TLS, pokud server podporuje rozšíření STARTTLS. Ve většině případů nastavte tuto hodnotu na true, pokud se připojujete k portu 465. Pro port 587 nebo 25 ponechte hodnotu false. (z nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Pokud je true, připojení bude při připojování k serveru používat TLS. Pokud je false, použije se protokol TLS, pokud server podporuje rozšíření STARTTLS. Ve většině případů nastavte tuto hodnotu na true, pokud se připojujete k portu 465. Pro port 587 nebo 25 ponechte hodnotu false. (z nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Testovací adresa", "LabelEmailSettingsTestAddress": "Testovací adresa",
@ -335,6 +338,7 @@
"LabelItem": "Položka", "LabelItem": "Položka",
"LabelLanguage": "Jazyk", "LabelLanguage": "Jazyk",
"LabelLanguageDefaultServer": "Výchozí jazyk serveru", "LabelLanguageDefaultServer": "Výchozí jazyk serveru",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Poslední kniha přidána", "LabelLastBookAdded": "Poslední kniha přidána",
"LabelLastBookUpdated": "Poslední kniha aktualizována", "LabelLastBookUpdated": "Poslední kniha aktualizována",
"LabelLastSeen": "Naposledy viděno", "LabelLastSeen": "Naposledy viděno",
@ -346,6 +350,7 @@
"LabelLess": "Méně", "LabelLess": "Méně",
"LabelLibrariesAccessibleToUser": "Knihovny přístupné uživateli", "LabelLibrariesAccessibleToUser": "Knihovny přístupné uživateli",
"LabelLibrary": "Knihovna", "LabelLibrary": "Knihovna",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Položka knihovny", "LabelLibraryItem": "Položka knihovny",
"LabelLibraryName": "Název knihovny", "LabelLibraryName": "Název knihovny",
"LabelLimit": "Omezit", "LabelLimit": "Omezit",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nové heslo", "LabelNewPassword": "Nové heslo",
"LabelNextBackupDate": "Datum příští zálohy", "LabelNextBackupDate": "Datum příští zálohy",
"LabelNextScheduledRun": "Další naplánované spuštění", "LabelNextScheduledRun": "Další naplánované spuštění",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Nebyly vybrány žádné epizody", "LabelNoEpisodesSelected": "Nebyly vybrány žádné epizody",
"LabelNotes": "Poznámky", "LabelNotes": "Poznámky",
"LabelNotFinished": "Nedokončeno", "LabelNotFinished": "Nedokončeno",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Může nahrávat", "LabelPermissionsUpload": "Může nahrávat",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Cesta k fotografii/URL", "LabelPhotoPathURL": "Cesta k fotografii/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Seznamy skladeb", "LabelPlaylists": "Seznamy skladeb",
"LabelPlayMethod": "Metoda přehrávání", "LabelPlayMethod": "Metoda přehrávání",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Poskytovatel", "LabelProvider": "Poskytovatel",
"LabelPubDate": "Datum vydání", "LabelPubDate": "Datum vydání",
"LabelPublisher": "Vydavatel", "LabelPublisher": "Vydavatel",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Rok vydání", "LabelPublishYear": "Rok vydání",
"LabelRead": "Číst", "LabelRead": "Číst",
"LabelReadAgain": "Číst znovu", "LabelReadAgain": "Číst znovu",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Rychlá párování se pokusí přidat chybějící obálky a metadata pro vybrané položky. Povolením níže uvedených možností umožníte funkci Rychlé párování přepsat stávající obálky a/nebo metadata.", "MessageBatchQuickMatchDescription": "Rychlá párování se pokusí přidat chybějící obálky a metadata pro vybrané položky. Povolením níže uvedených možností umožníte funkci Rychlé párování přepsat stávající obálky a/nebo metadata.",
"MessageBookshelfNoCollections": "Ještě jste nevytvořili žádnou sbírku", "MessageBookshelfNoCollections": "Ještě jste nevytvořili žádnou sbírku",
"MessageBookshelfNoResultsForFilter": "Filtr \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Filtr \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Nejsou otevřeny žádné RSS kanály", "MessageBookshelfNoRSSFeeds": "Nejsou otevřeny žádné RSS kanály",
"MessageBookshelfNoSeries": "Nemáte žádnou sérii", "MessageBookshelfNoSeries": "Nemáte žádnou sérii",
"MessageChapterEndIsAfter": "Konec kapitoly přesahuje konec audioknihy", "MessageChapterEndIsAfter": "Konec kapitoly přesahuje konec audioknihy",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Opravdu chcete označit všechny knihy z této série jako dokončené?", "MessageConfirmMarkSeriesFinished": "Opravdu chcete označit všechny knihy z této série jako dokončené?",
"MessageConfirmMarkSeriesNotFinished": "Opravdu chcete označit všechny knihy z této série jako nedokončené?", "MessageConfirmMarkSeriesNotFinished": "Opravdu chcete označit všechny knihy z této série jako nedokončené?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Varování! Rychlé vložení nezálohuje vaše zvukové soubory. Ujistěte se, že máte zálohu zvukových souborů. <br><br>Chcete pokračovat?", "MessageConfirmQuickEmbed": "Varování! Rychlé vložení nezálohuje vaše zvukové soubory. Ujistěte se, že máte zálohu zvukových souborů. <br><br>Chcete pokračovat?",
"MessageConfirmRemoveAllChapters": "Opravdu chcete odstranit všechny kapitoly?", "MessageConfirmRemoveAllChapters": "Opravdu chcete odstranit všechny kapitoly?",
"MessageConfirmRemoveAuthor": "Opravdu chcete odstranit autora \"{0}\"?", "MessageConfirmRemoveAuthor": "Opravdu chcete odstranit autora \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Anvend", "ButtonApply": "Anvend",
"ButtonApplyChapters": "Anvend kapitler", "ButtonApplyChapters": "Anvend kapitler",
"ButtonAuthors": "Forfattere", "ButtonAuthors": "Forfattere",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Gennemse mappe", "ButtonBrowseForFolder": "Gennemse mappe",
"ButtonCancel": "Annuller", "ButtonCancel": "Annuller",
"ButtonCancelEncode": "Annuller kodning", "ButtonCancelEncode": "Annuller kodning",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "Ryd al cache", "ButtonPurgeAllCache": "Ryd al cache",
"ButtonPurgeItemsCache": "Ryd elementcache", "ButtonPurgeItemsCache": "Ryd elementcache",
"ButtonPurgeMediaProgress": "Ryd Medieforløb",
"ButtonQueueAddItem": "Tilføj til kø", "ButtonQueueAddItem": "Tilføj til kø",
"ButtonQueueRemoveItem": "Fjern fra kø", "ButtonQueueRemoveItem": "Fjern fra kø",
"ButtonQuickMatch": "Hurtig Match", "ButtonQuickMatch": "Hurtig Match",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Samlingselementer", "HeaderCollectionItems": "Samlingselementer",
"HeaderCover": "Omslag", "HeaderCover": "Omslag",
"HeaderCurrentDownloads": "Nuværende Downloads", "HeaderCurrentDownloads": "Nuværende Downloads",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Detaljer", "HeaderDetails": "Detaljer",
"HeaderDownloadQueue": "Download Kø", "HeaderDownloadQueue": "Download Kø",
@ -279,6 +280,8 @@
"LabelEdit": "Rediger", "LabelEdit": "Rediger",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Fra Adresse", "LabelEmailSettingsFromAddress": "Fra Adresse",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Sikker", "LabelEmailSettingsSecure": "Sikker",
"LabelEmailSettingsSecureHelp": "Hvis sandt, vil forbindelsen bruge TLS ved tilslutning til serveren. Hvis falsk, bruges TLS, hvis serveren understøtter STARTTLS-udvidelsen. I de fleste tilfælde skal denne værdi sættes til sandt, hvis du tilslutter til port 465. Til port 587 eller 25 skal du holde det falsk. (fra nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Hvis sandt, vil forbindelsen bruge TLS ved tilslutning til serveren. Hvis falsk, bruges TLS, hvis serveren understøtter STARTTLS-udvidelsen. I de fleste tilfælde skal denne værdi sættes til sandt, hvis du tilslutter til port 465. Til port 587 eller 25 skal du holde det falsk. (fra nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Adresse", "LabelEmailSettingsTestAddress": "Test Adresse",
@ -335,6 +338,7 @@
"LabelItem": "Element", "LabelItem": "Element",
"LabelLanguage": "Sprog", "LabelLanguage": "Sprog",
"LabelLanguageDefaultServer": "Standard server sprog", "LabelLanguageDefaultServer": "Standard server sprog",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Senest tilføjede bog", "LabelLastBookAdded": "Senest tilføjede bog",
"LabelLastBookUpdated": "Senest opdaterede bog", "LabelLastBookUpdated": "Senest opdaterede bog",
"LabelLastSeen": "Sidst set", "LabelLastSeen": "Sidst set",
@ -346,6 +350,7 @@
"LabelLess": "Mindre", "LabelLess": "Mindre",
"LabelLibrariesAccessibleToUser": "Biblioteker tilgængelige for bruger", "LabelLibrariesAccessibleToUser": "Biblioteker tilgængelige for bruger",
"LabelLibrary": "Bibliotek", "LabelLibrary": "Bibliotek",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Bibliotekselement", "LabelLibraryItem": "Bibliotekselement",
"LabelLibraryName": "Biblioteksnavn", "LabelLibraryName": "Biblioteksnavn",
"LabelLimit": "Grænse", "LabelLimit": "Grænse",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nyt kodeord", "LabelNewPassword": "Nyt kodeord",
"LabelNextBackupDate": "Næste sikkerhedskopi dato", "LabelNextBackupDate": "Næste sikkerhedskopi dato",
"LabelNextScheduledRun": "Næste planlagte kørsel", "LabelNextScheduledRun": "Næste planlagte kørsel",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Ingen episoder valgt", "LabelNoEpisodesSelected": "Ingen episoder valgt",
"LabelNotes": "Noter", "LabelNotes": "Noter",
"LabelNotFinished": "Ikke færdig", "LabelNotFinished": "Ikke færdig",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Kan uploade", "LabelPermissionsUpload": "Kan uploade",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Foto sti/URL", "LabelPhotoPathURL": "Foto sti/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Afspilningslister", "LabelPlaylists": "Afspilningslister",
"LabelPlayMethod": "Afspilningsmetode", "LabelPlayMethod": "Afspilningsmetode",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Udbyder", "LabelProvider": "Udbyder",
"LabelPubDate": "Udgivelsesdato", "LabelPubDate": "Udgivelsesdato",
"LabelPublisher": "Forlag", "LabelPublisher": "Forlag",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Udgivelsesår", "LabelPublishYear": "Udgivelsesår",
"LabelRead": "Læst", "LabelRead": "Læst",
"LabelReadAgain": "Læs igen", "LabelReadAgain": "Læs igen",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Quick Match vil forsøge at tilføje manglende omslag og metadata til de valgte elementer. Aktivér indstillingerne nedenfor for at tillade Quick Match at overskrive eksisterende omslag og/eller metadata.", "MessageBatchQuickMatchDescription": "Quick Match vil forsøge at tilføje manglende omslag og metadata til de valgte elementer. Aktivér indstillingerne nedenfor for at tillade Quick Match at overskrive eksisterende omslag og/eller metadata.",
"MessageBookshelfNoCollections": "Du har ikke oprettet nogen samlinger endnu", "MessageBookshelfNoCollections": "Du har ikke oprettet nogen samlinger endnu",
"MessageBookshelfNoResultsForFilter": "Ingen resultater for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Ingen resultater for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Ingen RSS-feeds er åbne", "MessageBookshelfNoRSSFeeds": "Ingen RSS-feeds er åbne",
"MessageBookshelfNoSeries": "Du har ingen serier", "MessageBookshelfNoSeries": "Du har ingen serier",
"MessageChapterEndIsAfter": "Kapitelslutningen er efter slutningen af din lydbog", "MessageChapterEndIsAfter": "Kapitelslutningen er efter slutningen af din lydbog",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som afsluttet?", "MessageConfirmMarkSeriesFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som afsluttet?",
"MessageConfirmMarkSeriesNotFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som ikke afsluttet?", "MessageConfirmMarkSeriesNotFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som ikke afsluttet?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Er du sikker på, at du vil fjerne alle kapitler?", "MessageConfirmRemoveAllChapters": "Er du sikker på, at du vil fjerne alle kapitler?",
"MessageConfirmRemoveAuthor": "Er du sikker på, at du vil fjerne forfatteren \"{0}\"?", "MessageConfirmRemoveAuthor": "Er du sikker på, at du vil fjerne forfatteren \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Übernehmen", "ButtonApply": "Übernehmen",
"ButtonApplyChapters": "Kapitel anwenden", "ButtonApplyChapters": "Kapitel anwenden",
"ButtonAuthors": "Autoren", "ButtonAuthors": "Autoren",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Ordnersuche", "ButtonBrowseForFolder": "Ordnersuche",
"ButtonCancel": "Abbrechen", "ButtonCancel": "Abbrechen",
"ButtonCancelEncode": "Codierung abbrechen", "ButtonCancelEncode": "Codierung abbrechen",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Vorheriges Kapitel", "ButtonPreviousChapter": "Vorheriges Kapitel",
"ButtonPurgeAllCache": "Cache leeren", "ButtonPurgeAllCache": "Cache leeren",
"ButtonPurgeItemsCache": "Lösche Medien-Cache", "ButtonPurgeItemsCache": "Lösche Medien-Cache",
"ButtonPurgeMediaProgress": "Lösche Hörfortschritte",
"ButtonQueueAddItem": "Zur Warteschlange hinzufügen", "ButtonQueueAddItem": "Zur Warteschlange hinzufügen",
"ButtonQueueRemoveItem": "Aus der Warteschlange entfernen", "ButtonQueueRemoveItem": "Aus der Warteschlange entfernen",
"ButtonQuickMatch": "Schnellabgleich", "ButtonQuickMatch": "Schnellabgleich",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Sammlungseinträge", "HeaderCollectionItems": "Sammlungseinträge",
"HeaderCover": "Titelbild", "HeaderCover": "Titelbild",
"HeaderCurrentDownloads": "Aktuelle Downloads", "HeaderCurrentDownloads": "Aktuelle Downloads",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Benutzerdefinierte Metadata Anbieter", "HeaderCustomMetadataProviders": "Benutzerdefinierte Metadata Anbieter",
"HeaderDetails": "Details", "HeaderDetails": "Details",
"HeaderDownloadQueue": "Download Warteschlange", "HeaderDownloadQueue": "Download Warteschlange",
@ -279,6 +280,8 @@
"LabelEdit": "Bearbeiten", "LabelEdit": "Bearbeiten",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Von Adresse", "LabelEmailSettingsFromAddress": "Von Adresse",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Sicher", "LabelEmailSettingsSecure": "Sicher",
"LabelEmailSettingsSecureHelp": "Wenn \"an\", verwendet die Verbindung TLS, wenn du eine Verbindung zum Server herstellst. Bei \"aus\" wird TLS verwendet, wenn der Server die STARTTLS-Erweiterung unterstützt. In den meisten Fällen solltest du diesen Wert auf \"an\" schalten, wenn du eine Verbindung zu Port 465 herstellst. Für Port 587 oder 25 behalte den Wert \"aus\" bei. (von nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Wenn \"an\", verwendet die Verbindung TLS, wenn du eine Verbindung zum Server herstellst. Bei \"aus\" wird TLS verwendet, wenn der Server die STARTTLS-Erweiterung unterstützt. In den meisten Fällen solltest du diesen Wert auf \"an\" schalten, wenn du eine Verbindung zu Port 465 herstellst. Für Port 587 oder 25 behalte den Wert \"aus\" bei. (von nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Adresse", "LabelEmailSettingsTestAddress": "Test Adresse",
@ -335,6 +338,7 @@
"LabelItem": "Medium", "LabelItem": "Medium",
"LabelLanguage": "Sprache", "LabelLanguage": "Sprache",
"LabelLanguageDefaultServer": "Standard-Server-Sprache", "LabelLanguageDefaultServer": "Standard-Server-Sprache",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Zuletzt hinzugefügtes Buch", "LabelLastBookAdded": "Zuletzt hinzugefügtes Buch",
"LabelLastBookUpdated": "Zuletzt aktualisiertes Buch", "LabelLastBookUpdated": "Zuletzt aktualisiertes Buch",
"LabelLastSeen": "Zuletzt gesehen", "LabelLastSeen": "Zuletzt gesehen",
@ -346,6 +350,7 @@
"LabelLess": "Weniger", "LabelLess": "Weniger",
"LabelLibrariesAccessibleToUser": "Für Benutzer zugängliche Bibliotheken", "LabelLibrariesAccessibleToUser": "Für Benutzer zugängliche Bibliotheken",
"LabelLibrary": "Bibliothek", "LabelLibrary": "Bibliothek",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Bibliothekseintrag", "LabelLibraryItem": "Bibliothekseintrag",
"LabelLibraryName": "Bibliotheksname", "LabelLibraryName": "Bibliotheksname",
"LabelLimit": "Begrenzung", "LabelLimit": "Begrenzung",
@ -381,6 +386,7 @@
"LabelNewPassword": "Neues Passwort", "LabelNewPassword": "Neues Passwort",
"LabelNextBackupDate": "Nächstes Sicherungsdatum", "LabelNextBackupDate": "Nächstes Sicherungsdatum",
"LabelNextScheduledRun": "Nächster planmäßiger Durchlauf", "LabelNextScheduledRun": "Nächster planmäßiger Durchlauf",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Keine Episoden ausgewählt", "LabelNoEpisodesSelected": "Keine Episoden ausgewählt",
"LabelNotes": "Notizen", "LabelNotes": "Notizen",
"LabelNotFinished": "Nicht beendet", "LabelNotFinished": "Nicht beendet",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Hochladen", "LabelPermissionsUpload": "Hochladen",
"LabelPersonalYearReview": "Dein Jahr in Übersicht ({0})", "LabelPersonalYearReview": "Dein Jahr in Übersicht ({0})",
"LabelPhotoPathURL": "Foto Pfad/URL", "LabelPhotoPathURL": "Foto Pfad/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Wiedergabelisten", "LabelPlaylists": "Wiedergabelisten",
"LabelPlayMethod": "Abspielmethode", "LabelPlayMethod": "Abspielmethode",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Anbieter", "LabelProvider": "Anbieter",
"LabelPubDate": "Veröffentlichungsdatum", "LabelPubDate": "Veröffentlichungsdatum",
"LabelPublisher": "Herausgeber", "LabelPublisher": "Herausgeber",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Jahr", "LabelPublishYear": "Jahr",
"LabelRead": "Lesen", "LabelRead": "Lesen",
"LabelReadAgain": "Noch einmal Lesen", "LabelReadAgain": "Noch einmal Lesen",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Der Schnellabgleich versucht, fehlende Titelbilder und Metadaten für die ausgewählten Artikel hinzuzufügen. Aktiviere die nachstehenden Optionen, damit der Schnellabgleich vorhandene Titelbilder und/oder Metadaten überschreiben kann.", "MessageBatchQuickMatchDescription": "Der Schnellabgleich versucht, fehlende Titelbilder und Metadaten für die ausgewählten Artikel hinzuzufügen. Aktiviere die nachstehenden Optionen, damit der Schnellabgleich vorhandene Titelbilder und/oder Metadaten überschreiben kann.",
"MessageBookshelfNoCollections": "Es wurden noch keine Sammlungen erstellt", "MessageBookshelfNoCollections": "Es wurden noch keine Sammlungen erstellt",
"MessageBookshelfNoResultsForFilter": "Keine Ergebnisse für Filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Keine Ergebnisse für Filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Keine RSS-Feeds geöffnet", "MessageBookshelfNoRSSFeeds": "Keine RSS-Feeds geöffnet",
"MessageBookshelfNoSeries": "Keine Serien vorhanden", "MessageBookshelfNoSeries": "Keine Serien vorhanden",
"MessageChapterEndIsAfter": "Ungültige Kapitelendzeit: Kapitelende > Mediumende (Kapitelende liegt nach dem Ende des Mediums)", "MessageChapterEndIsAfter": "Ungültige Kapitelendzeit: Kapitelende > Mediumende (Kapitelende liegt nach dem Ende des Mediums)",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Alle Medien dieser Reihe werden als abgeschlossen markiert! Bist du dir sicher?", "MessageConfirmMarkSeriesFinished": "Alle Medien dieser Reihe werden als abgeschlossen markiert! Bist du dir sicher?",
"MessageConfirmMarkSeriesNotFinished": "Alle Medien dieser Reihe werden als nicht abgeschlossen markiert! Bist du dir sicher?", "MessageConfirmMarkSeriesNotFinished": "Alle Medien dieser Reihe werden als nicht abgeschlossen markiert! Bist du dir sicher?",
"MessageConfirmPurgeCache": "Cache leeren wird das ganze Verzeichnis <code>/metadata/cache</code> löschen. <br /><br />Bist du dir sicher, dass das Cache Verzeichnis gelöscht werden soll?", "MessageConfirmPurgeCache": "Cache leeren wird das ganze Verzeichnis <code>/metadata/cache</code> löschen. <br /><br />Bist du dir sicher, dass das Cache Verzeichnis gelöscht werden soll?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warnung! Audiodateien werden bei der Schnelleinbettung nicht gesichert! Achte darauf, dass du eine Sicherungskopie der Audiodateien besitzt. <br><br>Möchtest du fortfahren?", "MessageConfirmQuickEmbed": "Warnung! Audiodateien werden bei der Schnelleinbettung nicht gesichert! Achte darauf, dass du eine Sicherungskopie der Audiodateien besitzt. <br><br>Möchtest du fortfahren?",
"MessageConfirmRemoveAllChapters": "Alle Kapitel werden entfernt! Bist du dir sicher?", "MessageConfirmRemoveAllChapters": "Alle Kapitel werden entfernt! Bist du dir sicher?",
"MessageConfirmRemoveAuthor": "Autor \"{0}\" wird enfernt! Bist du dir sicher?", "MessageConfirmRemoveAuthor": "Autor \"{0}\" wird enfernt! Bist du dir sicher?",
@ -807,4 +817,4 @@
"ToastSortingPrefixesUpdateSuccess": "Die Sortier-Prefixe wirden geupdated ({0} Einträge)", "ToastSortingPrefixesUpdateSuccess": "Die Sortier-Prefixe wirden geupdated ({0} Einträge)",
"ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden", "ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
"ToastUserDeleteSuccess": "Benutzer gelöscht" "ToastUserDeleteSuccess": "Benutzer gelöscht"
} }

View file

@ -9,6 +9,7 @@
"ButtonApply": "Apply", "ButtonApply": "Apply",
"ButtonApplyChapters": "Apply Chapters", "ButtonApplyChapters": "Apply Chapters",
"ButtonAuthors": "Authors", "ButtonAuthors": "Authors",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Browse for Folder", "ButtonBrowseForFolder": "Browse for Folder",
"ButtonCancel": "Cancel", "ButtonCancel": "Cancel",
"ButtonCancelEncode": "Cancel Encode", "ButtonCancelEncode": "Cancel Encode",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "Purge All Cache", "ButtonPurgeAllCache": "Purge All Cache",
"ButtonPurgeItemsCache": "Purge Items Cache", "ButtonPurgeItemsCache": "Purge Items Cache",
"ButtonPurgeMediaProgress": "Purge Media Progress",
"ButtonQueueAddItem": "Add to queue", "ButtonQueueAddItem": "Add to queue",
"ButtonQueueRemoveItem": "Remove from queue", "ButtonQueueRemoveItem": "Remove from queue",
"ButtonQuickMatch": "Quick Match", "ButtonQuickMatch": "Quick Match",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Collection Items", "HeaderCollectionItems": "Collection Items",
"HeaderCover": "Cover", "HeaderCover": "Cover",
"HeaderCurrentDownloads": "Current Downloads", "HeaderCurrentDownloads": "Current Downloads",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Details", "HeaderDetails": "Details",
"HeaderDownloadQueue": "Download Queue", "HeaderDownloadQueue": "Download Queue",
@ -279,6 +280,8 @@
"LabelEdit": "Edit", "LabelEdit": "Edit",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "From Address", "LabelEmailSettingsFromAddress": "From Address",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Secure", "LabelEmailSettingsSecure": "Secure",
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Address", "LabelEmailSettingsTestAddress": "Test Address",
@ -335,6 +338,7 @@
"LabelItem": "Item", "LabelItem": "Item",
"LabelLanguage": "Language", "LabelLanguage": "Language",
"LabelLanguageDefaultServer": "Default Server Language", "LabelLanguageDefaultServer": "Default Server Language",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Last Book Added", "LabelLastBookAdded": "Last Book Added",
"LabelLastBookUpdated": "Last Book Updated", "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Last Seen", "LabelLastSeen": "Last Seen",
@ -346,6 +350,7 @@
"LabelLess": "Less", "LabelLess": "Less",
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User", "LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
"LabelLibrary": "Library", "LabelLibrary": "Library",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Library Item", "LabelLibraryItem": "Library Item",
"LabelLibraryName": "Library Name", "LabelLibraryName": "Library Name",
"LabelLimit": "Limit", "LabelLimit": "Limit",
@ -381,6 +386,7 @@
"LabelNewPassword": "New Password", "LabelNewPassword": "New Password",
"LabelNextBackupDate": "Next backup date", "LabelNextBackupDate": "Next backup date",
"LabelNextScheduledRun": "Next scheduled run", "LabelNextScheduledRun": "Next scheduled run",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "No episodes selected", "LabelNoEpisodesSelected": "No episodes selected",
"LabelNotes": "Notes", "LabelNotes": "Notes",
"LabelNotFinished": "Not Finished", "LabelNotFinished": "Not Finished",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Can Upload", "LabelPermissionsUpload": "Can Upload",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Photo Path/URL", "LabelPhotoPathURL": "Photo Path/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Playlists", "LabelPlaylists": "Playlists",
"LabelPlayMethod": "Play Method", "LabelPlayMethod": "Play Method",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Provider", "LabelProvider": "Provider",
"LabelPubDate": "Pub Date", "LabelPubDate": "Pub Date",
"LabelPublisher": "Publisher", "LabelPublisher": "Publisher",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Publish Year", "LabelPublishYear": "Publish Year",
"LabelRead": "Read", "LabelRead": "Read",
"LabelReadAgain": "Read Again", "LabelReadAgain": "Read Again",
@ -590,7 +598,8 @@
"MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in <code>/metadata/items</code> & <code>/metadata/authors</code>. Backups <strong>do not</strong> include any files stored in your library folders.", "MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in <code>/metadata/items</code> & <code>/metadata/authors</code>. Backups <strong>do not</strong> include any files stored in your library folders.",
"MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.", "MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.",
"MessageBookshelfNoCollections": "You haven't made any collections yet", "MessageBookshelfNoCollections": "You haven't made any collections yet",
"MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "No results for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open", "MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
"MessageBookshelfNoSeries": "You have no series", "MessageBookshelfNoSeries": "You have no series",
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook", "MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?", "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?", "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Aplicar", "ButtonApply": "Aplicar",
"ButtonApplyChapters": "Aplicar Capítulos", "ButtonApplyChapters": "Aplicar Capítulos",
"ButtonAuthors": "Autores", "ButtonAuthors": "Autores",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Buscar por Carpeta", "ButtonBrowseForFolder": "Buscar por Carpeta",
"ButtonCancel": "Cancelar", "ButtonCancel": "Cancelar",
"ButtonCancelEncode": "Cancelar Codificador", "ButtonCancelEncode": "Cancelar Codificador",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Capítulo Anterior", "ButtonPreviousChapter": "Capítulo Anterior",
"ButtonPurgeAllCache": "Purgar Todo el Cache", "ButtonPurgeAllCache": "Purgar Todo el Cache",
"ButtonPurgeItemsCache": "Purgar Elementos de Cache", "ButtonPurgeItemsCache": "Purgar Elementos de Cache",
"ButtonPurgeMediaProgress": "Purgar Progreso de Multimedia",
"ButtonQueueAddItem": "Agregar a la Fila", "ButtonQueueAddItem": "Agregar a la Fila",
"ButtonQueueRemoveItem": "Remover de la Fila", "ButtonQueueRemoveItem": "Remover de la Fila",
"ButtonQuickMatch": "Encontrar Rápido", "ButtonQuickMatch": "Encontrar Rápido",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Elementos en la Colección", "HeaderCollectionItems": "Elementos en la Colección",
"HeaderCover": "Portada", "HeaderCover": "Portada",
"HeaderCurrentDownloads": "Descargando Actualmente", "HeaderCurrentDownloads": "Descargando Actualmente",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Proveedores de metadatos personalizados", "HeaderCustomMetadataProviders": "Proveedores de metadatos personalizados",
"HeaderDetails": "Detalles", "HeaderDetails": "Detalles",
"HeaderDownloadQueue": "Lista de Descarga", "HeaderDownloadQueue": "Lista de Descarga",
@ -279,6 +280,8 @@
"LabelEdit": "Editar", "LabelEdit": "Editar",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Remitente", "LabelEmailSettingsFromAddress": "Remitente",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Seguridad", "LabelEmailSettingsSecure": "Seguridad",
"LabelEmailSettingsSecureHelp": "Si está activado, se usará TLS para conectarse al servidor. Si está apagado, se usará TLS si su servidor tiene soporte para la extensión STARTTLS. En la mayoría de los casos, puede dejar esta opción activada si se está conectando al puerto 465. Apáguela en el caso de usar los puertos 587 o 25. (de nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Si está activado, se usará TLS para conectarse al servidor. Si está apagado, se usará TLS si su servidor tiene soporte para la extensión STARTTLS. En la mayoría de los casos, puede dejar esta opción activada si se está conectando al puerto 465. Apáguela en el caso de usar los puertos 587 o 25. (de nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Probar Dirección", "LabelEmailSettingsTestAddress": "Probar Dirección",
@ -335,6 +338,7 @@
"LabelItem": "Elemento", "LabelItem": "Elemento",
"LabelLanguage": "Lenguaje", "LabelLanguage": "Lenguaje",
"LabelLanguageDefaultServer": "Lenguaje Predeterminado del Servidor", "LabelLanguageDefaultServer": "Lenguaje Predeterminado del Servidor",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Último Libro Agregado", "LabelLastBookAdded": "Último Libro Agregado",
"LabelLastBookUpdated": "Último Libro Actualizado", "LabelLastBookUpdated": "Último Libro Actualizado",
"LabelLastSeen": "Última Vez Visto", "LabelLastSeen": "Última Vez Visto",
@ -346,6 +350,7 @@
"LabelLess": "Menos", "LabelLess": "Menos",
"LabelLibrariesAccessibleToUser": "Bibliotecas Disponibles para el Usuario", "LabelLibrariesAccessibleToUser": "Bibliotecas Disponibles para el Usuario",
"LabelLibrary": "Biblioteca", "LabelLibrary": "Biblioteca",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Elemento de Biblioteca", "LabelLibraryItem": "Elemento de Biblioteca",
"LabelLibraryName": "Nombre de Biblioteca", "LabelLibraryName": "Nombre de Biblioteca",
"LabelLimit": "Limites", "LabelLimit": "Limites",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nueva Contraseña", "LabelNewPassword": "Nueva Contraseña",
"LabelNextBackupDate": "Fecha del Siguiente Respaldo", "LabelNextBackupDate": "Fecha del Siguiente Respaldo",
"LabelNextScheduledRun": "Próxima Ejecución Programada", "LabelNextScheduledRun": "Próxima Ejecución Programada",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Ningún Episodio Seleccionado", "LabelNoEpisodesSelected": "Ningún Episodio Seleccionado",
"LabelNotes": "Notas", "LabelNotes": "Notas",
"LabelNotFinished": "No Terminado", "LabelNotFinished": "No Terminado",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Puede Subir", "LabelPermissionsUpload": "Puede Subir",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Ruta de Acceso/URL de Foto", "LabelPhotoPathURL": "Ruta de Acceso/URL de Foto",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Lista de Reproducción", "LabelPlaylists": "Lista de Reproducción",
"LabelPlayMethod": "Método de Reproducción", "LabelPlayMethod": "Método de Reproducción",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Proveedor", "LabelProvider": "Proveedor",
"LabelPubDate": "Fecha de Publicación", "LabelPubDate": "Fecha de Publicación",
"LabelPublisher": "Editor", "LabelPublisher": "Editor",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Año de Publicación", "LabelPublishYear": "Año de Publicación",
"LabelRead": "Leído", "LabelRead": "Leído",
"LabelReadAgain": "Volver a leer", "LabelReadAgain": "Volver a leer",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "\"Encontrar Rápido\" tratará de agregar portadas y metadatos faltantes de los elementos seleccionados. Habilite la opción de abajo para que \"Encontrar Rápido\" pueda sobrescribir portadas y/o metadatos existentes.", "MessageBatchQuickMatchDescription": "\"Encontrar Rápido\" tratará de agregar portadas y metadatos faltantes de los elementos seleccionados. Habilite la opción de abajo para que \"Encontrar Rápido\" pueda sobrescribir portadas y/o metadatos existentes.",
"MessageBookshelfNoCollections": "No tienes ninguna colección.", "MessageBookshelfNoCollections": "No tienes ninguna colección.",
"MessageBookshelfNoResultsForFilter": "Ningún Resultado para el filtro \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Ningún Resultado para el filtro \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Ninguna Fuente RSS esta abierta", "MessageBookshelfNoRSSFeeds": "Ninguna Fuente RSS esta abierta",
"MessageBookshelfNoSeries": "No tienes ninguna serie", "MessageBookshelfNoSeries": "No tienes ninguna serie",
"MessageChapterEndIsAfter": "El final del capítulo es después del final de su audiolibro.", "MessageChapterEndIsAfter": "El final del capítulo es después del final de su audiolibro.",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como terminados?", "MessageConfirmMarkSeriesFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como terminados?",
"MessageConfirmMarkSeriesNotFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como no terminados?", "MessageConfirmMarkSeriesNotFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como no terminados?",
"MessageConfirmPurgeCache": "Purgar el caché eliminará el directorio completo ubicado en <code>/metadata/cache</code>. <br /><br />¿Está seguro que desea eliminar el directorio del caché?", "MessageConfirmPurgeCache": "Purgar el caché eliminará el directorio completo ubicado en <code>/metadata/cache</code>. <br /><br />¿Está seguro que desea eliminar el directorio del caché?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "¡Advertencia! La integración rápida no realiza copias de seguridad a ninguno de tus archivos de audio. Asegúrate de haber realizado una copia de los mismos previamente. <br><br>¿Deseas continuar?", "MessageConfirmQuickEmbed": "¡Advertencia! La integración rápida no realiza copias de seguridad a ninguno de tus archivos de audio. Asegúrate de haber realizado una copia de los mismos previamente. <br><br>¿Deseas continuar?",
"MessageConfirmRemoveAllChapters": "¿Está seguro de que desea remover todos los capitulos?", "MessageConfirmRemoveAllChapters": "¿Está seguro de que desea remover todos los capitulos?",
"MessageConfirmRemoveAuthor": "¿Está seguro de que desea remover el autor \"{0}\"?", "MessageConfirmRemoveAuthor": "¿Está seguro de que desea remover el autor \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Rakenda", "ButtonApply": "Rakenda",
"ButtonApplyChapters": "Rakenda peatükid", "ButtonApplyChapters": "Rakenda peatükid",
"ButtonAuthors": "Autorid", "ButtonAuthors": "Autorid",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Sirvi kausta", "ButtonBrowseForFolder": "Sirvi kausta",
"ButtonCancel": "Tühista", "ButtonCancel": "Tühista",
"ButtonCancelEncode": "Tühista kodeerimine", "ButtonCancelEncode": "Tühista kodeerimine",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Eelmine peatükk", "ButtonPreviousChapter": "Eelmine peatükk",
"ButtonPurgeAllCache": "Tühjenda kogu vahemälu", "ButtonPurgeAllCache": "Tühjenda kogu vahemälu",
"ButtonPurgeItemsCache": "Tühjenda esemete vahemälu", "ButtonPurgeItemsCache": "Tühjenda esemete vahemälu",
"ButtonPurgeMediaProgress": "Tühjenda meedia edenemine",
"ButtonQueueAddItem": "Lisa järjekorda", "ButtonQueueAddItem": "Lisa järjekorda",
"ButtonQueueRemoveItem": "Eemalda järjekorrast", "ButtonQueueRemoveItem": "Eemalda järjekorrast",
"ButtonQuickMatch": "Kiire sobitamine", "ButtonQuickMatch": "Kiire sobitamine",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Kogu esemed", "HeaderCollectionItems": "Kogu esemed",
"HeaderCover": "Ümbris", "HeaderCover": "Ümbris",
"HeaderCurrentDownloads": "Praegused allalaadimised", "HeaderCurrentDownloads": "Praegused allalaadimised",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Kohandatud metaandmete pakkujad", "HeaderCustomMetadataProviders": "Kohandatud metaandmete pakkujad",
"HeaderDetails": "Detailid", "HeaderDetails": "Detailid",
"HeaderDownloadQueue": "Allalaadimise järjekord", "HeaderDownloadQueue": "Allalaadimise järjekord",
@ -279,6 +280,8 @@
"LabelEdit": "Muuda", "LabelEdit": "Muuda",
"LabelEmail": "E-post", "LabelEmail": "E-post",
"LabelEmailSettingsFromAddress": "Saatja aadress", "LabelEmailSettingsFromAddress": "Saatja aadress",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Turvaline", "LabelEmailSettingsSecure": "Turvaline",
"LabelEmailSettingsSecureHelp": "Kui see on tõene, kasutab ühendus serveriga ühenduse loomisel TLS-i. Kui see on väär, kasutatakse TLS-i, kui server toetab STARTTLS-i laiendust. Enamikul juhtudest seadke see väärtus tõeks, kui ühendate pordile 465. Pordi 587 või 25 korral hoidke seda väär. (nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Kui see on tõene, kasutab ühendus serveriga ühenduse loomisel TLS-i. Kui see on väär, kasutatakse TLS-i, kui server toetab STARTTLS-i laiendust. Enamikul juhtudest seadke see väärtus tõeks, kui ühendate pordile 465. Pordi 587 või 25 korral hoidke seda väär. (nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Testi aadress", "LabelEmailSettingsTestAddress": "Testi aadress",
@ -335,6 +338,7 @@
"LabelItem": "Kirje", "LabelItem": "Kirje",
"LabelLanguage": "Keel", "LabelLanguage": "Keel",
"LabelLanguageDefaultServer": "Vaikeserveri keel", "LabelLanguageDefaultServer": "Vaikeserveri keel",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Viimati lisatud raamat", "LabelLastBookAdded": "Viimati lisatud raamat",
"LabelLastBookUpdated": "Viimati uuendatud raamat", "LabelLastBookUpdated": "Viimati uuendatud raamat",
"LabelLastSeen": "Viimati nähtud", "LabelLastSeen": "Viimati nähtud",
@ -346,6 +350,7 @@
"LabelLess": "Vähem", "LabelLess": "Vähem",
"LabelLibrariesAccessibleToUser": "Kasutajale ligipääsetavad raamatukogud", "LabelLibrariesAccessibleToUser": "Kasutajale ligipääsetavad raamatukogud",
"LabelLibrary": "Raamatukogu", "LabelLibrary": "Raamatukogu",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Raamatukogu kirje", "LabelLibraryItem": "Raamatukogu kirje",
"LabelLibraryName": "Raamatukogu nimi", "LabelLibraryName": "Raamatukogu nimi",
"LabelLimit": "Piirang", "LabelLimit": "Piirang",
@ -381,6 +386,7 @@
"LabelNewPassword": "Uus parool", "LabelNewPassword": "Uus parool",
"LabelNextBackupDate": "Järgmine varukoopia kuupäev", "LabelNextBackupDate": "Järgmine varukoopia kuupäev",
"LabelNextScheduledRun": "Järgmine ajakava järgmine", "LabelNextScheduledRun": "Järgmine ajakava järgmine",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Episoodid pole valitud", "LabelNoEpisodesSelected": "Episoodid pole valitud",
"LabelNotes": "Märkused", "LabelNotes": "Märkused",
"LabelNotFinished": "Ei ole lõpetatud", "LabelNotFinished": "Ei ole lõpetatud",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Saab üles laadida", "LabelPermissionsUpload": "Saab üles laadida",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Foto tee/URL", "LabelPhotoPathURL": "Foto tee/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Mänguloendid", "LabelPlaylists": "Mänguloendid",
"LabelPlayMethod": "Esitusmeetod", "LabelPlayMethod": "Esitusmeetod",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Pakkuja", "LabelProvider": "Pakkuja",
"LabelPubDate": "Avaldamise kuupäev", "LabelPubDate": "Avaldamise kuupäev",
"LabelPublisher": "Kirjastaja", "LabelPublisher": "Kirjastaja",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Aasta avaldamine", "LabelPublishYear": "Aasta avaldamine",
"LabelRead": "Lugenud", "LabelRead": "Lugenud",
"LabelReadAgain": "Loe uuesti", "LabelReadAgain": "Loe uuesti",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Kiire sobitamine üritab lisada valitud üksustele puuduvad kaaned ja metaandmed. Luba allpool olevad valikud, et lubada Kiire sobitamine'il üle kirjutada olemasolevaid kaasi ja/või metaandmeid.", "MessageBatchQuickMatchDescription": "Kiire sobitamine üritab lisada valitud üksustele puuduvad kaaned ja metaandmed. Luba allpool olevad valikud, et lubada Kiire sobitamine'il üle kirjutada olemasolevaid kaasi ja/või metaandmeid.",
"MessageBookshelfNoCollections": "Te pole veel ühtegi kogumit teinud", "MessageBookshelfNoCollections": "Te pole veel ühtegi kogumit teinud",
"MessageBookshelfNoResultsForFilter": "Filtrile \"{0}: {1}\" pole tulemusi", "MessageBookshelfNoResultsForFilter": "Filtrile \"{0}: {1}\" pole tulemusi",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Ühtegi RSS-i voogu pole avatud", "MessageBookshelfNoRSSFeeds": "Ühtegi RSS-i voogu pole avatud",
"MessageBookshelfNoSeries": "Teil pole ühtegi seeriat", "MessageBookshelfNoSeries": "Teil pole ühtegi seeriat",
"MessageChapterEndIsAfter": "Peatüki lõpp on pärast teie heliraamatu lõppu", "MessageChapterEndIsAfter": "Peatüki lõpp on pärast teie heliraamatu lõppu",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Olete kindel, et soovite selle seeria kõik raamatud lõpetatuks märkida?", "MessageConfirmMarkSeriesFinished": "Olete kindel, et soovite selle seeria kõik raamatud lõpetatuks märkida?",
"MessageConfirmMarkSeriesNotFinished": "Olete kindel, et soovite selle seeria kõik raamatud mitte lõpetatuks märkida?", "MessageConfirmMarkSeriesNotFinished": "Olete kindel, et soovite selle seeria kõik raamatud mitte lõpetatuks märkida?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Hoiatus! Quick Embed ei tee varukoopiaid teie helifailidest. Veenduge, et teil oleks varukoopia oma helifailidest. <br><br>Kas soovite jätkata?", "MessageConfirmQuickEmbed": "Hoiatus! Quick Embed ei tee varukoopiaid teie helifailidest. Veenduge, et teil oleks varukoopia oma helifailidest. <br><br>Kas soovite jätkata?",
"MessageConfirmRemoveAllChapters": "Olete kindel, et soovite eemaldada kõik peatükid?", "MessageConfirmRemoveAllChapters": "Olete kindel, et soovite eemaldada kõik peatükid?",
"MessageConfirmRemoveAuthor": "Olete kindel, et soovite autori \"{0}\" eemaldada?", "MessageConfirmRemoveAuthor": "Olete kindel, et soovite autori \"{0}\" eemaldada?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Appliquer", "ButtonApply": "Appliquer",
"ButtonApplyChapters": "Appliquer aux chapitres", "ButtonApplyChapters": "Appliquer aux chapitres",
"ButtonAuthors": "Auteurs", "ButtonAuthors": "Auteurs",
"ButtonBack": "Retour",
"ButtonBrowseForFolder": "Naviguer vers le répertoire", "ButtonBrowseForFolder": "Naviguer vers le répertoire",
"ButtonCancel": "Annuler", "ButtonCancel": "Annuler",
"ButtonCancelEncode": "Annuler lencodage", "ButtonCancelEncode": "Annuler lencodage",
@ -54,9 +55,8 @@
"ButtonPlaylists": "Listes de lecture", "ButtonPlaylists": "Listes de lecture",
"ButtonPrevious": "Précédent", "ButtonPrevious": "Précédent",
"ButtonPreviousChapter": "Chapitre précédent", "ButtonPreviousChapter": "Chapitre précédent",
"ButtonPurgeAllCache": "Purger le cache", "ButtonPurgeAllCache": "Purger tout le cache",
"ButtonPurgeItemsCache": "Purger le cache des articles", "ButtonPurgeItemsCache": "Purger le cache des éléments",
"ButtonPurgeMediaProgress": "Purger la progression des médias",
"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",
"ButtonQuickMatch": "Recherche rapide", "ButtonQuickMatch": "Recherche rapide",
@ -66,7 +66,7 @@
"ButtonRefresh": "Rafraîchir", "ButtonRefresh": "Rafraîchir",
"ButtonRemove": "Supprimer", "ButtonRemove": "Supprimer",
"ButtonRemoveAll": "Supprimer tout", "ButtonRemoveAll": "Supprimer tout",
"ButtonRemoveAllLibraryItems": "Supprimer tous les articles de la bibliothèque", "ButtonRemoveAllLibraryItems": "Supprimer tous les éléments de la bibliothèque",
"ButtonRemoveFromContinueListening": "Ne plus continuer à écouter", "ButtonRemoveFromContinueListening": "Ne plus continuer à écouter",
"ButtonRemoveFromContinueReading": "Ne plus continuer à lire", "ButtonRemoveFromContinueReading": "Ne plus continuer à lire",
"ButtonRemoveSeriesFromContinueSeries": "Ne plus continuer à écouter la série", "ButtonRemoveSeriesFromContinueSeries": "Ne plus continuer à écouter la série",
@ -75,7 +75,7 @@
"ButtonResetToDefault": "Réinitialiser aux valeurs par défaut", "ButtonResetToDefault": "Réinitialiser aux valeurs par défaut",
"ButtonRestore": "Rétablir", "ButtonRestore": "Rétablir",
"ButtonSave": "Sauvegarder", "ButtonSave": "Sauvegarder",
"ButtonSaveAndClose": "Sauvegarder et Fermer", "ButtonSaveAndClose": "Sauvegarder et fermer",
"ButtonSaveTracklist": "Sauvegarder la liste de lecture", "ButtonSaveTracklist": "Sauvegarder la liste de lecture",
"ButtonScan": "Analyser", "ButtonScan": "Analyser",
"ButtonScanLibrary": "Analyser la bibliothèque", "ButtonScanLibrary": "Analyser la bibliothèque",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Entrées de la collection", "HeaderCollectionItems": "Entrées de la collection",
"HeaderCover": "Couverture", "HeaderCover": "Couverture",
"HeaderCurrentDownloads": "Téléchargements en cours", "HeaderCurrentDownloads": "Téléchargements en cours",
"HeaderCustomMessageOnLogin": "Message personnalisé lors de la connexion",
"HeaderCustomMetadataProviders": "Fournisseurs de métadonnées personnalisés", "HeaderCustomMetadataProviders": "Fournisseurs de métadonnées personnalisés",
"HeaderDetails": "Détails", "HeaderDetails": "Détails",
"HeaderDownloadQueue": "File dattente de téléchargements", "HeaderDownloadQueue": "File dattente de téléchargements",
@ -127,7 +128,7 @@
"HeaderFiles": "Fichiers", "HeaderFiles": "Fichiers",
"HeaderFindChapters": "Trouver les chapitres", "HeaderFindChapters": "Trouver les chapitres",
"HeaderIgnoredFiles": "Fichiers ignorés", "HeaderIgnoredFiles": "Fichiers ignorés",
"HeaderItemFiles": "Fichiers des articles", "HeaderItemFiles": "Fichiers des éléments",
"HeaderItemMetadataUtils": "Outils de gestion des métadonnées", "HeaderItemMetadataUtils": "Outils de gestion des métadonnées",
"HeaderLastListeningSession": "Dernière session découte", "HeaderLastListeningSession": "Dernière session découte",
"HeaderLatestEpisodes": "Dernier épisodes", "HeaderLatestEpisodes": "Dernier épisodes",
@ -173,8 +174,8 @@
"HeaderSettingsGeneral": "Général", "HeaderSettingsGeneral": "Général",
"HeaderSettingsScanner": "Analyseur", "HeaderSettingsScanner": "Analyseur",
"HeaderSleepTimer": "Minuterie", "HeaderSleepTimer": "Minuterie",
"HeaderStatsLargestItems": "Articles les plus lourd", "HeaderStatsLargestItems": "Éléments les plus grands",
"HeaderStatsLongestItems": "Articles les plus long (heures)", "HeaderStatsLongestItems": "Éléments les plus long (hrs)",
"HeaderStatsMinutesListeningChart": "Minutes découte (7 derniers jours)", "HeaderStatsMinutesListeningChart": "Minutes découte (7 derniers jours)",
"HeaderStatsRecentSessions": "Sessions récentes", "HeaderStatsRecentSessions": "Sessions récentes",
"HeaderStatsTop10Authors": "Top 10 Auteurs", "HeaderStatsTop10Authors": "Top 10 Auteurs",
@ -191,7 +192,7 @@
"LabelAbridged": "Version courte", "LabelAbridged": "Version courte",
"LabelAbridgedChecked": "Abrégé (vérifié)", "LabelAbridgedChecked": "Abrégé (vérifié)",
"LabelAbridgedUnchecked": "Intégral (non vérifié)", "LabelAbridgedUnchecked": "Intégral (non vérifié)",
"LabelAccessibleBy": "Accessible by", "LabelAccessibleBy": "Accessible par",
"LabelAccountType": "Type de compte", "LabelAccountType": "Type de compte",
"LabelAccountTypeAdmin": "Admin", "LabelAccountTypeAdmin": "Admin",
"LabelAccountTypeGuest": "Invité", "LabelAccountTypeGuest": "Invité",
@ -279,6 +280,8 @@
"LabelEdit": "Modifier", "LabelEdit": "Modifier",
"LabelEmail": "Courriel", "LabelEmail": "Courriel",
"LabelEmailSettingsFromAddress": "Expéditeur", "LabelEmailSettingsFromAddress": "Expéditeur",
"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.",
"LabelEmailSettingsSecure": "Sécurisé", "LabelEmailSettingsSecure": "Sécurisé",
"LabelEmailSettingsSecureHelp": "Utiliser TLS lors de la connexion au serveur, autrement TLS sera utilisé si le serveur prend en charge lextension STARTTLS. Dans la plupart des cas, actviez loption si vous vous connectez au port 465. Désactivez loption pour utiliser port 587 ou 25. (source: nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Utiliser TLS lors de la connexion au serveur, autrement TLS sera utilisé si le serveur prend en charge lextension STARTTLS. Dans la plupart des cas, actviez loption si vous vous connectez au port 465. Désactivez loption pour utiliser port 587 ou 25. (source: nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Adresse de test", "LabelEmailSettingsTestAddress": "Adresse de test",
@ -332,9 +335,10 @@
"LabelIntervalEveryDay": "Tous les jours", "LabelIntervalEveryDay": "Tous les jours",
"LabelIntervalEveryHour": "Toutes les heures", "LabelIntervalEveryHour": "Toutes les heures",
"LabelInvert": "Inverser", "LabelInvert": "Inverser",
"LabelItem": "Article", "LabelItem": "Élément",
"LabelLanguage": "Langue", "LabelLanguage": "Langue",
"LabelLanguageDefaultServer": "Langue par défaut", "LabelLanguageDefaultServer": "Langue par défaut",
"LabelLanguages": "Langues",
"LabelLastBookAdded": "Dernier livre ajouté", "LabelLastBookAdded": "Dernier livre ajouté",
"LabelLastBookUpdated": "Dernier livre mis à jour", "LabelLastBookUpdated": "Dernier livre mis à jour",
"LabelLastSeen": "Vu dernièrement", "LabelLastSeen": "Vu dernièrement",
@ -346,17 +350,18 @@
"LabelLess": "Moins", "LabelLess": "Moins",
"LabelLibrariesAccessibleToUser": "Bibliothèque accessible à lutilisateur", "LabelLibrariesAccessibleToUser": "Bibliothèque accessible à lutilisateur",
"LabelLibrary": "Bibliothèque", "LabelLibrary": "Bibliothèque",
"LabelLibraryItem": "Article de bibliothèque", "LabelLibraryFilterSublistEmpty": "Aucun {0}",
"LabelLibraryItem": "Élément de bibliothèque",
"LabelLibraryName": "Nom de la bibliothèque", "LabelLibraryName": "Nom de la bibliothèque",
"LabelLimit": "Limite", "LabelLimit": "Limite",
"LabelLineSpacing": "Interligne", "LabelLineSpacing": "Espacement des lignes",
"LabelListenAgain": "Écouter à nouveau", "LabelListenAgain": "Écouter à nouveau",
"LabelLogLevelDebug": "Debug", "LabelLogLevelDebug": "Debug",
"LabelLogLevelInfo": "Info", "LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn", "LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Chercher de nouveaux épisode après cette date", "LabelLookForNewEpisodesAfterDate": "Rechercher les nouveaux épisodes après cette date",
"LabelLowestPriority": "Priorité la plus basse", "LabelLowestPriority": "Priorité la plus basse",
"LabelMatchExistingUsersBy": "Faire correspondre les utilisateurs existants par", "LabelMatchExistingUsersBy": "Correspondance avec les utilisateurs existants",
"LabelMatchExistingUsersByDescription": "Utilisé pour connecter les utilisateurs existants. Une fois connectés, les utilisateurs seront associés à un identifiant unique provenant de votre fournisseur SSO.", "LabelMatchExistingUsersByDescription": "Utilisé pour connecter les utilisateurs existants. Une fois connectés, les utilisateurs seront associés à un identifiant unique provenant de votre fournisseur SSO.",
"LabelMediaPlayer": "Lecteur multimédia", "LabelMediaPlayer": "Lecteur multimédia",
"LabelMediaType": "Type de média", "LabelMediaType": "Type de média",
@ -366,8 +371,8 @@
"LabelMetaTags": "Balises de métadonnée", "LabelMetaTags": "Balises de métadonnée",
"LabelMinute": "Minute", "LabelMinute": "Minute",
"LabelMissing": "Manquant", "LabelMissing": "Manquant",
"LabelMissingEbook": "Ne possède pas de livre numérique", "LabelMissingEbook": "Ne possède aucun livre numérique",
"LabelMissingSupplementaryEbook": "Ne possède pas de livre numérique supplémentaire", "LabelMissingSupplementaryEbook": "Ne possède aucun livre numérique supplémentaire",
"LabelMobileRedirectURIs": "URI de redirection mobile autorisés", "LabelMobileRedirectURIs": "URI de redirection mobile autorisés",
"LabelMobileRedirectURIsDescription": "Il sagit dune liste blanche dURI de redirection valides pour les applications mobiles. Celui par défaut est <code>audiobookshelf://oauth</code>, que vous pouvez supprimer ou compléter avec des URIs supplémentaires pour lintégration dapplications tierces. Lutilisation dun astérisque (<code>*</code>) comme seule entrée autorise nimporte quel URI.", "LabelMobileRedirectURIsDescription": "Il sagit dune liste blanche dURI de redirection valides pour les applications mobiles. Celui par défaut est <code>audiobookshelf://oauth</code>, que vous pouvez supprimer ou compléter avec des URIs supplémentaires pour lintégration dapplications tierces. Lutilisation dun astérisque (<code>*</code>) comme seule entrée autorise nimporte quel URI.",
"LabelMore": "Plus", "LabelMore": "Plus",
@ -381,12 +386,13 @@
"LabelNewPassword": "Nouveau mot de passe", "LabelNewPassword": "Nouveau mot de passe",
"LabelNextBackupDate": "Date de la prochaine sauvegarde", "LabelNextBackupDate": "Date de la prochaine sauvegarde",
"LabelNextScheduledRun": "Prochain lancement prévu", "LabelNextScheduledRun": "Prochain lancement prévu",
"LabelNoCustomMetadataProviders": "Aucun fournisseurs de métadonnées personnalisés",
"LabelNoEpisodesSelected": "Aucun épisode sélectionné", "LabelNoEpisodesSelected": "Aucun épisode sélectionné",
"LabelNotes": "Notes", "LabelNotes": "Notes",
"LabelNotFinished": "Non terminé", "LabelNotFinished": "Non terminé",
"LabelNotificationAppriseURL": "URL(s) dApprise", "LabelNotificationAppriseURL": "URL(s) dApprise",
"LabelNotificationAvailableVariables": "Variables disponibles", "LabelNotificationAvailableVariables": "Variables disponibles",
"LabelNotificationBodyTemplate": "Modèle de Message", "LabelNotificationBodyTemplate": "Modèle de message",
"LabelNotificationEvent": "Evènement de Notification", "LabelNotificationEvent": "Evènement de Notification",
"LabelNotificationsMaxFailedAttempts": "Nombres de tentatives denvoi", "LabelNotificationsMaxFailedAttempts": "Nombres de tentatives denvoi",
"LabelNotificationsMaxFailedAttemptsHelp": "La notification est abandonnée une fois ce seuil atteint", "LabelNotificationsMaxFailedAttemptsHelp": "La notification est abandonnée une fois ce seuil atteint",
@ -410,8 +416,9 @@
"LabelPermissionsDownload": "Peut télécharger", "LabelPermissionsDownload": "Peut télécharger",
"LabelPermissionsUpdate": "Peut mettre à jour", "LabelPermissionsUpdate": "Peut mettre à jour",
"LabelPermissionsUpload": "Peut téléverser", "LabelPermissionsUpload": "Peut téléverser",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Bilan de lannée ({0})",
"LabelPhotoPathURL": "Chemin / URL des photos", "LabelPhotoPathURL": "Chemin / URL des photos",
"LabelPlayerChapterNumberMarker": "{0} sur {1}",
"LabelPlaylists": "Listes de lecture", "LabelPlaylists": "Listes de lecture",
"LabelPlayMethod": "Méthode découte", "LabelPlayMethod": "Méthode découte",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,7 +433,8 @@
"LabelProvider": "Fournisseur", "LabelProvider": "Fournisseur",
"LabelPubDate": "Date de publication", "LabelPubDate": "Date de publication",
"LabelPublisher": "Éditeur", "LabelPublisher": "Éditeur",
"LabelPublishYear": "Année dédition", "LabelPublishers": "Éditeurs",
"LabelPublishYear": "Année de publication",
"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",
@ -462,7 +470,7 @@
"LabelSetEbookAsSupplementary": "Définir comme supplémentaire", "LabelSetEbookAsSupplementary": "Définir comme supplémentaire",
"LabelSettingsAudiobooksOnly": "Livres audios seulement", "LabelSettingsAudiobooksOnly": "Livres audios seulement",
"LabelSettingsAudiobooksOnlyHelp": "Lactivation de ce paramètre ignorera les fichiers de type « livre numériques », sauf sils se trouvent dans un dossier spécifique , auquel cas ils seront définis comme des livres numériques supplémentaires.", "LabelSettingsAudiobooksOnlyHelp": "Lactivation de ce paramètre ignorera les fichiers de type « livre numériques », sauf sils se trouvent dans un dossier spécifique , auquel cas ils seront définis comme des livres numériques supplémentaires.",
"LabelSettingsBookshelfViewHelp": "Interface skeuomorphique avec une étagère en bois", "LabelSettingsBookshelfViewHelp": "Interface skeumorphique avec étagères en bois",
"LabelSettingsChromecastSupport": "Support du Chromecast", "LabelSettingsChromecastSupport": "Support du Chromecast",
"LabelSettingsDateFormat": "Format de date", "LabelSettingsDateFormat": "Format de date",
"LabelSettingsDisableWatcher": "Désactiver la surveillance", "LabelSettingsDisableWatcher": "Désactiver la surveillance",
@ -471,43 +479,43 @@
"LabelSettingsEnableWatcher": "Activer la veille", "LabelSettingsEnableWatcher": "Activer la veille",
"LabelSettingsEnableWatcherForLibrary": "Activer la surveillance des dossiers pour la bibliothèque", "LabelSettingsEnableWatcherForLibrary": "Activer la surveillance des dossiers pour la bibliothèque",
"LabelSettingsEnableWatcherHelp": "Active la mise à jour automatique automatique lorsque des modifications de fichiers sont détectées. * nécessite le redémarrage du serveur", "LabelSettingsEnableWatcherHelp": "Active la mise à jour automatique automatique lorsque des modifications de fichiers sont détectées. * nécessite le redémarrage du serveur",
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs", "LabelSettingsEpubsAllowScriptedContent": "Autoriser le contenu scénarisé pour les fichiers EPUB",
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.", "LabelSettingsEpubsAllowScriptedContentHelp": "Autoriser les fichiers EPUB à exécuter des scripts. Il est recommandé de laisser ce paramètre désactivé, sauf si vous faites confiance à la source des fichiers EPUB.",
"LabelSettingsExperimentalFeatures": "Fonctionnalités expérimentales", "LabelSettingsExperimentalFeatures": "Fonctionnalités expérimentales",
"LabelSettingsExperimentalFeaturesHelp": "Fonctionnalités en cours de développement sur lesquelles nous attendons votre retour et expérience. Cliquez pour ouvrir la discussion GitHub.", "LabelSettingsExperimentalFeaturesHelp": "Fonctionnalités en cours de développement sur lesquelles nous attendons votre retour et expérience. Cliquez pour ouvrir la discussion GitHub.",
"LabelSettingsFindCovers": "Chercher des couvertures de livre", "LabelSettingsFindCovers": "Chercher des couvertures de livre",
"LabelSettingsFindCoversHelp": "Si votre livre audio ne possède pas de couverture intégrée ou une image de couverture dans le dossier, lanalyseur tentera de récupérer une couverture.<br>Attention, cela peut augmenter le temps danalyse.", "LabelSettingsFindCoversHelp": "Si votre livre audio ne possède aucune couverture intégrée ou une image de couverture dans le dossier, lanalyseur tentera de récupérer une couverture.<br>Attention, cela peut augmenter le temps danalyse.",
"LabelSettingsHideSingleBookSeries": "Masquer les séries de livres uniques", "LabelSettingsHideSingleBookSeries": "Masquer les séries de livres uniques",
"LabelSettingsHideSingleBookSeriesHelp": "Les séries qui ne comportent quun seul livre seront masquées sur la page de la série et sur les étagères de la page daccueil.", "LabelSettingsHideSingleBookSeriesHelp": "Les séries qui ne comportent quun seul livre seront masquées sur la page de la série et sur les étagères de la page daccueil.",
"LabelSettingsHomePageBookshelfView": "La page daccueil utilise la vue étagère", "LabelSettingsHomePageBookshelfView": "Utiliser la vue étagère sur la page daccueil",
"LabelSettingsLibraryBookshelfView": "La bibliothèque utilise la vue étagère", "LabelSettingsLibraryBookshelfView": "Utiliser la vue étagère pour la bibliothèque",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Sauter les livres précédents dans « Continuer la série »", "LabelSettingsOnlyShowLaterBooksInContinueSeries": "Sauter les livres précédents dans « Continuer la série »",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Létagère de la page daccueil « Continuer la série » affiche le premier livre non commencé dans les séries dont au moins un livre est terminé et aucun livre nest en cours. Lactivation de ce paramètre permet de poursuivre la série à partir du dernier livre terminé au lieu du premier livre non commencé.", "LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Létagère de la page daccueil « Continuer la série » affiche le premier livre non commencé dans les séries dont au moins un livre est terminé et aucun livre nest en cours. Lactivation de ce paramètre permet de poursuivre la série à partir du dernier livre terminé au lieu du premier livre non commencé.",
"LabelSettingsParseSubtitles": "Analyser les sous-titres", "LabelSettingsParseSubtitles": "Analyser les sous-titres",
"LabelSettingsParseSubtitlesHelp": "Extrait les sous-titres depuis le dossier du livre audio.<br>Les sous-titres doivent être séparés par des « - »<br>cest-à-dire : « Titre du livre - Ceci est un sous-titre » aura le sous-titre « Ceci est un sous-titre »", "LabelSettingsParseSubtitlesHelp": "Extrait les sous-titres depuis le dossier du livre audio.<br>Les sous-titres doivent être séparés par des « - »<br>cest-à-dire : « Titre du livre - Ceci est un sous-titre » aura le sous-titre « Ceci est un sous-titre »",
"LabelSettingsPreferMatchedMetadata": "Préférer les métadonnées par correspondance", "LabelSettingsPreferMatchedMetadata": "Préférer les métadonnées par correspondance",
"LabelSettingsPreferMatchedMetadataHelp": "Les métadonnées par correspondance écrase les détails de larticle lors dune recherche par correspondance rapide. Par défaut, la recherche par correspondance rapide ne comblera que les éléments manquant.", "LabelSettingsPreferMatchedMetadataHelp": "Les métadonnées mises en correspondance remplaceront les détails de lélément lors de lutilisation de la correspondance rapide. Par défaut, la correspondance rapide ne remplira que les détails manquants.",
"LabelSettingsSkipMatchingBooksWithASIN": "Ignorer la recherche par correspondance sur les livres ayant déjà un ASIN", "LabelSettingsSkipMatchingBooksWithASIN": "Ignorer la recherche par correspondance pour les livres ayant déjà un ASIN",
"LabelSettingsSkipMatchingBooksWithISBN": "Ignorer la recherche par correspondance sur les livres ayant déjà un ISBN", "LabelSettingsSkipMatchingBooksWithISBN": "Ignorer la recherche par correspondance pour les livres ayant déjà un ISBN",
"LabelSettingsSortingIgnorePrefixes": "Ignorer les préfixes lors du tri", "LabelSettingsSortingIgnorePrefixes": "Ignorer les préfixes lors du tri",
"LabelSettingsSortingIgnorePrefixesHelp": "cest-à-dire : pour le préfixe « le », le livre avec pour titre « Le Titre du Livre » sera trié en tant que « Titre du Livre, Le »", "LabelSettingsSortingIgnorePrefixesHelp": "cest-à-dire : pour le préfixe « le », le livre avec pour titre « Le Titre du Livre » sera trié en tant que « Titre du Livre, Le »",
"LabelSettingsSquareBookCovers": "Utiliser des couvertures carrées", "LabelSettingsSquareBookCovers": "Utiliser des couvertures carrées",
"LabelSettingsSquareBookCoversHelp": "Préférer les couvertures carrées par rapport aux couvertures standards de ratio 1.6:1.", "LabelSettingsSquareBookCoversHelp": "Préférer les couvertures carrées par rapport aux couvertures standards de ratio 1.6:1.",
"LabelSettingsStoreCoversWithItem": "Enregistrer la couverture avec les articles", "LabelSettingsStoreCoversWithItem": "Enregistrer la couverture avec les éléments",
"LabelSettingsStoreCoversWithItemHelp": "Par défaut, les couvertures sont enregistrées dans /metadata/items. Activer ce paramètre enregistrera les couvertures dans le dossier avec les fichiers de larticle. Seul un fichier nommé « cover » sera conservé.", "LabelSettingsStoreCoversWithItemHelp": "Par défaut, les couvertures sont enregistrées dans /metadata/items. Activer ce paramètre enregistrera les couvertures dans le dossier avec les fichiers de élément. Seul un fichier nommé « cover » sera conservé.",
"LabelSettingsStoreMetadataWithItem": "Enregistrer les Métadonnées avec les articles", "LabelSettingsStoreMetadataWithItem": "Enregistrer les métadonnées avec lélément",
"LabelSettingsStoreMetadataWithItemHelp": "Par défaut, les métadonnées sont enregistrées dans /metadata/items", "LabelSettingsStoreMetadataWithItemHelp": "Par défaut, les fichiers de métadonnées sont stockés dans /metadata/items. En activant ce paramètre, les fichiers de métadonnées seront stockés dans les dossiers des éléments de votre bibliothèque.",
"LabelSettingsTimeFormat": "Format dheure", "LabelSettingsTimeFormat": "Format dheure",
"LabelShowAll": "Tout afficher", "LabelShowAll": "Tout afficher",
"LabelShowSeconds": "Afficher le seondes", "LabelShowSeconds": "Afficher les seondes",
"LabelSize": "Taille", "LabelSize": "Taille",
"LabelSleepTimer": "Minuterie", "LabelSleepTimer": "Minuterie de mise en veille",
"LabelSlug": "Balise", "LabelSlug": "Balise",
"LabelStart": "Démarrer", "LabelStart": "Démarrer",
"LabelStarted": "Démarré", "LabelStarted": "Démarré",
"LabelStartedAt": "Démarré à", "LabelStartedAt": "Démarré à",
"LabelStartTime": "Heure de démarrage", "LabelStartTime": "Heure de démarrage",
"LabelStatsAudioTracks": "Pistes Audios", "LabelStatsAudioTracks": "Pistes audio",
"LabelStatsAuthors": "Auteurs", "LabelStatsAuthors": "Auteurs",
"LabelStatsBestDay": "Meilleur jour", "LabelStatsBestDay": "Meilleur jour",
"LabelStatsDailyAverage": "Moyenne journalière", "LabelStatsDailyAverage": "Moyenne journalière",
@ -515,8 +523,8 @@
"LabelStatsDaysListened": "Jours découte", "LabelStatsDaysListened": "Jours découte",
"LabelStatsHours": "Heures", "LabelStatsHours": "Heures",
"LabelStatsInARow": "daffilée(s)", "LabelStatsInARow": "daffilée(s)",
"LabelStatsItemsFinished": "Articles terminés", "LabelStatsItemsFinished": "Élément(s) terminé(s)",
"LabelStatsItemsInLibrary": "Articles dans la bibliothèque", "LabelStatsItemsInLibrary": "Éléments dans la bibliothèque",
"LabelStatsMinutes": "minutes", "LabelStatsMinutes": "minutes",
"LabelStatsMinutesListening": "Minutes découte", "LabelStatsMinutesListening": "Minutes découte",
"LabelStatsOverallDays": "Nombre total de jours", "LabelStatsOverallDays": "Nombre total de jours",
@ -587,10 +595,11 @@
"LabelYourProgress": "Votre progression", "LabelYourProgress": "Votre progression",
"MessageAddToPlayerQueue": "Ajouter en file dattente", "MessageAddToPlayerQueue": "Ajouter en file dattente",
"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>LURL de lAPI 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>LURL de lAPI 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>.",
"MessageBackupsDescription": "Les sauvegardes incluent les utilisateurs, la progression de lecture par utilisateur, les détails des articles des bibliothèques, les paramètres du serveur et les images sauvegardées. Les sauvegardes nincluent pas les fichiers de votre bibliothèque.", "MessageBackupsDescription": "Les sauvegardes incluent les utilisateurs, la progression des utilisateurs, les détails des éléments de la bibliothèque, les paramètres du serveur et les images stockées dans <code>/metadata/items</code> & <code>/metadata/authors</code>. Les sauvegardes <strong>nincluent pas</strong> les fichiers stockés dans les dossiers de votre bibliothèque.",
"MessageBatchQuickMatchDescription": "La recherche par correspondance rapide tentera dajouter les couvertures et les métadonnées manquantes pour les articles sélectionnés. Activer loption suivante pour autoriser la recherche par correspondance à écraser les données existantes.", "MessageBatchQuickMatchDescription": "La recherche par correspondance rapide tentera dajouter les couvertures et métadonnées manquantes pour les éléments sélectionnés. Activez les options ci-dessous pour permettre la Recherche par correspondance décraser les couvertures et/ou métadonnées existantes.",
"MessageBookshelfNoCollections": "Vous navez pas encore de collections", "MessageBookshelfNoCollections": "Vous navez pas encore de collections",
"MessageBookshelfNoResultsForFilter": "Aucun résultat pour le filtre « {0} : {1} »", "MessageBookshelfNoResultsForFilter": "Aucun résultat pour le filtre « {0} : {1} »",
"MessageBookshelfNoResultsForQuery": "Aucun résultat pour la requête",
"MessageBookshelfNoRSSFeeds": "Aucun flux RSS nest ouvert", "MessageBookshelfNoRSSFeeds": "Aucun flux RSS nest ouvert",
"MessageBookshelfNoSeries": "Vous navez aucune série", "MessageBookshelfNoSeries": "Vous navez aucune série",
"MessageChapterEndIsAfter": "La fin du chapitre se situe après la fin de votre livre audio.", "MessageChapterEndIsAfter": "La fin du chapitre se situe après la fin de votre livre audio.",
@ -612,20 +621,21 @@
"MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme terminées ?", "MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme terminées ?",
"MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme comme non terminés ?", "MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme comme non terminés ?",
"MessageConfirmPurgeCache": "La purge du cache supprimera lintégralité du répertoire à <code>/metadata/cache</code>.<br><br>Êtes-vous sûr de vouloir supprimer le répertoire de cache ?", "MessageConfirmPurgeCache": "La purge du cache supprimera lintégralité du répertoire à <code>/metadata/cache</code>.<br><br>Êtes-vous sûr de vouloir supprimer le répertoire de cache ?",
"MessageConfirmQuickEmbed": "Attention ! Lintégration rapide ne sauvegardera pas vos fichiers audio. Assurez-vous davoir effectuer une sauvegarde de vos fichiers audio.<br><br>Souhaitez-vous continuer ?", "MessageConfirmPurgeItemsCache": "Purger le cache des éléments supprimera l'ensemble du répertoire <code>/metadata/cache/items</code>.<br>Êtes-vous sûr ?",
"MessageConfirmQuickEmbed": "Attention ! L'intégration rapide ne permet pas de sauvegarder vos fichiers audio. Assurez-vous davoir effectuer une sauvegarde de vos fichiers audio.<br><br>Souhaitez-vous continuer ?",
"MessageConfirmRemoveAllChapters": "Êtes-vous sûr de vouloir supprimer tous les chapitres ?", "MessageConfirmRemoveAllChapters": "Êtes-vous sûr de vouloir supprimer tous les chapitres ?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Êtes-vous sûr de vouloir supprimer lauteur « {0} » ?",
"MessageConfirmRemoveCollection": "Êtes-vous sûr de vouloir supprimer la collection « {0} » ?", "MessageConfirmRemoveCollection": "Êtes-vous sûr de vouloir supprimer la collection « {0} » ?",
"MessageConfirmRemoveEpisode": "Êtes-vous sûr de vouloir supprimer lépisode « {0} » ?", "MessageConfirmRemoveEpisode": "Êtes-vous sûr de vouloir supprimer lépisode « {0} » ?",
"MessageConfirmRemoveEpisodes": "Êtes-vous sûr de vouloir supprimer {0} épisodes ?", "MessageConfirmRemoveEpisodes": "Êtes-vous sûr de vouloir supprimer {0} épisodes ?",
"MessageConfirmRemoveListeningSessions": "Are you sure you want to remove {0} listening sessions?", "MessageConfirmRemoveListeningSessions": "Are you sure you want to remove {0} listening sessions?",
"MessageConfirmRemoveNarrator": "Êtes-vous sûr de vouloir supprimer le narrateur « {0} » ?", "MessageConfirmRemoveNarrator": "Êtes-vous sûr de vouloir supprimer le narrateur « {0} » ?",
"MessageConfirmRemovePlaylist": "Êtes-vous sûr de vouloir supprimer la liste de lecture « {0} » ?", "MessageConfirmRemovePlaylist": "Êtes-vous sûr de vouloir supprimer la liste de lecture « {0} » ?",
"MessageConfirmRenameGenre": "Êtes-vous sûr de vouloir renommer le genre « {0} » en « {1} » pour tous les articles ?", "MessageConfirmRenameGenre": "Êtes-vous sûr de vouloir renommer le genre « {0} » en « {1} » pour tous les éléments ?",
"MessageConfirmRenameGenreMergeNote": "Information: Ce genre existe déjà et sera fusionné.", "MessageConfirmRenameGenreMergeNote": "Information : ce genre existe déjà et sera fusionné.",
"MessageConfirmRenameGenreWarning": "Attention ! Un genre similaire avec une casse différente existe déjà « {0} ».", "MessageConfirmRenameGenreWarning": "Attention ! Un genre similaire avec une casse différente existe déjà « {0} ».",
"MessageConfirmRenameTag": "Êtes-vous sûr de vouloir renommer létiquette « {0} » en « {1} » pour tous les articles ?", "MessageConfirmRenameTag": "Êtes-vous sûr de vouloir renommer létiquette « {0} » en « {1} » pour tous les éléments ?",
"MessageConfirmRenameTagMergeNote": "Information: Cette étiquette existe déjà et sera fusionnée.", "MessageConfirmRenameTagMergeNote": "Information : Cette étiquette existe déjà et sera fusionnée.",
"MessageConfirmRenameTagWarning": "Attention ! Une étiquette similaire avec une casse différente existe déjà « {0} ».", "MessageConfirmRenameTagWarning": "Attention ! Une étiquette similaire avec une casse différente existe déjà « {0} ».",
"MessageConfirmReScanLibraryItems": "Êtes-vous sûr de vouloir re-analyser {0} éléments ?", "MessageConfirmReScanLibraryItems": "Êtes-vous sûr de vouloir re-analyser {0} éléments ?",
"MessageConfirmSendEbookToDevice": "Êtes-vous sûr de vouloir envoyer le livre numérique {0} « {1} » à lappareil « {2} »?", "MessageConfirmSendEbookToDevice": "Êtes-vous sûr de vouloir envoyer le livre numérique {0} « {1} » à lappareil « {2} »?",
@ -633,22 +643,22 @@
"MessageDragFilesIntoTrackOrder": "Faites glisser les fichiers dans lordre correct des pistes", "MessageDragFilesIntoTrackOrder": "Faites glisser les fichiers dans lordre correct des pistes",
"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": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.", "MessageEreaderDevices": "Pour garantir lenvoie des livres électroniques, il se peut que vous deviez ajouter ladresse électronique ci-dessus en tant quexpéditeur valide pour chaque appareil répertorié ci-dessous.",
"MessageFeedURLWillBe": "LURL du flux sera {0}", "MessageFeedURLWillBe": "LURL du flux sera {0}",
"MessageFetching": "Récupération…", "MessageFetching": "Récupération…",
"MessageForceReScanDescription": "analysera de nouveau tous les fichiers. Les étiquettes ID3 des fichiers audio, les fichiers OPF et les fichiers texte seront analysés comme sils étaient nouveaux.", "MessageForceReScanDescription": "analysera de nouveau tous les fichiers. Les étiquettes ID3 des fichiers audio, les fichiers OPF et les fichiers texte seront analysés comme sils étaient nouveaux.",
"MessageImportantNotice": "Information importante !", "MessageImportantNotice": "Information importante !",
"MessageInsertChapterBelow": "Insérer le chapitre ci-dessous", "MessageInsertChapterBelow": "Insérer le chapitre ci-dessous",
"MessageItemsSelected": "{0} articles sélectionnés", "MessageItemsSelected": "{0} éléments sélectionnés",
"MessageItemsUpdated": "{0} articles mis à jour", "MessageItemsUpdated": "{0} éléments mis à jour",
"MessageJoinUsOn": "Rejoignez-nous sur", "MessageJoinUsOn": "Rejoignez-nous sur",
"MessageListeningSessionsInTheLastYear": "{0} sessions découte lan dernier", "MessageListeningSessionsInTheLastYear": "{0} sessions découte lan dernier",
"MessageLoading": "Chargement…", "MessageLoading": "Chargement…",
"MessageLoadingFolders": "Chargement des dossiers…", "MessageLoadingFolders": "Chargement des dossiers…",
"MessageLogsDescription": "Les journaux sont stockés dans <code>/metadata/logs</code> sous forme de fichiers JSON. Les journaux dincidents sont stockés dans <code>/metadata/logs/crash_logs.txt</code>.", "MessageLogsDescription": "Les journaux sont stockés dans <code>/metadata/logs</code> sous forme de fichiers JSON. Les journaux dincidents sont stockés dans <code>/metadata/logs/crash_logs.txt</code>.",
"MessageM4BFailed": "M4B échec", "MessageM4BFailed": "M4B a échoué !",
"MessageM4BFinished": "M4B terminé", "MessageM4BFinished": "M4B terminé !",
"MessageMapChapterTitles": "Faire correspondre les titres des chapitres aux chapitres existants de votre livre audio sans ajuster lhorodatage.", "MessageMapChapterTitles": "Faire correspondre les titres de chapitres avec ceux de vos livres audio existants sans ajuster les horodatages.",
"MessageMarkAllEpisodesFinished": "Marquer tous les épisodes terminés", "MessageMarkAllEpisodesFinished": "Marquer tous les épisodes terminés",
"MessageMarkAllEpisodesNotFinished": "Marquer tous les épisodes non terminés", "MessageMarkAllEpisodesNotFinished": "Marquer tous les épisodes non terminés",
"MessageMarkAsFinished": "Marquer comme terminé", "MessageMarkAsFinished": "Marquer comme terminé",
@ -663,14 +673,14 @@
"MessageNoCoversFound": "Aucune couverture trouvée", "MessageNoCoversFound": "Aucune couverture trouvée",
"MessageNoDescription": "Aucune description", "MessageNoDescription": "Aucune description",
"MessageNoDownloadsInProgress": "Aucun téléchargement en cours", "MessageNoDownloadsInProgress": "Aucun téléchargement en cours",
"MessageNoDownloadsQueued": "Aucun téléchargement en file dattente", "MessageNoDownloadsQueued": "Aucun téléchargement en attente",
"MessageNoEpisodeMatchesFound": "Aucune correspondance dépisode trouvée", "MessageNoEpisodeMatchesFound": "Aucune correspondance dépisode trouvée",
"MessageNoEpisodes": "Aucun épisode", "MessageNoEpisodes": "Aucun épisode",
"MessageNoFoldersAvailable": "Aucun dossier disponible", "MessageNoFoldersAvailable": "Aucun dossiers disponible",
"MessageNoGenres": "Aucun genre", "MessageNoGenres": "Aucun genre",
"MessageNoIssues": "Aucune parution", "MessageNoIssues": "Aucune parution",
"MessageNoItems": "Aucun article", "MessageNoItems": "Aucun élément",
"MessageNoItemsFound": "Aucun article trouvé", "MessageNoItemsFound": "Aucun élément trouvé",
"MessageNoListeningSessions": "Aucune session découte en cours", "MessageNoListeningSessions": "Aucune session découte en cours",
"MessageNoLogs": "Aucun journaux", "MessageNoLogs": "Aucun journaux",
"MessageNoMediaProgress": "Aucun média en cours", "MessageNoMediaProgress": "Aucun média en cours",
@ -679,7 +689,7 @@
"MessageNoResults": "Aucun résultat", "MessageNoResults": "Aucun résultat",
"MessageNoSearchResultsFor": "Aucun résultat pour la recherche « {0} »", "MessageNoSearchResultsFor": "Aucun résultat pour la recherche « {0} »",
"MessageNoSeries": "Aucune série", "MessageNoSeries": "Aucune série",
"MessageNoTags": "Aucune détiquettes", "MessageNoTags": "Aucune étiquette",
"MessageNoTasksRunning": "Aucune tâche en cours", "MessageNoTasksRunning": "Aucune tâche en cours",
"MessageNotYetImplemented": "Non implémenté", "MessageNotYetImplemented": "Non implémenté",
"MessageNoUpdateNecessary": "Aucune mise à jour nécessaire", "MessageNoUpdateNecessary": "Aucune mise à jour nécessaire",
@ -753,8 +763,8 @@
"ToastCachePurgeSuccess": "Cache purgé avec succès", "ToastCachePurgeSuccess": "Cache purgé avec succès",
"ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs", "ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs",
"ToastChaptersMustHaveTitles": "Les chapitre doivent avoir un titre", "ToastChaptersMustHaveTitles": "Les chapitre doivent avoir un titre",
"ToastCollectionItemsRemoveFailed": "Échec de la suppression de(s) article(s) de la collection", "ToastCollectionItemsRemoveFailed": "Échec de la suppression dun ou plusieurs éléments de la collection",
"ToastCollectionItemsRemoveSuccess": "Article(s) supprimé(s) de la collection", "ToastCollectionItemsRemoveSuccess": "Élément(s) supprimé(s) de la collection",
"ToastCollectionRemoveFailed": "Échec de la suppression de la collection", "ToastCollectionRemoveFailed": "Échec de la suppression de la collection",
"ToastCollectionRemoveSuccess": "Collection supprimée", "ToastCollectionRemoveSuccess": "Collection supprimée",
"ToastCollectionUpdateFailed": "Échec de la mise à jour de la collection", "ToastCollectionUpdateFailed": "Échec de la mise à jour de la collection",
@ -762,11 +772,11 @@
"ToastDeleteFileFailed": "Échec de la suppression du fichier", "ToastDeleteFileFailed": "Échec de la suppression du fichier",
"ToastDeleteFileSuccess": "Fichier supprimé", "ToastDeleteFileSuccess": "Fichier supprimé",
"ToastFailedToLoadData": "Échec du chargement des données", "ToastFailedToLoadData": "Échec du chargement des données",
"ToastItemCoverUpdateFailed": "Échec de la mise à jour de la couverture de larticle", "ToastItemCoverUpdateFailed": "Échec de la mise à jour de la couverture de lélément",
"ToastItemCoverUpdateSuccess": "Couverture de larticle mise à jour", "ToastItemCoverUpdateSuccess": "Couverture mise à jour",
"ToastItemDetailsUpdateFailed": "Échec de la mise à jour des détails de larticle", "ToastItemDetailsUpdateFailed": "Échec de la mise à jour des détails de lélément",
"ToastItemDetailsUpdateSuccess": "Détails de larticle mis à jour", "ToastItemDetailsUpdateSuccess": "Détails de lélément mis à jour",
"ToastItemDetailsUpdateUnneeded": "Pas de mise à jour nécessaire sur les détails de larticle", "ToastItemDetailsUpdateUnneeded": "Aucune mise à jour nest nécessaire pour les détails de lélément",
"ToastItemMarkedAsFinishedFailed": "Échec de lannotation terminée", "ToastItemMarkedAsFinishedFailed": "Échec de lannotation terminée",
"ToastItemMarkedAsFinishedSuccess": "Article marqué comme terminé", "ToastItemMarkedAsFinishedSuccess": "Article marqué comme terminé",
"ToastItemMarkedAsNotFinishedFailed": "Échec de lannotation non-terminée", "ToastItemMarkedAsNotFinishedFailed": "Échec de lannotation non-terminée",
@ -785,10 +795,10 @@
"ToastPlaylistRemoveSuccess": "Liste de lecture supprimée", "ToastPlaylistRemoveSuccess": "Liste de lecture supprimée",
"ToastPlaylistUpdateFailed": "Échec de la mise à jour de la liste de lecture", "ToastPlaylistUpdateFailed": "Échec de la mise à jour de la liste de lecture",
"ToastPlaylistUpdateSuccess": "Liste de lecture mise à jour", "ToastPlaylistUpdateSuccess": "Liste de lecture mise à jour",
"ToastPodcastCreateFailed": "Échec de la création du Podcast", "ToastPodcastCreateFailed": "Échec de la création du podcast",
"ToastPodcastCreateSuccess": "Podcast créé", "ToastPodcastCreateSuccess": "Podcast créé avec succès",
"ToastRemoveItemFromCollectionFailed": "Échec de la suppression de larticle de la collection", "ToastRemoveItemFromCollectionFailed": "Échec de la suppression dun élément de la collection",
"ToastRemoveItemFromCollectionSuccess": "Article supprimé de la collection", "ToastRemoveItemFromCollectionSuccess": "Élément supprimé de la collection",
"ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS", "ToastRSSFeedCloseFailed": "Échec de la fermeture du flux RSS",
"ToastRSSFeedCloseSuccess": "Flux RSS fermé", "ToastRSSFeedCloseSuccess": "Flux RSS fermé",
"ToastSendEbookToDeviceFailed": "Échec de lenvoi du livre numérique à lappareil", "ToastSendEbookToDeviceFailed": "Échec de lenvoi du livre numérique à lappareil",
@ -807,4 +817,4 @@
"ToastSortingPrefixesUpdateSuccess": "Mise à jour des préfixes de tri ({0} élément)", "ToastSortingPrefixesUpdateSuccess": "Mise à jour des préfixes de tri ({0} élément)",
"ToastUserDeleteFailed": "Échec de la suppression de lutilisateur", "ToastUserDeleteFailed": "Échec de la suppression de lutilisateur",
"ToastUserDeleteSuccess": "Utilisateur supprimé" "ToastUserDeleteSuccess": "Utilisateur supprimé"
} }

View file

@ -9,6 +9,7 @@
"ButtonApply": "લાગુ કરો", "ButtonApply": "લાગુ કરો",
"ButtonApplyChapters": "પ્રકરણો લાગુ કરો", "ButtonApplyChapters": "પ્રકરણો લાગુ કરો",
"ButtonAuthors": "લેખકો", "ButtonAuthors": "લેખકો",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "ફોલ્ડર માટે જુઓ", "ButtonBrowseForFolder": "ફોલ્ડર માટે જુઓ",
"ButtonCancel": "રદ કરો", "ButtonCancel": "રદ કરો",
"ButtonCancelEncode": "એન્કોડ રદ કરો", "ButtonCancelEncode": "એન્કોડ રદ કરો",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "બધો Cache કાઢી નાખો", "ButtonPurgeAllCache": "બધો Cache કાઢી નાખો",
"ButtonPurgeItemsCache": "વસ્તુઓનો Cache કાઢી નાખો", "ButtonPurgeItemsCache": "વસ્તુઓનો Cache કાઢી નાખો",
"ButtonPurgeMediaProgress": "બધું સાંભળ્યું કાઢી નાખો",
"ButtonQueueAddItem": "કતારમાં ઉમેરો", "ButtonQueueAddItem": "કતારમાં ઉમેરો",
"ButtonQueueRemoveItem": "કતારથી કાઢી નાખો", "ButtonQueueRemoveItem": "કતારથી કાઢી નાખો",
"ButtonQuickMatch": "ઝડપી મેળ ખવડાવો", "ButtonQuickMatch": "ઝડપી મેળ ખવડાવો",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "સંગ્રહ વસ્તુઓ", "HeaderCollectionItems": "સંગ્રહ વસ્તુઓ",
"HeaderCover": "આવરણ", "HeaderCover": "આવરણ",
"HeaderCurrentDownloads": "વર્તમાન ડાઉનલોડ્સ", "HeaderCurrentDownloads": "વર્તમાન ડાઉનલોડ્સ",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "વિગતો", "HeaderDetails": "વિગતો",
"HeaderDownloadQueue": "ડાઉનલોડ કતાર", "HeaderDownloadQueue": "ડાઉનલોડ કતાર",
@ -279,6 +280,8 @@
"LabelEdit": "Edit", "LabelEdit": "Edit",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "From Address", "LabelEmailSettingsFromAddress": "From Address",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Secure", "LabelEmailSettingsSecure": "Secure",
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Address", "LabelEmailSettingsTestAddress": "Test Address",
@ -335,6 +338,7 @@
"LabelItem": "Item", "LabelItem": "Item",
"LabelLanguage": "Language", "LabelLanguage": "Language",
"LabelLanguageDefaultServer": "Default Server Language", "LabelLanguageDefaultServer": "Default Server Language",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Last Book Added", "LabelLastBookAdded": "Last Book Added",
"LabelLastBookUpdated": "Last Book Updated", "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Last Seen", "LabelLastSeen": "Last Seen",
@ -346,6 +350,7 @@
"LabelLess": "Less", "LabelLess": "Less",
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User", "LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
"LabelLibrary": "Library", "LabelLibrary": "Library",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Library Item", "LabelLibraryItem": "Library Item",
"LabelLibraryName": "Library Name", "LabelLibraryName": "Library Name",
"LabelLimit": "Limit", "LabelLimit": "Limit",
@ -381,6 +386,7 @@
"LabelNewPassword": "New Password", "LabelNewPassword": "New Password",
"LabelNextBackupDate": "Next backup date", "LabelNextBackupDate": "Next backup date",
"LabelNextScheduledRun": "Next scheduled run", "LabelNextScheduledRun": "Next scheduled run",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "No episodes selected", "LabelNoEpisodesSelected": "No episodes selected",
"LabelNotes": "Notes", "LabelNotes": "Notes",
"LabelNotFinished": "Not Finished", "LabelNotFinished": "Not Finished",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Can Upload", "LabelPermissionsUpload": "Can Upload",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Photo Path/URL", "LabelPhotoPathURL": "Photo Path/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Playlists", "LabelPlaylists": "Playlists",
"LabelPlayMethod": "Play Method", "LabelPlayMethod": "Play Method",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Provider", "LabelProvider": "Provider",
"LabelPubDate": "Pub Date", "LabelPubDate": "Pub Date",
"LabelPublisher": "Publisher", "LabelPublisher": "Publisher",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Publish Year", "LabelPublishYear": "Publish Year",
"LabelRead": "Read", "LabelRead": "Read",
"LabelReadAgain": "Read Again", "LabelReadAgain": "Read Again",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.", "MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.",
"MessageBookshelfNoCollections": "You haven't made any collections yet", "MessageBookshelfNoCollections": "You haven't made any collections yet",
"MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open", "MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
"MessageBookshelfNoSeries": "You have no series", "MessageBookshelfNoSeries": "You have no series",
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook", "MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?", "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?", "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "החל", "ButtonApply": "החל",
"ButtonApplyChapters": "החל פרקים", "ButtonApplyChapters": "החל פרקים",
"ButtonAuthors": "יוצרים", "ButtonAuthors": "יוצרים",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "עיין בתיקייה", "ButtonBrowseForFolder": "עיין בתיקייה",
"ButtonCancel": "בטל", "ButtonCancel": "בטל",
"ButtonCancelEncode": "בטל קידוד", "ButtonCancelEncode": "בטל קידוד",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "פרק קודם", "ButtonPreviousChapter": "פרק קודם",
"ButtonPurgeAllCache": "נקה את כל המטמון", "ButtonPurgeAllCache": "נקה את כל המטמון",
"ButtonPurgeItemsCache": "נקה את מטמון הפריטים", "ButtonPurgeItemsCache": "נקה את מטמון הפריטים",
"ButtonPurgeMediaProgress": "נקה את ההתקדמות במדיה",
"ButtonQueueAddItem": "הוסף לתור", "ButtonQueueAddItem": "הוסף לתור",
"ButtonQueueRemoveItem": "הסר מהתור", "ButtonQueueRemoveItem": "הסר מהתור",
"ButtonQuickMatch": "התאמה מהירה", "ButtonQuickMatch": "התאמה מהירה",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "פריטי אוסף", "HeaderCollectionItems": "פריטי אוסף",
"HeaderCover": "כריכה", "HeaderCover": "כריכה",
"HeaderCurrentDownloads": "הורדות נוכחיות", "HeaderCurrentDownloads": "הורדות נוכחיות",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "ספקי מטא-נתונים מותאמים אישית", "HeaderCustomMetadataProviders": "ספקי מטא-נתונים מותאמים אישית",
"HeaderDetails": "פרטים", "HeaderDetails": "פרטים",
"HeaderDownloadQueue": "תור הורדה", "HeaderDownloadQueue": "תור הורדה",
@ -279,6 +280,8 @@
"LabelEdit": "עריכה", "LabelEdit": "עריכה",
"LabelEmail": "דואר אלקטרוני", "LabelEmail": "דואר אלקטרוני",
"LabelEmailSettingsFromAddress": "מאת", "LabelEmailSettingsFromAddress": "מאת",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "מאובטח", "LabelEmailSettingsSecure": "מאובטח",
"LabelEmailSettingsSecureHelp": "אם מופעל, החיבור ישתמש ב-TLS בעת ההתחברות לשרת. אם לא, אז TLS יהיה בשימוש אם השרת תומך בהרחבת STARTTLS. ברוב המקרים מומלץ להפעיל את הגדרה זו אם אתה מתחבר לפורט 465. לפורט 587 או 25, השאר כבוי. (from nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "אם מופעל, החיבור ישתמש ב-TLS בעת ההתחברות לשרת. אם לא, אז TLS יהיה בשימוש אם השרת תומך בהרחבת STARTTLS. ברוב המקרים מומלץ להפעיל את הגדרה זו אם אתה מתחבר לפורט 465. לפורט 587 או 25, השאר כבוי. (from nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "כתובת לבדיקה", "LabelEmailSettingsTestAddress": "כתובת לבדיקה",
@ -335,6 +338,7 @@
"LabelItem": "פריט", "LabelItem": "פריט",
"LabelLanguage": "שפה", "LabelLanguage": "שפה",
"LabelLanguageDefaultServer": "שפת ברירת המחדל של השרת", "LabelLanguageDefaultServer": "שפת ברירת המחדל של השרת",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "הספר האחרון שנוסף", "LabelLastBookAdded": "הספר האחרון שנוסף",
"LabelLastBookUpdated": "הספר האחרון שעודכן", "LabelLastBookUpdated": "הספר האחרון שעודכן",
"LabelLastSeen": "נראה לאחרונה", "LabelLastSeen": "נראה לאחרונה",
@ -346,6 +350,7 @@
"LabelLess": "פחות", "LabelLess": "פחות",
"LabelLibrariesAccessibleToUser": "ספריות נגישות למשתמש", "LabelLibrariesAccessibleToUser": "ספריות נגישות למשתמש",
"LabelLibrary": "ספרייה", "LabelLibrary": "ספרייה",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "פריט ספרייה", "LabelLibraryItem": "פריט ספרייה",
"LabelLibraryName": "שם הספרייה", "LabelLibraryName": "שם הספרייה",
"LabelLimit": "מגבלה", "LabelLimit": "מגבלה",
@ -381,6 +386,7 @@
"LabelNewPassword": "סיסמה חדשה", "LabelNewPassword": "סיסמה חדשה",
"LabelNextBackupDate": "תאריך הגיבוי הבא", "LabelNextBackupDate": "תאריך הגיבוי הבא",
"LabelNextScheduledRun": "הרצה מתוזמנת הבאה", "LabelNextScheduledRun": "הרצה מתוזמנת הבאה",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "לא נבחרו פרקים", "LabelNoEpisodesSelected": "לא נבחרו פרקים",
"LabelNotes": "הערות", "LabelNotes": "הערות",
"LabelNotFinished": "לא הושלם", "LabelNotFinished": "לא הושלם",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "מותר להעלות", "LabelPermissionsUpload": "מותר להעלות",
"LabelPersonalYearReview": "השנה שלך בסקירה ({0})", "LabelPersonalYearReview": "השנה שלך בסקירה ({0})",
"LabelPhotoPathURL": "נתיב/URL לתמונה", "LabelPhotoPathURL": "נתיב/URL לתמונה",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "רשימות השמעה", "LabelPlaylists": "רשימות השמעה",
"LabelPlayMethod": "שיטת הפעלה", "LabelPlayMethod": "שיטת הפעלה",
"LabelPodcast": "פודקאסט", "LabelPodcast": "פודקאסט",
@ -426,6 +433,7 @@
"LabelProvider": "ספק", "LabelProvider": "ספק",
"LabelPubDate": "תאריך פרסום", "LabelPubDate": "תאריך פרסום",
"LabelPublisher": "מוציא לאור", "LabelPublisher": "מוציא לאור",
"LabelPublishers": "Publishers",
"LabelPublishYear": "שנת הפרסום", "LabelPublishYear": "שנת הפרסום",
"LabelRead": "קריאה", "LabelRead": "קריאה",
"LabelReadAgain": "קרא שוב", "LabelReadAgain": "קרא שוב",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "התאמה מהירה תנסה להוסיף כריכות ומטה-נתונים חסרים עבור הפריטים הנבחרים. הפעל את האפשרויות למטה כדי לאפשר להתאמה מהירה להחליף כריכות קיימות ו/או מטה-נתונים.", "MessageBatchQuickMatchDescription": "התאמה מהירה תנסה להוסיף כריכות ומטה-נתונים חסרים עבור הפריטים הנבחרים. הפעל את האפשרויות למטה כדי לאפשר להתאמה מהירה להחליף כריכות קיימות ו/או מטה-נתונים.",
"MessageBookshelfNoCollections": "עדיין לא יצרת אוספים", "MessageBookshelfNoCollections": "עדיין לא יצרת אוספים",
"MessageBookshelfNoResultsForFilter": "אין תוצאות עבור סינון \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "אין תוצאות עבור סינון \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "אין ערוצי RSS פתוחים", "MessageBookshelfNoRSSFeeds": "אין ערוצי RSS פתוחים",
"MessageBookshelfNoSeries": "אין לך סדרות", "MessageBookshelfNoSeries": "אין לך סדרות",
"MessageChapterEndIsAfter": "זמן סיום הפרק אחרי סיום הספר הקולי שלך", "MessageChapterEndIsAfter": "זמן סיום הפרק אחרי סיום הספר הקולי שלך",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כהסתיימו?", "MessageConfirmMarkSeriesFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כהסתיימו?",
"MessageConfirmMarkSeriesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כלא הסתיימו?", "MessageConfirmMarkSeriesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כלא הסתיימו?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "אזהרה! הטמעה מהירה לא תגבה גיבוי של קבצי האודיו שלך. וודא שיש לך גיבוי של קבצי האודיו שלך. <br><br>האם ברצונך להמשיך?", "MessageConfirmQuickEmbed": "אזהרה! הטמעה מהירה לא תגבה גיבוי של קבצי האודיו שלך. וודא שיש לך גיבוי של קבצי האודיו שלך. <br><br>האם ברצונך להמשיך?",
"MessageConfirmRemoveAllChapters": "האם אתה בטוח שברצונך להסיר את כל הפרקים?", "MessageConfirmRemoveAllChapters": "האם אתה בטוח שברצונך להסיר את כל הפרקים?",
"MessageConfirmRemoveAuthor": "האם אתה בטוח שברצונך להסיר את המחבר \"{0}\"?", "MessageConfirmRemoveAuthor": "האם אתה בטוח שברצונך להסיר את המחבר \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "लागू करें", "ButtonApply": "लागू करें",
"ButtonApplyChapters": "अध्यायों में परिवर्तन लागू करें", "ButtonApplyChapters": "अध्यायों में परिवर्तन लागू करें",
"ButtonAuthors": "लेखक", "ButtonAuthors": "लेखक",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "फ़ोल्डर खोजें", "ButtonBrowseForFolder": "फ़ोल्डर खोजें",
"ButtonCancel": "रद्द करें", "ButtonCancel": "रद्द करें",
"ButtonCancelEncode": "एनकोड रद्द करें", "ButtonCancelEncode": "एनकोड रद्द करें",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "सभी Cache मिटाएं", "ButtonPurgeAllCache": "सभी Cache मिटाएं",
"ButtonPurgeItemsCache": "आइटम Cache मिटाएं", "ButtonPurgeItemsCache": "आइटम Cache मिटाएं",
"ButtonPurgeMediaProgress": "अभी तक सुना हुआ सब हटा दे",
"ButtonQueueAddItem": "क़तार में जोड़ें", "ButtonQueueAddItem": "क़तार में जोड़ें",
"ButtonQueueRemoveItem": "कतार से हटाएं", "ButtonQueueRemoveItem": "कतार से हटाएं",
"ButtonQuickMatch": "जल्दी से समानता की तलाश करें", "ButtonQuickMatch": "जल्दी से समानता की तलाश करें",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Collection Items", "HeaderCollectionItems": "Collection Items",
"HeaderCover": "Cover", "HeaderCover": "Cover",
"HeaderCurrentDownloads": "Current Downloads", "HeaderCurrentDownloads": "Current Downloads",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Details", "HeaderDetails": "Details",
"HeaderDownloadQueue": "Download Queue", "HeaderDownloadQueue": "Download Queue",
@ -279,6 +280,8 @@
"LabelEdit": "Edit", "LabelEdit": "Edit",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "From Address", "LabelEmailSettingsFromAddress": "From Address",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Secure", "LabelEmailSettingsSecure": "Secure",
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Address", "LabelEmailSettingsTestAddress": "Test Address",
@ -335,6 +338,7 @@
"LabelItem": "Item", "LabelItem": "Item",
"LabelLanguage": "Language", "LabelLanguage": "Language",
"LabelLanguageDefaultServer": "Default Server Language", "LabelLanguageDefaultServer": "Default Server Language",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Last Book Added", "LabelLastBookAdded": "Last Book Added",
"LabelLastBookUpdated": "Last Book Updated", "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Last Seen", "LabelLastSeen": "Last Seen",
@ -346,6 +350,7 @@
"LabelLess": "Less", "LabelLess": "Less",
"LabelLibrariesAccessibleToUser": "Libraries Accessible to User", "LabelLibrariesAccessibleToUser": "Libraries Accessible to User",
"LabelLibrary": "Library", "LabelLibrary": "Library",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Library Item", "LabelLibraryItem": "Library Item",
"LabelLibraryName": "Library Name", "LabelLibraryName": "Library Name",
"LabelLimit": "Limit", "LabelLimit": "Limit",
@ -381,6 +386,7 @@
"LabelNewPassword": "New Password", "LabelNewPassword": "New Password",
"LabelNextBackupDate": "Next backup date", "LabelNextBackupDate": "Next backup date",
"LabelNextScheduledRun": "Next scheduled run", "LabelNextScheduledRun": "Next scheduled run",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "No episodes selected", "LabelNoEpisodesSelected": "No episodes selected",
"LabelNotes": "Notes", "LabelNotes": "Notes",
"LabelNotFinished": "Not Finished", "LabelNotFinished": "Not Finished",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Can Upload", "LabelPermissionsUpload": "Can Upload",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Photo Path/URL", "LabelPhotoPathURL": "Photo Path/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Playlists", "LabelPlaylists": "Playlists",
"LabelPlayMethod": "Play Method", "LabelPlayMethod": "Play Method",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Provider", "LabelProvider": "Provider",
"LabelPubDate": "Pub Date", "LabelPubDate": "Pub Date",
"LabelPublisher": "Publisher", "LabelPublisher": "Publisher",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Publish Year", "LabelPublishYear": "Publish Year",
"LabelRead": "Read", "LabelRead": "Read",
"LabelReadAgain": "Read Again", "LabelReadAgain": "Read Again",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.", "MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.",
"MessageBookshelfNoCollections": "You haven't made any collections yet", "MessageBookshelfNoCollections": "You haven't made any collections yet",
"MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open", "MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
"MessageBookshelfNoSeries": "You have no series", "MessageBookshelfNoSeries": "You have no series",
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook", "MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?", "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?", "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Primijeni", "ButtonApply": "Primijeni",
"ButtonApplyChapters": "Primijeni poglavlja", "ButtonApplyChapters": "Primijeni poglavlja",
"ButtonAuthors": "Autori", "ButtonAuthors": "Autori",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Browse for Folder", "ButtonBrowseForFolder": "Browse for Folder",
"ButtonCancel": "Odustani", "ButtonCancel": "Odustani",
"ButtonCancelEncode": "Otkaži kodiranje", "ButtonCancelEncode": "Otkaži kodiranje",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "Isprazni sav cache", "ButtonPurgeAllCache": "Isprazni sav cache",
"ButtonPurgeItemsCache": "Isprazni Items Cache", "ButtonPurgeItemsCache": "Isprazni Items Cache",
"ButtonPurgeMediaProgress": "Purge Media Progress",
"ButtonQueueAddItem": "Add to queue", "ButtonQueueAddItem": "Add to queue",
"ButtonQueueRemoveItem": "Remove from queue", "ButtonQueueRemoveItem": "Remove from queue",
"ButtonQuickMatch": "Brzi match", "ButtonQuickMatch": "Brzi match",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Stvari u kolekciji", "HeaderCollectionItems": "Stvari u kolekciji",
"HeaderCover": "Cover", "HeaderCover": "Cover",
"HeaderCurrentDownloads": "Current Downloads", "HeaderCurrentDownloads": "Current Downloads",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Detalji", "HeaderDetails": "Detalji",
"HeaderDownloadQueue": "Download Queue", "HeaderDownloadQueue": "Download Queue",
@ -279,6 +280,8 @@
"LabelEdit": "Uredi", "LabelEdit": "Uredi",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "From Address", "LabelEmailSettingsFromAddress": "From Address",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Secure", "LabelEmailSettingsSecure": "Secure",
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Address", "LabelEmailSettingsTestAddress": "Test Address",
@ -335,6 +338,7 @@
"LabelItem": "Stavka", "LabelItem": "Stavka",
"LabelLanguage": "Jezik", "LabelLanguage": "Jezik",
"LabelLanguageDefaultServer": "Default jezik servera", "LabelLanguageDefaultServer": "Default jezik servera",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Last Book Added", "LabelLastBookAdded": "Last Book Added",
"LabelLastBookUpdated": "Last Book Updated", "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Zadnje pogledano", "LabelLastSeen": "Zadnje pogledano",
@ -346,6 +350,7 @@
"LabelLess": "Manje", "LabelLess": "Manje",
"LabelLibrariesAccessibleToUser": "Biblioteke pristupačne korisniku", "LabelLibrariesAccessibleToUser": "Biblioteke pristupačne korisniku",
"LabelLibrary": "Biblioteka", "LabelLibrary": "Biblioteka",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Stavka biblioteke", "LabelLibraryItem": "Stavka biblioteke",
"LabelLibraryName": "Ime biblioteke", "LabelLibraryName": "Ime biblioteke",
"LabelLimit": "Limit", "LabelLimit": "Limit",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nova lozinka", "LabelNewPassword": "Nova lozinka",
"LabelNextBackupDate": "Next backup date", "LabelNextBackupDate": "Next backup date",
"LabelNextScheduledRun": "Next scheduled run", "LabelNextScheduledRun": "Next scheduled run",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "No episodes selected", "LabelNoEpisodesSelected": "No episodes selected",
"LabelNotes": "Bilješke", "LabelNotes": "Bilješke",
"LabelNotFinished": "Nedovršeno", "LabelNotFinished": "Nedovršeno",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Smije uploadati", "LabelPermissionsUpload": "Smije uploadati",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Slika putanja/URL", "LabelPhotoPathURL": "Slika putanja/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Playlists", "LabelPlaylists": "Playlists",
"LabelPlayMethod": "Vrsta reprodukcije", "LabelPlayMethod": "Vrsta reprodukcije",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Dobavljač", "LabelProvider": "Dobavljač",
"LabelPubDate": "Datam izdavanja", "LabelPubDate": "Datam izdavanja",
"LabelPublisher": "Izdavač", "LabelPublisher": "Izdavač",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Godina izdavanja", "LabelPublishYear": "Godina izdavanja",
"LabelRead": "Read", "LabelRead": "Read",
"LabelReadAgain": "Read Again", "LabelReadAgain": "Read Again",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Quick Match će probati dodati nedostale covere i metapodatke za odabrane stavke. Uključi postavke ispod da omočutie Quick Mathchu da zamijeni postojeće covere i/ili metapodatke.", "MessageBatchQuickMatchDescription": "Quick Match će probati dodati nedostale covere i metapodatke za odabrane stavke. Uključi postavke ispod da omočutie Quick Mathchu da zamijeni postojeće covere i/ili metapodatke.",
"MessageBookshelfNoCollections": "You haven't made any collections yet", "MessageBookshelfNoCollections": "You haven't made any collections yet",
"MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "No Results for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open", "MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
"MessageBookshelfNoSeries": "You have no series", "MessageBookshelfNoSeries": "You have no series",
"MessageChapterEndIsAfter": "Kraj poglavlja je nakon kraja audioknjige.", "MessageChapterEndIsAfter": "Kraj poglavlja je nakon kraja audioknjige.",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?", "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?", "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Alkalmaz", "ButtonApply": "Alkalmaz",
"ButtonApplyChapters": "Fejezetek alkalmazása", "ButtonApplyChapters": "Fejezetek alkalmazása",
"ButtonAuthors": "Szerzők", "ButtonAuthors": "Szerzők",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Mappa keresése", "ButtonBrowseForFolder": "Mappa keresése",
"ButtonCancel": "Mégse", "ButtonCancel": "Mégse",
"ButtonCancelEncode": "Kódolás megszakítása", "ButtonCancelEncode": "Kódolás megszakítása",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Előző fejezet", "ButtonPreviousChapter": "Előző fejezet",
"ButtonPurgeAllCache": "Összes gyorsítótár törlése", "ButtonPurgeAllCache": "Összes gyorsítótár törlése",
"ButtonPurgeItemsCache": "Elemek gyorsítótárának törlése", "ButtonPurgeItemsCache": "Elemek gyorsítótárának törlése",
"ButtonPurgeMediaProgress": "Médialejátszás állapotának törlése",
"ButtonQueueAddItem": "Hozzáadás a sorhoz", "ButtonQueueAddItem": "Hozzáadás a sorhoz",
"ButtonQueueRemoveItem": "Eltávolítás a sorból", "ButtonQueueRemoveItem": "Eltávolítás a sorból",
"ButtonQuickMatch": "Gyors egyeztetés", "ButtonQuickMatch": "Gyors egyeztetés",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Gyűjtemény elemek", "HeaderCollectionItems": "Gyűjtemény elemek",
"HeaderCover": "Borító", "HeaderCover": "Borító",
"HeaderCurrentDownloads": "Jelenlegi letöltések", "HeaderCurrentDownloads": "Jelenlegi letöltések",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Egyéni metaadat-szolgáltatók", "HeaderCustomMetadataProviders": "Egyéni metaadat-szolgáltatók",
"HeaderDetails": "Részletek", "HeaderDetails": "Részletek",
"HeaderDownloadQueue": "Letöltési sor", "HeaderDownloadQueue": "Letöltési sor",
@ -279,6 +280,8 @@
"LabelEdit": "Szerkesztés", "LabelEdit": "Szerkesztés",
"LabelEmail": "E-mail", "LabelEmail": "E-mail",
"LabelEmailSettingsFromAddress": "Feladó címe", "LabelEmailSettingsFromAddress": "Feladó címe",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Biztonságos", "LabelEmailSettingsSecure": "Biztonságos",
"LabelEmailSettingsSecureHelp": "Ha igaz, a kapcsolat TLS-t használ a szerverhez való csatlakozáskor. Ha hamis, akkor TLS-t használ, ha a szerver támogatja a STARTTLS kiterjesztést. A legtöbb esetben állítsa ezt az értéket igazra, ha a 465-ös portra csatlakozik. A 587-es vagy 25-ös port esetében tartsa hamis értéken. (a nodemailer.com/smtp/#authentication oldalról)", "LabelEmailSettingsSecureHelp": "Ha igaz, a kapcsolat TLS-t használ a szerverhez való csatlakozáskor. Ha hamis, akkor TLS-t használ, ha a szerver támogatja a STARTTLS kiterjesztést. A legtöbb esetben állítsa ezt az értéket igazra, ha a 465-ös portra csatlakozik. A 587-es vagy 25-ös port esetében tartsa hamis értéken. (a nodemailer.com/smtp/#authentication oldalról)",
"LabelEmailSettingsTestAddress": "Teszt cím", "LabelEmailSettingsTestAddress": "Teszt cím",
@ -335,6 +338,7 @@
"LabelItem": "Elem", "LabelItem": "Elem",
"LabelLanguage": "Nyelv", "LabelLanguage": "Nyelv",
"LabelLanguageDefaultServer": "Szerver alapértelmezett nyelve", "LabelLanguageDefaultServer": "Szerver alapértelmezett nyelve",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Utolsó hozzáadott könyv", "LabelLastBookAdded": "Utolsó hozzáadott könyv",
"LabelLastBookUpdated": "Utolsó frissített könyv", "LabelLastBookUpdated": "Utolsó frissített könyv",
"LabelLastSeen": "Utolsó látogatás", "LabelLastSeen": "Utolsó látogatás",
@ -346,6 +350,7 @@
"LabelLess": "Kevesebb", "LabelLess": "Kevesebb",
"LabelLibrariesAccessibleToUser": "A felhasználó számára elérhető könyvtárak", "LabelLibrariesAccessibleToUser": "A felhasználó számára elérhető könyvtárak",
"LabelLibrary": "Könyvtár", "LabelLibrary": "Könyvtár",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Könyvtári elem", "LabelLibraryItem": "Könyvtári elem",
"LabelLibraryName": "Könyvtár neve", "LabelLibraryName": "Könyvtár neve",
"LabelLimit": "Korlát", "LabelLimit": "Korlát",
@ -381,6 +386,7 @@
"LabelNewPassword": "Új jelszó", "LabelNewPassword": "Új jelszó",
"LabelNextBackupDate": "Következő biztonsági másolat dátuma", "LabelNextBackupDate": "Következő biztonsági másolat dátuma",
"LabelNextScheduledRun": "Következő ütemezett futtatás", "LabelNextScheduledRun": "Következő ütemezett futtatás",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Nincsenek kiválasztott epizódok", "LabelNoEpisodesSelected": "Nincsenek kiválasztott epizódok",
"LabelNotes": "Megjegyzések", "LabelNotes": "Megjegyzések",
"LabelNotFinished": "Nem befejezett", "LabelNotFinished": "Nem befejezett",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Feltölthet", "LabelPermissionsUpload": "Feltölthet",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Fénykép útvonal/URL", "LabelPhotoPathURL": "Fénykép útvonal/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Lejátszási listák", "LabelPlaylists": "Lejátszási listák",
"LabelPlayMethod": "Lejátszási módszer", "LabelPlayMethod": "Lejátszási módszer",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Szolgáltató", "LabelProvider": "Szolgáltató",
"LabelPubDate": "Kiadás dátuma", "LabelPubDate": "Kiadás dátuma",
"LabelPublisher": "Kiadó", "LabelPublisher": "Kiadó",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Kiadás éve", "LabelPublishYear": "Kiadás éve",
"LabelRead": "Olvasás", "LabelRead": "Olvasás",
"LabelReadAgain": "Újraolvasás", "LabelReadAgain": "Újraolvasás",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "A Gyors egyeztetés megpróbálja hozzáadni a hiányzó borítókat és metaadatokat a kiválasztott elemekhez. Engedélyezze az alábbi opciókat, hogy a Gyors egyeztetés felülírhassa a meglévő borítókat és/vagy metaadatokat.", "MessageBatchQuickMatchDescription": "A Gyors egyeztetés megpróbálja hozzáadni a hiányzó borítókat és metaadatokat a kiválasztott elemekhez. Engedélyezze az alábbi opciókat, hogy a Gyors egyeztetés felülírhassa a meglévő borítókat és/vagy metaadatokat.",
"MessageBookshelfNoCollections": "Még nem készített gyűjteményeket", "MessageBookshelfNoCollections": "Még nem készített gyűjteményeket",
"MessageBookshelfNoResultsForFilter": "Nincs eredmény a \"{0}: {1}\" szűrőre", "MessageBookshelfNoResultsForFilter": "Nincs eredmény a \"{0}: {1}\" szűrőre",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Nincsenek nyitott RSS hírcsatornák", "MessageBookshelfNoRSSFeeds": "Nincsenek nyitott RSS hírcsatornák",
"MessageBookshelfNoSeries": "Nincsenek sorozatai", "MessageBookshelfNoSeries": "Nincsenek sorozatai",
"MessageChapterEndIsAfter": "A fejezet vége a hangoskönyv végét követi", "MessageChapterEndIsAfter": "A fejezet vége a hangoskönyv végét követi",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Biztosan meg szeretné jelölni a sorozat összes könyvét befejezettnek?", "MessageConfirmMarkSeriesFinished": "Biztosan meg szeretné jelölni a sorozat összes könyvét befejezettnek?",
"MessageConfirmMarkSeriesNotFinished": "Biztosan meg szeretné jelölni a sorozat összes könyvét nem befejezettnek?", "MessageConfirmMarkSeriesNotFinished": "Biztosan meg szeretné jelölni a sorozat összes könyvét nem befejezettnek?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Figyelem! A Gyors beágyazás nem készít biztonsági másolatot az audiofájlokról. Győződjön meg arról, hogy van biztonsági másolata az audiofájlokról. <br><br>Szeretné folytatni?", "MessageConfirmQuickEmbed": "Figyelem! A Gyors beágyazás nem készít biztonsági másolatot az audiofájlokról. Győződjön meg arról, hogy van biztonsági másolata az audiofájlokról. <br><br>Szeretné folytatni?",
"MessageConfirmRemoveAllChapters": "Biztosan eltávolítja az összes fejezetet?", "MessageConfirmRemoveAllChapters": "Biztosan eltávolítja az összes fejezetet?",
"MessageConfirmRemoveAuthor": "Biztosan eltávolítja a(z) \"{0}\" szerzőt?", "MessageConfirmRemoveAuthor": "Biztosan eltávolítja a(z) \"{0}\" szerzőt?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Applica", "ButtonApply": "Applica",
"ButtonApplyChapters": "Applica", "ButtonApplyChapters": "Applica",
"ButtonAuthors": "Autori", "ButtonAuthors": "Autori",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Per Cartella", "ButtonBrowseForFolder": "Per Cartella",
"ButtonCancel": "Cancella", "ButtonCancel": "Cancella",
"ButtonCancelEncode": "Ferma la codifica", "ButtonCancelEncode": "Ferma la codifica",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Capitolo Precendente", "ButtonPreviousChapter": "Capitolo Precendente",
"ButtonPurgeAllCache": "Elimina tutta la Cache", "ButtonPurgeAllCache": "Elimina tutta la Cache",
"ButtonPurgeItemsCache": "Elimina la Cache selezionata", "ButtonPurgeItemsCache": "Elimina la Cache selezionata",
"ButtonPurgeMediaProgress": "Elimina info dei media ascoltati",
"ButtonQueueAddItem": "Aggiungi alla Coda", "ButtonQueueAddItem": "Aggiungi alla Coda",
"ButtonQueueRemoveItem": "Rimuovi dalla Coda", "ButtonQueueRemoveItem": "Rimuovi dalla Coda",
"ButtonQuickMatch": "Controlla Metadata Auto", "ButtonQuickMatch": "Controlla Metadata Auto",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Elementi della Raccolta", "HeaderCollectionItems": "Elementi della Raccolta",
"HeaderCover": "Cover", "HeaderCover": "Cover",
"HeaderCurrentDownloads": "Download Correnti", "HeaderCurrentDownloads": "Download Correnti",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": " Metadata Providers Personalizzato", "HeaderCustomMetadataProviders": " Metadata Providers Personalizzato",
"HeaderDetails": "Dettagli", "HeaderDetails": "Dettagli",
"HeaderDownloadQueue": "Download coda", "HeaderDownloadQueue": "Download coda",
@ -279,6 +280,8 @@
"LabelEdit": "Modifica", "LabelEdit": "Modifica",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Da Indirizzo", "LabelEmailSettingsFromAddress": "Da Indirizzo",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Secure", "LabelEmailSettingsSecure": "Secure",
"LabelEmailSettingsSecureHelp": "Se vero, la connessione utilizzerà TLS durante la connessione al server. Se false, viene utilizzato TLS se il server supporta l'estensione STARTTLS. Nella maggior parte dei casi impostare questo valore su true se ci si connette alla porta 465. Per la porta 587 o 25 mantenerlo false. (da nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Se vero, la connessione utilizzerà TLS durante la connessione al server. Se false, viene utilizzato TLS se il server supporta l'estensione STARTTLS. Nella maggior parte dei casi impostare questo valore su true se ci si connette alla porta 465. Per la porta 587 o 25 mantenerlo false. (da nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Indirizzo", "LabelEmailSettingsTestAddress": "Test Indirizzo",
@ -335,6 +338,7 @@
"LabelItem": "Oggetti", "LabelItem": "Oggetti",
"LabelLanguage": "Lingua", "LabelLanguage": "Lingua",
"LabelLanguageDefaultServer": "Lingua di Default", "LabelLanguageDefaultServer": "Lingua di Default",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Ultimo Libro Aggiunto", "LabelLastBookAdded": "Ultimo Libro Aggiunto",
"LabelLastBookUpdated": "Ultimo Libro Aggiornato", "LabelLastBookUpdated": "Ultimo Libro Aggiornato",
"LabelLastSeen": "Ultimi Visti", "LabelLastSeen": "Ultimi Visti",
@ -346,6 +350,7 @@
"LabelLess": "Poco", "LabelLess": "Poco",
"LabelLibrariesAccessibleToUser": "Librerie Accessibili agli Utenti", "LabelLibrariesAccessibleToUser": "Librerie Accessibili agli Utenti",
"LabelLibrary": "Libreria", "LabelLibrary": "Libreria",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Elementi della Library", "LabelLibraryItem": "Elementi della Library",
"LabelLibraryName": "Nome Libreria", "LabelLibraryName": "Nome Libreria",
"LabelLimit": "Limiti", "LabelLimit": "Limiti",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nuova Password", "LabelNewPassword": "Nuova Password",
"LabelNextBackupDate": "Data Prossimo Backup", "LabelNextBackupDate": "Data Prossimo Backup",
"LabelNextScheduledRun": "Data prossima esecuzione schedulata", "LabelNextScheduledRun": "Data prossima esecuzione schedulata",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Nessun Episodio Selezionato", "LabelNoEpisodesSelected": "Nessun Episodio Selezionato",
"LabelNotes": "Note", "LabelNotes": "Note",
"LabelNotFinished": "Da Completare", "LabelNotFinished": "Da Completare",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Può caricare", "LabelPermissionsUpload": "Può caricare",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "foto Path/URL", "LabelPhotoPathURL": "foto Path/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Playlists", "LabelPlaylists": "Playlists",
"LabelPlayMethod": "Metodo di riproduzione", "LabelPlayMethod": "Metodo di riproduzione",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Provider", "LabelProvider": "Provider",
"LabelPubDate": "Data Pubblicazione", "LabelPubDate": "Data Pubblicazione",
"LabelPublisher": "Editore", "LabelPublisher": "Editore",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Anno Pubblicazione", "LabelPublishYear": "Anno Pubblicazione",
"LabelRead": "Leggi", "LabelRead": "Leggi",
"LabelReadAgain": "Leggi Ancora", "LabelReadAgain": "Leggi Ancora",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Quick Match tenterà di aggiungere copertine e metadati mancanti per gli elementi selezionati. Attiva l'opzione per consentire a Quick Match di sovrascrivere copertine e/o metadati esistenti.", "MessageBatchQuickMatchDescription": "Quick Match tenterà di aggiungere copertine e metadati mancanti per gli elementi selezionati. Attiva l'opzione per consentire a Quick Match di sovrascrivere copertine e/o metadati esistenti.",
"MessageBookshelfNoCollections": "Non hai ancora creato nessuna raccolta ", "MessageBookshelfNoCollections": "Non hai ancora creato nessuna raccolta ",
"MessageBookshelfNoResultsForFilter": "Nessun risultato per il filtro \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Nessun risultato per il filtro \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Nessun RSS feeds aperto", "MessageBookshelfNoRSSFeeds": "Nessun RSS feeds aperto",
"MessageBookshelfNoSeries": "Non c'è nessuna Serie", "MessageBookshelfNoSeries": "Non c'è nessuna Serie",
"MessageChapterEndIsAfter": "La fine del capitolo è dopo la fine del tuo audiolibro", "MessageChapterEndIsAfter": "La fine del capitolo è dopo la fine del tuo audiolibro",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come completati?", "MessageConfirmMarkSeriesFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come completati?",
"MessageConfirmMarkSeriesNotFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come non completati?", "MessageConfirmMarkSeriesNotFinished": "Sei sicuro di voler contrassegnare tutti i libri di questa serie come non completati?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Attenzione! L'incorporamento rapido non eseguirà il backup dei file audio. Assicurati di avere un backup dei tuoi file audio. <br><br>Vuoi Continuare?", "MessageConfirmQuickEmbed": "Attenzione! L'incorporamento rapido non eseguirà il backup dei file audio. Assicurati di avere un backup dei tuoi file audio. <br><br>Vuoi Continuare?",
"MessageConfirmRemoveAllChapters": "Sei sicuro di voler rimuovere tutti i capitoli?", "MessageConfirmRemoveAllChapters": "Sei sicuro di voler rimuovere tutti i capitoli?",
"MessageConfirmRemoveAuthor": "Sei sicuro di voler rimuovere l'autore? \"{0}\"?", "MessageConfirmRemoveAuthor": "Sei sicuro di voler rimuovere l'autore? \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Taikyti", "ButtonApply": "Taikyti",
"ButtonApplyChapters": "Taikyti skyrius", "ButtonApplyChapters": "Taikyti skyrius",
"ButtonAuthors": "Autoriai", "ButtonAuthors": "Autoriai",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Naršyti aplanko", "ButtonBrowseForFolder": "Naršyti aplanko",
"ButtonCancel": "Atšaukti", "ButtonCancel": "Atšaukti",
"ButtonCancelEncode": "Atšaukti kodavimą", "ButtonCancelEncode": "Atšaukti kodavimą",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "Valyti visą saugyklą", "ButtonPurgeAllCache": "Valyti visą saugyklą",
"ButtonPurgeItemsCache": "Valyti elementų saugyklą", "ButtonPurgeItemsCache": "Valyti elementų saugyklą",
"ButtonPurgeMediaProgress": "Valyti medijos progresą",
"ButtonQueueAddItem": "Pridėti į eilę", "ButtonQueueAddItem": "Pridėti į eilę",
"ButtonQueueRemoveItem": "Pašalinti iš eilės", "ButtonQueueRemoveItem": "Pašalinti iš eilės",
"ButtonQuickMatch": "Greitas pritaikymas", "ButtonQuickMatch": "Greitas pritaikymas",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Kolekcijos elementai", "HeaderCollectionItems": "Kolekcijos elementai",
"HeaderCover": "Viršelis", "HeaderCover": "Viršelis",
"HeaderCurrentDownloads": "Dabartiniai parsisiuntimai", "HeaderCurrentDownloads": "Dabartiniai parsisiuntimai",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Detalės", "HeaderDetails": "Detalės",
"HeaderDownloadQueue": "Parsisiuntimo eilė", "HeaderDownloadQueue": "Parsisiuntimo eilė",
@ -279,6 +280,8 @@
"LabelEdit": "Redaguoti", "LabelEdit": "Redaguoti",
"LabelEmail": "El. paštas", "LabelEmail": "El. paštas",
"LabelEmailSettingsFromAddress": "Siuntėjo adresas", "LabelEmailSettingsFromAddress": "Siuntėjo adresas",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Apsaugota", "LabelEmailSettingsSecure": "Apsaugota",
"LabelEmailSettingsSecureHelp": "Jei ši reikšmė yra \"true\", ryšys naudos TLS protokolą. Jei \"false\", TLS bus naudojamas tik tada, jei serveris palaiko STARTTLS plėtinį. Daugumos atveju, jei jungiamasi prie 465 prievado, šią reikšmę turėtumėte nustatyti kaip \"true\". Jei jungiamasi prie 587 arba 25 prievado, turi būti nustatyta \"false\". (iš nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Jei ši reikšmė yra \"true\", ryšys naudos TLS protokolą. Jei \"false\", TLS bus naudojamas tik tada, jei serveris palaiko STARTTLS plėtinį. Daugumos atveju, jei jungiamasi prie 465 prievado, šią reikšmę turėtumėte nustatyti kaip \"true\". Jei jungiamasi prie 587 arba 25 prievado, turi būti nustatyta \"false\". (iš nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Testinis adresas", "LabelEmailSettingsTestAddress": "Testinis adresas",
@ -335,6 +338,7 @@
"LabelItem": "Elementas", "LabelItem": "Elementas",
"LabelLanguage": "Kalba", "LabelLanguage": "Kalba",
"LabelLanguageDefaultServer": "Numatytoji serverio kalba", "LabelLanguageDefaultServer": "Numatytoji serverio kalba",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Paskutinė pridėta knyga", "LabelLastBookAdded": "Paskutinė pridėta knyga",
"LabelLastBookUpdated": "Paskutinė atnaujinta knyga", "LabelLastBookUpdated": "Paskutinė atnaujinta knyga",
"LabelLastSeen": "Paskutinį kartą matyta", "LabelLastSeen": "Paskutinį kartą matyta",
@ -346,6 +350,7 @@
"LabelLess": "Mažiau", "LabelLess": "Mažiau",
"LabelLibrariesAccessibleToUser": "Naudotojui pasiekiamos bibliotekos", "LabelLibrariesAccessibleToUser": "Naudotojui pasiekiamos bibliotekos",
"LabelLibrary": "Biblioteka", "LabelLibrary": "Biblioteka",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Bibliotekos elementas", "LabelLibraryItem": "Bibliotekos elementas",
"LabelLibraryName": "Bibliotekos pavadinimas", "LabelLibraryName": "Bibliotekos pavadinimas",
"LabelLimit": "Limitas", "LabelLimit": "Limitas",
@ -381,6 +386,7 @@
"LabelNewPassword": "Naujas slaptažodis", "LabelNewPassword": "Naujas slaptažodis",
"LabelNextBackupDate": "Kitos atsarginės kopijos data", "LabelNextBackupDate": "Kitos atsarginės kopijos data",
"LabelNextScheduledRun": "Kito planuoto vykdymo data", "LabelNextScheduledRun": "Kito planuoto vykdymo data",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Nepasirinkti jokie epizodai", "LabelNoEpisodesSelected": "Nepasirinkti jokie epizodai",
"LabelNotes": "Užrašai", "LabelNotes": "Užrašai",
"LabelNotFinished": "Nebaigta", "LabelNotFinished": "Nebaigta",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Gali įkelti", "LabelPermissionsUpload": "Gali įkelti",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Nuotraukos kelias/URL", "LabelPhotoPathURL": "Nuotraukos kelias/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Grojaraščiai", "LabelPlaylists": "Grojaraščiai",
"LabelPlayMethod": "Grojimo metodas", "LabelPlayMethod": "Grojimo metodas",
"LabelPodcast": "Tinklalaidė", "LabelPodcast": "Tinklalaidė",
@ -426,6 +433,7 @@
"LabelProvider": "Tiekėjas", "LabelProvider": "Tiekėjas",
"LabelPubDate": "Publikavimo data", "LabelPubDate": "Publikavimo data",
"LabelPublisher": "Leidėjas", "LabelPublisher": "Leidėjas",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Leidimo metai", "LabelPublishYear": "Leidimo metai",
"LabelRead": "Skaityta", "LabelRead": "Skaityta",
"LabelReadAgain": "Skaityti dar kartą", "LabelReadAgain": "Skaityti dar kartą",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Greitas atitikmens rasti bandys pridėti trūkstamus viršelius ir metaduomenis pasirinktiems elementams. Įjunkite žemiau esančias parinktis, kad leistumėte Greitajam atitikmeniui perrašyti esamus viršelius ir/ar metaduomenis.", "MessageBatchQuickMatchDescription": "Greitas atitikmens rasti bandys pridėti trūkstamus viršelius ir metaduomenis pasirinktiems elementams. Įjunkite žemiau esančias parinktis, kad leistumėte Greitajam atitikmeniui perrašyti esamus viršelius ir/ar metaduomenis.",
"MessageBookshelfNoCollections": "Dar nepridėjote jokių kolekcijų", "MessageBookshelfNoCollections": "Dar nepridėjote jokių kolekcijų",
"MessageBookshelfNoResultsForFilter": "Rezultatų pagal filtrą \"{0}: {1}\" nėra", "MessageBookshelfNoResultsForFilter": "Rezultatų pagal filtrą \"{0}: {1}\" nėra",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Nėra atvertų RSS srautų", "MessageBookshelfNoRSSFeeds": "Nėra atvertų RSS srautų",
"MessageBookshelfNoSeries": "Neturite jokių serijų", "MessageBookshelfNoSeries": "Neturite jokių serijų",
"MessageChapterEndIsAfter": "Skyriaus pabaiga yra po jūsų garso knygos pabaigos", "MessageChapterEndIsAfter": "Skyriaus pabaiga yra po jūsų garso knygos pabaigos",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip užbaigtas?", "MessageConfirmMarkSeriesFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip užbaigtas?",
"MessageConfirmMarkSeriesNotFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip nebaigtas?", "MessageConfirmMarkSeriesNotFinished": "Ar tikrai norite pažymėti visas knygas šioje serijoje kaip nebaigtas?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Ar tikrai norite pašalinti visus skyrius?", "MessageConfirmRemoveAllChapters": "Ar tikrai norite pašalinti visus skyrius?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Pas toe", "ButtonApply": "Pas toe",
"ButtonApplyChapters": "Hoofdstukken toepassen", "ButtonApplyChapters": "Hoofdstukken toepassen",
"ButtonAuthors": "Auteurs", "ButtonAuthors": "Auteurs",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Bladeren naar map", "ButtonBrowseForFolder": "Bladeren naar map",
"ButtonCancel": "Annuleren", "ButtonCancel": "Annuleren",
"ButtonCancelEncode": "Encoding annuleren", "ButtonCancelEncode": "Encoding annuleren",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "Volledige cache legen", "ButtonPurgeAllCache": "Volledige cache legen",
"ButtonPurgeItemsCache": "Onderdelen-cache legen", "ButtonPurgeItemsCache": "Onderdelen-cache legen",
"ButtonPurgeMediaProgress": "Mediavoortgang legen",
"ButtonQueueAddItem": "In wachtrij zetten", "ButtonQueueAddItem": "In wachtrij zetten",
"ButtonQueueRemoveItem": "Uit wachtrij verwijderen", "ButtonQueueRemoveItem": "Uit wachtrij verwijderen",
"ButtonQuickMatch": "Snelle match", "ButtonQuickMatch": "Snelle match",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Collectie-objecten", "HeaderCollectionItems": "Collectie-objecten",
"HeaderCover": "Cover", "HeaderCover": "Cover",
"HeaderCurrentDownloads": "Huidige downloads", "HeaderCurrentDownloads": "Huidige downloads",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Details", "HeaderDetails": "Details",
"HeaderDownloadQueue": "Download-wachtrij", "HeaderDownloadQueue": "Download-wachtrij",
@ -279,6 +280,8 @@
"LabelEdit": "Wijzig", "LabelEdit": "Wijzig",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Van-adres", "LabelEmailSettingsFromAddress": "Van-adres",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Veilig", "LabelEmailSettingsSecure": "Veilig",
"LabelEmailSettingsSecureHelp": "Als 'waar', dan gebruikt de verbinding TLS om met de server te verbinden. Als 'onwaar', dan wordt TLS gebruikt als de server de STARTTLS-extensie ondersteunt. In de meeste gevallen kies je voor 'waar' verbindt met poort 465. Voo poort 587 of 25, laat op 'onwaar'. (van nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Als 'waar', dan gebruikt de verbinding TLS om met de server te verbinden. Als 'onwaar', dan wordt TLS gebruikt als de server de STARTTLS-extensie ondersteunt. In de meeste gevallen kies je voor 'waar' verbindt met poort 465. Voo poort 587 of 25, laat op 'onwaar'. (van nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test-adres", "LabelEmailSettingsTestAddress": "Test-adres",
@ -335,6 +338,7 @@
"LabelItem": "Onderdeel", "LabelItem": "Onderdeel",
"LabelLanguage": "Taal", "LabelLanguage": "Taal",
"LabelLanguageDefaultServer": "Standaard servertaal", "LabelLanguageDefaultServer": "Standaard servertaal",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Laatst toegevoegde boek", "LabelLastBookAdded": "Laatst toegevoegde boek",
"LabelLastBookUpdated": "Laatst bijgewerkte boek", "LabelLastBookUpdated": "Laatst bijgewerkte boek",
"LabelLastSeen": "Laatst gezien", "LabelLastSeen": "Laatst gezien",
@ -346,6 +350,7 @@
"LabelLess": "Minder", "LabelLess": "Minder",
"LabelLibrariesAccessibleToUser": "Voor gebruiker toegankelijke bibliotheken", "LabelLibrariesAccessibleToUser": "Voor gebruiker toegankelijke bibliotheken",
"LabelLibrary": "Bibliotheek", "LabelLibrary": "Bibliotheek",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Bibliotheekonderdeel", "LabelLibraryItem": "Bibliotheekonderdeel",
"LabelLibraryName": "Bibliotheeknaam", "LabelLibraryName": "Bibliotheeknaam",
"LabelLimit": "Limiet", "LabelLimit": "Limiet",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nieuw wachtwoord", "LabelNewPassword": "Nieuw wachtwoord",
"LabelNextBackupDate": "Volgende back-up datum", "LabelNextBackupDate": "Volgende back-up datum",
"LabelNextScheduledRun": "Volgende geplande run", "LabelNextScheduledRun": "Volgende geplande run",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Geen afleveringen geselecteerd", "LabelNoEpisodesSelected": "Geen afleveringen geselecteerd",
"LabelNotes": "Notities", "LabelNotes": "Notities",
"LabelNotFinished": "Niet Voltooid", "LabelNotFinished": "Niet Voltooid",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Kan uploaden", "LabelPermissionsUpload": "Kan uploaden",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Foto pad/URL", "LabelPhotoPathURL": "Foto pad/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Afspeellijsten", "LabelPlaylists": "Afspeellijsten",
"LabelPlayMethod": "Afspeelwijze", "LabelPlayMethod": "Afspeelwijze",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Bron", "LabelProvider": "Bron",
"LabelPubDate": "Publicatiedatum", "LabelPubDate": "Publicatiedatum",
"LabelPublisher": "Uitgever", "LabelPublisher": "Uitgever",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Jaar van uitgave", "LabelPublishYear": "Jaar van uitgave",
"LabelRead": "Lees", "LabelRead": "Lees",
"LabelReadAgain": "Lees opnieuw", "LabelReadAgain": "Lees opnieuw",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Quick Match zal proberen ontbrekende covers en metadata voor de geselecteerde onderdelen te matchten. Schakel de opties hieronder in om Quick Match toe te staan bestaande covers en/of metadata te overschrijven.", "MessageBatchQuickMatchDescription": "Quick Match zal proberen ontbrekende covers en metadata voor de geselecteerde onderdelen te matchten. Schakel de opties hieronder in om Quick Match toe te staan bestaande covers en/of metadata te overschrijven.",
"MessageBookshelfNoCollections": "Je hebt nog geen collecties gemaakt", "MessageBookshelfNoCollections": "Je hebt nog geen collecties gemaakt",
"MessageBookshelfNoResultsForFilter": "Geen resultaten voor filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Geen resultaten voor filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Geen RSS-feeds geopend", "MessageBookshelfNoRSSFeeds": "Geen RSS-feeds geopend",
"MessageBookshelfNoSeries": "Je hebt geen series", "MessageBookshelfNoSeries": "Je hebt geen series",
"MessageChapterEndIsAfter": "Hoofdstukeinde is na het einde van je audioboek", "MessageChapterEndIsAfter": "Hoofdstukeinde is na het einde van je audioboek",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als voltooid?", "MessageConfirmMarkSeriesFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als voltooid?",
"MessageConfirmMarkSeriesNotFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als niet voltooid?", "MessageConfirmMarkSeriesNotFinished": "Weet je zeker dat je alle boeken in deze serie wil markeren als niet voltooid?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Weet je zeker dat je alle hoofdstukken wil verwijderen?", "MessageConfirmRemoveAllChapters": "Weet je zeker dat je alle hoofdstukken wil verwijderen?",
"MessageConfirmRemoveAuthor": "Weet je zeker dat je auteur \"{0}\" wil verwijderen?", "MessageConfirmRemoveAuthor": "Weet je zeker dat je auteur \"{0}\" wil verwijderen?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Bruk", "ButtonApply": "Bruk",
"ButtonApplyChapters": "Bruk kapittel", "ButtonApplyChapters": "Bruk kapittel",
"ButtonAuthors": "Forfatter", "ButtonAuthors": "Forfatter",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Bla gjennom mappe", "ButtonBrowseForFolder": "Bla gjennom mappe",
"ButtonCancel": "Avbryt", "ButtonCancel": "Avbryt",
"ButtonCancelEncode": "Avbryt Encode", "ButtonCancelEncode": "Avbryt Encode",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "Tøm alle mellomlager", "ButtonPurgeAllCache": "Tøm alle mellomlager",
"ButtonPurgeItemsCache": "Tøm mellomlager", "ButtonPurgeItemsCache": "Tøm mellomlager",
"ButtonPurgeMediaProgress": "Slett medie fremgang",
"ButtonQueueAddItem": "Legg til kø", "ButtonQueueAddItem": "Legg til kø",
"ButtonQueueRemoveItem": "Fjern fra kø", "ButtonQueueRemoveItem": "Fjern fra kø",
"ButtonQuickMatch": "Kjapt søk", "ButtonQuickMatch": "Kjapt søk",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Samlingsgjenstander", "HeaderCollectionItems": "Samlingsgjenstander",
"HeaderCover": "Omslag", "HeaderCover": "Omslag",
"HeaderCurrentDownloads": "Aktive nedlastinger", "HeaderCurrentDownloads": "Aktive nedlastinger",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Detaljer", "HeaderDetails": "Detaljer",
"HeaderDownloadQueue": "Last ned kø", "HeaderDownloadQueue": "Last ned kø",
@ -279,6 +280,8 @@
"LabelEdit": "Rediger", "LabelEdit": "Rediger",
"LabelEmail": "Epost", "LabelEmail": "Epost",
"LabelEmailSettingsFromAddress": "Fra Adresse", "LabelEmailSettingsFromAddress": "Fra Adresse",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Sikker", "LabelEmailSettingsSecure": "Sikker",
"LabelEmailSettingsSecureHelp": "Hvis aktivert, vil tilkoblingen bruke TLS under tilkobling til tjeneren. Ellers vil TLS bli brukt hvis tjeneren støtter STARTTLS utvidelsen. I de fleste tilfeller aktiver valget hvis du kobler til med port 465. Med port 587 eller 25 deaktiver valget. (fra nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Hvis aktivert, vil tilkoblingen bruke TLS under tilkobling til tjeneren. Ellers vil TLS bli brukt hvis tjeneren støtter STARTTLS utvidelsen. I de fleste tilfeller aktiver valget hvis du kobler til med port 465. Med port 587 eller 25 deaktiver valget. (fra nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Adresse", "LabelEmailSettingsTestAddress": "Test Adresse",
@ -335,6 +338,7 @@
"LabelItem": "Enhet", "LabelItem": "Enhet",
"LabelLanguage": "Språk", "LabelLanguage": "Språk",
"LabelLanguageDefaultServer": "Standard tjener språk", "LabelLanguageDefaultServer": "Standard tjener språk",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Siste bok lagt til", "LabelLastBookAdded": "Siste bok lagt til",
"LabelLastBookUpdated": "Siste bok oppdatert", "LabelLastBookUpdated": "Siste bok oppdatert",
"LabelLastSeen": "Sist sett", "LabelLastSeen": "Sist sett",
@ -346,6 +350,7 @@
"LabelLess": "Mindre", "LabelLess": "Mindre",
"LabelLibrariesAccessibleToUser": "Biblioteker tilgjengelig for bruker", "LabelLibrariesAccessibleToUser": "Biblioteker tilgjengelig for bruker",
"LabelLibrary": "Bibliotek", "LabelLibrary": "Bibliotek",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Bibliotek enhet", "LabelLibraryItem": "Bibliotek enhet",
"LabelLibraryName": "Bibliotek navn", "LabelLibraryName": "Bibliotek navn",
"LabelLimit": "Begrensning", "LabelLimit": "Begrensning",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nytt passord", "LabelNewPassword": "Nytt passord",
"LabelNextBackupDate": "Neste sikkerhetskopi dato", "LabelNextBackupDate": "Neste sikkerhetskopi dato",
"LabelNextScheduledRun": "Neste planlagte kjøring", "LabelNextScheduledRun": "Neste planlagte kjøring",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Ingen episoder valgt", "LabelNoEpisodesSelected": "Ingen episoder valgt",
"LabelNotes": "Notat", "LabelNotes": "Notat",
"LabelNotFinished": "Ikke fullført", "LabelNotFinished": "Ikke fullført",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Kan laste opp", "LabelPermissionsUpload": "Kan laste opp",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Bilde sti/URL", "LabelPhotoPathURL": "Bilde sti/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Spilleliste", "LabelPlaylists": "Spilleliste",
"LabelPlayMethod": "Avspillingsmetode", "LabelPlayMethod": "Avspillingsmetode",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Tilbyder", "LabelProvider": "Tilbyder",
"LabelPubDate": "Publiseringsdato", "LabelPubDate": "Publiseringsdato",
"LabelPublisher": "Forlegger", "LabelPublisher": "Forlegger",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Publikasjonsår", "LabelPublishYear": "Publikasjonsår",
"LabelRead": "Les", "LabelRead": "Les",
"LabelReadAgain": "Les igjen", "LabelReadAgain": "Les igjen",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Kjapt søk vil forsøke å legge til manglende omslag og metadata for de valgte gjenstandene. Aktiver dette valget for å tillate Kjapt søk til å overskrive eksisterende omslag og/eller metadata.", "MessageBatchQuickMatchDescription": "Kjapt søk vil forsøke å legge til manglende omslag og metadata for de valgte gjenstandene. Aktiver dette valget for å tillate Kjapt søk til å overskrive eksisterende omslag og/eller metadata.",
"MessageBookshelfNoCollections": "Du har ikke laget noen samlinger ennå", "MessageBookshelfNoCollections": "Du har ikke laget noen samlinger ennå",
"MessageBookshelfNoResultsForFilter": "Ingen resultat for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Ingen resultat for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Ingen RSS feed er åpen", "MessageBookshelfNoRSSFeeds": "Ingen RSS feed er åpen",
"MessageBookshelfNoSeries": "Du har ingen serier", "MessageBookshelfNoSeries": "Du har ingen serier",
"MessageChapterEndIsAfter": "Kapittel slutt er etter slutt av lydboken", "MessageChapterEndIsAfter": "Kapittel slutt er etter slutt av lydboken",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Er du sikker på at du vil markere alle bøkene i serien som fullført?", "MessageConfirmMarkSeriesFinished": "Er du sikker på at du vil markere alle bøkene i serien som fullført?",
"MessageConfirmMarkSeriesNotFinished": "Er du sikker på at du vil markere alle bøkene i serien som ikke fullført?", "MessageConfirmMarkSeriesNotFinished": "Er du sikker på at du vil markere alle bøkene i serien som ikke fullført?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Er du sikker på at du vil fjerne alle kapitler?", "MessageConfirmRemoveAllChapters": "Er du sikker på at du vil fjerne alle kapitler?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Zatwierdź", "ButtonApply": "Zatwierdź",
"ButtonApplyChapters": "Zatwierdź rozdziały", "ButtonApplyChapters": "Zatwierdź rozdziały",
"ButtonAuthors": "Autorzy", "ButtonAuthors": "Autorzy",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Wyszukaj folder", "ButtonBrowseForFolder": "Wyszukaj folder",
"ButtonCancel": "Anuluj", "ButtonCancel": "Anuluj",
"ButtonCancelEncode": "Anuluj enkodowanie", "ButtonCancelEncode": "Anuluj enkodowanie",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Poprzedni rozdział", "ButtonPreviousChapter": "Poprzedni rozdział",
"ButtonPurgeAllCache": "Wyczyść dane tymczasowe", "ButtonPurgeAllCache": "Wyczyść dane tymczasowe",
"ButtonPurgeItemsCache": "Wyczyść dane tymczasowe pozycji", "ButtonPurgeItemsCache": "Wyczyść dane tymczasowe pozycji",
"ButtonPurgeMediaProgress": "Wyczyść postęp",
"ButtonQueueAddItem": "Dodaj do kolejki", "ButtonQueueAddItem": "Dodaj do kolejki",
"ButtonQueueRemoveItem": "Usuń z kolejki", "ButtonQueueRemoveItem": "Usuń z kolejki",
"ButtonQuickMatch": "Szybkie dopasowanie", "ButtonQuickMatch": "Szybkie dopasowanie",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Elementy kolekcji", "HeaderCollectionItems": "Elementy kolekcji",
"HeaderCover": "Okładka", "HeaderCover": "Okładka",
"HeaderCurrentDownloads": "Current Downloads", "HeaderCurrentDownloads": "Current Downloads",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Szczegóły", "HeaderDetails": "Szczegóły",
"HeaderDownloadQueue": "Download Queue", "HeaderDownloadQueue": "Download Queue",
@ -279,6 +280,8 @@
"LabelEdit": "Edytuj", "LabelEdit": "Edytuj",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "From Address", "LabelEmailSettingsFromAddress": "From Address",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Secure", "LabelEmailSettingsSecure": "Secure",
"LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "If true the connection will use TLS when connecting to server. If false then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false. (from nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Address", "LabelEmailSettingsTestAddress": "Test Address",
@ -335,6 +338,7 @@
"LabelItem": "Pozycja", "LabelItem": "Pozycja",
"LabelLanguage": "Język", "LabelLanguage": "Język",
"LabelLanguageDefaultServer": "Domyślny język serwera", "LabelLanguageDefaultServer": "Domyślny język serwera",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Last Book Added", "LabelLastBookAdded": "Last Book Added",
"LabelLastBookUpdated": "Last Book Updated", "LabelLastBookUpdated": "Last Book Updated",
"LabelLastSeen": "Ostatnio widziany", "LabelLastSeen": "Ostatnio widziany",
@ -346,6 +350,7 @@
"LabelLess": "Mniej", "LabelLess": "Mniej",
"LabelLibrariesAccessibleToUser": "Biblioteki dostępne dla użytkownika", "LabelLibrariesAccessibleToUser": "Biblioteki dostępne dla użytkownika",
"LabelLibrary": "Biblioteka", "LabelLibrary": "Biblioteka",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Element biblioteki", "LabelLibraryItem": "Element biblioteki",
"LabelLibraryName": "Nazwa biblioteki", "LabelLibraryName": "Nazwa biblioteki",
"LabelLimit": "Limit", "LabelLimit": "Limit",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nowe hasło", "LabelNewPassword": "Nowe hasło",
"LabelNextBackupDate": "Next backup date", "LabelNextBackupDate": "Next backup date",
"LabelNextScheduledRun": "Next scheduled run", "LabelNextScheduledRun": "Next scheduled run",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "No episodes selected", "LabelNoEpisodesSelected": "No episodes selected",
"LabelNotes": "Uwagi", "LabelNotes": "Uwagi",
"LabelNotFinished": "Nieukończone", "LabelNotFinished": "Nieukończone",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Ma możliwość dodawania", "LabelPermissionsUpload": "Ma możliwość dodawania",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Scieżka/URL do zdjęcia", "LabelPhotoPathURL": "Scieżka/URL do zdjęcia",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Playlists", "LabelPlaylists": "Playlists",
"LabelPlayMethod": "Metoda odtwarzania", "LabelPlayMethod": "Metoda odtwarzania",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Dostawca", "LabelProvider": "Dostawca",
"LabelPubDate": "Data publikacji", "LabelPubDate": "Data publikacji",
"LabelPublisher": "Wydawca", "LabelPublisher": "Wydawca",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Rok publikacji", "LabelPublishYear": "Rok publikacji",
"LabelRead": "Read", "LabelRead": "Read",
"LabelReadAgain": "Read Again", "LabelReadAgain": "Read Again",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Quick Match będzie próbował dodać brakujące okładki i metadane dla wybranych elementów. Włącz poniższe opcje, aby umożliwić Quick Match nadpisanie istniejących okładek i/lub metadanych.", "MessageBatchQuickMatchDescription": "Quick Match będzie próbował dodać brakujące okładki i metadane dla wybranych elementów. Włącz poniższe opcje, aby umożliwić Quick Match nadpisanie istniejących okładek i/lub metadanych.",
"MessageBookshelfNoCollections": "Nie posiadasz jeszcze żadnych kolekcji", "MessageBookshelfNoCollections": "Nie posiadasz jeszcze żadnych kolekcji",
"MessageBookshelfNoResultsForFilter": "Nie znaleziono żadnych pozycji przy aktualnym filtrowaniu \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Nie znaleziono żadnych pozycji przy aktualnym filtrowaniu \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Nie posiadasz żadnych otwartych feedów RSS", "MessageBookshelfNoRSSFeeds": "Nie posiadasz żadnych otwartych feedów RSS",
"MessageBookshelfNoSeries": "Nie masz jeszcze żadnych serii", "MessageBookshelfNoSeries": "Nie masz jeszcze żadnych serii",
"MessageChapterEndIsAfter": "Koniec rozdziału następuje po zakończeniu audiobooka", "MessageChapterEndIsAfter": "Koniec rozdziału następuje po zakończeniu audiobooka",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?", "MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?", "MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Aplicar", "ButtonApply": "Aplicar",
"ButtonApplyChapters": "Aplicar Capítulos", "ButtonApplyChapters": "Aplicar Capítulos",
"ButtonAuthors": "Autores", "ButtonAuthors": "Autores",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Procurar por Pasta", "ButtonBrowseForFolder": "Procurar por Pasta",
"ButtonCancel": "Cancelar", "ButtonCancel": "Cancelar",
"ButtonCancelEncode": "Cancelar Codificação", "ButtonCancelEncode": "Cancelar Codificação",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Capítulo Anterior", "ButtonPreviousChapter": "Capítulo Anterior",
"ButtonPurgeAllCache": "Apagar Todo o Cache", "ButtonPurgeAllCache": "Apagar Todo o Cache",
"ButtonPurgeItemsCache": "Apagar o Cache de Itens", "ButtonPurgeItemsCache": "Apagar o Cache de Itens",
"ButtonPurgeMediaProgress": "Apagar o Progresso nas Mídias",
"ButtonQueueAddItem": "Adicionar à Lista", "ButtonQueueAddItem": "Adicionar à Lista",
"ButtonQueueRemoveItem": "Remover da Lista", "ButtonQueueRemoveItem": "Remover da Lista",
"ButtonQuickMatch": "Consulta rápida", "ButtonQuickMatch": "Consulta rápida",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Itens da Coleção", "HeaderCollectionItems": "Itens da Coleção",
"HeaderCover": "Capas", "HeaderCover": "Capas",
"HeaderCurrentDownloads": "Downloads em andamento", "HeaderCurrentDownloads": "Downloads em andamento",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Fontes de Metadados Customizados", "HeaderCustomMetadataProviders": "Fontes de Metadados Customizados",
"HeaderDetails": "Detalhes", "HeaderDetails": "Detalhes",
"HeaderDownloadQueue": "Fila de Download", "HeaderDownloadQueue": "Fila de Download",
@ -279,6 +280,8 @@
"LabelEdit": "Editar", "LabelEdit": "Editar",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Remetente", "LabelEmailSettingsFromAddress": "Remetente",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Seguro", "LabelEmailSettingsSecure": "Seguro",
"LabelEmailSettingsSecureHelp": "Se ativado, a conexão utilizará TLS para a conexão ao servidor. Se desativado TLS será usado se o servidor suportar a extensão STARTTLS. Na maioria dos casos ative esse valor se estiver conectando pela porta 465. Para portas 587 ou 25, mantenha inativo. (de nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Se ativado, a conexão utilizará TLS para a conexão ao servidor. Se desativado TLS será usado se o servidor suportar a extensão STARTTLS. Na maioria dos casos ative esse valor se estiver conectando pela porta 465. Para portas 587 ou 25, mantenha inativo. (de nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Endereço de teste", "LabelEmailSettingsTestAddress": "Endereço de teste",
@ -335,6 +338,7 @@
"LabelItem": "Item", "LabelItem": "Item",
"LabelLanguage": "Idioma", "LabelLanguage": "Idioma",
"LabelLanguageDefaultServer": "Idioma Padrão do Servidor", "LabelLanguageDefaultServer": "Idioma Padrão do Servidor",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Último Livro Acrescentado", "LabelLastBookAdded": "Último Livro Acrescentado",
"LabelLastBookUpdated": "Último Livro Atualizado", "LabelLastBookUpdated": "Último Livro Atualizado",
"LabelLastSeen": "Visto pela Última Vez", "LabelLastSeen": "Visto pela Última Vez",
@ -346,6 +350,7 @@
"LabelLess": "Menos", "LabelLess": "Menos",
"LabelLibrariesAccessibleToUser": "Bibliotecas Acessíveis ao Usuário", "LabelLibrariesAccessibleToUser": "Bibliotecas Acessíveis ao Usuário",
"LabelLibrary": "Biblioteca", "LabelLibrary": "Biblioteca",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Item da Biblioteca", "LabelLibraryItem": "Item da Biblioteca",
"LabelLibraryName": "Nome da Biblioteca", "LabelLibraryName": "Nome da Biblioteca",
"LabelLimit": "Limite", "LabelLimit": "Limite",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nova Senha", "LabelNewPassword": "Nova Senha",
"LabelNextBackupDate": "Data do próximo backup", "LabelNextBackupDate": "Data do próximo backup",
"LabelNextScheduledRun": "Próxima execução programada", "LabelNextScheduledRun": "Próxima execução programada",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Nenhum episódio selecionado", "LabelNoEpisodesSelected": "Nenhum episódio selecionado",
"LabelNotes": "Notas", "LabelNotes": "Notas",
"LabelNotFinished": "Não concluído", "LabelNotFinished": "Não concluído",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Pode Fazer Upload", "LabelPermissionsUpload": "Pode Fazer Upload",
"LabelPersonalYearReview": "Sua Retrospectiva Anual ({0})", "LabelPersonalYearReview": "Sua Retrospectiva Anual ({0})",
"LabelPhotoPathURL": "Caminho/URL para Foto", "LabelPhotoPathURL": "Caminho/URL para Foto",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Listas de Reprodução", "LabelPlaylists": "Listas de Reprodução",
"LabelPlayMethod": "Método de Reprodução", "LabelPlayMethod": "Método de Reprodução",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Fonte", "LabelProvider": "Fonte",
"LabelPubDate": "Data de Publicação", "LabelPubDate": "Data de Publicação",
"LabelPublisher": "Editora", "LabelPublisher": "Editora",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Ano de Publicação", "LabelPublishYear": "Ano de Publicação",
"LabelRead": "Lido", "LabelRead": "Lido",
"LabelReadAgain": "Ler novamente", "LabelReadAgain": "Ler novamente",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Consulta Rápida tentará adicionar capas e metadados ausentes para os itens selecionados. Ative as opções abaixo para permitir que a Consulta Rápida sobrescreva capas e/ou metadados existentes.", "MessageBatchQuickMatchDescription": "Consulta Rápida tentará adicionar capas e metadados ausentes para os itens selecionados. Ative as opções abaixo para permitir que a Consulta Rápida sobrescreva capas e/ou metadados existentes.",
"MessageBookshelfNoCollections": "Você ainda não criou coleções", "MessageBookshelfNoCollections": "Você ainda não criou coleções",
"MessageBookshelfNoResultsForFilter": "Sem Resultados para o filtro \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Sem Resultados para o filtro \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Não existem feeds RSS abertos", "MessageBookshelfNoRSSFeeds": "Não existem feeds RSS abertos",
"MessageBookshelfNoSeries": "Você não tem séries", "MessageBookshelfNoSeries": "Você não tem séries",
"MessageChapterEndIsAfter": "O final do capítulo está além do final do seu audiobook", "MessageChapterEndIsAfter": "O final do capítulo está além do final do seu audiobook",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Tem certeza de que deseja marcar todos os livros nesta série como concluídos?", "MessageConfirmMarkSeriesFinished": "Tem certeza de que deseja marcar todos os livros nesta série como concluídos?",
"MessageConfirmMarkSeriesNotFinished": "Tem certeza de que deseja marcar todos os livros nesta série como não concluídos?", "MessageConfirmMarkSeriesNotFinished": "Tem certeza de que deseja marcar todos os livros nesta série como não concluídos?",
"MessageConfirmPurgeCache": "Apagar o cache irá apagar o diretório todo localizado em <code>/metadata/cache</code>. <br /><br />Tem certeza que deseja apagar o diretório de cache?", "MessageConfirmPurgeCache": "Apagar o cache irá apagar o diretório todo localizado em <code>/metadata/cache</code>. <br /><br />Tem certeza que deseja apagar o diretório de cache?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Aviso! Inclusão rápida não fará backup dos seus arquivos de áudio. Verifique se tem um backup dos seus arquivos de áudio. <br><br>Quer continuar?", "MessageConfirmQuickEmbed": "Aviso! Inclusão rápida não fará backup dos seus arquivos de áudio. Verifique se tem um backup dos seus arquivos de áudio. <br><br>Quer continuar?",
"MessageConfirmRemoveAllChapters": "Tem certeza de que deseja remover todos os capítulos?", "MessageConfirmRemoveAllChapters": "Tem certeza de que deseja remover todos os capítulos?",
"MessageConfirmRemoveAuthor": "Tem certeza de que deseja remover o autor \"{0}\"?", "MessageConfirmRemoveAuthor": "Tem certeza de que deseja remover o autor \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Применить", "ButtonApply": "Применить",
"ButtonApplyChapters": "Применить главы", "ButtonApplyChapters": "Применить главы",
"ButtonAuthors": "Авторы", "ButtonAuthors": "Авторы",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Выбрать папку", "ButtonBrowseForFolder": "Выбрать папку",
"ButtonCancel": "Отмена", "ButtonCancel": "Отмена",
"ButtonCancelEncode": "Отменить кодирование", "ButtonCancelEncode": "Отменить кодирование",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "Очистить весь кэш", "ButtonPurgeAllCache": "Очистить весь кэш",
"ButtonPurgeItemsCache": "Очистить кэш элементов", "ButtonPurgeItemsCache": "Очистить кэш элементов",
"ButtonPurgeMediaProgress": "Очистить прогресс медиа",
"ButtonQueueAddItem": "Добавить в очередь", "ButtonQueueAddItem": "Добавить в очередь",
"ButtonQueueRemoveItem": "Удалить из очереди", "ButtonQueueRemoveItem": "Удалить из очереди",
"ButtonQuickMatch": "Быстрый поиск", "ButtonQuickMatch": "Быстрый поиск",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Элементы коллекции", "HeaderCollectionItems": "Элементы коллекции",
"HeaderCover": "Обложка", "HeaderCover": "Обложка",
"HeaderCurrentDownloads": "Текущие закачки", "HeaderCurrentDownloads": "Текущие закачки",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Подробности", "HeaderDetails": "Подробности",
"HeaderDownloadQueue": "Очередь скачивания", "HeaderDownloadQueue": "Очередь скачивания",
@ -279,6 +280,8 @@
"LabelEdit": "Редактировать", "LabelEdit": "Редактировать",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Адрес От", "LabelEmailSettingsFromAddress": "Адрес От",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Безопасность", "LabelEmailSettingsSecure": "Безопасность",
"LabelEmailSettingsSecureHelp": "Если значение истинно, то соединение будет использовать TLS при подключении к серверу. Если значение ложно, то TLS будет использован, если сервер поддерживает расширение STARTTLS. В большинстве случаев установите это значение в истину, если вы подключаетесь к порту 465. Для порта 587 или 25 оставьте значение ложным. (из nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Если значение истинно, то соединение будет использовать TLS при подключении к серверу. Если значение ложно, то TLS будет использован, если сервер поддерживает расширение STARTTLS. В большинстве случаев установите это значение в истину, если вы подключаетесь к порту 465. Для порта 587 или 25 оставьте значение ложным. (из nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Тестовый адрес", "LabelEmailSettingsTestAddress": "Тестовый адрес",
@ -335,6 +338,7 @@
"LabelItem": "Элемент", "LabelItem": "Элемент",
"LabelLanguage": "Язык", "LabelLanguage": "Язык",
"LabelLanguageDefaultServer": "Язык сервера по умолчанию", "LabelLanguageDefaultServer": "Язык сервера по умолчанию",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Последняя книга добавлена", "LabelLastBookAdded": "Последняя книга добавлена",
"LabelLastBookUpdated": "Последняя книга обновлена", "LabelLastBookUpdated": "Последняя книга обновлена",
"LabelLastSeen": "Последнее сканирование", "LabelLastSeen": "Последнее сканирование",
@ -346,6 +350,7 @@
"LabelLess": "Менее", "LabelLess": "Менее",
"LabelLibrariesAccessibleToUser": "Библиотеки доступные для пользователя", "LabelLibrariesAccessibleToUser": "Библиотеки доступные для пользователя",
"LabelLibrary": "Библиотека", "LabelLibrary": "Библиотека",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Элемент библиотеки", "LabelLibraryItem": "Элемент библиотеки",
"LabelLibraryName": "Имя библиотеки", "LabelLibraryName": "Имя библиотеки",
"LabelLimit": "Лимит", "LabelLimit": "Лимит",
@ -381,6 +386,7 @@
"LabelNewPassword": "Новый пароль", "LabelNewPassword": "Новый пароль",
"LabelNextBackupDate": "Следующая дата бэкапирования", "LabelNextBackupDate": "Следующая дата бэкапирования",
"LabelNextScheduledRun": "Следущий запланированный запуск", "LabelNextScheduledRun": "Следущий запланированный запуск",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Эпизоды не выбраны", "LabelNoEpisodesSelected": "Эпизоды не выбраны",
"LabelNotes": "Заметки", "LabelNotes": "Заметки",
"LabelNotFinished": "Не завершено", "LabelNotFinished": "Не завершено",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Может закачивать", "LabelPermissionsUpload": "Может закачивать",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Путь к фото/URL", "LabelPhotoPathURL": "Путь к фото/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Плейлисты", "LabelPlaylists": "Плейлисты",
"LabelPlayMethod": "Метод воспроизведения", "LabelPlayMethod": "Метод воспроизведения",
"LabelPodcast": "Подкаст", "LabelPodcast": "Подкаст",
@ -426,6 +433,7 @@
"LabelProvider": "Провайдер", "LabelProvider": "Провайдер",
"LabelPubDate": "Дата публикации", "LabelPubDate": "Дата публикации",
"LabelPublisher": "Издатель", "LabelPublisher": "Издатель",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Год публикации", "LabelPublishYear": "Год публикации",
"LabelRead": "Читать", "LabelRead": "Читать",
"LabelReadAgain": "Читать снова", "LabelReadAgain": "Читать снова",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Быстрый Поиск попытается добавить отсутствующие обложки и метаданные для выбранных элементов. Включите параметры ниже, чтобы разрешить Быстрому Поиску перезаписывать существующие обложки и/или метаданные.", "MessageBatchQuickMatchDescription": "Быстрый Поиск попытается добавить отсутствующие обложки и метаданные для выбранных элементов. Включите параметры ниже, чтобы разрешить Быстрому Поиску перезаписывать существующие обложки и/или метаданные.",
"MessageBookshelfNoCollections": "Вы еще не создали ни одной коллекции", "MessageBookshelfNoCollections": "Вы еще не создали ни одной коллекции",
"MessageBookshelfNoResultsForFilter": "Нет Результатов для фильтра \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Нет Результатов для фильтра \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Нет открытых RSS-каналов", "MessageBookshelfNoRSSFeeds": "Нет открытых RSS-каналов",
"MessageBookshelfNoSeries": "У вас нет серий", "MessageBookshelfNoSeries": "У вас нет серий",
"MessageChapterEndIsAfter": "Конец главы после окончания вашей аудиокниги", "MessageChapterEndIsAfter": "Конец главы после окончания вашей аудиокниги",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как завершенные?", "MessageConfirmMarkSeriesFinished": "Вы уверены, что хотите отметить все книги этой серии как завершенные?",
"MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как не завершенные?", "MessageConfirmMarkSeriesNotFinished": "Вы уверены, что хотите отметить все книги этой серии как не завершенные?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Предупреждение! Быстрое встраивание не позволяет создавать резервные копии аудиофайлов. Убедитесь, что у вас есть резервная копия аудиофайлов. <br><br>Хотите продолжить?", "MessageConfirmQuickEmbed": "Предупреждение! Быстрое встраивание не позволяет создавать резервные копии аудиофайлов. Убедитесь, что у вас есть резервная копия аудиофайлов. <br><br>Хотите продолжить?",
"MessageConfirmRemoveAllChapters": "Вы уверены, что хотите удалить все главы?", "MessageConfirmRemoveAllChapters": "Вы уверены, что хотите удалить все главы?",
"MessageConfirmRemoveAuthor": "Вы уверены, что хотите удалить автора \"{0}\"?", "MessageConfirmRemoveAuthor": "Вы уверены, что хотите удалить автора \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Tillämpa", "ButtonApply": "Tillämpa",
"ButtonApplyChapters": "Tillämpa kapitel", "ButtonApplyChapters": "Tillämpa kapitel",
"ButtonAuthors": "Författare", "ButtonAuthors": "Författare",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Bläddra efter mapp", "ButtonBrowseForFolder": "Bläddra efter mapp",
"ButtonCancel": "Avbryt", "ButtonCancel": "Avbryt",
"ButtonCancelEncode": "Avbryt kodning", "ButtonCancelEncode": "Avbryt kodning",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Previous Chapter", "ButtonPreviousChapter": "Previous Chapter",
"ButtonPurgeAllCache": "Rensa all cache", "ButtonPurgeAllCache": "Rensa all cache",
"ButtonPurgeItemsCache": "Rensa föremåls-cache", "ButtonPurgeItemsCache": "Rensa föremåls-cache",
"ButtonPurgeMediaProgress": "Rensa medieförlopp",
"ButtonQueueAddItem": "Lägg till i kön", "ButtonQueueAddItem": "Lägg till i kön",
"ButtonQueueRemoveItem": "Ta bort från kön", "ButtonQueueRemoveItem": "Ta bort från kön",
"ButtonQuickMatch": "Snabb matchning", "ButtonQuickMatch": "Snabb matchning",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Samlingselement", "HeaderCollectionItems": "Samlingselement",
"HeaderCover": "Omslag", "HeaderCover": "Omslag",
"HeaderCurrentDownloads": "Aktuella nedladdningar", "HeaderCurrentDownloads": "Aktuella nedladdningar",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Custom Metadata Providers", "HeaderCustomMetadataProviders": "Custom Metadata Providers",
"HeaderDetails": "Detaljer", "HeaderDetails": "Detaljer",
"HeaderDownloadQueue": "Nedladdningskö", "HeaderDownloadQueue": "Nedladdningskö",
@ -279,6 +280,8 @@
"LabelEdit": "Redigera", "LabelEdit": "Redigera",
"LabelEmail": "E-post", "LabelEmail": "E-post",
"LabelEmailSettingsFromAddress": "Från adress", "LabelEmailSettingsFromAddress": "Från adress",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Säker", "LabelEmailSettingsSecure": "Säker",
"LabelEmailSettingsSecureHelp": "Om sant kommer anslutningen att använda TLS vid anslutning till servern. Om falskt används TLS om servern stöder STARTTLS-tillägget. I de flesta fall, om du ansluter till port 465, bör du ställa in detta värde till sant. För port 587 eller 25, låt det vara falskt. (från nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Om sant kommer anslutningen att använda TLS vid anslutning till servern. Om falskt används TLS om servern stöder STARTTLS-tillägget. I de flesta fall, om du ansluter till port 465, bör du ställa in detta värde till sant. För port 587 eller 25, låt det vara falskt. (från nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Testadress", "LabelEmailSettingsTestAddress": "Testadress",
@ -335,6 +338,7 @@
"LabelItem": "Objekt", "LabelItem": "Objekt",
"LabelLanguage": "Språk", "LabelLanguage": "Språk",
"LabelLanguageDefaultServer": "Standardspråk för server", "LabelLanguageDefaultServer": "Standardspråk för server",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Senaste bok tillagd", "LabelLastBookAdded": "Senaste bok tillagd",
"LabelLastBookUpdated": "Senaste bok uppdaterad", "LabelLastBookUpdated": "Senaste bok uppdaterad",
"LabelLastSeen": "Senast sedd", "LabelLastSeen": "Senast sedd",
@ -346,6 +350,7 @@
"LabelLess": "Mindre", "LabelLess": "Mindre",
"LabelLibrariesAccessibleToUser": "Åtkomliga bibliotek för användare", "LabelLibrariesAccessibleToUser": "Åtkomliga bibliotek för användare",
"LabelLibrary": "Bibliotek", "LabelLibrary": "Bibliotek",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Biblioteksobjekt", "LabelLibraryItem": "Biblioteksobjekt",
"LabelLibraryName": "Biblioteksnamn", "LabelLibraryName": "Biblioteksnamn",
"LabelLimit": "Begränsning", "LabelLimit": "Begränsning",
@ -381,6 +386,7 @@
"LabelNewPassword": "Nytt lösenord", "LabelNewPassword": "Nytt lösenord",
"LabelNextBackupDate": "Nästa säkerhetskopia datum", "LabelNextBackupDate": "Nästa säkerhetskopia datum",
"LabelNextScheduledRun": "Nästa schemalagda körning", "LabelNextScheduledRun": "Nästa schemalagda körning",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Inga avsnitt valda", "LabelNoEpisodesSelected": "Inga avsnitt valda",
"LabelNotes": "Anteckningar", "LabelNotes": "Anteckningar",
"LabelNotFinished": "Ej avslutad", "LabelNotFinished": "Ej avslutad",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Kan ladda upp", "LabelPermissionsUpload": "Kan ladda upp",
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Bildsökväg/URL", "LabelPhotoPathURL": "Bildsökväg/URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Spellistor", "LabelPlaylists": "Spellistor",
"LabelPlayMethod": "Spelläge", "LabelPlayMethod": "Spelläge",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Leverantör", "LabelProvider": "Leverantör",
"LabelPubDate": "Publiceringsdatum", "LabelPubDate": "Publiceringsdatum",
"LabelPublisher": "Utgivare", "LabelPublisher": "Utgivare",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Publiceringsår", "LabelPublishYear": "Publiceringsår",
"LabelRead": "Läst", "LabelRead": "Läst",
"LabelReadAgain": "Läs igen", "LabelReadAgain": "Läs igen",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Quick Match kommer försöka lägga till saknade omslag och metadata för de valda föremålen. Aktivera alternativen nedan för att tillåta Quick Match att överskriva befintliga omslag och/eller metadata.", "MessageBatchQuickMatchDescription": "Quick Match kommer försöka lägga till saknade omslag och metadata för de valda föremålen. Aktivera alternativen nedan för att tillåta Quick Match att överskriva befintliga omslag och/eller metadata.",
"MessageBookshelfNoCollections": "Du har ännu inte skapat några samlingar", "MessageBookshelfNoCollections": "Du har ännu inte skapat några samlingar",
"MessageBookshelfNoResultsForFilter": "Inga resultat för filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Inga resultat för filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Inga RSS-flöden är öppna", "MessageBookshelfNoRSSFeeds": "Inga RSS-flöden är öppna",
"MessageBookshelfNoSeries": "Du har inga serier", "MessageBookshelfNoSeries": "Du har inga serier",
"MessageChapterEndIsAfter": "Kapitelns slut är efter din ljudboks slut", "MessageChapterEndIsAfter": "Kapitelns slut är efter din ljudboks slut",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Är du säker på att du vill markera alla böcker i denna serie som avslutade?", "MessageConfirmMarkSeriesFinished": "Är du säker på att du vill markera alla böcker i denna serie som avslutade?",
"MessageConfirmMarkSeriesNotFinished": "Är du säker på att du vill markera alla böcker i denna serie som inte avslutade?", "MessageConfirmMarkSeriesNotFinished": "Är du säker på att du vill markera alla böcker i denna serie som inte avslutade?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Varning! Quick embed kommer inte att säkerhetskopiera dina ljudfiler. Se till att du har en säkerhetskopia av dina ljudfiler. <br><br>Vill du fortsätta?", "MessageConfirmQuickEmbed": "Varning! Quick embed kommer inte att säkerhetskopiera dina ljudfiler. Se till att du har en säkerhetskopia av dina ljudfiler. <br><br>Vill du fortsätta?",
"MessageConfirmRemoveAllChapters": "Är du säker på att du vill ta bort alla kapitel?", "MessageConfirmRemoveAllChapters": "Är du säker på att du vill ta bort alla kapitel?",
"MessageConfirmRemoveAuthor": "Är du säker på att du vill ta bort författaren \"{0}\"?", "MessageConfirmRemoveAuthor": "Är du säker på att du vill ta bort författaren \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Застосувати", "ButtonApply": "Застосувати",
"ButtonApplyChapters": "Зберегти глави", "ButtonApplyChapters": "Зберегти глави",
"ButtonAuthors": "Автори", "ButtonAuthors": "Автори",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Огляд тек", "ButtonBrowseForFolder": "Огляд тек",
"ButtonCancel": "Скасувати", "ButtonCancel": "Скасувати",
"ButtonCancelEncode": "Скасувати кодування", "ButtonCancelEncode": "Скасувати кодування",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Попередня глава", "ButtonPreviousChapter": "Попередня глава",
"ButtonPurgeAllCache": "Очистити весь кеш", "ButtonPurgeAllCache": "Очистити весь кеш",
"ButtonPurgeItemsCache": "Очистити кеш елементів", "ButtonPurgeItemsCache": "Очистити кеш елементів",
"ButtonPurgeMediaProgress": "Очистити прогрес",
"ButtonQueueAddItem": "Додати до черги", "ButtonQueueAddItem": "Додати до черги",
"ButtonQueueRemoveItem": "Вилучити з черги", "ButtonQueueRemoveItem": "Вилучити з черги",
"ButtonQuickMatch": "Швидкий пошук", "ButtonQuickMatch": "Швидкий пошук",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Елементи добірки", "HeaderCollectionItems": "Елементи добірки",
"HeaderCover": "Обкладинка", "HeaderCover": "Обкладинка",
"HeaderCurrentDownloads": "Поточні завантаження", "HeaderCurrentDownloads": "Поточні завантаження",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Постачальники метаданих", "HeaderCustomMetadataProviders": "Постачальники метаданих",
"HeaderDetails": "Подробиці", "HeaderDetails": "Подробиці",
"HeaderDownloadQueue": "Черга завантажень", "HeaderDownloadQueue": "Черга завантажень",
@ -279,6 +280,8 @@
"LabelEdit": "Редагувати", "LabelEdit": "Редагувати",
"LabelEmail": "Електронна пошта", "LabelEmail": "Електронна пошта",
"LabelEmailSettingsFromAddress": "Адреса відправника", "LabelEmailSettingsFromAddress": "Адреса відправника",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Безпечне", "LabelEmailSettingsSecure": "Безпечне",
"LabelEmailSettingsSecureHelp": "Увімкніть, аби використовувати TLS при підключенні до сервера. Якщо вимкнути, то TLS буде використано, якщо сервер підтримує STARTTLS. Увімкніть, якщо ви підключаєтеся до порту 465. Вимкніть для портів 587 або 25. (з nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Увімкніть, аби використовувати TLS при підключенні до сервера. Якщо вимкнути, то TLS буде використано, якщо сервер підтримує STARTTLS. Увімкніть, якщо ви підключаєтеся до порту 465. Вимкніть для портів 587 або 25. (з nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Тестова адреса", "LabelEmailSettingsTestAddress": "Тестова адреса",
@ -335,6 +338,7 @@
"LabelItem": "Елемент", "LabelItem": "Елемент",
"LabelLanguage": "Мова", "LabelLanguage": "Мова",
"LabelLanguageDefaultServer": "Типова мова сервера", "LabelLanguageDefaultServer": "Типова мова сервера",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Останню книгу додано", "LabelLastBookAdded": "Останню книгу додано",
"LabelLastBookUpdated": "Останню книгу оновлено", "LabelLastBookUpdated": "Останню книгу оновлено",
"LabelLastSeen": "Активність", "LabelLastSeen": "Активність",
@ -346,6 +350,7 @@
"LabelLess": "Менше", "LabelLess": "Менше",
"LabelLibrariesAccessibleToUser": "Бібліотеки, доступні користувачу", "LabelLibrariesAccessibleToUser": "Бібліотеки, доступні користувачу",
"LabelLibrary": "Бібліотека", "LabelLibrary": "Бібліотека",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Елемент бібліотеки", "LabelLibraryItem": "Елемент бібліотеки",
"LabelLibraryName": "Назва бібліотеки", "LabelLibraryName": "Назва бібліотеки",
"LabelLimit": "Обмеження", "LabelLimit": "Обмеження",
@ -381,6 +386,7 @@
"LabelNewPassword": "Новий пароль", "LabelNewPassword": "Новий пароль",
"LabelNextBackupDate": "Дата наступного резервного копіювання", "LabelNextBackupDate": "Дата наступного резервного копіювання",
"LabelNextScheduledRun": "Наступний запланований запуск", "LabelNextScheduledRun": "Наступний запланований запуск",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Не вибрано жодного епізоду", "LabelNoEpisodesSelected": "Не вибрано жодного епізоду",
"LabelNotes": "Примітки", "LabelNotes": "Примітки",
"LabelNotFinished": "Незавершені", "LabelNotFinished": "Незавершені",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Може завантажувати", "LabelPermissionsUpload": "Може завантажувати",
"LabelPersonalYearReview": "Ваші підсумки року ({0})", "LabelPersonalYearReview": "Ваші підсумки року ({0})",
"LabelPhotoPathURL": "Шлях/URL фото", "LabelPhotoPathURL": "Шлях/URL фото",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Списки відтворення", "LabelPlaylists": "Списки відтворення",
"LabelPlayMethod": "Метод відтворення", "LabelPlayMethod": "Метод відтворення",
"LabelPodcast": "Подкаст", "LabelPodcast": "Подкаст",
@ -426,6 +433,7 @@
"LabelProvider": "Джерело", "LabelProvider": "Джерело",
"LabelPubDate": "Дата публікації", "LabelPubDate": "Дата публікації",
"LabelPublisher": "Видавець", "LabelPublisher": "Видавець",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Рік публікації", "LabelPublishYear": "Рік публікації",
"LabelRead": "Читати", "LabelRead": "Читати",
"LabelReadAgain": "Читати знову", "LabelReadAgain": "Читати знову",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Швидкий пошук спробує знайти відсутні обкладинки та метадані обраних елементів. Увімкніть налаштування нижче, аби дозволити заміну наявних обкладинок та/або метаданих під час швидкого пошуку.", "MessageBatchQuickMatchDescription": "Швидкий пошук спробує знайти відсутні обкладинки та метадані обраних елементів. Увімкніть налаштування нижче, аби дозволити заміну наявних обкладинок та/або метаданих під час швидкого пошуку.",
"MessageBookshelfNoCollections": "Ви не створили жодної добірки", "MessageBookshelfNoCollections": "Ви не створили жодної добірки",
"MessageBookshelfNoResultsForFilter": "Немає результатів з фільтром \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Немає результатів з фільтром \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Немає відкритих RSS-каналів", "MessageBookshelfNoRSSFeeds": "Немає відкритих RSS-каналів",
"MessageBookshelfNoSeries": "Серії відсутні", "MessageBookshelfNoSeries": "Серії відсутні",
"MessageChapterEndIsAfter": "Кінець глави знаходиться після закінчення книги", "MessageChapterEndIsAfter": "Кінець глави знаходиться після закінчення книги",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Ви дійсно бажаєте позначити усі книги серії завершеними?", "MessageConfirmMarkSeriesFinished": "Ви дійсно бажаєте позначити усі книги серії завершеними?",
"MessageConfirmMarkSeriesNotFinished": "Ви дійсно бажаєте позначити всі книги серії незавершеними?", "MessageConfirmMarkSeriesNotFinished": "Ви дійсно бажаєте позначити всі книги серії незавершеними?",
"MessageConfirmPurgeCache": "Очищення кешу видалить усю теку <code>/metadata/cache</code>. <br /><br />Ви дійсно бажаєте видалити теку кешу?", "MessageConfirmPurgeCache": "Очищення кешу видалить усю теку <code>/metadata/cache</code>. <br /><br />Ви дійсно бажаєте видалити теку кешу?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Увага! Швидке вбудування не створює резервних копій ваших аудіо. Переконайтеся, що маєте копію ваших файлів.<br><br>Продовжити?", "MessageConfirmQuickEmbed": "Увага! Швидке вбудування не створює резервних копій ваших аудіо. Переконайтеся, що маєте копію ваших файлів.<br><br>Продовжити?",
"MessageConfirmRemoveAllChapters": "Ви дійсно бажаєте видалити усі глави?", "MessageConfirmRemoveAllChapters": "Ви дійсно бажаєте видалити усі глави?",
"MessageConfirmRemoveAuthor": "Ви дійсно бажаєте видалити автора \"{0}\"?", "MessageConfirmRemoveAuthor": "Ви дійсно бажаєте видалити автора \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "Áp Dụng", "ButtonApply": "Áp Dụng",
"ButtonApplyChapters": "Áp Dụng Chương", "ButtonApplyChapters": "Áp Dụng Chương",
"ButtonAuthors": "Tác Giả", "ButtonAuthors": "Tác Giả",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "Duyệt Thư Mục", "ButtonBrowseForFolder": "Duyệt Thư Mục",
"ButtonCancel": "Hủy", "ButtonCancel": "Hủy",
"ButtonCancelEncode": "Hủy Mã Hóa", "ButtonCancelEncode": "Hủy Mã Hóa",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "Chương Trước", "ButtonPreviousChapter": "Chương Trước",
"ButtonPurgeAllCache": "Xóa Sạch Tất Cả Bộ Nhớ Cache", "ButtonPurgeAllCache": "Xóa Sạch Tất Cả Bộ Nhớ Cache",
"ButtonPurgeItemsCache": "Xóa Sạch Bộ Nhớ Cache Các Mục", "ButtonPurgeItemsCache": "Xóa Sạch Bộ Nhớ Cache Các Mục",
"ButtonPurgeMediaProgress": "Xóa Sạch Tiến Trình Phương Tiện",
"ButtonQueueAddItem": "Thêm vào hàng đợi", "ButtonQueueAddItem": "Thêm vào hàng đợi",
"ButtonQueueRemoveItem": "Xóa khỏi hàng đợi", "ButtonQueueRemoveItem": "Xóa khỏi hàng đợi",
"ButtonQuickMatch": "Khớp Nhanh", "ButtonQuickMatch": "Khớp Nhanh",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "Các Mục Bộ Sưu Tập", "HeaderCollectionItems": "Các Mục Bộ Sưu Tập",
"HeaderCover": "Bìa", "HeaderCover": "Bìa",
"HeaderCurrentDownloads": "Tải Xuống Hiện Tại", "HeaderCurrentDownloads": "Tải Xuống Hiện Tại",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "Các Nhà Cung Cấp Metadata Tùy Chỉnh", "HeaderCustomMetadataProviders": "Các Nhà Cung Cấp Metadata Tùy Chỉnh",
"HeaderDetails": "Chi Tiết", "HeaderDetails": "Chi Tiết",
"HeaderDownloadQueue": "Hàng Đợi Tải Xuống", "HeaderDownloadQueue": "Hàng Đợi Tải Xuống",
@ -279,6 +280,8 @@
"LabelEdit": "Chỉnh Sửa", "LabelEdit": "Chỉnh Sửa",
"LabelEmail": "Email", "LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Địa chỉ Gửi từ", "LabelEmailSettingsFromAddress": "Địa chỉ Gửi từ",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "Bảo Mật", "LabelEmailSettingsSecure": "Bảo Mật",
"LabelEmailSettingsSecureHelp": "Nếu đúng thì kết nối sẽ sử dụng TLS khi kết nối đến máy chủ. Nếu sai thì TLS sẽ được sử dụng nếu máy chủ hỗ trợ phần mở rộng STARTTLS. Trong hầu hết các trường hợp, hãy đặt giá trị này là đúng nếu bạn kết nối đến cổng 465. Đối với cổng 587 hoặc 25, giữ nó sai. (từ nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Nếu đúng thì kết nối sẽ sử dụng TLS khi kết nối đến máy chủ. Nếu sai thì TLS sẽ được sử dụng nếu máy chủ hỗ trợ phần mở rộng STARTTLS. Trong hầu hết các trường hợp, hãy đặt giá trị này là đúng nếu bạn kết nối đến cổng 465. Đối với cổng 587 hoặc 25, giữ nó sai. (từ nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Địa Chỉ Kiểm Tra", "LabelEmailSettingsTestAddress": "Địa Chỉ Kiểm Tra",
@ -335,6 +338,7 @@
"LabelItem": "Mục", "LabelItem": "Mục",
"LabelLanguage": "Ngôn ngữ", "LabelLanguage": "Ngôn ngữ",
"LabelLanguageDefaultServer": "Ngôn ngữ Máy chủ mặc định", "LabelLanguageDefaultServer": "Ngôn ngữ Máy chủ mặc định",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Sách mới nhất được thêm", "LabelLastBookAdded": "Sách mới nhất được thêm",
"LabelLastBookUpdated": "Sách mới nhất được cập nhật", "LabelLastBookUpdated": "Sách mới nhất được cập nhật",
"LabelLastSeen": "Lần cuối nhìn thấy", "LabelLastSeen": "Lần cuối nhìn thấy",
@ -346,6 +350,7 @@
"LabelLess": "Ít hơn", "LabelLess": "Ít hơn",
"LabelLibrariesAccessibleToUser": "Thư viện có thể truy cập cho người dùng", "LabelLibrariesAccessibleToUser": "Thư viện có thể truy cập cho người dùng",
"LabelLibrary": "Thư viện", "LabelLibrary": "Thư viện",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Mục thư viện", "LabelLibraryItem": "Mục thư viện",
"LabelLibraryName": "Tên thư viện", "LabelLibraryName": "Tên thư viện",
"LabelLimit": "Giới hạn", "LabelLimit": "Giới hạn",
@ -381,6 +386,7 @@
"LabelNewPassword": "Mật khẩu mới", "LabelNewPassword": "Mật khẩu mới",
"LabelNextBackupDate": "Ngày sao lưu tiếp theo", "LabelNextBackupDate": "Ngày sao lưu tiếp theo",
"LabelNextScheduledRun": "Chạy tiếp theo theo lịch trình", "LabelNextScheduledRun": "Chạy tiếp theo theo lịch trình",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "Không có tập nào được chọn", "LabelNoEpisodesSelected": "Không có tập nào được chọn",
"LabelNotes": "Ghi chú", "LabelNotes": "Ghi chú",
"LabelNotFinished": "Chưa hoàn thành", "LabelNotFinished": "Chưa hoàn thành",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "Có Thể Tải Lên", "LabelPermissionsUpload": "Có Thể Tải Lên",
"LabelPersonalYearReview": "Năm của Bạn trong Bài Đánh Giá ({0})", "LabelPersonalYearReview": "Năm của Bạn trong Bài Đánh Giá ({0})",
"LabelPhotoPathURL": "Đường dẫn/URL ảnh", "LabelPhotoPathURL": "Đường dẫn/URL ảnh",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Danh sách phát", "LabelPlaylists": "Danh sách phát",
"LabelPlayMethod": "Phương pháp phát", "LabelPlayMethod": "Phương pháp phát",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@ -426,6 +433,7 @@
"LabelProvider": "Nhà cung cấp", "LabelProvider": "Nhà cung cấp",
"LabelPubDate": "Ngày Xuất bản", "LabelPubDate": "Ngày Xuất bản",
"LabelPublisher": "Nhà xuất bản", "LabelPublisher": "Nhà xuất bản",
"LabelPublishers": "Publishers",
"LabelPublishYear": "Năm Xuất bản", "LabelPublishYear": "Năm Xuất bản",
"LabelRead": "Đọc", "LabelRead": "Đọc",
"LabelReadAgain": "Đọc lại", "LabelReadAgain": "Đọc lại",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "Quick Match sẽ cố gắng thêm các ảnh bìa và siêu dữ liệu bị thiếu cho các mục đã chọn. Bật các tùy chọn dưới đây để cho phép Quick Match ghi đè lên các ảnh bìa hiện có và / hoặc siêu dữ liệu.", "MessageBatchQuickMatchDescription": "Quick Match sẽ cố gắng thêm các ảnh bìa và siêu dữ liệu bị thiếu cho các mục đã chọn. Bật các tùy chọn dưới đây để cho phép Quick Match ghi đè lên các ảnh bìa hiện có và / hoặc siêu dữ liệu.",
"MessageBookshelfNoCollections": "Bạn chưa tạo bất kỳ bộ sưu tập nào", "MessageBookshelfNoCollections": "Bạn chưa tạo bất kỳ bộ sưu tập nào",
"MessageBookshelfNoResultsForFilter": "Không có Kết quả cho bộ lọc \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Không có Kết quả cho bộ lọc \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "Không có nguồn cung cấp RSS nào đang mở", "MessageBookshelfNoRSSFeeds": "Không có nguồn cung cấp RSS nào đang mở",
"MessageBookshelfNoSeries": "Bạn không có bộ sách", "MessageBookshelfNoSeries": "Bạn không có bộ sách",
"MessageChapterEndIsAfter": "Kết thúc chương sau khi kết thúc sách nói của bạn", "MessageChapterEndIsAfter": "Kết thúc chương sau khi kết thúc sách nói của bạn",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "Bạn có chắc chắn muốn đánh dấu tất cả các sách trong loạt sách này đã kết thúc không?", "MessageConfirmMarkSeriesFinished": "Bạn có chắc chắn muốn đánh dấu tất cả các sách trong loạt sách này đã kết thúc không?",
"MessageConfirmMarkSeriesNotFinished": "Bạn có chắc chắn muốn đánh dấu tất cả các sách trong loạt sách này chưa kết thúc không?", "MessageConfirmMarkSeriesNotFinished": "Bạn có chắc chắn muốn đánh dấu tất cả các sách trong loạt sách này chưa kết thúc không?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Cảnh báo! Quick embed sẽ không sao lưu các tệp âm thanh của bạn. Đảm bảo bạn có một bản sao lưu của các tệp âm thanh của bạn. <br><br>Bạn có muốn tiếp tục không?", "MessageConfirmQuickEmbed": "Cảnh báo! Quick embed sẽ không sao lưu các tệp âm thanh của bạn. Đảm bảo bạn có một bản sao lưu của các tệp âm thanh của bạn. <br><br>Bạn có muốn tiếp tục không?",
"MessageConfirmRemoveAllChapters": "Bạn có chắc chắn muốn xóa tất cả các chương không?", "MessageConfirmRemoveAllChapters": "Bạn có chắc chắn muốn xóa tất cả các chương không?",
"MessageConfirmRemoveAuthor": "Bạn có chắc chắn muốn xóa tác giả \"{0}\" không?", "MessageConfirmRemoveAuthor": "Bạn có chắc chắn muốn xóa tác giả \"{0}\" không?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "应用", "ButtonApply": "应用",
"ButtonApplyChapters": "应用到章节", "ButtonApplyChapters": "应用到章节",
"ButtonAuthors": "作者", "ButtonAuthors": "作者",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "浏览文件夹", "ButtonBrowseForFolder": "浏览文件夹",
"ButtonCancel": "取消", "ButtonCancel": "取消",
"ButtonCancelEncode": "取消编码", "ButtonCancelEncode": "取消编码",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "上一章节", "ButtonPreviousChapter": "上一章节",
"ButtonPurgeAllCache": "清理所有缓存", "ButtonPurgeAllCache": "清理所有缓存",
"ButtonPurgeItemsCache": "清理项目缓存", "ButtonPurgeItemsCache": "清理项目缓存",
"ButtonPurgeMediaProgress": "清理媒体进度",
"ButtonQueueAddItem": "添加到队列", "ButtonQueueAddItem": "添加到队列",
"ButtonQueueRemoveItem": "从队列中移除", "ButtonQueueRemoveItem": "从队列中移除",
"ButtonQuickMatch": "快速匹配", "ButtonQuickMatch": "快速匹配",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "收藏项目", "HeaderCollectionItems": "收藏项目",
"HeaderCover": "封面", "HeaderCover": "封面",
"HeaderCurrentDownloads": "当前下载", "HeaderCurrentDownloads": "当前下载",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "自定义元数据提供者", "HeaderCustomMetadataProviders": "自定义元数据提供者",
"HeaderDetails": "详情", "HeaderDetails": "详情",
"HeaderDownloadQueue": "下载队列", "HeaderDownloadQueue": "下载队列",
@ -279,6 +280,8 @@
"LabelEdit": "编辑", "LabelEdit": "编辑",
"LabelEmail": "邮箱", "LabelEmail": "邮箱",
"LabelEmailSettingsFromAddress": "发件人地址", "LabelEmailSettingsFromAddress": "发件人地址",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "安全", "LabelEmailSettingsSecure": "安全",
"LabelEmailSettingsSecureHelp": "如果选是, 则连接将在连接到服务器时使用TLS. 如果选否, 则若服务器支持STARTTLS扩展, 则使用TLS. 在大多数情况下, 如果连接到端口465, 请将该值设置为是. 对于端口587或25, 请保持为否. (来自nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "如果选是, 则连接将在连接到服务器时使用TLS. 如果选否, 则若服务器支持STARTTLS扩展, 则使用TLS. 在大多数情况下, 如果连接到端口465, 请将该值设置为是. 对于端口587或25, 请保持为否. (来自nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "测试地址", "LabelEmailSettingsTestAddress": "测试地址",
@ -335,6 +338,7 @@
"LabelItem": "项目", "LabelItem": "项目",
"LabelLanguage": "语言", "LabelLanguage": "语言",
"LabelLanguageDefaultServer": "默认服务器语言", "LabelLanguageDefaultServer": "默认服务器语言",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "最后添加的书", "LabelLastBookAdded": "最后添加的书",
"LabelLastBookUpdated": "最后更新的书", "LabelLastBookUpdated": "最后更新的书",
"LabelLastSeen": "上次查看时间", "LabelLastSeen": "上次查看时间",
@ -346,6 +350,7 @@
"LabelLess": "较少", "LabelLess": "较少",
"LabelLibrariesAccessibleToUser": "用户可访问的媒体库", "LabelLibrariesAccessibleToUser": "用户可访问的媒体库",
"LabelLibrary": "媒体库", "LabelLibrary": "媒体库",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "媒体库项目", "LabelLibraryItem": "媒体库项目",
"LabelLibraryName": "媒体库名称", "LabelLibraryName": "媒体库名称",
"LabelLimit": "限制", "LabelLimit": "限制",
@ -381,6 +386,7 @@
"LabelNewPassword": "新密码", "LabelNewPassword": "新密码",
"LabelNextBackupDate": "下次备份日期", "LabelNextBackupDate": "下次备份日期",
"LabelNextScheduledRun": "下次任务运行", "LabelNextScheduledRun": "下次任务运行",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "未选择任何剧集", "LabelNoEpisodesSelected": "未选择任何剧集",
"LabelNotes": "注释", "LabelNotes": "注释",
"LabelNotFinished": "未听完", "LabelNotFinished": "未听完",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "可以上传", "LabelPermissionsUpload": "可以上传",
"LabelPersonalYearReview": "你的年度回顾 ({0})", "LabelPersonalYearReview": "你的年度回顾 ({0})",
"LabelPhotoPathURL": "图片路径或 URL", "LabelPhotoPathURL": "图片路径或 URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "播放列表", "LabelPlaylists": "播放列表",
"LabelPlayMethod": "播放方法", "LabelPlayMethod": "播放方法",
"LabelPodcast": "播客", "LabelPodcast": "播客",
@ -426,6 +433,7 @@
"LabelProvider": "供应商", "LabelProvider": "供应商",
"LabelPubDate": "出版日期", "LabelPubDate": "出版日期",
"LabelPublisher": "出版商", "LabelPublisher": "出版商",
"LabelPublishers": "Publishers",
"LabelPublishYear": "发布年份", "LabelPublishYear": "发布年份",
"LabelRead": "阅读", "LabelRead": "阅读",
"LabelReadAgain": "再次阅读", "LabelReadAgain": "再次阅读",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.", "MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
"MessageBookshelfNoCollections": "你尚未进行任何收藏", "MessageBookshelfNoCollections": "你尚未进行任何收藏",
"MessageBookshelfNoResultsForFilter": "过滤器无结果 \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "过滤器无结果 \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "没有打开的 RSS 源", "MessageBookshelfNoRSSFeeds": "没有打开的 RSS 源",
"MessageBookshelfNoSeries": "你没有系列", "MessageBookshelfNoSeries": "你没有系列",
"MessageChapterEndIsAfter": "章节结束是在有声读物结束之后", "MessageChapterEndIsAfter": "章节结束是在有声读物结束之后",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?", "MessageConfirmMarkSeriesFinished": "你确定要将此系列中的所有书籍都标记为已听完吗?",
"MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?", "MessageConfirmMarkSeriesNotFinished": "你确定要将此系列中的所有书籍都标记为未听完吗?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "警告! 快速嵌入不会备份你的音频文件. 确保你有音频文件的备份. <br><br>你是否想继续吗?", "MessageConfirmQuickEmbed": "警告! 快速嵌入不会备份你的音频文件. 确保你有音频文件的备份. <br><br>你是否想继续吗?",
"MessageConfirmRemoveAllChapters": "你确定要移除所有章节吗?", "MessageConfirmRemoveAllChapters": "你确定要移除所有章节吗?",
"MessageConfirmRemoveAuthor": "你确定要删除作者 \"{0}\"?", "MessageConfirmRemoveAuthor": "你确定要删除作者 \"{0}\"?",

View file

@ -9,6 +9,7 @@
"ButtonApply": "應用", "ButtonApply": "應用",
"ButtonApplyChapters": "應用到章節", "ButtonApplyChapters": "應用到章節",
"ButtonAuthors": "作者", "ButtonAuthors": "作者",
"ButtonBack": "Back",
"ButtonBrowseForFolder": "瀏覽資料夾", "ButtonBrowseForFolder": "瀏覽資料夾",
"ButtonCancel": "取消", "ButtonCancel": "取消",
"ButtonCancelEncode": "取消編碼", "ButtonCancelEncode": "取消編碼",
@ -56,7 +57,6 @@
"ButtonPreviousChapter": "過去的章節", "ButtonPreviousChapter": "過去的章節",
"ButtonPurgeAllCache": "清理所有快取", "ButtonPurgeAllCache": "清理所有快取",
"ButtonPurgeItemsCache": "清理項目快取", "ButtonPurgeItemsCache": "清理項目快取",
"ButtonPurgeMediaProgress": "清理媒體進度",
"ButtonQueueAddItem": "新增到佇列", "ButtonQueueAddItem": "新增到佇列",
"ButtonQueueRemoveItem": "從佇列中移除", "ButtonQueueRemoveItem": "從佇列中移除",
"ButtonQuickMatch": "快速匹配", "ButtonQuickMatch": "快速匹配",
@ -115,6 +115,7 @@
"HeaderCollectionItems": "收藏項目", "HeaderCollectionItems": "收藏項目",
"HeaderCover": "封面", "HeaderCover": "封面",
"HeaderCurrentDownloads": "當前下載", "HeaderCurrentDownloads": "當前下載",
"HeaderCustomMessageOnLogin": "Custom Message on Login",
"HeaderCustomMetadataProviders": "自訂 Metadata 提供者", "HeaderCustomMetadataProviders": "自訂 Metadata 提供者",
"HeaderDetails": "詳情", "HeaderDetails": "詳情",
"HeaderDownloadQueue": "下載佇列", "HeaderDownloadQueue": "下載佇列",
@ -279,6 +280,8 @@
"LabelEdit": "編輯", "LabelEdit": "編輯",
"LabelEmail": "郵箱", "LabelEmail": "郵箱",
"LabelEmailSettingsFromAddress": "發件人位址", "LabelEmailSettingsFromAddress": "發件人位址",
"LabelEmailSettingsRejectUnauthorized": "Reject unauthorized certificates",
"LabelEmailSettingsRejectUnauthorizedHelp": "Disabling SSL certificate validation may expose your connection to security risks, such as man-in-the-middle attacks. Only disable this option if you understand the implications and trust the mail server you are connecting to.",
"LabelEmailSettingsSecure": "安全", "LabelEmailSettingsSecure": "安全",
"LabelEmailSettingsSecureHelp": "如果選是, 則連接將在連接到伺服器時使用TLS. 如果選否, 則若伺服器支援STARTTLS擴展, 則使用TLS. 在大多數情況下, 如果連接到465埠, 請將該值設定為是. 對於587或25埠, 請保持為否. (來自nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "如果選是, 則連接將在連接到伺服器時使用TLS. 如果選否, 則若伺服器支援STARTTLS擴展, 則使用TLS. 在大多數情況下, 如果連接到465埠, 請將該值設定為是. 對於587或25埠, 請保持為否. (來自nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "測試位址", "LabelEmailSettingsTestAddress": "測試位址",
@ -335,6 +338,7 @@
"LabelItem": "項目", "LabelItem": "項目",
"LabelLanguage": "語言", "LabelLanguage": "語言",
"LabelLanguageDefaultServer": "預設伺服器語言", "LabelLanguageDefaultServer": "預設伺服器語言",
"LabelLanguages": "Languages",
"LabelLastBookAdded": "最後新增的書", "LabelLastBookAdded": "最後新增的書",
"LabelLastBookUpdated": "最後更新的書", "LabelLastBookUpdated": "最後更新的書",
"LabelLastSeen": "上次查看時間", "LabelLastSeen": "上次查看時間",
@ -346,6 +350,7 @@
"LabelLess": "較少", "LabelLess": "較少",
"LabelLibrariesAccessibleToUser": "使用者可存取的媒體庫", "LabelLibrariesAccessibleToUser": "使用者可存取的媒體庫",
"LabelLibrary": "媒體庫", "LabelLibrary": "媒體庫",
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "媒體庫項目", "LabelLibraryItem": "媒體庫項目",
"LabelLibraryName": "媒體庫名稱", "LabelLibraryName": "媒體庫名稱",
"LabelLimit": "限制", "LabelLimit": "限制",
@ -381,6 +386,7 @@
"LabelNewPassword": "新密碼", "LabelNewPassword": "新密碼",
"LabelNextBackupDate": "下次備份日期", "LabelNextBackupDate": "下次備份日期",
"LabelNextScheduledRun": "下次任務運行", "LabelNextScheduledRun": "下次任務運行",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "未選擇任何劇集", "LabelNoEpisodesSelected": "未選擇任何劇集",
"LabelNotes": "注釋", "LabelNotes": "注釋",
"LabelNotFinished": "未聽完", "LabelNotFinished": "未聽完",
@ -412,6 +418,7 @@
"LabelPermissionsUpload": "可以上傳", "LabelPermissionsUpload": "可以上傳",
"LabelPersonalYearReview": "你的年度回顧 ({0})", "LabelPersonalYearReview": "你的年度回顧 ({0})",
"LabelPhotoPathURL": "圖片路徑或 URL", "LabelPhotoPathURL": "圖片路徑或 URL",
"LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "播放列表", "LabelPlaylists": "播放列表",
"LabelPlayMethod": "播放方法", "LabelPlayMethod": "播放方法",
"LabelPodcast": "播客", "LabelPodcast": "播客",
@ -426,6 +433,7 @@
"LabelProvider": "供應商", "LabelProvider": "供應商",
"LabelPubDate": "出版日期", "LabelPubDate": "出版日期",
"LabelPublisher": "出版商", "LabelPublisher": "出版商",
"LabelPublishers": "Publishers",
"LabelPublishYear": "發布年份", "LabelPublishYear": "發布年份",
"LabelRead": "閱讀", "LabelRead": "閱讀",
"LabelReadAgain": "再次閱讀", "LabelReadAgain": "再次閱讀",
@ -591,6 +599,7 @@
"MessageBatchQuickMatchDescription": "快速匹配將嘗試為所選項目新增缺少的封面和元數據. 啟用以下選項以允許快速匹配覆蓋現有封面和或元數據.", "MessageBatchQuickMatchDescription": "快速匹配將嘗試為所選項目新增缺少的封面和元數據. 啟用以下選項以允許快速匹配覆蓋現有封面和或元數據.",
"MessageBookshelfNoCollections": "你尚未進行任何收藏", "MessageBookshelfNoCollections": "你尚未進行任何收藏",
"MessageBookshelfNoResultsForFilter": "過濾器無結果 \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "過濾器無結果 \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoRSSFeeds": "沒有打開的 RSS 源", "MessageBookshelfNoRSSFeeds": "沒有打開的 RSS 源",
"MessageBookshelfNoSeries": "你沒有系列", "MessageBookshelfNoSeries": "你沒有系列",
"MessageChapterEndIsAfter": "章節結束是在有聲書結束之後", "MessageChapterEndIsAfter": "章節結束是在有聲書結束之後",
@ -612,6 +621,7 @@
"MessageConfirmMarkSeriesFinished": "你確定要將此系列中的所有書籍都標記為已聽完嗎?", "MessageConfirmMarkSeriesFinished": "你確定要將此系列中的所有書籍都標記為已聽完嗎?",
"MessageConfirmMarkSeriesNotFinished": "你確定要將此系列中的所有書籍都標記為未聽完嗎?", "MessageConfirmMarkSeriesNotFinished": "你確定要將此系列中的所有書籍都標記為未聽完嗎?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?", "MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "警告! 快速嵌入不會備份你的音頻檔案. 確保你有音頻檔案的備份. <br><br>你是否想繼續嗎?", "MessageConfirmQuickEmbed": "警告! 快速嵌入不會備份你的音頻檔案. 確保你有音頻檔案的備份. <br><br>你是否想繼續嗎?",
"MessageConfirmRemoveAllChapters": "你確定要移除所有章節嗎?", "MessageConfirmRemoveAllChapters": "你確定要移除所有章節嗎?",
"MessageConfirmRemoveAuthor": "你確定要刪除作者 \"{0}\"?", "MessageConfirmRemoveAuthor": "你確定要刪除作者 \"{0}\"?",

8
package-lock.json generated
View file

@ -17,7 +17,7 @@
"htmlparser2": "^8.0.1", "htmlparser2": "^8.0.1",
"lru-cache": "^10.0.3", "lru-cache": "^10.0.3",
"node-tone": "^1.0.1", "node-tone": "^1.0.1",
"nodemailer": "^6.9.2", "nodemailer": "^6.9.13",
"openid-client": "^5.6.1", "openid-client": "^5.6.1",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
@ -3619,9 +3619,9 @@
"integrity": "sha512-wi7L0taDZMN6tM5l85TDKHsYzdhqJTtPNgvgpk2zHeZzPt6ZIUZ9vBLTJRRDpm0xzCvbsvFHjAaudeQjLHTE4w==" "integrity": "sha512-wi7L0taDZMN6tM5l85TDKHsYzdhqJTtPNgvgpk2zHeZzPt6ZIUZ9vBLTJRRDpm0xzCvbsvFHjAaudeQjLHTE4w=="
}, },
"node_modules/nodemailer": { "node_modules/nodemailer": {
"version": "6.9.8", "version": "6.9.13",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.8.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz",
"integrity": "sha512-cfrYUk16e67Ks051i4CntM9kshRYei1/o/Gi8K1d+R34OIs21xdFnW7Pt7EucmVKA0LKtqUGNcjMZ7ehjl49mQ==", "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }

View file

@ -43,7 +43,7 @@
"htmlparser2": "^8.0.1", "htmlparser2": "^8.0.1",
"lru-cache": "^10.0.3", "lru-cache": "^10.0.3",
"node-tone": "^1.0.1", "node-tone": "^1.0.1",
"nodemailer": "^6.9.2", "nodemailer": "^6.9.13",
"openid-client": "^5.6.1", "openid-client": "^5.6.1",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",

View file

@ -51,6 +51,7 @@ class Server {
global.RouterBasePath = ROUTER_BASE_PATH global.RouterBasePath = ROUTER_BASE_PATH
global.XAccel = process.env.USE_X_ACCEL global.XAccel = process.env.USE_X_ACCEL
global.AllowCors = process.env.ALLOW_CORS === '1' global.AllowCors = process.env.ALLOW_CORS === '1'
global.DisableSsrfRequestFilter = process.env.DISABLE_SSRF_REQUEST_FILTER === '1'
if (!fs.pathExistsSync(global.ConfigPath)) { if (!fs.pathExistsSync(global.ConfigPath)) {
fs.mkdirSync(global.ConfigPath) fs.mkdirSync(global.ConfigPath)

View file

@ -15,7 +15,7 @@ var ffCodecRegexp = /^\s*([D\.])([E\.])([VAS])([I\.])([L\.])([S\.]) ([^ ]+) +(.*
var ffEncodersRegexp = /\(encoders:([^\)]+)\)/; var ffEncodersRegexp = /\(encoders:([^\)]+)\)/;
var ffDecodersRegexp = /\(decoders:([^\)]+)\)/; var ffDecodersRegexp = /\(decoders:([^\)]+)\)/;
var encodersRegexp = /^\s*([VAS\.])([F\.])([S\.])([X\.])([B\.])([D\.]) ([^ ]+) +(.*)$/; var encodersRegexp = /^\s*([VAS\.])([F\.])([S\.])([X\.])([B\.])([D\.]) ([^ ]+) +(.*)$/;
var formatRegexp = /^\s*([D ])([E ]) ([^ ]+) +(.*)$/; var formatRegexp = /^\s*([D ])([E ])\s+([^ ]+)\s+(.*)$/;
var lineBreakRegexp = /\r\n|\r|\n/; var lineBreakRegexp = /\r\n|\r|\n/;
var filterRegexp = /^(?: [T\.][S\.][C\.] )?([^ ]+) +(AA?|VV?|\|)->(AA?|VV?|\|) +(.*)$/; var filterRegexp = /^(?: [T\.][S\.][C\.] )?([^ ]+) +(AA?|VV?|\|)->(AA?|VV?|\|) +(.*)$/;

View file

@ -26,11 +26,6 @@ class Author extends Model {
this.createdAt this.createdAt
} }
static async getOldAuthors() {
const authors = await this.findAll()
return authors.map(au => au.getOldAuthor())
}
getOldAuthor() { getOldAuthor() {
return new oldAuthor({ return new oldAuthor({
id: this.id, id: this.id,
@ -85,7 +80,7 @@ class Author extends Model {
/** /**
* Get oldAuthor by id * Get oldAuthor by id
* @param {string} authorId * @param {string} authorId
* @returns {Promise<oldAuthor>} * @returns {Promise<oldAuthor>}
*/ */
static async getOldById(authorId) { static async getOldById(authorId) {
@ -96,7 +91,7 @@ class Author extends Model {
/** /**
* Check if author exists * Check if author exists
* @param {string} authorId * @param {string} authorId
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
static async checkExistsById(authorId) { static async checkExistsById(authorId) {
@ -106,60 +101,67 @@ class Author extends Model {
/** /**
* Get old author by name and libraryId. name case insensitive * Get old author by name and libraryId. name case insensitive
* TODO: Look for authors ignoring punctuation * TODO: Look for authors ignoring punctuation
* *
* @param {string} authorName * @param {string} authorName
* @param {string} libraryId * @param {string} libraryId
* @returns {Promise<oldAuthor>} * @returns {Promise<oldAuthor>}
*/ */
static async getOldByNameAndLibrary(authorName, libraryId) { static async getOldByNameAndLibrary(authorName, libraryId) {
const author = (await this.findOne({ const author = (
where: [ await this.findOne({
where(fn('lower', col('name')), authorName.toLowerCase()), where: [
{ where(fn('lower', col('name')), authorName.toLowerCase()),
libraryId {
} libraryId
] }
}))?.getOldAuthor() ]
})
)?.getOldAuthor()
return author return author
} }
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
name: DataTypes.STRING,
lastFirst: DataTypes.STRING,
asin: DataTypes.STRING,
description: DataTypes.TEXT,
imagePath: DataTypes.STRING
}, {
sequelize,
modelName: 'author',
indexes: [
{
fields: [{
name: 'name',
collate: 'NOCASE'
}]
}, },
// { name: DataTypes.STRING,
// fields: [{ lastFirst: DataTypes.STRING,
// name: 'lastFirst', asin: DataTypes.STRING,
// collate: 'NOCASE' description: DataTypes.TEXT,
// }] imagePath: DataTypes.STRING
// }, },
{ {
fields: ['libraryId'] sequelize,
} modelName: 'author',
] indexes: [
}) {
fields: [
{
name: 'name',
collate: 'NOCASE'
}
]
},
// {
// fields: [{
// name: 'lastFirst',
// collate: 'NOCASE'
// }]
// },
{
fields: ['libraryId']
}
]
}
)
const { library } = sequelize.models const { library } = sequelize.models
library.hasMany(Author, { library.hasMany(Author, {

View file

@ -21,13 +21,13 @@ const Logger = require('../Logger')
/** /**
* @typedef SeriesExpandedProperties * @typedef SeriesExpandedProperties
* @property {{sequence:string}} bookSeries * @property {{sequence:string}} bookSeries
* *
* @typedef {import('./Series') & SeriesExpandedProperties} SeriesExpanded * @typedef {import('./Series') & SeriesExpandedProperties} SeriesExpanded
* *
* @typedef BookExpandedProperties * @typedef BookExpandedProperties
* @property {import('./Author')[]} authors * @property {import('./Author')[]} authors
* @property {SeriesExpanded[]} series * @property {SeriesExpanded[]} series
* *
* @typedef {Book & BookExpandedProperties} BookExpanded * @typedef {Book & BookExpandedProperties} BookExpanded
*/ */
@ -112,29 +112,31 @@ class Book extends Model {
const bookExpanded = libraryItemExpanded.media const bookExpanded = libraryItemExpanded.media
let authors = [] let authors = []
if (bookExpanded.authors?.length) { if (bookExpanded.authors?.length) {
authors = bookExpanded.authors.map(au => { authors = bookExpanded.authors.map((au) => {
return { return {
id: au.id, id: au.id,
name: au.name name: au.name
} }
}) })
} else if (bookExpanded.bookAuthors?.length) { } else if (bookExpanded.bookAuthors?.length) {
authors = bookExpanded.bookAuthors.map(ba => { authors = bookExpanded.bookAuthors
if (ba.author) { .map((ba) => {
return { if (ba.author) {
id: ba.author.id, return {
name: ba.author.name id: ba.author.id,
name: ba.author.name
}
} else {
Logger.error(`[Book] Invalid bookExpanded bookAuthors: no author`, ba)
return null
} }
} else { })
Logger.error(`[Book] Invalid bookExpanded bookAuthors: no author`, ba) .filter((a) => a)
return null
}
}).filter(a => a)
} }
let series = [] let series = []
if (bookExpanded.series?.length) { if (bookExpanded.series?.length) {
series = bookExpanded.series.map(se => { series = bookExpanded.series.map((se) => {
return { return {
id: se.id, id: se.id,
name: se.name, name: se.name,
@ -142,18 +144,20 @@ class Book extends Model {
} }
}) })
} else if (bookExpanded.bookSeries?.length) { } else if (bookExpanded.bookSeries?.length) {
series = bookExpanded.bookSeries.map(bs => { series = bookExpanded.bookSeries
if (bs.series) { .map((bs) => {
return { if (bs.series) {
id: bs.series.id, return {
name: bs.series.name, id: bs.series.id,
sequence: bs.sequence name: bs.series.name,
sequence: bs.sequence
}
} else {
Logger.error(`[Book] Invalid bookExpanded bookSeries: no series`, bs)
return null
} }
} else { })
Logger.error(`[Book] Invalid bookExpanded bookSeries: no series`, bs) .filter((s) => s)
return null
}
}).filter(s => s)
} }
return { return {
@ -185,7 +189,7 @@ class Book extends Model {
} }
/** /**
* @param {object} oldBook * @param {object} oldBook
* @returns {boolean} true if updated * @returns {boolean} true if updated
*/ */
static saveFromOld(oldBook) { static saveFromOld(oldBook) {
@ -194,10 +198,12 @@ class Book extends Model {
where: { where: {
id: book.id id: book.id
} }
}).then(result => result[0] > 0).catch((error) => {
Logger.error(`[Book] Failed to save book ${book.id}`, error)
return false
}) })
.then((result) => result[0] > 0)
.catch((error) => {
Logger.error(`[Book] Failed to save book ${book.id}`, error)
return false
})
} }
static getFromOld(oldBook) { static getFromOld(oldBook) {
@ -219,7 +225,7 @@ class Book extends Model {
ebookFile: oldBook.ebookFile?.toJSON() || null, ebookFile: oldBook.ebookFile?.toJSON() || null,
coverPath: oldBook.coverPath, coverPath: oldBook.coverPath,
duration: oldBook.duration, duration: oldBook.duration,
audioFiles: oldBook.audioFiles?.map(af => af.toJSON()) || [], audioFiles: oldBook.audioFiles?.map((af) => af.toJSON()) || [],
chapters: oldBook.chapters, chapters: oldBook.chapters,
tags: oldBook.tags, tags: oldBook.tags,
genres: oldBook.metadata.genres genres: oldBook.metadata.genres
@ -229,12 +235,12 @@ class Book extends Model {
getAbsMetadataJson() { getAbsMetadataJson() {
return { return {
tags: this.tags || [], tags: this.tags || [],
chapters: this.chapters?.map(c => ({ ...c })) || [], chapters: this.chapters?.map((c) => ({ ...c })) || [],
title: this.title, title: this.title,
subtitle: this.subtitle, subtitle: this.subtitle,
authors: this.authors.map(a => a.name), authors: this.authors.map((a) => a.name),
narrators: this.narrators, narrators: this.narrators,
series: this.series.map(se => { series: this.series.map((se) => {
const sequence = se.bookSeries?.sequence || '' const sequence = se.bookSeries?.sequence || ''
if (!sequence) return se.name if (!sequence) return se.name
return `${se.name} #${sequence}` return `${se.name} #${sequence}`
@ -254,61 +260,66 @@ class Book extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
title: DataTypes.STRING, },
titleIgnorePrefix: DataTypes.STRING, title: DataTypes.STRING,
subtitle: DataTypes.STRING, titleIgnorePrefix: DataTypes.STRING,
publishedYear: DataTypes.STRING, subtitle: DataTypes.STRING,
publishedDate: DataTypes.STRING, publishedYear: DataTypes.STRING,
publisher: DataTypes.STRING, publishedDate: DataTypes.STRING,
description: DataTypes.TEXT, publisher: DataTypes.STRING,
isbn: DataTypes.STRING, description: DataTypes.TEXT,
asin: DataTypes.STRING, isbn: DataTypes.STRING,
language: DataTypes.STRING, asin: DataTypes.STRING,
explicit: DataTypes.BOOLEAN, language: DataTypes.STRING,
abridged: DataTypes.BOOLEAN, explicit: DataTypes.BOOLEAN,
coverPath: DataTypes.STRING, abridged: DataTypes.BOOLEAN,
duration: DataTypes.FLOAT, coverPath: DataTypes.STRING,
duration: DataTypes.FLOAT,
narrators: DataTypes.JSON, narrators: DataTypes.JSON,
audioFiles: DataTypes.JSON, audioFiles: DataTypes.JSON,
ebookFile: DataTypes.JSON, ebookFile: DataTypes.JSON,
chapters: DataTypes.JSON, chapters: DataTypes.JSON,
tags: DataTypes.JSON, tags: DataTypes.JSON,
genres: DataTypes.JSON genres: DataTypes.JSON
}, { },
sequelize, {
modelName: 'book', sequelize,
indexes: [ modelName: 'book',
{ indexes: [
fields: [{ {
name: 'title', fields: [
collate: 'NOCASE' {
}] name: 'title',
}, collate: 'NOCASE'
// { }
// fields: [{ ]
// name: 'titleIgnorePrefix', },
// collate: 'NOCASE' // {
// }] // fields: [{
// }, // name: 'titleIgnorePrefix',
{ // collate: 'NOCASE'
fields: ['publishedYear'] // }]
}, // },
// { {
// fields: ['duration'] fields: ['publishedYear']
// } }
] // {
}) // fields: ['duration']
// }
]
}
)
} }
} }
module.exports = Book module.exports = Book

View file

@ -25,21 +25,24 @@ class BookAuthor extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
}
},
{
sequelize,
modelName: 'bookAuthor',
timestamps: true,
updatedAt: false
} }
}, { )
sequelize,
modelName: 'bookAuthor',
timestamps: true,
updatedAt: false
})
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
@ -58,4 +61,4 @@ class BookAuthor extends Model {
BookAuthor.belongsTo(author) BookAuthor.belongsTo(author)
} }
} }
module.exports = BookAuthor module.exports = BookAuthor

View file

@ -27,22 +27,25 @@ class BookSeries extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
sequence: DataTypes.STRING
}, },
sequence: DataTypes.STRING {
}, { sequelize,
sequelize, modelName: 'bookSeries',
modelName: 'bookSeries', timestamps: true,
timestamps: true, updatedAt: false
updatedAt: false }
}) )
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
@ -62,4 +65,4 @@ class BookSeries extends Model {
} }
} }
module.exports = BookSeries module.exports = BookSeries

View file

@ -2,7 +2,6 @@ const { DataTypes, Model, Sequelize } = require('sequelize')
const oldCollection = require('../objects/Collection') const oldCollection = require('../objects/Collection')
class Collection extends Model { class Collection extends Model {
constructor(values, options) { constructor(values, options) {
super(values, options) super(values, options)
@ -20,27 +19,13 @@ class Collection extends Model {
/** @type {Date} */ /** @type {Date} */
this.createdAt this.createdAt
} }
/**
* Get all old collections
* @returns {Promise<oldCollection[]>}
*/
static async getOldCollections() {
const collections = await this.findAll({
include: {
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
})
return collections.map(c => this.getOldCollection(c))
}
/** /**
* Get all old collections toJSONExpanded, items filtered for user permissions * Get all old collections toJSONExpanded, items filtered for user permissions
* @param {[oldUser]} user * @param {oldUser} [user]
* @param {[string]} libraryId * @param {string} [libraryId]
* @param {[string[]]} include * @param {string[]} [include]
* @returns {Promise<object[]>} oldCollection.toJSONExpanded * @returns {Promise<oldCollection[]>} oldCollection.toJSONExpanded
*/ */
static async getOldCollectionsJsonExpanded(user, libraryId, include) { static async getOldCollectionsJsonExpanded(user, libraryId, include) {
let collectionWhere = null let collectionWhere = null
@ -78,8 +63,7 @@ class Collection extends Model {
through: { through: {
attributes: ['sequence'] attributes: ['sequence']
} }
}, }
] ]
}, },
...collectionIncludes ...collectionIncludes
@ -87,11 +71,84 @@ class Collection extends Model {
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']] order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
}) })
// TODO: Handle user permission restrictions on initial query // TODO: Handle user permission restrictions on initial query
return collections.map(c => { return collections
const oldCollection = this.getOldCollection(c) .map((c) => {
const oldCollection = this.getOldCollection(c)
// Filter books using user permissions // Filter books using user permissions
const books = c.books?.filter(b => { const books =
c.books?.filter((b) => {
if (user) {
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
return false
}
if (b.explicit === true && !user.canAccessExplicitContent) {
return false
}
}
return true
}) || []
// Map to library items
const libraryItems = books.map((b) => {
const libraryItem = b.libraryItem
delete b.libraryItem
libraryItem.media = b
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
})
// Users with restricted permissions will not see this collection
if (!books.length && oldCollection.books.length) {
return null
}
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
// Map feed if found
if (c.feeds?.length) {
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(c.feeds[0])
}
return collectionExpanded
})
.filter((c) => c)
}
/**
* Get old collection toJSONExpanded, items filtered for user permissions
* @param {oldUser} [user]
* @param {string[]} [include]
* @returns {Promise<oldCollection>} oldCollection.toJSONExpanded
*/
async getOldJsonExpanded(user, include) {
this.books =
(await this.getBooks({
include: [
{
model: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.author,
through: {
attributes: []
}
},
{
model: this.sequelize.models.series,
through: {
attributes: ['sequence']
}
}
],
order: [Sequelize.literal('`collectionBook.order` ASC')]
})) || []
const oldCollection = this.sequelize.models.collection.getOldCollection(this)
// Filter books using user permissions
// TODO: Handle user permission restrictions on initial query
const books =
this.books?.filter((b) => {
if (user) { if (user) {
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) { if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
return false return false
@ -103,77 +160,8 @@ class Collection extends Model {
return true return true
}) || [] }) || []
// Map to library items
const libraryItems = books.map(b => {
const libraryItem = b.libraryItem
delete b.libraryItem
libraryItem.media = b
return this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
})
// Users with restricted permissions will not see this collection
if (!books.length && oldCollection.books.length) {
return null
}
const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)
// Map feed if found
if (c.feeds?.length) {
collectionExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(c.feeds[0])
}
return collectionExpanded
}).filter(c => c)
}
/**
* Get old collection toJSONExpanded, items filtered for user permissions
* @param {[oldUser]} user
* @param {[string[]]} include
* @returns {Promise<object>} oldCollection.toJSONExpanded
*/
async getOldJsonExpanded(user, include) {
this.books = await this.getBooks({
include: [
{
model: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.author,
through: {
attributes: []
}
},
{
model: this.sequelize.models.series,
through: {
attributes: ['sequence']
}
},
],
order: [Sequelize.literal('`collectionBook.order` ASC')]
}) || []
const oldCollection = this.sequelize.models.collection.getOldCollection(this)
// Filter books using user permissions
// TODO: Handle user permission restrictions on initial query
const books = this.books?.filter(b => {
if (user) {
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
return false
}
if (b.explicit === true && !user.canAccessExplicitContent) {
return false
}
}
return true
}) || []
// Map to library items // Map to library items
const libraryItems = books.map(b => { const libraryItems = books.map((b) => {
const libraryItem = b.libraryItem const libraryItem = b.libraryItem
delete b.libraryItem delete b.libraryItem
libraryItem.media = b libraryItem.media = b
@ -199,11 +187,11 @@ class Collection extends Model {
/** /**
* Get old collection from Collection * Get old collection from Collection
* @param {Collection} collectionExpanded * @param {Collection} collectionExpanded
* @returns {oldCollection} * @returns {oldCollection}
*/ */
static getOldCollection(collectionExpanded) { static getOldCollection(collectionExpanded) {
const libraryItemIds = collectionExpanded.books?.map(b => b.libraryItem?.id || null).filter(lid => lid) || [] const libraryItemIds = collectionExpanded.books?.map((b) => b.libraryItem?.id || null).filter((lid) => lid) || []
return new oldCollection({ return new oldCollection({
id: collectionExpanded.id, id: collectionExpanded.id,
libraryId: collectionExpanded.libraryId, libraryId: collectionExpanded.libraryId,
@ -215,6 +203,11 @@ class Collection extends Model {
}) })
} }
/**
*
* @param {oldCollection} oldCollection
* @returns {Promise<Collection>}
*/
static createFromOld(oldCollection) { static createFromOld(oldCollection) {
const collection = this.getFromOld(oldCollection) const collection = this.getFromOld(oldCollection)
return this.create(collection) return this.create(collection)
@ -239,7 +232,7 @@ class Collection extends Model {
/** /**
* Get old collection by id * Get old collection by id
* @param {string} collectionId * @param {string} collectionId
* @returns {Promise<oldCollection|null>} returns null if not found * @returns {Promise<oldCollection|null>} returns null if not found
*/ */
static async getOldById(collectionId) { static async getOldById(collectionId) {
@ -260,34 +253,34 @@ class Collection extends Model {
* @returns {Promise<oldCollection>} * @returns {Promise<oldCollection>}
*/ */
async getOld() { async getOld() {
this.books = await this.getBooks({ this.books =
include: [ (await this.getBooks({
{ include: [
model: this.sequelize.models.libraryItem {
}, model: this.sequelize.models.libraryItem
{ },
model: this.sequelize.models.author, {
through: { model: this.sequelize.models.author,
attributes: [] through: {
attributes: []
}
},
{
model: this.sequelize.models.series,
through: {
attributes: ['sequence']
}
} }
}, ],
{ order: [Sequelize.literal('`collectionBook.order` ASC')]
model: this.sequelize.models.series, })) || []
through: {
attributes: ['sequence']
}
},
],
order: [Sequelize.literal('`collectionBook.order` ASC')]
}) || []
return this.sequelize.models.collection.getOldCollection(this) return this.sequelize.models.collection.getOldCollection(this)
} }
/** /**
* Remove all collections belonging to library * Remove all collections belonging to library
* @param {string} libraryId * @param {string} libraryId
* @returns {Promise<number>} number of collections destroyed * @returns {Promise<number>} number of collections destroyed
*/ */
static async removeAllForLibrary(libraryId) { static async removeAllForLibrary(libraryId) {
@ -299,38 +292,26 @@ class Collection extends Model {
}) })
} }
static async getAllForBook(bookId) {
const collections = await this.findAll({
include: {
model: this.sequelize.models.book,
where: {
id: bookId
},
required: true,
include: this.sequelize.models.libraryItem
},
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
})
return collections.map(c => this.getOldCollection(c))
}
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: DataTypes.STRING,
description: DataTypes.TEXT
}, },
name: DataTypes.STRING, {
description: DataTypes.TEXT sequelize,
}, { modelName: 'collection'
sequelize, }
modelName: 'collection' )
})
const { library } = sequelize.models const { library } = sequelize.models
@ -339,4 +320,4 @@ class Collection extends Model {
} }
} }
module.exports = Collection module.exports = Collection

View file

@ -26,19 +26,22 @@ class CollectionBook extends Model {
} }
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
order: DataTypes.INTEGER
}, },
order: DataTypes.INTEGER {
}, { sequelize,
sequelize, timestamps: true,
timestamps: true, updatedAt: false,
updatedAt: false, modelName: 'collectionBook'
modelName: 'collectionBook' }
}) )
// Super Many-to-Many // Super Many-to-Many
// ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship // ref: https://sequelize.org/docs/v6/advanced-association-concepts/advanced-many-to-many/#the-best-of-both-worlds-the-super-many-to-many-relationship
@ -58,4 +61,4 @@ class CollectionBook extends Model {
} }
} }
module.exports = CollectionBook module.exports = CollectionBook

View file

@ -114,26 +114,29 @@ class Device extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
deviceId: DataTypes.STRING,
clientName: DataTypes.STRING, // e.g. Abs Web, Abs Android
clientVersion: DataTypes.STRING, // e.g. Server version or mobile version
ipAddress: DataTypes.STRING,
deviceName: DataTypes.STRING, // e.g. Windows 10 Chrome, Google Pixel 6, Apple iPhone 10,3
deviceVersion: DataTypes.STRING, // e.g. Browser version or Android SDK
extraData: DataTypes.JSON
}, },
deviceId: DataTypes.STRING, {
clientName: DataTypes.STRING, // e.g. Abs Web, Abs Android sequelize,
clientVersion: DataTypes.STRING, // e.g. Server version or mobile version modelName: 'device'
ipAddress: DataTypes.STRING, }
deviceName: DataTypes.STRING, // e.g. Windows 10 Chrome, Google Pixel 6, Apple iPhone 10,3 )
deviceVersion: DataTypes.STRING, // e.g. Browser version or Android SDK
extraData: DataTypes.JSON
}, {
sequelize,
modelName: 'device'
})
const { user } = sequelize.models const { user } = sequelize.models
@ -144,4 +147,4 @@ class Device extends Model {
} }
} }
module.exports = Device module.exports = Device

View file

@ -58,7 +58,7 @@ class Feed extends Model {
model: this.sequelize.models.feedEpisode model: this.sequelize.models.feedEpisode
} }
}) })
return feeds.map(f => this.getOldFeed(f)) return feeds.map((f) => this.getOldFeed(f))
} }
/** /**
@ -117,7 +117,7 @@ class Feed extends Model {
entityType: 'libraryItem' entityType: 'libraryItem'
} }
}) })
return feeds.map(f => f.entityId).filter(f => f) || [] return feeds.map((f) => f.entityId).filter((f) => f) || []
} }
/** /**
@ -179,7 +179,7 @@ class Feed extends Model {
// Remove and update existing feed episodes // Remove and update existing feed episodes
for (const feedEpisode of existingFeed.feedEpisodes) { for (const feedEpisode of existingFeed.feedEpisodes) {
const oldFeedEpisode = oldFeedEpisodes.find(ep => ep.id === feedEpisode.id) const oldFeedEpisode = oldFeedEpisodes.find((ep) => ep.id === feedEpisode.id)
// Episode removed // Episode removed
if (!oldFeedEpisode) { if (!oldFeedEpisode) {
feedEpisode.destroy() feedEpisode.destroy()
@ -200,7 +200,7 @@ class Feed extends Model {
// Add new feed episodes // Add new feed episodes
for (const episode of oldFeedEpisodes) { for (const episode of oldFeedEpisodes) {
if (!existingFeed.feedEpisodes.some(fe => fe.id === episode.id)) { if (!existingFeed.feedEpisodes.some((fe) => fe.id === episode.id)) {
await this.sequelize.models.feedEpisode.createFromOld(feedObj.id, episode) await this.sequelize.models.feedEpisode.createFromOld(feedObj.id, episode)
hasUpdates = true hasUpdates = true
} }
@ -258,41 +258,44 @@ class Feed extends Model {
/** /**
* Initialize model * Initialize model
* *
* Polymorphic association: Feeds can be created from LibraryItem, Collection, Playlist or Series * Polymorphic association: Feeds can be created from LibraryItem, Collection, Playlist or Series
* @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/ * @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
* *
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
slug: DataTypes.STRING,
entityType: DataTypes.STRING,
entityId: DataTypes.UUIDV4,
entityUpdatedAt: DataTypes.DATE,
serverAddress: DataTypes.STRING,
feedURL: DataTypes.STRING,
imageURL: DataTypes.STRING,
siteURL: DataTypes.STRING,
title: DataTypes.STRING,
description: DataTypes.TEXT,
author: DataTypes.STRING,
podcastType: DataTypes.STRING,
language: DataTypes.STRING,
ownerName: DataTypes.STRING,
ownerEmail: DataTypes.STRING,
explicit: DataTypes.BOOLEAN,
preventIndexing: DataTypes.BOOLEAN,
coverPath: DataTypes.STRING
}, },
slug: DataTypes.STRING, {
entityType: DataTypes.STRING, sequelize,
entityId: DataTypes.UUIDV4, modelName: 'feed'
entityUpdatedAt: DataTypes.DATE, }
serverAddress: DataTypes.STRING, )
feedURL: DataTypes.STRING,
imageURL: DataTypes.STRING,
siteURL: DataTypes.STRING,
title: DataTypes.STRING,
description: DataTypes.TEXT,
author: DataTypes.STRING,
podcastType: DataTypes.STRING,
language: DataTypes.STRING,
ownerName: DataTypes.STRING,
ownerEmail: DataTypes.STRING,
explicit: DataTypes.BOOLEAN,
preventIndexing: DataTypes.BOOLEAN,
coverPath: DataTypes.STRING
}, {
sequelize,
modelName: 'feed'
})
const { user, libraryItem, collection, series, playlist } = sequelize.models const { user, libraryItem, collection, series, playlist } = sequelize.models
@ -335,7 +338,7 @@ class Feed extends Model {
}) })
Feed.belongsTo(playlist, { foreignKey: 'entityId', constraints: false }) Feed.belongsTo(playlist, { foreignKey: 'entityId', constraints: false })
Feed.addHook('afterFind', findResult => { Feed.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
@ -368,4 +371,4 @@ class Feed extends Model {
} }
} }
module.exports = Feed module.exports = Feed

View file

@ -65,9 +65,9 @@ class FeedEpisode extends Model {
/** /**
* Create feed episode from old model * Create feed episode from old model
* *
* @param {string} feedId * @param {string} feedId
* @param {Object} oldFeedEpisode * @param {Object} oldFeedEpisode
* @returns {Promise<FeedEpisode>} * @returns {Promise<FeedEpisode>}
*/ */
static createFromOld(feedId, oldFeedEpisode) { static createFromOld(feedId, oldFeedEpisode) {
@ -98,33 +98,36 @@ class FeedEpisode extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
title: DataTypes.STRING,
author: DataTypes.STRING,
description: DataTypes.TEXT,
siteURL: DataTypes.STRING,
enclosureURL: DataTypes.STRING,
enclosureType: DataTypes.STRING,
enclosureSize: DataTypes.BIGINT,
pubDate: DataTypes.STRING,
season: DataTypes.STRING,
episode: DataTypes.STRING,
episodeType: DataTypes.STRING,
duration: DataTypes.FLOAT,
filePath: DataTypes.STRING,
explicit: DataTypes.BOOLEAN
}, },
title: DataTypes.STRING, {
author: DataTypes.STRING, sequelize,
description: DataTypes.TEXT, modelName: 'feedEpisode'
siteURL: DataTypes.STRING, }
enclosureURL: DataTypes.STRING, )
enclosureType: DataTypes.STRING,
enclosureSize: DataTypes.BIGINT,
pubDate: DataTypes.STRING,
season: DataTypes.STRING,
episode: DataTypes.STRING,
episodeType: DataTypes.STRING,
duration: DataTypes.FLOAT,
filePath: DataTypes.STRING,
explicit: DataTypes.BOOLEAN
}, {
sequelize,
modelName: 'feedEpisode'
})
const { feed } = sequelize.models const { feed } = sequelize.models
@ -135,4 +138,4 @@ class FeedEpisode extends Model {
} }
} }
module.exports = FeedEpisode module.exports = FeedEpisode

View file

@ -10,7 +10,7 @@ const oldLibrary = require('../objects/Library')
* @property {boolean} skipMatchingMediaWithIsbn * @property {boolean} skipMatchingMediaWithIsbn
* @property {string} autoScanCronExpression * @property {string} autoScanCronExpression
* @property {boolean} audiobooksOnly * @property {boolean} audiobooksOnly
* @property {boolean} hideSingleBookSeries Do not show series that only have 1 book * @property {boolean} hideSingleBookSeries Do not show series that only have 1 book
* @property {boolean} onlyShowLaterBooksInContinueSeries Skip showing books that are earlier than the max sequence read * @property {boolean} onlyShowLaterBooksInContinueSeries Skip showing books that are earlier than the max sequence read
* @property {string[]} metadataPrecedence * @property {string[]} metadataPrecedence
*/ */
@ -54,16 +54,16 @@ class Library extends Model {
include: this.sequelize.models.libraryFolder, include: this.sequelize.models.libraryFolder,
order: [['displayOrder', 'ASC']] order: [['displayOrder', 'ASC']]
}) })
return libraries.map(lib => this.getOldLibrary(lib)) return libraries.map((lib) => this.getOldLibrary(lib))
} }
/** /**
* Convert expanded Library to oldLibrary * Convert expanded Library to oldLibrary
* @param {Library} libraryExpanded * @param {Library} libraryExpanded
* @returns {Promise<oldLibrary>} * @returns {Promise<oldLibrary>}
*/ */
static getOldLibrary(libraryExpanded) { static getOldLibrary(libraryExpanded) {
const folders = libraryExpanded.libraryFolders.map(folder => { const folders = libraryExpanded.libraryFolders.map((folder) => {
return { return {
id: folder.id, id: folder.id,
fullPath: folder.path, fullPath: folder.path,
@ -90,13 +90,13 @@ class Library extends Model {
} }
/** /**
* @param {object} oldLibrary * @param {object} oldLibrary
* @returns {Library|null} * @returns {Library|null}
*/ */
static async createFromOld(oldLibrary) { static async createFromOld(oldLibrary) {
const library = this.getFromOld(oldLibrary) const library = this.getFromOld(oldLibrary)
library.libraryFolders = oldLibrary.folders.map(folder => { library.libraryFolders = oldLibrary.folders.map((folder) => {
return { return {
id: folder.id, id: folder.id,
path: folder.fullPath path: folder.fullPath
@ -113,8 +113,8 @@ class Library extends Model {
/** /**
* Update library and library folders * Update library and library folders
* @param {object} oldLibrary * @param {object} oldLibrary
* @returns * @returns
*/ */
static async updateFromOld(oldLibrary) { static async updateFromOld(oldLibrary) {
const existingLibrary = await this.findByPk(oldLibrary.id, { const existingLibrary = await this.findByPk(oldLibrary.id, {
@ -127,7 +127,7 @@ class Library extends Model {
const library = this.getFromOld(oldLibrary) const library = this.getFromOld(oldLibrary)
const libraryFolders = oldLibrary.folders.map(folder => { const libraryFolders = oldLibrary.folders.map((folder) => {
return { return {
id: folder.id, id: folder.id,
path: folder.fullPath, path: folder.fullPath,
@ -135,7 +135,7 @@ class Library extends Model {
} }
}) })
for (const libraryFolder of libraryFolders) { for (const libraryFolder of libraryFolders) {
const existingLibraryFolder = existingLibrary.libraryFolders.find(lf => lf.id === libraryFolder.id) const existingLibraryFolder = existingLibrary.libraryFolders.find((lf) => lf.id === libraryFolder.id)
if (!existingLibraryFolder) { if (!existingLibraryFolder) {
await this.sequelize.models.libraryFolder.create(libraryFolder) await this.sequelize.models.libraryFolder.create(libraryFolder)
} else if (existingLibraryFolder.path !== libraryFolder.path) { } else if (existingLibraryFolder.path !== libraryFolder.path) {
@ -143,7 +143,7 @@ class Library extends Model {
} }
} }
const libraryFoldersRemoved = existingLibrary.libraryFolders.filter(lf => !libraryFolders.some(_lf => _lf.id === lf.id)) const libraryFoldersRemoved = existingLibrary.libraryFolders.filter((lf) => !libraryFolders.some((_lf) => _lf.id === lf.id))
for (const existingLibraryFolder of libraryFoldersRemoved) { for (const existingLibraryFolder of libraryFoldersRemoved) {
await existingLibraryFolder.destroy() await existingLibraryFolder.destroy()
} }
@ -177,8 +177,8 @@ class Library extends Model {
/** /**
* Destroy library by id * Destroy library by id
* @param {string} libraryId * @param {string} libraryId
* @returns * @returns
*/ */
static removeById(libraryId) { static removeById(libraryId) {
return this.destroy({ return this.destroy({
@ -197,12 +197,12 @@ class Library extends Model {
attributes: ['id', 'displayOrder'], attributes: ['id', 'displayOrder'],
order: [['displayOrder', 'ASC']] order: [['displayOrder', 'ASC']]
}) })
return libraries.map(l => l.id) return libraries.map((l) => l.id)
} }
/** /**
* Find Library by primary key & return oldLibrary * Find Library by primary key & return oldLibrary
* @param {string} libraryId * @param {string} libraryId
* @returns {Promise<oldLibrary|null>} Returns null if not found * @returns {Promise<oldLibrary|null>} Returns null if not found
*/ */
static async getOldById(libraryId) { static async getOldById(libraryId) {
@ -244,29 +244,32 @@ class Library extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: DataTypes.STRING,
displayOrder: DataTypes.INTEGER,
icon: DataTypes.STRING,
mediaType: DataTypes.STRING,
provider: DataTypes.STRING,
lastScan: DataTypes.DATE,
lastScanVersion: DataTypes.STRING,
settings: DataTypes.JSON,
extraData: DataTypes.JSON
}, },
name: DataTypes.STRING, {
displayOrder: DataTypes.INTEGER, sequelize,
icon: DataTypes.STRING, modelName: 'library'
mediaType: DataTypes.STRING, }
provider: DataTypes.STRING, )
lastScan: DataTypes.DATE,
lastScanVersion: DataTypes.STRING,
settings: DataTypes.JSON,
extraData: DataTypes.JSON
}, {
sequelize,
modelName: 'library'
})
} }
} }
module.exports = Library module.exports = Library

View file

@ -16,33 +16,25 @@ class LibraryFolder extends Model {
this.updatedAt this.updatedAt
} }
/**
* Gets all library folder path strings
* @returns {Promise<string[]>} array of library folder paths
*/
static async getAllLibraryFolderPaths() {
const libraryFolders = await this.findAll({
attributes: ['path']
})
return libraryFolders.map(l => l.path)
}
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
path: DataTypes.STRING
}, },
path: DataTypes.STRING {
}, { sequelize,
sequelize, modelName: 'libraryFolder'
modelName: 'libraryFolder' }
}) )
const { library } = sequelize.models const { library } = sequelize.models
library.hasMany(LibraryFolder, { library.hasMany(LibraryFolder, {
@ -52,4 +44,4 @@ class LibraryFolder extends Model {
} }
} }
module.exports = LibraryFolder module.exports = LibraryFolder

View file

@ -21,8 +21,8 @@ const Podcast = require('./Podcast')
/** /**
* @typedef LibraryItemExpandedProperties * @typedef LibraryItemExpandedProperties
* @property {Book.BookExpanded|Podcast.PodcastExpanded} media * @property {Book.BookExpanded|Podcast.PodcastExpanded} media
* *
* @typedef {LibraryItem & LibraryItemExpandedProperties} LibraryItemExpanded * @typedef {LibraryItem & LibraryItemExpandedProperties} LibraryItemExpanded
*/ */
@ -77,7 +77,7 @@ class LibraryItem extends Model {
/** /**
* Gets library items partially expanded, not including podcast episodes * Gets library items partially expanded, not including podcast episodes
* @todo temporary solution * @todo temporary solution
* *
* @param {number} offset * @param {number} offset
* @param {number} limit * @param {number} limit
* @returns {Promise<LibraryItem[]>} LibraryItem * @returns {Promise<LibraryItem[]>} LibraryItem
@ -154,13 +154,13 @@ class LibraryItem extends Model {
} }
] ]
}) })
return libraryItems.map(ti => this.getOldLibraryItem(ti)) return libraryItems.map((ti) => this.getOldLibraryItem(ti))
} }
/** /**
* Convert an expanded LibraryItem into an old library item * Convert an expanded LibraryItem into an old library item
* *
* @param {Model<LibraryItem>} libraryItemExpanded * @param {Model<LibraryItem>} libraryItemExpanded
* @returns {oldLibraryItem} * @returns {oldLibraryItem}
*/ */
static getOldLibraryItem(libraryItemExpanded) { static getOldLibraryItem(libraryItemExpanded) {
@ -231,8 +231,8 @@ class LibraryItem extends Model {
/** /**
* Updates libraryItem, book, authors and series from old library item * Updates libraryItem, book, authors and series from old library item
* *
* @param {oldLibraryItem} oldLibraryItem * @param {oldLibraryItem} oldLibraryItem
* @returns {Promise<boolean>} true if updates were made * @returns {Promise<boolean>} true if updates were made
*/ */
static async fullUpdateFromOld(oldLibraryItem) { static async fullUpdateFromOld(oldLibraryItem) {
@ -280,14 +280,14 @@ class LibraryItem extends Model {
for (const existingPodcastEpisode of existingPodcastEpisodes) { for (const existingPodcastEpisode of existingPodcastEpisodes) {
// Episode was removed // Episode was removed
if (!updatedPodcastEpisodes.some(ep => ep.id === existingPodcastEpisode.id)) { if (!updatedPodcastEpisodes.some((ep) => ep.id === existingPodcastEpisode.id)) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${existingPodcastEpisode.title}" was removed`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${existingPodcastEpisode.title}" was removed`)
await existingPodcastEpisode.destroy() await existingPodcastEpisode.destroy()
hasUpdates = true hasUpdates = true
} }
} }
for (const updatedPodcastEpisode of updatedPodcastEpisodes) { for (const updatedPodcastEpisode of updatedPodcastEpisodes) {
const existingEpisodeMatch = existingPodcastEpisodes.find(ep => ep.id === updatedPodcastEpisode.id) const existingEpisodeMatch = existingPodcastEpisodes.find((ep) => ep.id === updatedPodcastEpisode.id)
if (!existingEpisodeMatch) { if (!existingEpisodeMatch) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${updatedPodcastEpisode.title}" was added`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" episode "${updatedPodcastEpisode.title}" was added`)
await this.sequelize.models.podcastEpisode.createFromOld(updatedPodcastEpisode) await this.sequelize.models.podcastEpisode.createFromOld(updatedPodcastEpisode)
@ -316,12 +316,12 @@ class LibraryItem extends Model {
const existingAuthors = libraryItemExpanded.media.authors || [] const existingAuthors = libraryItemExpanded.media.authors || []
const existingSeriesAll = libraryItemExpanded.media.series || [] const existingSeriesAll = libraryItemExpanded.media.series || []
const updatedAuthors = oldLibraryItem.media.metadata.authors || [] const updatedAuthors = oldLibraryItem.media.metadata.authors || []
const uniqueUpdatedAuthors = updatedAuthors.filter((au, idx) => updatedAuthors.findIndex(a => a.id === au.id) === idx) const uniqueUpdatedAuthors = updatedAuthors.filter((au, idx) => updatedAuthors.findIndex((a) => a.id === au.id) === idx)
const updatedSeriesAll = oldLibraryItem.media.metadata.series || [] const updatedSeriesAll = oldLibraryItem.media.metadata.series || []
for (const existingAuthor of existingAuthors) { for (const existingAuthor of existingAuthors) {
// Author was removed from Book // Author was removed from Book
if (!uniqueUpdatedAuthors.some(au => au.id === existingAuthor.id)) { if (!uniqueUpdatedAuthors.some((au) => au.id === existingAuthor.id)) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${existingAuthor.name}" was removed`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${existingAuthor.name}" was removed`)
await this.sequelize.models.bookAuthor.removeByIds(existingAuthor.id, libraryItemExpanded.media.id) await this.sequelize.models.bookAuthor.removeByIds(existingAuthor.id, libraryItemExpanded.media.id)
hasUpdates = true hasUpdates = true
@ -329,7 +329,7 @@ class LibraryItem extends Model {
} }
for (const updatedAuthor of uniqueUpdatedAuthors) { for (const updatedAuthor of uniqueUpdatedAuthors) {
// Author was added // Author was added
if (!existingAuthors.some(au => au.id === updatedAuthor.id)) { if (!existingAuthors.some((au) => au.id === updatedAuthor.id)) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${updatedAuthor.name}" was added`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" author "${updatedAuthor.name}" was added`)
await this.sequelize.models.bookAuthor.create({ authorId: updatedAuthor.id, bookId: libraryItemExpanded.media.id }) await this.sequelize.models.bookAuthor.create({ authorId: updatedAuthor.id, bookId: libraryItemExpanded.media.id })
hasUpdates = true hasUpdates = true
@ -337,7 +337,7 @@ class LibraryItem extends Model {
} }
for (const existingSeries of existingSeriesAll) { for (const existingSeries of existingSeriesAll) {
// Series was removed // Series was removed
if (!updatedSeriesAll.some(se => se.id === existingSeries.id)) { if (!updatedSeriesAll.some((se) => se.id === existingSeries.id)) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${existingSeries.name}" was removed`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${existingSeries.name}" was removed`)
await this.sequelize.models.bookSeries.removeByIds(existingSeries.id, libraryItemExpanded.media.id) await this.sequelize.models.bookSeries.removeByIds(existingSeries.id, libraryItemExpanded.media.id)
hasUpdates = true hasUpdates = true
@ -345,7 +345,7 @@ class LibraryItem extends Model {
} }
for (const updatedSeries of updatedSeriesAll) { for (const updatedSeries of updatedSeriesAll) {
// Series was added/updated // Series was added/updated
const existingSeriesMatch = existingSeriesAll.find(se => se.id === updatedSeries.id) const existingSeriesMatch = existingSeriesAll.find((se) => se.id === updatedSeries.id)
if (!existingSeriesMatch) { if (!existingSeriesMatch) {
Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${updatedSeries.name}" was added`) Logger.debug(`[LibraryItem] "${libraryItemExpanded.media.title}" series "${updatedSeries.name}" was added`)
await this.sequelize.models.bookSeries.create({ seriesId: updatedSeries.id, bookId: libraryItemExpanded.media.id, sequence: updatedSeries.sequence }) await this.sequelize.models.bookSeries.create({ seriesId: updatedSeries.id, bookId: libraryItemExpanded.media.id, sequence: updatedSeries.sequence })
@ -420,7 +420,7 @@ class LibraryItem extends Model {
lastScanVersion: oldLibraryItem.scanVersion, lastScanVersion: oldLibraryItem.scanVersion,
libraryId: oldLibraryItem.libraryId, libraryId: oldLibraryItem.libraryId,
libraryFolderId: oldLibraryItem.folderId, libraryFolderId: oldLibraryItem.folderId,
libraryFiles: oldLibraryItem.libraryFiles?.map(lf => lf.toJSON()) || [], libraryFiles: oldLibraryItem.libraryFiles?.map((lf) => lf.toJSON()) || [],
extraData extraData
} }
} }
@ -435,8 +435,8 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @param {string} libraryItemId * @param {string} libraryItemId
* @returns {Promise<LibraryItemExpanded>} * @returns {Promise<LibraryItemExpanded>}
*/ */
static async getExpandedById(libraryItemId) { static async getExpandedById(libraryItemId) {
@ -485,7 +485,7 @@ class LibraryItem extends Model {
/** /**
* Get old library item by id * Get old library item by id
* @param {string} libraryItemId * @param {string} libraryItemId
* @returns {oldLibraryItem} * @returns {oldLibraryItem}
*/ */
static async getOldById(libraryItemId) { static async getOldById(libraryItemId) {
@ -534,9 +534,9 @@ class LibraryItem extends Model {
/** /**
* Get library items using filter and sort * Get library items using filter and sort
* @param {oldLibrary} library * @param {oldLibrary} library
* @param {oldUser} user * @param {oldUser} user
* @param {object} options * @param {object} options
* @returns {object} { libraryItems:oldLibraryItem[], count:number } * @returns {object} { libraryItems:oldLibraryItem[], count:number }
*/ */
static async getByFilterAndSort(library, user, options) { static async getByFilterAndSort(library, user, options) {
@ -545,7 +545,7 @@ class LibraryItem extends Model {
Logger.debug(`Loaded ${libraryItems.length} of ${count} items for libary page in ${((Date.now() - start) / 1000).toFixed(2)}s`) Logger.debug(`Loaded ${libraryItems.length} of ${count} items for libary page in ${((Date.now() - start) / 1000).toFixed(2)}s`)
return { return {
libraryItems: libraryItems.map(li => { libraryItems: libraryItems.map((li) => {
const oldLibraryItem = this.getOldLibraryItem(li).toJSONMinified() const oldLibraryItem = this.getOldLibraryItem(li).toJSONMinified()
if (li.collapsedSeries) { if (li.collapsedSeries) {
oldLibraryItem.collapsedSeries = li.collapsedSeries oldLibraryItem.collapsedSeries = li.collapsedSeries
@ -574,10 +574,10 @@ class LibraryItem extends Model {
/** /**
* Get home page data personalized shelves * Get home page data personalized shelves
* @param {oldLibrary} library * @param {oldLibrary} library
* @param {oldUser} user * @param {oldUser} user
* @param {string[]} include * @param {string[]} include
* @param {number} limit * @param {number} limit
* @returns {object[]} array of shelf objects * @returns {object[]} array of shelf objects
*/ */
static async getPersonalizedShelves(library, user, include, limit) { static async getPersonalizedShelves(library, user, include, limit) {
@ -588,8 +588,8 @@ class LibraryItem extends Model {
// "Continue Listening" shelf // "Continue Listening" shelf
const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, user, include, limit, false) const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, user, include, limit, false)
if (itemsInProgressPayload.items.length) { if (itemsInProgressPayload.items.length) {
const ebookOnlyItemsInProgress = itemsInProgressPayload.items.filter(li => li.media.isEBookOnly) const ebookOnlyItemsInProgress = itemsInProgressPayload.items.filter((li) => li.media.isEBookOnly)
const audioOnlyItemsInProgress = itemsInProgressPayload.items.filter(li => !li.media.isEBookOnly) const audioOnlyItemsInProgress = itemsInProgressPayload.items.filter((li) => !li.media.isEBookOnly)
shelves.push({ shelves.push({
id: 'continue-listening', id: 'continue-listening',
@ -697,8 +697,8 @@ class LibraryItem extends Model {
// "Listen Again" shelf // "Listen Again" shelf
const mediaFinishedPayload = await libraryFilters.getMediaFinished(library, user, include, limit) const mediaFinishedPayload = await libraryFilters.getMediaFinished(library, user, include, limit)
if (mediaFinishedPayload.items.length) { if (mediaFinishedPayload.items.length) {
const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter(li => li.media.isEBookOnly) const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.isEBookOnly)
const audioOnlyItemsInProgress = mediaFinishedPayload.items.filter(li => !li.media.isEBookOnly) const audioOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => !li.media.isEBookOnly)
shelves.push({ shelves.push({
id: 'listen-again', id: 'listen-again',
@ -748,27 +748,27 @@ class LibraryItem extends Model {
/** /**
* Get book library items for author, optional use user permissions * Get book library items for author, optional use user permissions
* @param {oldAuthor} author * @param {oldAuthor} author
* @param {[oldUser]} user * @param {[oldUser]} user
* @returns {Promise<oldLibraryItem[]>} * @returns {Promise<oldLibraryItem[]>}
*/ */
static async getForAuthor(author, user = null) { static async getForAuthor(author, user = null) {
const { libraryItems } = await libraryFilters.getLibraryItemsForAuthor(author, user, undefined, undefined) const { libraryItems } = await libraryFilters.getLibraryItemsForAuthor(author, user, undefined, undefined)
return libraryItems.map(li => this.getOldLibraryItem(li)) return libraryItems.map((li) => this.getOldLibraryItem(li))
} }
/** /**
* Get book library items in a collection * Get book library items in a collection
* @param {oldCollection} collection * @param {oldCollection} collection
* @returns {Promise<oldLibraryItem[]>} * @returns {Promise<oldLibraryItem[]>}
*/ */
static async getForCollection(collection) { static async getForCollection(collection) {
const libraryItems = await libraryFilters.getLibraryItemsForCollection(collection) const libraryItems = await libraryFilters.getLibraryItemsForCollection(collection)
return libraryItems.map(li => this.getOldLibraryItem(li)) return libraryItems.map((li) => this.getOldLibraryItem(li))
} }
/** /**
* Check if library item exists * Check if library item exists
* @param {string} libraryItemId * @param {string} libraryItemId
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
static async checkExistsById(libraryItemId) { static async checkExistsById(libraryItemId) {
@ -776,8 +776,8 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @param {import('sequelize').WhereOptions} where * @param {import('sequelize').WhereOptions} where
* @param {import('sequelize').BindOrReplacements} replacements * @param {import('sequelize').BindOrReplacements} replacements
* @returns {Object} oldLibraryItem * @returns {Object} oldLibraryItem
*/ */
@ -822,8 +822,8 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @param {import('sequelize').FindOptions} options * @param {import('sequelize').FindOptions} options
* @returns {Promise<Book|Podcast>} * @returns {Promise<Book|Podcast>}
*/ */
getMedia(options) { getMedia(options) {
@ -833,7 +833,7 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @returns {Promise<Book|Podcast>} * @returns {Promise<Book|Podcast>}
*/ */
getMediaExpanded() { getMediaExpanded() {
@ -870,7 +870,7 @@ class LibraryItem extends Model {
} }
/** /**
* *
* @returns {Promise} * @returns {Promise}
*/ */
async saveMetadataFile() { async saveMetadataFile() {
@ -887,18 +887,18 @@ class LibraryItem extends Model {
const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`) const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
// Expanded with series, authors, podcastEpisodes // Expanded with series, authors, podcastEpisodes
const mediaExpanded = this.media || await this.getMediaExpanded() const mediaExpanded = this.media || (await this.getMediaExpanded())
let jsonObject = {} let jsonObject = {}
if (this.mediaType === 'book') { if (this.mediaType === 'book') {
jsonObject = { jsonObject = {
tags: mediaExpanded.tags || [], tags: mediaExpanded.tags || [],
chapters: mediaExpanded.chapters?.map(c => ({ ...c })) || [], chapters: mediaExpanded.chapters?.map((c) => ({ ...c })) || [],
title: mediaExpanded.title, title: mediaExpanded.title,
subtitle: mediaExpanded.subtitle, subtitle: mediaExpanded.subtitle,
authors: mediaExpanded.authors.map(a => a.name), authors: mediaExpanded.authors.map((a) => a.name),
narrators: mediaExpanded.narrators, narrators: mediaExpanded.narrators,
series: mediaExpanded.series.map(se => { series: mediaExpanded.series.map((se) => {
const sequence = se.bookSeries?.sequence || '' const sequence = se.bookSeries?.sequence || ''
if (!sequence) return se.name if (!sequence) return se.name
return `${se.name} #${sequence}` return `${se.name} #${sequence}`
@ -934,96 +934,101 @@ class LibraryItem extends Model {
} }
} }
return fsExtra
return fsExtra.writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2)).then(async () => { .writeFile(metadataFilePath, JSON.stringify(jsonObject, null, 2))
// Add metadata.json to libraryFiles array if it is new .then(async () => {
let metadataLibraryFile = this.libraryFiles.find(lf => lf.metadata.path === filePathToPOSIX(metadataFilePath)) // Add metadata.json to libraryFiles array if it is new
if (storeMetadataWithItem) { let metadataLibraryFile = this.libraryFiles.find((lf) => lf.metadata.path === filePathToPOSIX(metadataFilePath))
if (!metadataLibraryFile) { if (storeMetadataWithItem) {
const newLibraryFile = new LibraryFile() if (!metadataLibraryFile) {
await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`) const newLibraryFile = new LibraryFile()
metadataLibraryFile = newLibraryFile.toJSON() await newLibraryFile.setDataFromPath(metadataFilePath, `metadata.json`)
this.libraryFiles.push(metadataLibraryFile) metadataLibraryFile = newLibraryFile.toJSON()
} else { this.libraryFiles.push(metadataLibraryFile)
const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath) } else {
if (fileTimestamps) { const fileTimestamps = await getFileTimestampsWithIno(metadataFilePath)
metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs if (fileTimestamps) {
metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs metadataLibraryFile.metadata.mtimeMs = fileTimestamps.mtimeMs
metadataLibraryFile.metadata.size = fileTimestamps.size metadataLibraryFile.metadata.ctimeMs = fileTimestamps.ctimeMs
metadataLibraryFile.ino = fileTimestamps.ino metadataLibraryFile.metadata.size = fileTimestamps.size
metadataLibraryFile.ino = fileTimestamps.ino
}
}
const libraryItemDirTimestamps = await getFileTimestampsWithIno(this.path)
if (libraryItemDirTimestamps) {
this.mtime = libraryItemDirTimestamps.mtimeMs
this.ctime = libraryItemDirTimestamps.ctimeMs
let size = 0
this.libraryFiles.forEach((lf) => (size += !isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
this.size = size
await this.save()
} }
} }
const libraryItemDirTimestamps = await getFileTimestampsWithIno(this.path)
if (libraryItemDirTimestamps) {
this.mtime = libraryItemDirTimestamps.mtimeMs
this.ctime = libraryItemDirTimestamps.ctimeMs
let size = 0
this.libraryFiles.forEach((lf) => size += (!isNaN(lf.metadata.size) ? Number(lf.metadata.size) : 0))
this.size = size
await this.save()
}
}
Logger.debug(`Success saving abmetadata to "${metadataFilePath}"`) Logger.debug(`Success saving abmetadata to "${metadataFilePath}"`)
return metadataLibraryFile return metadataLibraryFile
}).catch((error) => { })
Logger.error(`Failed to save json file at "${metadataFilePath}"`, error) .catch((error) => {
return null Logger.error(`Failed to save json file at "${metadataFilePath}"`, error)
}) return null
})
} }
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
ino: DataTypes.STRING,
path: DataTypes.STRING,
relPath: DataTypes.STRING,
mediaId: DataTypes.UUIDV4,
mediaType: DataTypes.STRING,
isFile: DataTypes.BOOLEAN,
isMissing: DataTypes.BOOLEAN,
isInvalid: DataTypes.BOOLEAN,
mtime: DataTypes.DATE(6),
ctime: DataTypes.DATE(6),
birthtime: DataTypes.DATE(6),
size: DataTypes.BIGINT,
lastScan: DataTypes.DATE,
lastScanVersion: DataTypes.STRING,
libraryFiles: DataTypes.JSON,
extraData: DataTypes.JSON
}, },
ino: DataTypes.STRING, {
path: DataTypes.STRING, sequelize,
relPath: DataTypes.STRING, modelName: 'libraryItem',
mediaId: DataTypes.UUIDV4, indexes: [
mediaType: DataTypes.STRING, {
isFile: DataTypes.BOOLEAN, fields: ['createdAt']
isMissing: DataTypes.BOOLEAN, },
isInvalid: DataTypes.BOOLEAN, {
mtime: DataTypes.DATE(6), fields: ['mediaId']
ctime: DataTypes.DATE(6), },
birthtime: DataTypes.DATE(6), {
size: DataTypes.BIGINT, fields: ['libraryId', 'mediaType']
lastScan: DataTypes.DATE, },
lastScanVersion: DataTypes.STRING, {
libraryFiles: DataTypes.JSON, fields: ['libraryId', 'mediaId', 'mediaType']
extraData: DataTypes.JSON },
}, { {
sequelize, fields: ['birthtime']
modelName: 'libraryItem', },
indexes: [ {
{ fields: ['mtime']
fields: ['createdAt'] }
}, ]
{ }
fields: ['mediaId'] )
},
{
fields: ['libraryId', 'mediaType']
},
{
fields: ['libraryId', 'mediaId', 'mediaType']
},
{
fields: ['birthtime']
},
{
fields: ['mtime']
}
]
})
const { library, libraryFolder, book, podcast } = sequelize.models const { library, libraryFolder, book, podcast } = sequelize.models
library.hasMany(LibraryItem) library.hasMany(LibraryItem)
@ -1050,7 +1055,7 @@ class LibraryItem extends Model {
}) })
LibraryItem.belongsTo(podcast, { foreignKey: 'mediaId', constraints: false }) LibraryItem.belongsTo(podcast, { foreignKey: 'mediaId', constraints: false })
LibraryItem.addHook('afterFind', findResult => { LibraryItem.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
@ -1070,7 +1075,7 @@ class LibraryItem extends Model {
} }
}) })
LibraryItem.addHook('afterDestroy', async instance => { LibraryItem.addHook('afterDestroy', async (instance) => {
if (!instance) return if (!instance) return
const media = await instance.getMedia() const media = await instance.getMedia()
if (media) { if (media) {

View file

@ -100,38 +100,41 @@ class MediaProgress extends Model {
/** /**
* Initialize model * Initialize model
* *
* Polymorphic association: Book has many MediaProgress. PodcastEpisode has many MediaProgress. * Polymorphic association: Book has many MediaProgress. PodcastEpisode has many MediaProgress.
* @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/ * @see https://sequelize.org/docs/v6/advanced-association-concepts/polymorphic-associations/
* *
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING,
duration: DataTypes.FLOAT,
currentTime: DataTypes.FLOAT,
isFinished: DataTypes.BOOLEAN,
hideFromContinueListening: DataTypes.BOOLEAN,
ebookLocation: DataTypes.STRING,
ebookProgress: DataTypes.FLOAT,
finishedAt: DataTypes.DATE,
extraData: DataTypes.JSON
}, },
mediaItemId: DataTypes.UUIDV4, {
mediaItemType: DataTypes.STRING, sequelize,
duration: DataTypes.FLOAT, modelName: 'mediaProgress',
currentTime: DataTypes.FLOAT, indexes: [
isFinished: DataTypes.BOOLEAN, {
hideFromContinueListening: DataTypes.BOOLEAN, fields: ['updatedAt']
ebookLocation: DataTypes.STRING, }
ebookProgress: DataTypes.FLOAT, ]
finishedAt: DataTypes.DATE, }
extraData: DataTypes.JSON )
}, {
sequelize,
modelName: 'mediaProgress',
indexes: [
{
fields: ['updatedAt']
}
]
})
const { book, podcastEpisode, user } = sequelize.models const { book, podcastEpisode, user } = sequelize.models
@ -153,7 +156,7 @@ class MediaProgress extends Model {
}) })
MediaProgress.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false }) MediaProgress.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
MediaProgress.addHook('afterFind', findResult => { MediaProgress.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]
@ -181,4 +184,4 @@ class MediaProgress extends Model {
} }
} }
module.exports = MediaProgress module.exports = MediaProgress

View file

@ -2,7 +2,6 @@ const { DataTypes, Model } = require('sequelize')
const oldPlaybackSession = require('../objects/PlaybackSession') const oldPlaybackSession = require('../objects/PlaybackSession')
class PlaybackSession extends Model { class PlaybackSession extends Model {
constructor(values, options) { constructor(values, options) {
super(values, options) super(values, options)
@ -62,7 +61,7 @@ class PlaybackSession extends Model {
} }
] ]
}) })
return playbackSessions.map(session => this.getOldPlaybackSession(session)) return playbackSessions.map((session) => this.getOldPlaybackSession(session))
} }
static async getById(sessionId) { static async getById(sessionId) {
@ -170,35 +169,38 @@ class PlaybackSession extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING,
displayTitle: DataTypes.STRING,
displayAuthor: DataTypes.STRING,
duration: DataTypes.FLOAT,
playMethod: DataTypes.INTEGER,
mediaPlayer: DataTypes.STRING,
startTime: DataTypes.FLOAT,
currentTime: DataTypes.FLOAT,
serverVersion: DataTypes.STRING,
coverPath: DataTypes.STRING,
timeListening: DataTypes.INTEGER,
mediaMetadata: DataTypes.JSON,
date: DataTypes.STRING,
dayOfWeek: DataTypes.STRING,
extraData: DataTypes.JSON
}, },
mediaItemId: DataTypes.UUIDV4, {
mediaItemType: DataTypes.STRING, sequelize,
displayTitle: DataTypes.STRING, modelName: 'playbackSession'
displayAuthor: DataTypes.STRING, }
duration: DataTypes.FLOAT, )
playMethod: DataTypes.INTEGER,
mediaPlayer: DataTypes.STRING,
startTime: DataTypes.FLOAT,
currentTime: DataTypes.FLOAT,
serverVersion: DataTypes.STRING,
coverPath: DataTypes.STRING,
timeListening: DataTypes.INTEGER,
mediaMetadata: DataTypes.JSON,
date: DataTypes.STRING,
dayOfWeek: DataTypes.STRING,
extraData: DataTypes.JSON
}, {
sequelize,
modelName: 'playbackSession'
})
const { book, podcastEpisode, user, device, library } = sequelize.models const { book, podcastEpisode, user, device, library } = sequelize.models
@ -229,7 +231,7 @@ class PlaybackSession extends Model {
}) })
PlaybackSession.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false }) PlaybackSession.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaybackSession.addHook('afterFind', findResult => { PlaybackSession.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]

View file

@ -23,29 +23,6 @@ class Playlist extends Model {
this.updatedAt this.updatedAt
} }
static async getOldPlaylists() {
const playlists = await this.findAll({
include: {
model: this.sequelize.models.playlistMediaItem,
include: [
{
model: this.sequelize.models.book,
include: this.sequelize.models.libraryItem
},
{
model: this.sequelize.models.podcastEpisode,
include: {
model: this.sequelize.models.podcast,
include: this.sequelize.models.libraryItem
}
}
]
},
order: [['playlistMediaItems', 'order', 'ASC']]
})
return playlists.map((p) => this.getOldPlaylist(p))
}
static getOldPlaylist(playlistExpanded) { static getOldPlaylist(playlistExpanded) {
const items = playlistExpanded.playlistMediaItems const items = playlistExpanded.playlistMediaItems
.map((pmi) => { .map((pmi) => {
@ -76,8 +53,8 @@ class Playlist extends Model {
/** /**
* Get old playlist toJSONExpanded * Get old playlist toJSONExpanded
* @param {[string[]]} include * @param {string[]} [include]
* @returns {Promise<object>} oldPlaylist.toJSONExpanded * @returns {Promise<oldPlaylist>} oldPlaylist.toJSONExpanded
*/ */
async getOldJsonExpanded(include) { async getOldJsonExpanded(include) {
this.playlistMediaItems = this.playlistMediaItems =

View file

@ -35,24 +35,27 @@ class PlaylistMediaItem extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
mediaItemId: DataTypes.UUIDV4,
mediaItemType: DataTypes.STRING,
order: DataTypes.INTEGER
}, },
mediaItemId: DataTypes.UUIDV4, {
mediaItemType: DataTypes.STRING, sequelize,
order: DataTypes.INTEGER timestamps: true,
}, { updatedAt: false,
sequelize, modelName: 'playlistMediaItem'
timestamps: true, }
updatedAt: false, )
modelName: 'playlistMediaItem'
})
const { book, podcastEpisode, playlist } = sequelize.models const { book, podcastEpisode, playlist } = sequelize.models
@ -74,7 +77,7 @@ class PlaylistMediaItem extends Model {
}) })
PlaylistMediaItem.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false }) PlaylistMediaItem.belongsTo(podcastEpisode, { foreignKey: 'mediaItemId', constraints: false })
PlaylistMediaItem.addHook('afterFind', findResult => { PlaylistMediaItem.addHook('afterFind', (findResult) => {
if (!findResult) return if (!findResult) return
if (!Array.isArray(findResult)) findResult = [findResult] if (!Array.isArray(findResult)) findResult = [findResult]

View file

@ -3,7 +3,7 @@ const { DataTypes, Model } = require('sequelize')
/** /**
* @typedef PodcastExpandedProperties * @typedef PodcastExpandedProperties
* @property {import('./PodcastEpisode')[]} podcastEpisodes * @property {import('./PodcastEpisode')[]} podcastEpisodes
* *
* @typedef {Podcast & PodcastExpandedProperties} PodcastExpanded * @typedef {Podcast & PodcastExpandedProperties} PodcastExpanded
*/ */
@ -61,7 +61,7 @@ class Podcast extends Model {
static getOldPodcast(libraryItemExpanded) { static getOldPodcast(libraryItemExpanded) {
const podcastExpanded = libraryItemExpanded.media const podcastExpanded = libraryItemExpanded.media
const podcastEpisodes = podcastExpanded.podcastEpisodes?.map(ep => ep.getOldPodcastEpisode(libraryItemExpanded.id).toJSON()).sort((a, b) => a.index - b.index) const podcastEpisodes = podcastExpanded.podcastEpisodes?.map((ep) => ep.getOldPodcastEpisode(libraryItemExpanded.id).toJSON()).sort((a, b) => a.index - b.index)
return { return {
id: podcastExpanded.id, id: podcastExpanded.id,
libraryItemId: libraryItemExpanded.id, libraryItemId: libraryItemExpanded.id,
@ -140,42 +140,45 @@ class Podcast extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
title: DataTypes.STRING, },
titleIgnorePrefix: DataTypes.STRING, title: DataTypes.STRING,
author: DataTypes.STRING, titleIgnorePrefix: DataTypes.STRING,
releaseDate: DataTypes.STRING, author: DataTypes.STRING,
feedURL: DataTypes.STRING, releaseDate: DataTypes.STRING,
imageURL: DataTypes.STRING, feedURL: DataTypes.STRING,
description: DataTypes.TEXT, imageURL: DataTypes.STRING,
itunesPageURL: DataTypes.STRING, description: DataTypes.TEXT,
itunesId: DataTypes.STRING, itunesPageURL: DataTypes.STRING,
itunesArtistId: DataTypes.STRING, itunesId: DataTypes.STRING,
language: DataTypes.STRING, itunesArtistId: DataTypes.STRING,
podcastType: DataTypes.STRING, language: DataTypes.STRING,
explicit: DataTypes.BOOLEAN, podcastType: DataTypes.STRING,
explicit: DataTypes.BOOLEAN,
autoDownloadEpisodes: DataTypes.BOOLEAN, autoDownloadEpisodes: DataTypes.BOOLEAN,
autoDownloadSchedule: DataTypes.STRING, autoDownloadSchedule: DataTypes.STRING,
lastEpisodeCheck: DataTypes.DATE, lastEpisodeCheck: DataTypes.DATE,
maxEpisodesToKeep: DataTypes.INTEGER, maxEpisodesToKeep: DataTypes.INTEGER,
maxNewEpisodesToDownload: DataTypes.INTEGER, maxNewEpisodesToDownload: DataTypes.INTEGER,
coverPath: DataTypes.STRING, coverPath: DataTypes.STRING,
tags: DataTypes.JSON, tags: DataTypes.JSON,
genres: DataTypes.JSON genres: DataTypes.JSON
}, { },
sequelize, {
modelName: 'podcast' sequelize,
}) modelName: 'podcast'
}
)
} }
} }
module.exports = Podcast module.exports = Podcast

View file

@ -54,7 +54,7 @@ class PodcastEpisode extends Model {
} }
/** /**
* @param {string} libraryItemId * @param {string} libraryItemId
* @returns {oldPodcastEpisode} * @returns {oldPodcastEpisode}
*/ */
getOldPodcastEpisode(libraryItemId = null) { getOldPodcastEpisode(libraryItemId = null) {
@ -125,40 +125,43 @@ class PodcastEpisode extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
index: DataTypes.INTEGER, },
season: DataTypes.STRING, index: DataTypes.INTEGER,
episode: DataTypes.STRING, season: DataTypes.STRING,
episodeType: DataTypes.STRING, episode: DataTypes.STRING,
title: DataTypes.STRING, episodeType: DataTypes.STRING,
subtitle: DataTypes.STRING(1000), title: DataTypes.STRING,
description: DataTypes.TEXT, subtitle: DataTypes.STRING(1000),
pubDate: DataTypes.STRING, description: DataTypes.TEXT,
enclosureURL: DataTypes.STRING, pubDate: DataTypes.STRING,
enclosureSize: DataTypes.BIGINT, enclosureURL: DataTypes.STRING,
enclosureType: DataTypes.STRING, enclosureSize: DataTypes.BIGINT,
publishedAt: DataTypes.DATE, enclosureType: DataTypes.STRING,
publishedAt: DataTypes.DATE,
audioFile: DataTypes.JSON, audioFile: DataTypes.JSON,
chapters: DataTypes.JSON, chapters: DataTypes.JSON,
extraData: DataTypes.JSON extraData: DataTypes.JSON
}, { },
sequelize, {
modelName: 'podcastEpisode', sequelize,
indexes: [ modelName: 'podcastEpisode',
{ indexes: [
fields: ['createdAt'] {
} fields: ['createdAt']
] }
}) ]
}
)
const { podcast } = sequelize.models const { podcast } = sequelize.models
podcast.hasMany(PodcastEpisode, { podcast.hasMany(PodcastEpisode, {
@ -168,4 +171,4 @@ class PodcastEpisode extends Model {
} }
} }
module.exports = PodcastEpisode module.exports = PodcastEpisode

View file

@ -24,7 +24,7 @@ class Series extends Model {
static async getAllOldSeries() { static async getAllOldSeries() {
const series = await this.findAll() const series = await this.findAll()
return series.map(se => se.getOldSeries()) return series.map((se) => se.getOldSeries())
} }
getOldSeries() { getOldSeries() {
@ -77,7 +77,7 @@ class Series extends Model {
/** /**
* Get oldSeries by id * Get oldSeries by id
* @param {string} seriesId * @param {string} seriesId
* @returns {Promise<oldSeries>} * @returns {Promise<oldSeries>}
*/ */
static async getOldById(seriesId) { static async getOldById(seriesId) {
@ -88,7 +88,7 @@ class Series extends Model {
/** /**
* Check if series exists * Check if series exists
* @param {string} seriesId * @param {string} seriesId
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
static async checkExistsById(seriesId) { static async checkExistsById(seriesId) {
@ -97,58 +97,65 @@ class Series extends Model {
/** /**
* Get old series by name and libraryId. name case insensitive * Get old series by name and libraryId. name case insensitive
* *
* @param {string} seriesName * @param {string} seriesName
* @param {string} libraryId * @param {string} libraryId
* @returns {Promise<oldSeries>} * @returns {Promise<oldSeries>}
*/ */
static async getOldByNameAndLibrary(seriesName, libraryId) { static async getOldByNameAndLibrary(seriesName, libraryId) {
const series = (await this.findOne({ const series = (
where: [ await this.findOne({
where(fn('lower', col('name')), seriesName.toLowerCase()), where: [
{ where(fn('lower', col('name')), seriesName.toLowerCase()),
libraryId {
} libraryId
] }
}))?.getOldSeries() ]
})
)?.getOldSeries()
return series return series
} }
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
}, primaryKey: true
name: DataTypes.STRING,
nameIgnorePrefix: DataTypes.STRING,
description: DataTypes.TEXT
}, {
sequelize,
modelName: 'series',
indexes: [
{
fields: [{
name: 'name',
collate: 'NOCASE'
}]
}, },
// { name: DataTypes.STRING,
// fields: [{ nameIgnorePrefix: DataTypes.STRING,
// name: 'nameIgnorePrefix', description: DataTypes.TEXT
// collate: 'NOCASE' },
// }] {
// }, sequelize,
{ modelName: 'series',
fields: ['libraryId'] indexes: [
} {
] fields: [
}) {
name: 'name',
collate: 'NOCASE'
}
]
},
// {
// fields: [{
// name: 'nameIgnorePrefix',
// collate: 'NOCASE'
// }]
// },
{
fields: ['libraryId']
}
]
}
)
const { library } = sequelize.models const { library } = sequelize.models
library.hasMany(Series, { library.hasMany(Series, {
@ -158,4 +165,4 @@ class Series extends Model {
} }
} }
module.exports = Series module.exports = Series

View file

@ -19,12 +19,11 @@ class Setting extends Model {
} }
static async getOldSettings() { static async getOldSettings() {
const settings = (await this.findAll()).map(se => se.value) const settings = (await this.findAll()).map((se) => se.value)
const emailSettingsJson = settings.find((se) => se.id === 'email-settings')
const emailSettingsJson = settings.find(se => se.id === 'email-settings') const serverSettingsJson = settings.find((se) => se.id === 'server-settings')
const serverSettingsJson = settings.find(se => se.id === 'server-settings') const notificationSettingsJson = settings.find((se) => se.id === 'notification-settings')
const notificationSettingsJson = settings.find(se => se.id === 'notification-settings')
return { return {
settings, settings,
@ -43,20 +42,23 @@ class Setting extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
key: { {
type: DataTypes.STRING, key: {
primaryKey: true type: DataTypes.STRING,
primaryKey: true
},
value: DataTypes.JSON
}, },
value: DataTypes.JSON {
}, { sequelize,
sequelize, modelName: 'setting'
modelName: 'setting' }
}) )
} }
} }
module.exports = Setting module.exports = Setting

View file

@ -1,4 +1,4 @@
const uuidv4 = require("uuid").v4 const uuidv4 = require('uuid').v4
const sequelize = require('sequelize') const sequelize = require('sequelize')
const Logger = require('../Logger') const Logger = require('../Logger')
const oldUser = require('../objects/user/User') const oldUser = require('../objects/user/User')
@ -45,17 +45,17 @@ class User extends Model {
const users = await this.findAll({ const users = await this.findAll({
include: this.sequelize.models.mediaProgress include: this.sequelize.models.mediaProgress
}) })
return users.map(u => this.getOldUser(u)) return users.map((u) => this.getOldUser(u))
} }
/** /**
* Get old user model from new * Get old user model from new
* *
* @param {Object} userExpanded * @param {Object} userExpanded
* @returns {oldUser} * @returns {oldUser}
*/ */
static getOldUser(userExpanded) { static getOldUser(userExpanded) {
const mediaProgress = userExpanded.mediaProgresses.map(mp => mp.getOldMediaProgress()) const mediaProgress = userExpanded.mediaProgresses.map((mp) => mp.getOldMediaProgress())
const librariesAccessible = userExpanded.permissions?.librariesAccessible || [] const librariesAccessible = userExpanded.permissions?.librariesAccessible || []
const itemTagsSelected = userExpanded.permissions?.itemTagsSelected || [] const itemTagsSelected = userExpanded.permissions?.itemTagsSelected || []
@ -86,8 +86,8 @@ class User extends Model {
} }
/** /**
* *
* @param {oldUser} oldUser * @param {oldUser} oldUser
* @returns {Promise<User>} * @returns {Promise<User>}
*/ */
static createFromOld(oldUser) { static createFromOld(oldUser) {
@ -97,8 +97,8 @@ class User extends Model {
/** /**
* Update User from old user model * Update User from old user model
* *
* @param {oldUser} oldUser * @param {oldUser} oldUser
* @param {boolean} [hooks=true] Run before / after bulk update hooks? * @param {boolean} [hooks=true] Run before / after bulk update hooks?
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
@ -109,16 +109,18 @@ class User extends Model {
where: { where: {
id: user.id id: user.id
} }
}).then((result) => result[0] > 0).catch((error) => {
Logger.error(`[User] Failed to save user ${oldUser.id}`, error)
return false
}) })
.then((result) => result[0] > 0)
.catch((error) => {
Logger.error(`[User] Failed to save user ${oldUser.id}`, error)
return false
})
} }
/** /**
* Get new User model from old * Get new User model from old
* *
* @param {oldUser} oldUser * @param {oldUser} oldUser
* @returns {Object} * @returns {Object}
*/ */
static getFromOld(oldUser) { static getFromOld(oldUser) {
@ -160,9 +162,9 @@ class User extends Model {
/** /**
* Create root user * Create root user
* @param {string} username * @param {string} username
* @param {string} pash * @param {string} pash
* @param {Auth} auth * @param {Auth} auth
* @returns {Promise<oldUser>} * @returns {Promise<oldUser>}
*/ */
static async createRootUser(username, pash, auth) { static async createRootUser(username, pash, auth) {
@ -185,15 +187,15 @@ class User extends Model {
/** /**
* Create user from openid userinfo * Create user from openid userinfo
* @param {Object} userinfo * @param {Object} userinfo
* @param {Auth} auth * @param {Auth} auth
* @returns {Promise<oldUser>} * @returns {Promise<oldUser>}
*/ */
static async createUserFromOpenIdUserInfo(userinfo, auth) { static async createUserFromOpenIdUserInfo(userinfo, auth) {
const userId = uuidv4() const userId = uuidv4()
// TODO: Ensure username is unique? // TODO: Ensure username is unique?
const username = userinfo.preferred_username || userinfo.name || userinfo.sub const username = userinfo.preferred_username || userinfo.name || userinfo.sub
const email = (userinfo.email && userinfo.email_verified) ? userinfo.email : null const email = userinfo.email && userinfo.email_verified ? userinfo.email : null
const token = await auth.generateAccessToken({ id: userId, username }) const token = await auth.generateAccessToken({ id: userId, username })
@ -218,7 +220,7 @@ class User extends Model {
/** /**
* Get a user by id or by the old database id * Get a user by id or by the old database id
* @temp User ids were updated in v2.3.0 migration and old API tokens may still use that id * @temp User ids were updated in v2.3.0 migration and old API tokens may still use that id
* @param {string} userId * @param {string} userId
* @returns {Promise<oldUser|null>} null if not found * @returns {Promise<oldUser|null>} null if not found
*/ */
static async getUserByIdOrOldId(userId) { static async getUserByIdOrOldId(userId) {
@ -244,7 +246,7 @@ class User extends Model {
/** /**
* Get user by username case insensitive * Get user by username case insensitive
* @param {string} username * @param {string} username
* @returns {Promise<oldUser|null>} returns null if not found * @returns {Promise<oldUser|null>} returns null if not found
*/ */
static async getUserByUsername(username) { static async getUserByUsername(username) {
@ -263,7 +265,7 @@ class User extends Model {
/** /**
* Get user by email case insensitive * Get user by email case insensitive
* @param {string} username * @param {string} username
* @returns {Promise<oldUser|null>} returns null if not found * @returns {Promise<oldUser|null>} returns null if not found
*/ */
static async getUserByEmail(email) { static async getUserByEmail(email) {
@ -282,7 +284,7 @@ class User extends Model {
/** /**
* Get user by id * Get user by id
* @param {string} userId * @param {string} userId
* @returns {Promise<oldUser|null>} returns null if not found * @returns {Promise<oldUser|null>} returns null if not found
*/ */
static async getUserById(userId) { static async getUserById(userId) {
@ -296,7 +298,7 @@ class User extends Model {
/** /**
* Get user by openid sub * Get user by openid sub
* @param {string} sub * @param {string} sub
* @returns {Promise<oldUser|null>} returns null if not found * @returns {Promise<oldUser|null>} returns null if not found
*/ */
static async getUserByOpenIDSub(sub) { static async getUserByOpenIDSub(sub) {
@ -317,7 +319,7 @@ class User extends Model {
const users = await this.findAll({ const users = await this.findAll({
attributes: ['id', 'username'] attributes: ['id', 'username']
}) })
return users.map(u => { return users.map((u) => {
return { return {
id: u.id, id: u.id,
username: u.username username: u.username
@ -340,37 +342,40 @@ class User extends Model {
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize
*/ */
static init(sequelize) { static init(sequelize) {
super.init({ super.init(
id: { {
type: DataTypes.UUID, id: {
defaultValue: DataTypes.UUIDV4, type: DataTypes.UUID,
primaryKey: true defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
username: DataTypes.STRING,
email: DataTypes.STRING,
pash: DataTypes.STRING,
type: DataTypes.STRING,
token: DataTypes.STRING,
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
isLocked: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
lastSeen: DataTypes.DATE,
permissions: DataTypes.JSON,
bookmarks: DataTypes.JSON,
extraData: DataTypes.JSON
}, },
username: DataTypes.STRING, {
email: DataTypes.STRING, sequelize,
pash: DataTypes.STRING, modelName: 'user'
type: DataTypes.STRING, }
token: DataTypes.STRING, )
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
isLocked: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
lastSeen: DataTypes.DATE,
permissions: DataTypes.JSON,
bookmarks: DataTypes.JSON,
extraData: DataTypes.JSON
}, {
sequelize,
modelName: 'user'
})
} }
} }
module.exports = User module.exports = User

View file

@ -16,6 +16,7 @@ class EmailSettings {
this.host = null this.host = null
this.port = 465 this.port = 465
this.secure = true this.secure = true
this.rejectUnauthorized = true
this.user = null this.user = null
this.pass = null this.pass = null
this.testAddress = null this.testAddress = null
@ -33,11 +34,17 @@ class EmailSettings {
this.host = settings.host this.host = settings.host
this.port = settings.port this.port = settings.port
this.secure = !!settings.secure this.secure = !!settings.secure
this.rejectUnauthorized = !!settings.rejectUnauthorized
this.user = settings.user this.user = settings.user
this.pass = settings.pass this.pass = settings.pass
this.testAddress = settings.testAddress this.testAddress = settings.testAddress
this.fromAddress = settings.fromAddress this.fromAddress = settings.fromAddress
this.ereaderDevices = settings.ereaderDevices?.map(d => ({ ...d })) || [] this.ereaderDevices = settings.ereaderDevices?.map((d) => ({ ...d })) || []
// rejectUnauthorized added after v2.10.1 - defaults to true
if (settings.rejectUnauthorized === undefined) {
this.rejectUnauthorized = true
}
} }
toJSON() { toJSON() {
@ -46,11 +53,12 @@ class EmailSettings {
host: this.host, host: this.host,
port: this.port, port: this.port,
secure: this.secure, secure: this.secure,
rejectUnauthorized: this.rejectUnauthorized,
user: this.user, user: this.user,
pass: this.pass, pass: this.pass,
testAddress: this.testAddress, testAddress: this.testAddress,
fromAddress: this.fromAddress, fromAddress: this.fromAddress,
ereaderDevices: this.ereaderDevices.map(d => ({ ...d })) ereaderDevices: this.ereaderDevices.map((d) => ({ ...d }))
} }
} }
@ -62,27 +70,30 @@ class EmailSettings {
else payload.port = Number(payload.port) else payload.port = Number(payload.port)
} }
if (payload.secure !== undefined) payload.secure = !!payload.secure if (payload.secure !== undefined) payload.secure = !!payload.secure
if (payload.rejectUnauthorized !== undefined) payload.rejectUnauthorized = !!payload.rejectUnauthorized
if (payload.ereaderDevices !== undefined && !Array.isArray(payload.ereaderDevices)) payload.ereaderDevices = undefined if (payload.ereaderDevices !== undefined && !Array.isArray(payload.ereaderDevices)) payload.ereaderDevices = undefined
if (payload.ereaderDevices?.length) { if (payload.ereaderDevices?.length) {
// Validate ereader devices // Validate ereader devices
payload.ereaderDevices = payload.ereaderDevices.map((device) => { payload.ereaderDevices = payload.ereaderDevices
if (!device.name || !device.email) { .map((device) => {
Logger.error(`[EmailSettings] Update ereader device is invalid`, device) if (!device.name || !device.email) {
return null Logger.error(`[EmailSettings] Update ereader device is invalid`, device)
} return null
if (!device.availabilityOption || !['adminOrUp', 'userOrUp', 'guestOrUp', 'specificUsers'].includes(device.availabilityOption)) { }
device.availabilityOption = 'adminOrUp' if (!device.availabilityOption || !['adminOrUp', 'userOrUp', 'guestOrUp', 'specificUsers'].includes(device.availabilityOption)) {
} device.availabilityOption = 'adminOrUp'
if (device.availabilityOption === 'specificUsers' && !device.users?.length) { }
device.availabilityOption = 'adminOrUp' if (device.availabilityOption === 'specificUsers' && !device.users?.length) {
} device.availabilityOption = 'adminOrUp'
if (device.availabilityOption !== 'specificUsers' && device.users?.length) { }
device.users = [] if (device.availabilityOption !== 'specificUsers' && device.users?.length) {
} device.users = []
return device }
}).filter(d => d) return device
})
.filter((d) => d)
} }
let hasUpdates = false let hasUpdates = false
@ -116,14 +127,20 @@ class EmailSettings {
pass: this.pass pass: this.pass
} }
} }
// Allow self-signed certs (https://nodemailer.com/smtp/#3-allow-self-signed-certificates)
if (!this.rejectUnauthorized) {
payload.tls = {
rejectUnauthorized: false
}
}
return payload return payload
} }
/** /**
* *
* @param {EreaderDeviceObject} device * @param {EreaderDeviceObject} device
* @param {import('../user/User')} user * @param {import('../user/User')} user
* @returns {boolean} * @returns {boolean}
*/ */
checkUserCanAccessDevice(device, user) { checkUserCanAccessDevice(device, user) {
@ -140,8 +157,8 @@ class EmailSettings {
/** /**
* Get ereader devices accessible to user * Get ereader devices accessible to user
* *
* @param {import('../user/User')} user * @param {import('../user/User')} user
* @returns {EreaderDeviceObject[]} * @returns {EreaderDeviceObject[]}
*/ */
getEReaderDevices(user) { getEReaderDevices(user) {
@ -150,12 +167,12 @@ class EmailSettings {
/** /**
* Get ereader device by name * Get ereader device by name
* *
* @param {string} deviceName * @param {string} deviceName
* @returns {EreaderDeviceObject} * @returns {EreaderDeviceObject}
*/ */
getEReaderDevice(deviceName) { getEReaderDevice(deviceName) {
return this.ereaderDevices.find(d => d.name === deviceName) return this.ereaderDevices.find((d) => d.name === deviceName)
} }
} }
module.exports = EmailSettings module.exports = EmailSettings

View file

@ -7,13 +7,12 @@ const rra = require('../libs/recursiveReaddirAsync')
const Logger = require('../Logger') const Logger = require('../Logger')
const { AudioMimeType } = require('./constants') const { AudioMimeType } = require('./constants')
/** /**
* Make sure folder separator is POSIX for Windows file paths. e.g. "C:\Users\Abs" becomes "C:/Users/Abs" * Make sure folder separator is POSIX for Windows file paths. e.g. "C:\Users\Abs" becomes "C:/Users/Abs"
* *
* @param {String} path - Ugly file path * @param {String} path - Ugly file path
* @return {String} Pretty posix file path * @return {String} Pretty posix file path
*/ */
const filePathToPOSIX = (path) => { const filePathToPOSIX = (path) => {
if (!global.isWin || !path) return path if (!global.isWin || !path) return path
return path.replace(/\\/g, '/') return path.replace(/\\/g, '/')
@ -22,9 +21,9 @@ module.exports.filePathToPOSIX = filePathToPOSIX
/** /**
* Check path is a child of or equal to another path * Check path is a child of or equal to another path
* *
* @param {string} parentPath * @param {string} parentPath
* @param {string} childPath * @param {string} childPath
* @returns {boolean} * @returns {boolean}
*/ */
function isSameOrSubPath(parentPath, childPath) { function isSameOrSubPath(parentPath, childPath) {
@ -33,8 +32,8 @@ function isSameOrSubPath(parentPath, childPath) {
if (parentPath === childPath) return true if (parentPath === childPath) return true
const relativePath = Path.relative(parentPath, childPath) const relativePath = Path.relative(parentPath, childPath)
return ( return (
relativePath === '' // Same path (e.g. parentPath = '/a/b/', childPath = '/a/b') relativePath === '' || // Same path (e.g. parentPath = '/a/b/', childPath = '/a/b')
|| !relativePath.startsWith('..') && !Path.isAbsolute(relativePath) // Sub path (!relativePath.startsWith('..') && !Path.isAbsolute(relativePath)) // Sub path
) )
} }
module.exports.isSameOrSubPath = isSameOrSubPath module.exports.isSameOrSubPath = isSameOrSubPath
@ -67,8 +66,8 @@ module.exports.getFileTimestampsWithIno = getFileTimestampsWithIno
/** /**
* Get file size * Get file size
* *
* @param {string} path * @param {string} path
* @returns {Promise<number>} * @returns {Promise<number>}
*/ */
module.exports.getFileSize = async (path) => { module.exports.getFileSize = async (path) => {
@ -77,8 +76,8 @@ module.exports.getFileSize = async (path) => {
/** /**
* Get file mtimeMs * Get file mtimeMs
* *
* @param {string} path * @param {string} path
* @returns {Promise<number>} epoch timestamp * @returns {Promise<number>} epoch timestamp
*/ */
module.exports.getFileMTimeMs = async (path) => { module.exports.getFileMTimeMs = async (path) => {
@ -91,8 +90,8 @@ module.exports.getFileMTimeMs = async (path) => {
} }
/** /**
* *
* @param {string} filepath * @param {string} filepath
* @returns {boolean} * @returns {boolean}
*/ */
async function checkPathIsFile(filepath) { async function checkPathIsFile(filepath) {
@ -106,16 +105,19 @@ async function checkPathIsFile(filepath) {
module.exports.checkPathIsFile = checkPathIsFile module.exports.checkPathIsFile = checkPathIsFile
function getIno(path) { function getIno(path) {
return fs.stat(path, { bigint: true }).then((data => String(data.ino))).catch((err) => { return fs
Logger.error('[Utils] Failed to get ino for path', path, err) .stat(path, { bigint: true })
return null .then((data) => String(data.ino))
}) .catch((err) => {
Logger.error('[Utils] Failed to get ino for path', path, err)
return null
})
} }
module.exports.getIno = getIno module.exports.getIno = getIno
/** /**
* Read contents of file * Read contents of file
* @param {string} path * @param {string} path
* @returns {string} * @returns {string}
*/ */
async function readTextFile(path) { async function readTextFile(path) {
@ -144,8 +146,8 @@ module.exports.bytesPretty = bytesPretty
/** /**
* Get array of files inside dir * Get array of files inside dir
* @param {string} path * @param {string} path
* @param {string} [relPathToReplace] * @param {string} [relPathToReplace]
* @returns {{name:string, path:string, dirpath:string, reldirpath:string, fullpath:string, extension:string, deep:number}[]} * @returns {{name:string, path:string, dirpath:string, reldirpath:string, fullpath:string, extension:string, deep:number}[]}
*/ */
async function recurseFiles(path, relPathToReplace = null) { async function recurseFiles(path, relPathToReplace = null) {
@ -177,55 +179,58 @@ async function recurseFiles(path, relPathToReplace = null) {
const directoriesToIgnore = [] const directoriesToIgnore = []
list = list.filter((item) => { list = list
if (item.error) { .filter((item) => {
Logger.error(`[fileUtils] Recurse files file "${item.fullname}" has error`, item.error) if (item.error) {
return false Logger.error(`[fileUtils] Recurse files file "${item.fullname}" has error`, item.error)
} return false
}
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 = ''
const dirname = Path.dirname(item.fullname) const dirname = Path.dirname(item.fullname)
// Directory has a file named ".ignore" flag directory and ignore // Directory has a file named ".ignore" flag directory and ignore
if (item.name === '.ignore' && reldirname && reldirname !== '.' && !directoriesToIgnore.includes(dirname)) { if (item.name === '.ignore' && reldirname && reldirname !== '.' && !directoriesToIgnore.includes(dirname)) {
Logger.debug(`[fileUtils] .ignore found - ignoring directory "${reldirname}"`) Logger.debug(`[fileUtils] .ignore found - ignoring directory "${reldirname}"`)
directoriesToIgnore.push(dirname) directoriesToIgnore.push(dirname)
return false return false
} }
if (item.extension === '.part') { if (item.extension === '.part') {
Logger.debug(`[fileUtils] Ignoring .part file "${relpath}"`) Logger.debug(`[fileUtils] Ignoring .part file "${relpath}"`)
return false return false
} }
// Ignore any file if a directory or the filename starts with "." // Ignore any file if a directory or the filename starts with "."
if (relpath.split('/').find(p => p.startsWith('.'))) { if (relpath.split('/').find((p) => p.startsWith('.'))) {
Logger.debug(`[fileUtils] Ignoring path has . "${relpath}"`) Logger.debug(`[fileUtils] Ignoring path has . "${relpath}"`)
return false return false
} }
return true return true
}).filter(item => { })
// Filter out items in ignore directories .filter((item) => {
if (directoriesToIgnore.some(dir => item.fullname.startsWith(dir))) { // Filter out items in ignore directories
Logger.debug(`[fileUtils] Ignoring path in dir with .ignore "${item.fullname}"`) if (directoriesToIgnore.some((dir) => item.fullname.startsWith(dir))) {
return false Logger.debug(`[fileUtils] Ignoring path in dir with .ignore "${item.fullname}"`)
} return false
return true }
}).map((item) => { return true
var isInRoot = (item.path + '/' === relPathToReplace) })
return { .map((item) => {
name: item.name, var isInRoot = item.path + '/' === relPathToReplace
path: item.fullname.replace(relPathToReplace, ''), return {
dirpath: item.path, name: item.name,
reldirpath: isInRoot ? '' : item.path.replace(relPathToReplace, ''), path: item.fullname.replace(relPathToReplace, ''),
fullpath: item.fullname, dirpath: item.path,
extension: item.extension, reldirpath: isInRoot ? '' : item.path.replace(relPathToReplace, ''),
deep: item.deep fullpath: item.fullname,
} extension: item.extension,
}) deep: item.deep
}
})
// Sort from least deep to most // Sort from least deep to most
list.sort((a, b) => a.deep - b.deep) list.sort((a, b) => a.deep - b.deep)
@ -237,8 +242,8 @@ module.exports.recurseFiles = recurseFiles
/** /**
* Download file from web to local file system * Download file from web to local file system
* Uses SSRF filter to prevent internal URLs * Uses SSRF filter to prevent internal URLs
* *
* @param {string} url * @param {string} url
* @param {string} filepath path to download the file to * @param {string} filepath path to download the file to
* @param {Function} [contentTypeFilter] validate content type before writing * @param {Function} [contentTypeFilter] validate content type before writing
* @returns {Promise} * @returns {Promise}
@ -251,33 +256,35 @@ module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => {
method: 'GET', method: 'GET',
responseType: 'stream', responseType: 'stream',
timeout: 30000, timeout: 30000,
httpAgent: ssrfFilter(url), httpAgent: global.DisableSsrfRequestFilter ? null : ssrfFilter(url),
httpsAgent: ssrfFilter(url) httpsAgent: global.DisableSsrfRequestFilter ? null : ssrfFilter(url)
}).then((response) => {
// Validate content type
if (contentTypeFilter && !contentTypeFilter?.(response.headers?.['content-type'])) {
return reject(new Error(`Invalid content type "${response.headers?.['content-type'] || ''}"`))
}
// Write to filepath
const writer = fs.createWriteStream(filepath)
response.data.pipe(writer)
writer.on('finish', resolve)
writer.on('error', reject)
}).catch((err) => {
Logger.error(`[fileUtils] Failed to download file "${filepath}"`, err)
reject(err)
}) })
.then((response) => {
// Validate content type
if (contentTypeFilter && !contentTypeFilter?.(response.headers?.['content-type'])) {
return reject(new Error(`Invalid content type "${response.headers?.['content-type'] || ''}"`))
}
// Write to filepath
const writer = fs.createWriteStream(filepath)
response.data.pipe(writer)
writer.on('finish', resolve)
writer.on('error', reject)
})
.catch((err) => {
Logger.error(`[fileUtils] Failed to download file "${filepath}"`, err)
reject(err)
})
}) })
} }
/** /**
* Download image file from web to local file system * Download image file from web to local file system
* Response header must have content-type of image/ (excluding svg) * Response header must have content-type of image/ (excluding svg)
* *
* @param {string} url * @param {string} url
* @param {string} filepath * @param {string} filepath
* @returns {Promise} * @returns {Promise}
*/ */
module.exports.downloadImageFile = (url, filepath) => { module.exports.downloadImageFile = (url, filepath) => {
@ -350,14 +357,17 @@ module.exports.getAudioMimeTypeFromExtname = (extname) => {
module.exports.removeFile = (path) => { module.exports.removeFile = (path) => {
if (!path) return false if (!path) return false
return fs.remove(path).then(() => true).catch((error) => { return fs
Logger.error(`[fileUtils] Failed remove file "${path}"`, error) .remove(path)
return false .then(() => true)
}) .catch((error) => {
Logger.error(`[fileUtils] Failed remove file "${path}"`, error)
return false
})
} }
module.exports.encodeUriPath = (path) => { module.exports.encodeUriPath = (path) => {
const uri = new URL('/', "file://") const uri = new URL('/', 'file://')
// we assign the path here to assure that URL control characters like # are // we assign the path here to assure that URL control characters like # are
// actually interpreted as part of the URL path // actually interpreted as part of the URL path
uri.pathname = path uri.pathname = path
@ -367,8 +377,8 @@ module.exports.encodeUriPath = (path) => {
/** /**
* Check if directory is writable. * Check if directory is writable.
* This method is necessary because fs.access(directory, fs.constants.W_OK) does not work on Windows * This method is necessary because fs.access(directory, fs.constants.W_OK) does not work on Windows
* *
* @param {string} directory * @param {string} directory
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
module.exports.isWritable = async (directory) => { module.exports.isWritable = async (directory) => {
@ -385,7 +395,7 @@ module.exports.isWritable = async (directory) => {
/** /**
* Get Windows drives as array e.g. ["C:/", "F:/"] * Get Windows drives as array e.g. ["C:/", "F:/"]
* *
* @returns {Promise<string[]>} * @returns {Promise<string[]>}
*/ */
module.exports.getWindowsDrives = async () => { module.exports.getWindowsDrives = async () => {
@ -398,7 +408,11 @@ module.exports.getWindowsDrives = async () => {
reject(error) reject(error)
return return
} }
let drives = stdout?.split(/\r?\n/).map(line => line.trim()).filter(line => line).slice(1) let drives = stdout
?.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line)
.slice(1)
const validDrives = [] const validDrives = []
for (const drive of drives) { for (const drive of drives) {
let drivepath = drive + '/' let drivepath = drive + '/'
@ -415,33 +429,35 @@ module.exports.getWindowsDrives = async () => {
/** /**
* Get array of directory paths in a directory * Get array of directory paths in a directory
* *
* @param {string} dirPath * @param {string} dirPath
* @param {number} level * @param {number} level
* @returns {Promise<{ path:string, dirname:string, level:number }[]>} * @returns {Promise<{ path:string, dirname:string, level:number }[]>}
*/ */
module.exports.getDirectoriesInPath = async (dirPath, level) => { module.exports.getDirectoriesInPath = async (dirPath, level) => {
try { try {
const paths = await fs.readdir(dirPath) const paths = await fs.readdir(dirPath)
let dirs = await Promise.all(paths.map(async dirname => { let dirs = await Promise.all(
const fullPath = Path.join(dirPath, dirname) paths.map(async (dirname) => {
const fullPath = Path.join(dirPath, dirname)
const lstat = await fs.lstat(fullPath).catch((error) => { const lstat = await fs.lstat(fullPath).catch((error) => {
Logger.debug(`Failed to lstat "${fullPath}"`, error) Logger.debug(`Failed to lstat "${fullPath}"`, error)
return null return null
})
if (!lstat?.isDirectory()) return null
return {
path: this.filePathToPOSIX(fullPath),
dirname,
level
}
}) })
if (!lstat?.isDirectory()) return null )
dirs = dirs.filter((d) => d)
return {
path: this.filePathToPOSIX(fullPath),
dirname,
level
}
}))
dirs = dirs.filter(d => d)
return dirs return dirs
} catch (error) { } catch (error) {
Logger.error('Failed to readdir', dirPath, error) Logger.error('Failed to readdir', dirPath, error)
return [] return []
} }
} }

View file

@ -220,8 +220,8 @@ module.exports.parsePodcastRssFeedXml = async (xml, excludeEpisodeMetadata = fal
/** /**
* Get podcast RSS feed as JSON * Get podcast RSS feed as JSON
* Uses SSRF filter to prevent internal URLs * Uses SSRF filter to prevent internal URLs
* *
* @param {string} feedUrl * @param {string} feedUrl
* @param {boolean} [excludeEpisodeMetadata=false] * @param {boolean} [excludeEpisodeMetadata=false]
* @returns {Promise} * @returns {Promise}
*/ */
@ -234,37 +234,38 @@ module.exports.getPodcastFeed = (feedUrl, excludeEpisodeMetadata = false) => {
timeout: 12000, timeout: 12000,
responseType: 'arraybuffer', responseType: 'arraybuffer',
headers: { Accept: 'application/rss+xml, application/xhtml+xml, application/xml, */*;q=0.8' }, headers: { Accept: 'application/rss+xml, application/xhtml+xml, application/xml, */*;q=0.8' },
httpAgent: ssrfFilter(feedUrl), httpAgent: global.DisableSsrfRequestFilter ? null : ssrfFilter(feedUrl),
httpsAgent: ssrfFilter(feedUrl) httpsAgent: global.DisableSsrfRequestFilter ? null : ssrfFilter(feedUrl)
}).then(async (data) => {
// Adding support for ios-8859-1 encoded RSS feeds.
// See: https://github.com/advplyr/audiobookshelf/issues/1489
const contentType = data.headers?.['content-type'] || '' // e.g. text/xml; charset=iso-8859-1
if (contentType.toLowerCase().includes('iso-8859-1')) {
data.data = data.data.toString('latin1')
} else {
data.data = data.data.toString()
}
if (!data?.data) {
Logger.error(`[podcastUtils] getPodcastFeed: Invalid podcast feed request response (${feedUrl})`)
return null
}
Logger.debug(`[podcastUtils] getPodcastFeed for "${feedUrl}" success - parsing xml`)
const payload = await this.parsePodcastRssFeedXml(data.data, excludeEpisodeMetadata)
if (!payload) {
return null
}
// RSS feed may be a private RSS feed
payload.podcast.metadata.feedUrl = feedUrl
return payload.podcast
}).catch((error) => {
Logger.error('[podcastUtils] getPodcastFeed Error', error)
return null
}) })
.then(async (data) => {
// Adding support for ios-8859-1 encoded RSS feeds.
// See: https://github.com/advplyr/audiobookshelf/issues/1489
const contentType = data.headers?.['content-type'] || '' // e.g. text/xml; charset=iso-8859-1
if (contentType.toLowerCase().includes('iso-8859-1')) {
data.data = data.data.toString('latin1')
} else {
data.data = data.data.toString()
}
if (!data?.data) {
Logger.error(`[podcastUtils] getPodcastFeed: Invalid podcast feed request response (${feedUrl})`)
return null
}
Logger.debug(`[podcastUtils] getPodcastFeed for "${feedUrl}" success - parsing xml`)
const payload = await this.parsePodcastRssFeedXml(data.data, excludeEpisodeMetadata)
if (!payload) {
return null
}
// RSS feed may be a private RSS feed
payload.podcast.metadata.feedUrl = feedUrl
return payload.podcast
})
.catch((error) => {
Logger.error('[podcastUtils] getPodcastFeed Error', error)
return null
})
} }
// Return array of episodes ordered by closest match (Levenshtein distance of 6 or less) // Return array of episodes ordered by closest match (Levenshtein distance of 6 or less)
@ -283,7 +284,7 @@ module.exports.findMatchingEpisodesInFeed = (feed, searchTitle) => {
} }
const matches = [] const matches = []
feed.episodes.forEach(ep => { feed.episodes.forEach((ep) => {
if (!ep.title) return if (!ep.title) return
const epTitle = ep.title.toLowerCase().trim() const epTitle = ep.title.toLowerCase().trim()