mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-12-31 11:38:47 -05:00
Compare commits
72 Commits
fix_set_to
...
v2.27.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3337b3af18 | ||
|
|
835d2c7f36 | ||
|
|
03f91099e0 | ||
|
|
c04afd0787 | ||
|
|
b03bd79f5d | ||
|
|
79b4042e8e | ||
|
|
8f718ef91c | ||
|
|
4053b20623 | ||
|
|
c4d654635f | ||
|
|
ef5d0ffa48 | ||
|
|
6a826cdb36 | ||
|
|
1d837f5f21 | ||
|
|
80873b379c | ||
|
|
82a8f8f126 | ||
|
|
4725a466da | ||
|
|
031edc870c | ||
|
|
625e2445b5 | ||
|
|
1640af2f1c | ||
|
|
c76f76cc27 | ||
|
|
74af212293 | ||
|
|
e04efb9c6a | ||
|
|
ee17e7a555 | ||
|
|
694a852c07 | ||
|
|
18068bb261 | ||
|
|
71257f6c6c | ||
|
|
4d70929d2e | ||
|
|
578e9559e4 | ||
|
|
894ea0b80a | ||
|
|
e54571f011 | ||
|
|
32da0f1224 | ||
|
|
2054accdc9 | ||
|
|
7d8b857c77 | ||
|
|
0107cb4782 | ||
|
|
f273eee807 | ||
|
|
4af21b079a | ||
|
|
c9eaf2db2d | ||
|
|
a5fb0d9cdb | ||
|
|
53c80d9798 | ||
|
|
832165716b | ||
|
|
d9f2d8bf1d | ||
|
|
a7a3a56509 | ||
|
|
4082fadf90 | ||
|
|
93160b83bf | ||
|
|
472240f994 | ||
|
|
c3f0fb8e5e | ||
|
|
b156ebeb9f | ||
|
|
e4c775c847 | ||
|
|
45e8e72759 | ||
|
|
0ae7340889 | ||
|
|
8c38987d92 | ||
|
|
878f0787ba | ||
|
|
880d85eaef | ||
|
|
f7aaebc1de | ||
|
|
d96ebbe82d | ||
|
|
70d67156f0 | ||
|
|
f293b317be | ||
|
|
1f23794f88 | ||
|
|
e6bfd118f6 | ||
|
|
1166400ab1 | ||
|
|
55f0ac871b | ||
|
|
3584f6e24f | ||
|
|
23bf2594c8 | ||
|
|
8fb460ce05 | ||
|
|
8c4bbfd6a2 | ||
|
|
742961e0b8 | ||
|
|
e9a705587a | ||
|
|
bf6d81b333 | ||
|
|
9c44fc0d01 | ||
|
|
5017e7ce9e | ||
|
|
de25763a74 | ||
|
|
a894ceb9cf | ||
|
|
387e58a714 |
@@ -116,7 +116,7 @@
|
||||
</div>
|
||||
|
||||
<div class="w-full h-12 px-1 py-2 border-t border-black/20 bg-bg absolute left-0" :style="{ bottom: streamLibraryItem ? '224px' : '65px' }">
|
||||
<p class="underline font-mono text-xs text-center text-gray-300 leading-3 mb-1" @click="clickChangelog">v{{ $config.version }}</p>
|
||||
<p class="underline font-mono text-xs text-center text-gray-300 leading-3 mb-1 cursor-pointer" @click="clickChangelog">v{{ $config.version }}</p>
|
||||
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xxs text-center block leading-3">Update</a>
|
||||
<p v-else class="text-xxs text-gray-400 leading-3 text-center italic">{{ Source }}</p>
|
||||
</div>
|
||||
|
||||
@@ -13,9 +13,17 @@
|
||||
<div class="grow" />
|
||||
<p class="text-sm md:text-base">{{ book.publishedYear }}</p>
|
||||
</div>
|
||||
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">{{ $getString('LabelByAuthor', [book.author]) }}</p>
|
||||
<p v-if="book.narrator" class="text-gray-400 text-xs">{{ $strings.LabelNarrators }}: {{ book.narrator }}</p>
|
||||
<p v-if="book.duration" class="text-gray-400 text-xs">{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">{{ $getString('LabelByAuthor', [book.author]) }}</p>
|
||||
<p v-if="book.narrator" class="text-gray-400 text-xs">{{ $strings.LabelNarrators }}: {{ book.narrator }}</p>
|
||||
<p v-if="book.duration" class="text-gray-400 text-xs">{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
|
||||
</div>
|
||||
<div class="grow" />
|
||||
<div v-if="book.matchConfidence" class="rounded-full px-2 py-1 text-xs whitespace-nowrap text-white" :class="book.matchConfidence > 0.95 ? 'bg-success/80' : 'bg-info/80'">{{ $strings.LabelMatchConfidence }}: {{ (book.matchConfidence * 100).toFixed(0) }}%</div>
|
||||
</div>
|
||||
|
||||
<div v-if="book.series?.length" class="flex py-1 -mx-1">
|
||||
<div v-for="(series, index) in book.series" :key="index" class="bg-white/10 rounded-full px-1 py-0.5 mx-1">
|
||||
<p class="leading-3 text-xs text-gray-400">
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
<!-- Podcast Episode # -->
|
||||
<div cy-id="podcastEpisodeNumber" v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black/90 box-shadow-md z-10" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }">
|
||||
<p :style="{ fontSize: 0.8 + 'em' }">
|
||||
Episode<span v-if="recentEpisodeNumber"> #{{ recentEpisodeNumber }}</span>
|
||||
Episode
|
||||
<span v-if="recentEpisodeNumber">#{{ recentEpisodeNumber }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -200,6 +201,9 @@ export default {
|
||||
dateFormat() {
|
||||
return this.store.getters['getServerSetting']('dateFormat')
|
||||
},
|
||||
timeFormat() {
|
||||
return this.store.getters['getServerSetting']('timeFormat')
|
||||
},
|
||||
_libraryItem() {
|
||||
return this.libraryItem || {}
|
||||
},
|
||||
@@ -345,6 +349,10 @@ export default {
|
||||
if (this.mediaMetadata.publishedYear) return this.$getString('LabelPublishedDate', [this.mediaMetadata.publishedYear])
|
||||
return '\u00A0'
|
||||
}
|
||||
if (this.orderBy === 'progress') {
|
||||
if (!this.userProgressLastUpdated) return '\u00A0'
|
||||
return this.$getString('LabelLastProgressDate', [this.$formatDatetime(this.userProgressLastUpdated, this.dateFormat, this.timeFormat)])
|
||||
}
|
||||
return null
|
||||
},
|
||||
episodeProgress() {
|
||||
@@ -377,6 +385,10 @@ export default {
|
||||
let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0
|
||||
return Math.max(Math.min(1, progressPercent), 0)
|
||||
},
|
||||
userProgressLastUpdated() {
|
||||
if (!this.userProgress) return null
|
||||
return this.userProgress.lastUpdate
|
||||
},
|
||||
itemIsFinished() {
|
||||
if (this.booksInSeries) return this.seriesIsFinished
|
||||
return this.userProgress ? !!this.userProgress.isFinished : false
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 ring-1 ring-black/5 overflow-auto focus:outline-hidden text-sm" role="menu">
|
||||
<ul v-show="showMenu" class="librarySortMenu absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 ring-1 ring-black/5 overflow-auto focus:outline-hidden text-sm" role="menu">
|
||||
<template v-for="item in selectItems">
|
||||
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="menuitem" @click="clickedOption(item.value)">
|
||||
<div class="flex items-center">
|
||||
@@ -130,6 +130,10 @@ export default {
|
||||
text: this.$strings.LabelFileModified,
|
||||
value: 'mtimeMs'
|
||||
},
|
||||
{
|
||||
text: this.$strings.LabelLibrarySortByProgress,
|
||||
value: 'progress'
|
||||
},
|
||||
{
|
||||
text: this.$strings.LabelRandomly,
|
||||
value: 'random'
|
||||
@@ -191,3 +195,9 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.librarySortMenu {
|
||||
max-height: calc(100vh - 125px);
|
||||
}
|
||||
</style>
|
||||
@@ -99,22 +99,32 @@ export default {
|
||||
return `/api/items/${this.libraryItemId}/ebook`
|
||||
},
|
||||
themeRules() {
|
||||
const isDark = this.ereaderSettings.theme === 'dark'
|
||||
const fontColor = isDark ? '#fff' : '#000'
|
||||
const backgroundColor = isDark ? 'rgb(35 35 35)' : 'rgb(255, 255, 255)'
|
||||
const theme = this.ereaderSettings.theme
|
||||
const isDark = theme === 'dark'
|
||||
const isSepia = theme === 'sepia'
|
||||
|
||||
const fontColor = isDark
|
||||
? '#fff'
|
||||
: isSepia
|
||||
? '#5b4636'
|
||||
: '#000'
|
||||
|
||||
const backgroundColor = isDark
|
||||
? 'rgb(35 35 35)'
|
||||
: isSepia
|
||||
? 'rgb(244, 236, 216)'
|
||||
: 'rgb(255, 255, 255)'
|
||||
|
||||
const lineSpacing = this.ereaderSettings.lineSpacing / 100
|
||||
|
||||
const fontScale = this.ereaderSettings.fontScale / 100
|
||||
|
||||
const textStroke = this.ereaderSettings.textStroke / 100
|
||||
const fontScale = this.ereaderSettings.fontScale / 100
|
||||
const textStroke = this.ereaderSettings.textStroke / 100
|
||||
|
||||
return {
|
||||
'*': {
|
||||
color: `${fontColor}!important`,
|
||||
'background-color': `${backgroundColor}!important`,
|
||||
'line-height': lineSpacing * fontScale + 'rem!important',
|
||||
'-webkit-text-stroke': textStroke + 'px ' + fontColor + '!important'
|
||||
'line-height': `${lineSpacing * fontScale}rem!important`,
|
||||
'-webkit-text-stroke': `${textStroke}px ${fontColor}!important`
|
||||
},
|
||||
a: {
|
||||
color: `${fontColor}!important`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="show" id="reader" :data-theme="ereaderTheme" class="group absolute top-0 left-0 w-full z-60 data-[theme=dark]:bg-primary data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black" :class="{ 'reader-player-open': !!streamLibraryItem }">
|
||||
<div v-if="show" id="reader" :data-theme="ereaderTheme" class="group absolute top-0 left-0 w-full z-60 data-[theme=dark]:bg-primary data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black data-[theme=sepia]:bg-[rgb(244,236,216)] data-[theme=sepia]:text-[#5b4636]" :class="{ 'reader-player-open': !!streamLibraryItem }">
|
||||
<div class="absolute top-4 left-4 z-20 flex items-center">
|
||||
<button v-if="isEpub" @click="toggleToC" type="button" aria-label="Table of contents menu" class="inline-flex opacity-80 hover:opacity-100">
|
||||
<span class="material-symbols text-2xl">menu</span>
|
||||
@@ -27,7 +27,12 @@
|
||||
|
||||
<!-- TOC side nav -->
|
||||
<div v-if="tocOpen" class="w-full h-full overflow-y-scroll absolute inset-0 bg-black/20 z-20" @click.stop.prevent="toggleToC"></div>
|
||||
<div v-if="isEpub" class="w-96 h-full max-h-full absolute top-0 left-0 shadow-xl transition-transform z-30 group-data-[theme=dark]:bg-primary group-data-[theme=dark]:text-white group-data-[theme=light]:bg-white group-data-[theme=light]:text-black" :class="tocOpen ? 'translate-x-0' : '-translate-x-96'" @click.stop.prevent>
|
||||
<div
|
||||
v-if="isEpub"
|
||||
class="w-96 h-full max-h-full absolute top-0 left-0 shadow-xl transition-transform z-30 group-data-[theme=dark]:bg-primary group-data-[theme=dark]:text-white group-data-[theme=light]:bg-white group-data-[theme=light]:text-black group-data-[theme=sepia]:bg-[rgb(244,236,216)] group-data-[theme=sepia]:text-[#5b4636]"
|
||||
:class="tocOpen ? 'translate-x-0' : '-translate-x-96'"
|
||||
@click.stop.prevent
|
||||
>
|
||||
<div class="flex flex-col p-4 h-full">
|
||||
<div class="flex items-center mb-2">
|
||||
<button @click.stop.prevent="toggleToC" type="button" aria-label="Close table of contents" class="inline-flex opacity-80 hover:opacity-100">
|
||||
@@ -37,7 +42,7 @@
|
||||
<p class="text-lg font-semibold ml-2">{{ $strings.HeaderTableOfContents }}</p>
|
||||
</div>
|
||||
<form @submit.prevent="searchBook" @click.stop.prevent>
|
||||
<ui-text-input clearable ref="input" @clear="searchBook" v-model="searchQuery" :placeholder="$strings.PlaceholderSearch" class="h-8 w-full text-sm flex mb-2" />
|
||||
<ui-text-input clearable ref="input" @clear="searchBook" v-model="searchQuery" :placeholder="$strings.PlaceholderSearch" custom-input-class="text-inherit !bg-inherit" class="h-8 w-full text-sm flex mb-2" />
|
||||
</form>
|
||||
|
||||
<div class="overflow-y-auto">
|
||||
@@ -181,6 +186,10 @@ export default {
|
||||
text: this.$strings.LabelThemeDark,
|
||||
value: 'dark'
|
||||
},
|
||||
{
|
||||
text: this.$strings.LabelThemeSepia,
|
||||
value: 'sepia'
|
||||
},
|
||||
{
|
||||
text: this.$strings.LabelThemeLight,
|
||||
value: 'light'
|
||||
|
||||
@@ -152,7 +152,7 @@ export default {
|
||||
|
||||
this.showingTooltipIndex = index
|
||||
this.tooltipEl.style.display = 'block'
|
||||
this.tooltipTextEl.innerHTML = block.value ? `<strong>${this.$elapsedPretty(block.value, true)} listening</strong> on ${block.datePretty}` : `No listening sessions on ${block.datePretty}`
|
||||
this.tooltipTextEl.innerHTML = block.value ? this.$getString('MessageHeatmapListeningTimeTooltip', [this.$elapsedPrettyLocalized(block.value, true), block.datePretty]) : this.$getString('MessageHeatmapNoListeningSessions', [block.datePretty])
|
||||
|
||||
const calculateRect = this.tooltipEl.getBoundingClientRect()
|
||||
|
||||
|
||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.26.1",
|
||||
"version": "2.27.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.26.0",
|
||||
"version": "2.27.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nuxtjs/axios": "^5.13.6",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.26.1",
|
||||
"version": "2.27.0",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast client",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -572,7 +572,7 @@ export default {
|
||||
if (data.error) {
|
||||
this.asinError = this.$getString(data.stringKey)
|
||||
} else {
|
||||
console.log('Chapter data', data)
|
||||
console.log('Chapter data', { ...data })
|
||||
this.chapterData = this.removeBranding ? this.removeBrandingFromData(data) : data
|
||||
}
|
||||
})
|
||||
@@ -609,6 +609,11 @@ export default {
|
||||
data.chapters.pop()
|
||||
}
|
||||
|
||||
// Remove Branding durations from Runtime totals
|
||||
data.runtimeLengthMs -= introDuration + outroDuration
|
||||
data.runtimeLengthSec = Math.floor(data.runtimeLengthMs / 1000)
|
||||
console.log('Brandless Chapter data', data)
|
||||
|
||||
return data
|
||||
} catch {
|
||||
return data
|
||||
|
||||
@@ -6,80 +6,82 @@
|
||||
</div>
|
||||
|
||||
<div v-if="listeningSessions.length" class="block max-w-full relative">
|
||||
<table class="userSessionsTable">
|
||||
<tr class="bg-primary/40">
|
||||
<th class="w-6 min-w-6 text-left hidden md:table-cell h-11">
|
||||
<ui-checkbox v-model="isAllSelected" :partial="numSelected > 0 && !isAllSelected" small checkbox-bg="bg" />
|
||||
</th>
|
||||
<th v-if="numSelected" class="grow text-left" :colspan="7">
|
||||
<div class="flex items-center">
|
||||
<p>{{ $getString('MessageSelected', [numSelected]) }}</p>
|
||||
<div class="grow" />
|
||||
<ui-btn small color="bg-error" :loading="deletingSessions" @click.stop="removeSessionsClick">{{ $strings.ButtonRemove }}</ui-btn>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="grow sm:grow-0 sm:w-48 sm:max-w-48 text-left group cursor-pointer" @click.stop="sortColumn('displayTitle')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelItem }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('displayTitle') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="w-20 min-w-20 text-left hidden md:table-cell">{{ $strings.LabelUser }}</th>
|
||||
<th v-if="!numSelected" class="w-26 min-w-26 text-left hidden md:table-cell group cursor-pointer" @click.stop="sortColumn('playMethod')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelPlayMethod }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('playMethod') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
|
||||
<th v-if="!numSelected" class="w-24 min-w-24 sm:w-32 sm:min-w-32 group cursor-pointer" @click.stop="sortColumn('timeListening')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelTimeListened }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('timeListening') }" class="material-symbols text-base pl-px hidden sm:inline-block">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="w-24 min-w-24 group cursor-pointer" @click.stop="sortColumn('currentTime')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelLastTime }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('currentTime') }" class="material-symbols text-base pl-px hidden sm:inline-block">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="grow hidden sm:table-cell cursor-pointer group" @click.stop="sortColumn('updatedAt')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelLastUpdate }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('updatedAt') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="userSessionsTable">
|
||||
<tr class="bg-primary/40">
|
||||
<th class="w-6 min-w-6 text-left hidden md:table-cell h-11">
|
||||
<ui-checkbox v-model="isAllSelected" :partial="numSelected > 0 && !isAllSelected" small checkbox-bg="bg" />
|
||||
</th>
|
||||
<th v-if="numSelected" class="grow text-left" :colspan="7">
|
||||
<div class="flex items-center">
|
||||
<p>{{ $getString('MessageSelected', [numSelected]) }}</p>
|
||||
<div class="grow" />
|
||||
<ui-btn small color="bg-error" :loading="deletingSessions" @click.stop="removeSessionsClick">{{ $strings.ButtonRemove }}</ui-btn>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="grow sm:grow-0 sm:w-48 sm:max-w-48 text-left group cursor-pointer" @click.stop="sortColumn('displayTitle')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelItem }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('displayTitle') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="w-20 min-w-20 text-left hidden md:table-cell">{{ $strings.LabelUser }}</th>
|
||||
<th v-if="!numSelected" class="w-26 min-w-26 text-left hidden md:table-cell group cursor-pointer" @click.stop="sortColumn('playMethod')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelPlayMethod }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('playMethod') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
|
||||
<th v-if="!numSelected" class="w-24 min-w-24 sm:w-32 sm:min-w-32 group cursor-pointer" @click.stop="sortColumn('timeListening')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelTimeListened }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('timeListening') }" class="material-symbols text-base pl-px hidden sm:inline-block">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="w-24 min-w-24 group cursor-pointer" @click.stop="sortColumn('currentTime')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelLastTime }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('currentTime') }" class="material-symbols text-base pl-px hidden sm:inline-block">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="!numSelected" class="grow hidden sm:table-cell cursor-pointer group" @click.stop="sortColumn('updatedAt')">
|
||||
<div class="inline-flex items-center">
|
||||
{{ $strings.LabelLastUpdate }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('updatedAt') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="session in listeningSessions" :key="session.id" :class="{ selected: session.selected }" class="cursor-pointer" @click="clickSessionRow(session)">
|
||||
<td class="hidden md:table-cell py-1 max-w-6 relative">
|
||||
<ui-checkbox v-model="session.selected" small checkbox-bg="bg" />
|
||||
<!-- overlay of the checkbox so that the entire box is clickable -->
|
||||
<div class="absolute inset-0 w-full h-full" @click.stop="session.selected = !session.selected" />
|
||||
</td>
|
||||
<td class="py-1 grow sm:grow-0 sm:w-48 sm:max-w-48">
|
||||
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
|
||||
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
|
||||
</td>
|
||||
<td class="hidden md:table-cell w-20 min-w-20">
|
||||
<p v-if="filteredUserUsername" class="text-xs">{{ filteredUserUsername }}</p>
|
||||
<p v-else class="text-xs">{{ session.user ? session.user.username : 'N/A' }}</p>
|
||||
</td>
|
||||
<td class="hidden md:table-cell w-26 min-w-26">
|
||||
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
|
||||
</td>
|
||||
<td class="hidden sm:table-cell max-w-32 min-w-32">
|
||||
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
|
||||
</td>
|
||||
<td class="text-center w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<p class="text-xs font-mono">{{ $elapsedPrettyLocalized(session.timeListening) }}</p>
|
||||
</td>
|
||||
<td class="text-center hover:underline w-24 min-w-24" @click.stop="clickCurrentTime(session)">
|
||||
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
||||
</td>
|
||||
<td class="text-center hidden sm:table-cell">
|
||||
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
|
||||
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
|
||||
</ui-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<tr v-for="session in listeningSessions" :key="session.id" :class="{ selected: session.selected }" class="cursor-pointer" @click="clickSessionRow(session)">
|
||||
<td class="hidden md:table-cell py-1 max-w-6 relative">
|
||||
<ui-checkbox v-model="session.selected" small checkbox-bg="bg" />
|
||||
<!-- overlay of the checkbox so that the entire box is clickable -->
|
||||
<div class="absolute inset-0 w-full h-full" @click.stop="session.selected = !session.selected" />
|
||||
</td>
|
||||
<td class="py-1 grow sm:grow-0 sm:w-48 sm:max-w-48">
|
||||
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
|
||||
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
|
||||
</td>
|
||||
<td class="hidden md:table-cell w-20 min-w-20">
|
||||
<p v-if="filteredUserUsername" class="text-xs">{{ filteredUserUsername }}</p>
|
||||
<p v-else class="text-xs">{{ session.user ? session.user.username : 'N/A' }}</p>
|
||||
</td>
|
||||
<td class="hidden md:table-cell w-26 min-w-26">
|
||||
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
|
||||
</td>
|
||||
<td class="hidden sm:table-cell max-w-32 min-w-32">
|
||||
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
|
||||
</td>
|
||||
<td class="text-center w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||
<p class="text-xs font-mono">{{ $elapsedPrettyLocalized(session.timeListening) }}</p>
|
||||
</td>
|
||||
<td class="text-center hover:underline w-24 min-w-24" @click.stop="clickCurrentTime(session)">
|
||||
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
||||
</td>
|
||||
<td class="text-center hidden sm:table-cell">
|
||||
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
|
||||
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
|
||||
</ui-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- table bottom options -->
|
||||
<div class="flex items-center my-2">
|
||||
<div class="grow" />
|
||||
|
||||
@@ -19,39 +19,41 @@
|
||||
<div class="py-2">
|
||||
<h1 class="text-lg mb-2 text-white/90 px-2 sm:px-0">{{ $strings.HeaderListeningSessions }}</h1>
|
||||
<div v-if="listeningSessions.length">
|
||||
<table class="userSessionsTable">
|
||||
<tr class="bg-primary/40">
|
||||
<th class="w-48 min-w-48 text-left">{{ $strings.LabelItem }}</th>
|
||||
<th class="w-32 min-w-32 text-left hidden md:table-cell">{{ $strings.LabelPlayMethod }}</th>
|
||||
<th class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
|
||||
<th class="w-32 min-w-32">{{ $strings.LabelTimeListened }}</th>
|
||||
<th class="w-16 min-w-16">{{ $strings.LabelLastTime }}</th>
|
||||
<th class="grow hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th>
|
||||
</tr>
|
||||
<tr v-for="session in listeningSessions" :key="session.id" class="cursor-pointer" @click="showSession(session)">
|
||||
<td class="py-1 max-w-48">
|
||||
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
|
||||
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
|
||||
</td>
|
||||
<td class="hidden md:table-cell">
|
||||
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
|
||||
</td>
|
||||
<td class="hidden sm:table-cell min-w-32 max-w-32">
|
||||
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<p class="text-xs font-mono">{{ $elapsedPrettyLocalized(session.timeListening) }}</p>
|
||||
</td>
|
||||
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
|
||||
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
||||
</td>
|
||||
<td class="text-center hidden sm:table-cell">
|
||||
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
|
||||
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
|
||||
</ui-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="userSessionsTable">
|
||||
<tr class="bg-primary/40">
|
||||
<th class="w-48 min-w-48 text-left">{{ $strings.LabelItem }}</th>
|
||||
<th class="w-32 min-w-32 text-left hidden md:table-cell">{{ $strings.LabelPlayMethod }}</th>
|
||||
<th class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
|
||||
<th class="w-32 min-w-32">{{ $strings.LabelTimeListened }}</th>
|
||||
<th class="w-16 min-w-16">{{ $strings.LabelLastTime }}</th>
|
||||
<th class="grow hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th>
|
||||
</tr>
|
||||
<tr v-for="session in listeningSessions" :key="session.id" class="cursor-pointer" @click="showSession(session)">
|
||||
<td class="py-1 max-w-48">
|
||||
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
|
||||
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
|
||||
</td>
|
||||
<td class="hidden md:table-cell">
|
||||
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
|
||||
</td>
|
||||
<td class="hidden sm:table-cell min-w-32 max-w-32">
|
||||
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<p class="text-xs font-mono">{{ $elapsedPrettyLocalized(session.timeListening) }}</p>
|
||||
</td>
|
||||
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
|
||||
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
||||
</td>
|
||||
<td class="text-center hidden sm:table-cell">
|
||||
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
|
||||
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
|
||||
</ui-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="flex items-center justify-end py-1">
|
||||
<ui-icon-btn icon="arrow_back_ios_new" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
|
||||
<p class="text-sm mx-1">{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}</p>
|
||||
|
||||
@@ -92,7 +92,7 @@ export const actions = {
|
||||
if (state.settings.orderBy == 'media.duration') {
|
||||
settingsUpdate.orderBy = 'media.numTracks'
|
||||
}
|
||||
if (state.settings.orderBy == 'media.metadata.publishedYear') {
|
||||
if (state.settings.orderBy == 'media.metadata.publishedYear' || state.settings.orderBy == 'progress') {
|
||||
settingsUpdate.orderBy = 'media.metadata.title'
|
||||
}
|
||||
const invalidFilters = ['series', 'authors', 'narrators', 'publishers', 'publishedDecades', 'languages', 'progress', 'issues', 'ebooks', 'abridged']
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "Дадаць",
|
||||
"ButtonAddApiKey": "Дадаць API-ключ",
|
||||
"ButtonAddChapters": "Дадаць раздзелы",
|
||||
"ButtonAddDevice": "Дадаць прыладу",
|
||||
"ButtonAddLibrary": "Дадаць бібліятэку",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "Выбраць тэчку",
|
||||
"ButtonChooseFiles": "Выбраць файлы",
|
||||
"ButtonClearFilter": "Ачысціць фільтр",
|
||||
"ButtonClose": "Закрыць",
|
||||
"ButtonCloseFeed": "Закрыць стужку",
|
||||
"ButtonCloseSession": "Закрыць адкрыты сеанс",
|
||||
"ButtonCollections": "Калекцыі",
|
||||
@@ -69,7 +71,7 @@
|
||||
"ButtonQueueAddItem": "Дадаць у чаргу",
|
||||
"ButtonQueueRemoveItem": "Выдаліць з чаргі",
|
||||
"ButtonQuickEmbed": "Хуткае ўбудаванне",
|
||||
"ButtonQuickEmbedMetadata": "Хуткае ўбудаванне метаданых",
|
||||
"ButtonQuickEmbedMetadata": "Хуткае ўбудаванне метададзеных",
|
||||
"ButtonQuickMatch": "Хуткі пошук",
|
||||
"ButtonReScan": "Паўторнае сканаванне",
|
||||
"ButtonRead": "Чытаць",
|
||||
@@ -98,8 +100,9 @@
|
||||
"ButtonSetChaptersFromTracks": "Усталяваць раздзелы з трэкаў",
|
||||
"ButtonShare": "Падзяліцца",
|
||||
"ButtonShiftTimes": "Карэкцыя часу",
|
||||
"ButtonShow": "Паказаць",
|
||||
"ButtonStartM4BEncode": "Пачаць кадзіраванне ў M4B",
|
||||
"ButtonStartMetadataEmbed": "Пачаць убудаванне метаданых",
|
||||
"ButtonStartMetadataEmbed": "Пачаць убудаванне метададзеных",
|
||||
"ButtonStats": "Статыстыка",
|
||||
"ButtonSubmit": "Адправіць",
|
||||
"ButtonTest": "Тэст",
|
||||
@@ -116,8 +119,9 @@
|
||||
"ErrorUploadFetchMetadataNoResults": "Не ўдалося атрымаць метададзеныя – паспрабуйце абнавіць назву і/або аўтара",
|
||||
"ErrorUploadLacksTitle": "Павінна быць назва",
|
||||
"HeaderAccount": "Уліковы запіс",
|
||||
"HeaderAddCustomMetadataProvider": "Дадаць карыстальніцкага пастаўшчыка метаданных",
|
||||
"HeaderAddCustomMetadataProvider": "Дадаць карыстальніцкага пастаўшчыка метададзенных",
|
||||
"HeaderAdvanced": "Дадаткова",
|
||||
"HeaderApiKeys": "API-ключы",
|
||||
"HeaderAppriseNotificationSettings": "Налады апавяшчэнняў Apprise",
|
||||
"HeaderAudioTracks": "Аўдыядарожкі",
|
||||
"HeaderAudiobookTools": "Сродкі кіравання файламі аўдыякніг",
|
||||
@@ -157,9 +161,11 @@
|
||||
"HeaderManageGenres": "Кіраванне жанрамі",
|
||||
"HeaderManageTags": "Кіраванне тэгамі",
|
||||
"HeaderMapDetails": "Падрабязнасці адлюстравання",
|
||||
"HeaderMatch": "Супадзенне",
|
||||
"HeaderMetadataOrderOfPrecedence": "Парадак прыярытэтнасці метададзеных",
|
||||
"HeaderMetadataToEmbed": "Метададзеныя для ўбудавання",
|
||||
"HeaderNewAccount": "Новы ўліковы запіс",
|
||||
"HeaderNewApiKey": "Новы API-ключ",
|
||||
"HeaderNewLibrary": "Новая бібліятэка",
|
||||
"HeaderNotificationCreate": "Стварыць апавяшчэнне",
|
||||
"HeaderNotificationUpdate": "Абнавіць апавяшчэнне",
|
||||
@@ -175,9 +181,10 @@
|
||||
"HeaderPlaylist": "Спіс прайгравання",
|
||||
"HeaderPlaylistItems": "Элементы спіса прайгравання",
|
||||
"HeaderPodcastsToAdd": "Падкасты для дадання",
|
||||
"HeaderPresets": "Прадустаноўкі",
|
||||
"HeaderPreviewCover": "Прадпрагляд вокладкі",
|
||||
"HeaderRSSFeedGeneral": "Падрабязнасці RSS",
|
||||
"HeaderRSSFeedIsOpen": "RSS-стужка адкрыта",
|
||||
"HeaderRSSFeedIsOpen": "RSS-стужка адкрытая",
|
||||
"HeaderRSSFeeds": "RSS-стужкі",
|
||||
"HeaderRemoveEpisode": "Выдаліць эпізод",
|
||||
"HeaderRemoveEpisodes": "Выдаліць {0} эпізодаў",
|
||||
@@ -203,6 +210,7 @@
|
||||
"HeaderTableOfContents": "Змест",
|
||||
"HeaderTools": "Інструменты",
|
||||
"HeaderUpdateAccount": "Абнавіць уліковы запіс",
|
||||
"HeaderUpdateApiKey": "Абнавіць API-ключ",
|
||||
"HeaderUpdateAuthor": "Абнавіць аўтара",
|
||||
"HeaderUpdateDetails": "Абнавіць падрабязнасці",
|
||||
"HeaderUpdateLibrary": "Абнавіць бібліятэку",
|
||||
@@ -227,10 +235,15 @@
|
||||
"LabelAddedDate": "Дададзена {0}",
|
||||
"LabelAdminUsersOnly": "Толькі для адміністратараў",
|
||||
"LabelAll": "Усе",
|
||||
"LabelAllEpisodesDownloaded": "Усе эпізоды спампаваныя",
|
||||
"LabelAllUsers": "Усе карыстальнікі",
|
||||
"LabelAllUsersExcludingGuests": "Усе карыстальнікі, акрамя гасцей",
|
||||
"LabelAllUsersIncludingGuests": "Усе карыстальнікі, уключаючы гасцей",
|
||||
"LabelAlreadyInYourLibrary": "Ужо ў вашай бібліятэцы",
|
||||
"LabelApiKeyCreated": "API-ключ «{0}» паспяхова створаны.",
|
||||
"LabelApiKeyCreatedDescription": "Пераканайцеся, што вы скапіявалі API-ключ зараз, бо паўторна яго ўбачыць не атрымаецца.",
|
||||
"LabelApiKeyUser": "Дзейнічаць ад імя карыстальніка",
|
||||
"LabelApiKeyUserDescription": "Гэты API-ключ будзе мець тыя ж правы, што і карыстальнік, ад імя якога ён дзейнічае. У журналах гэта будзе выглядаць так, быццам запыт робіць сам карыстальнік.",
|
||||
"LabelApiToken": "Токен API",
|
||||
"LabelAppend": "Дадаць",
|
||||
"LabelAudioBitrate": "Бітрэйт аўдыё (напрыклад, 128к)",
|
||||
@@ -242,39 +255,106 @@
|
||||
"LabelAuthors": "Аўтары",
|
||||
"LabelAutoDownloadEpisodes": "Аўтаматычнае спампаванне эпізодаў",
|
||||
"LabelAutoFetchMetadata": "Аўтаматычнае атрыманне метададзеных",
|
||||
"LabelAutoFetchMetadataHelp": "Атрыманне звестак пра назву, аўтара і серыю для падыходнага фарматавання перад загрузкай. Далей можа быць неабходна дапоўніць метададзеныя.",
|
||||
"LabelAutoLaunch": "Аўтазапуск",
|
||||
"LabelAutoLaunchDescription": "Аўтаматычна перанакіроўваць да пастаўшчыка аўтэнтыфікацыі пры переходзе на старонку ўваходу (ручное пераключэнне праз шлях <code>/login?autoLaunch=0</code>)",
|
||||
"LabelAutoRegister": "Аўтарэгістрацыя",
|
||||
"LabelAutoRegisterDescription": "Аўтаматычна ствараць новых карыстальнікаў пасля ўваходу ў сістэму",
|
||||
"LabelBackToUser": "Вярнуцца да карыстальніка",
|
||||
"LabelBackupAudioFiles": "Рэзервовае капіраванне аўдыёфайлаў",
|
||||
"LabelBackupLocation": "Месцазнаходжанне рэзервовых копій",
|
||||
"LabelBackupsEnableAutomaticBackups": "Аўтаматычнае рэзервовае капіраванне",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Рэзервовыя копіі захаваныя ў /metadata/backups",
|
||||
"LabelBackupsMaxBackupSize": "Максімальны памер рэзервовай копіі (у ГБ) (0 — неабмежавана)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "Для таго, каб пазбегнуць няправільных налад, рэзервовыя копіі не будуць створаны, калі іх памер будзе больш за дапушчальны.",
|
||||
"LabelBackupsNumberToKeep": "Колькасць захаваных рэзервовых копій",
|
||||
"LabelBackupsNumberToKeepHelp": "Адначасова будзе выдаляцца толькі 1 рэзервовая копія, таму, калі ў вас іх больш, вам варта выдаліць іх уручную.",
|
||||
"LabelBitrate": "Бітрэйт",
|
||||
"LabelBonus": "Бонус",
|
||||
"LabelBooks": "Кнігі",
|
||||
"LabelButtonText": "Тэкст кнопкі",
|
||||
"LabelByAuthor": "ад {0}",
|
||||
"LabelChangePassword": "Змяніць пароль",
|
||||
"LabelChannels": "Каналы",
|
||||
"LabelChapterCount": "{0} раздзелаў",
|
||||
"LabelChapterTitle": "Назва раздзела",
|
||||
"LabelChapters": "Раздзелы",
|
||||
"LabelChaptersFound": "раздзелаў знойдзена",
|
||||
"LabelClickForMoreInfo": "Націсніце для больш падрабязнай інфармацыі",
|
||||
"LabelClickToUseCurrentValue": "Націсніце, каб выкарыстоўваць бягучае значэнне",
|
||||
"LabelClosePlayer": "Зачыніць прайгравальнік",
|
||||
"LabelCodec": "Кодэк",
|
||||
"LabelCollapseSeries": "Згарнуць серыі",
|
||||
"LabelCollapseSubSeries": "Згарнуць падсерыі",
|
||||
"LabelCollection": "Калекцыя",
|
||||
"LabelCollections": "Калекцыі",
|
||||
"LabelComplete": "Завершана",
|
||||
"LabelConfirmPassword": "Пацвердзіце пароль",
|
||||
"LabelContinueListening": "Працягваць слухаць",
|
||||
"LabelContinueReading": "Працягнуць чытанне",
|
||||
"LabelContinueSeries": "Працягнуць серыі",
|
||||
"LabelCover": "Вокладка",
|
||||
"LabelCoverImageURL": "URL малюнка вокладкі",
|
||||
"LabelCoverProvider": "Крыніца вокладак",
|
||||
"LabelCreatedAt": "Дата стварэння",
|
||||
"LabelCronExpression": "Запіс Cron",
|
||||
"LabelCurrent": "Бягучы",
|
||||
"LabelCurrently": "Бягучы:",
|
||||
"LabelCustomCronExpression": "Уласны запіс Cron:",
|
||||
"LabelDatetime": "Дата і час",
|
||||
"LabelDays": "Дзён",
|
||||
"LabelDeleteFromFileSystemCheckbox": "Выдаліць з файлавай сістэмы (зніміце галачку, каб выдаліць толькі з базы даных)",
|
||||
"LabelDescription": "Апісанне",
|
||||
"LabelDeselectAll": "Скасаваць выбар усяго",
|
||||
"LabelDevice": "Прылада",
|
||||
"LabelDeviceInfo": "Інфармацыя пра прыладу",
|
||||
"LabelDeviceIsAvailableTo": "Прылада даступная для...",
|
||||
"LabelDirectory": "Дырэкторыя",
|
||||
"LabelDiscFromFilename": "Дыск з імя файла",
|
||||
"LabelDiscFromMetadata": "Дыск па метададзеных",
|
||||
"LabelDiscover": "Знайсці",
|
||||
"LabelDownload": "Спампаваць",
|
||||
"LabelDownloadNEpisodes": "Спампована {0} эпізодаў",
|
||||
"LabelDownloadable": "Спампоўваецца",
|
||||
"LabelDuration": "Працягласць",
|
||||
"LabelDurationComparisonExactMatch": "(дакладнае супадзенне)",
|
||||
"LabelDurationComparisonLonger": "(на {0} даўжэй)",
|
||||
"LabelDurationComparisonShorter": "(на {0} карацей)",
|
||||
"LabelDurationFound": "Знойдзеная працягласць:",
|
||||
"LabelEbook": "Электронная кніга",
|
||||
"LabelEbooks": "Электронныя кнігі",
|
||||
"LabelEdit": "Рэдагаваць",
|
||||
"LabelEmail": "Электронная пошта",
|
||||
"LabelEmailSettingsFromAddress": "Адрас адпраўніка",
|
||||
"LabelEmailSettingsRejectUnauthorized": "Адхіляць неаўтарызаваныя сертыфікаты",
|
||||
"LabelEmailSettingsRejectUnauthorizedHelp": "Адключэнне праверкі SSL-сертыфіката можа зрабіць ваша злучэнне ўразлівым перад пагрозамі бяспекі, такімі як атакі «чалавек пасярэдзіне». Адключайце гэтую опцыю толькі калі цалкам разумееце наступствы і ўпэўнены ў надзейнасці паштовага сервера.",
|
||||
"LabelEmailSettingsSecure": "Бяспечныя",
|
||||
"LabelEmailSettingsSecureHelp": "Калі ўключана, злучэнне будзе выкарыстоўваць TLS пры падключэнні да сервера. Калі выключана, TLS будзе выкарыстоўвацца толькі ў выпадку падтрымкі пашырэння STARTTLS на серверы. У большасці выпадкаў усталюйце значэнне true пры падключэнні да порта 465. Для партоў 587 або 25 не ўключайце яго. (інфармацыя з nodemailer.com/smtp/#authentication)",
|
||||
"LabelEmailSettingsTestAddress": "Тэставы адрас",
|
||||
"LabelEmbeddedCover": "Убудаваная вокладка",
|
||||
"LabelEnable": "Уключыць",
|
||||
"LabelEncodingBackupLocation": "Рэзервовая копія вашых арыгінальных аўдыёфайлаў будзе захавана ў:",
|
||||
"LabelEncodingChaptersNotEmbedded": "Раздзелы не ўбудаваны ў шматдарожкавыя аўдыякнігі.",
|
||||
"LabelEncodingClearItemCache": "Пераканайцеся, што перыядычна ачышчаеце кэш элементаў.",
|
||||
"LabelEncodingFinishedM4B": "Гатовы файл M4B будзе змешчаны ў вашу тэчку з аўдыякнігамі па адрасе:",
|
||||
"LabelEncodingInfoEmbedded": "Метаданыя будуць убудаваны ў аўдыядарожкі ўнутры вашай тэчкі з аўдыякнігамі.",
|
||||
"LabelEncodingInfoEmbedded": "Метададзеныя будуць убудаваны ў аўдыядарожкі ўнутры вашай тэчкі з аўдыякнігамі.",
|
||||
"LabelEncodingTimeWarning": "Кадаванне можа заняць да 30 хвілін.",
|
||||
"LabelEnd": "Канец",
|
||||
"LabelEndOfChapter": "Канец раздзела",
|
||||
"LabelEpisode": "Эпізод",
|
||||
"LabelEpisodeNotLinkedToRssFeed": "Эпізод не звязаны з RSS-стужкай",
|
||||
"LabelEpisodeUrlFromRssFeed": "URL эпізоду з RSS-стужкі",
|
||||
"LabelEpisodic": "Эпізадычны",
|
||||
"LabelExample": "Прыклад",
|
||||
"LabelExpandSeries": "Разгарнуць серыю",
|
||||
"LabelExpandSubSeries": "Разгарнуць падсерыі",
|
||||
"LabelExpired": "Пратэрмінаваны",
|
||||
"LabelExpiresAt": "Тэрмін дзеяння заканчваецца ў",
|
||||
"LabelExpiresInSeconds": "Тэрмін дзеяння заканчваецца праз (секунд)",
|
||||
"LabelExpiresNever": "Ніколі",
|
||||
"LabelExplicit": "Відверты",
|
||||
"LabelFeedURL": "URL стужкі",
|
||||
"LabelFetchingMetadata": "Атрыманне метададзеных",
|
||||
"LabelFile": "Файл",
|
||||
"LabelFileBirthtime": "Час стварэння файла",
|
||||
"LabelFileModified": "Час змянення файла",
|
||||
@@ -327,6 +407,8 @@
|
||||
"LabelMaxEpisodesToKeepHelp": "Значэнне 0 не ўстанаўлівае максімальнага абмежавання. Пасля аўтаматычнай спампоўкі новага эпізоду будзе выдалены самы стары эпізод, калі ў вас больш за X эпізодаў. Пры кожнай новай спампоўцы будзе выдаляцца толькі 1 эпізод.",
|
||||
"LabelMediaPlayer": "Медыяпрайгравальнік",
|
||||
"LabelMediaType": "Тып медыя",
|
||||
"LabelMetadataOrderOfPrecedenceDescription": "Крыніцы метададзеных з вышэйшым прыярытэтам будуць замяняць крыніцы з ніжэйшым прыярытэтам",
|
||||
"LabelMetadataProvider": "Пастаўшчык метададзеных",
|
||||
"LabelMissing": "Адсутнічае",
|
||||
"LabelMore": "Больш",
|
||||
"LabelMoreInfo": "Больш інфармацыі",
|
||||
@@ -335,6 +417,7 @@
|
||||
"LabelNarrators": "Чытальнікі",
|
||||
"LabelNewestAuthors": "Новыя аўтары",
|
||||
"LabelNewestEpisodes": "Новыя эпізоды",
|
||||
"LabelNoCustomMetadataProviders": "Няма карыстацкіх пастаўшчыкоў метададзеных",
|
||||
"LabelNotFinished": "Не скончана",
|
||||
"LabelNotStarted": "Не пачата",
|
||||
"LabelNotificationsMaxFailedAttemptsHelp": "Апавяшчэнні адключаюцца пасля таго, як не ўдаецца іх адправіць гэтулькі разоў",
|
||||
@@ -353,7 +436,7 @@
|
||||
"LabelPublishedDate": "Апублікавана {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Карыстальніцкая электронная пошта ўладальніка",
|
||||
"LabelRSSFeedCustomOwnerName": "Карыстальніцкае імя ўладальніка",
|
||||
"LabelRSSFeedOpen": "RSS-стужка адкрытая",
|
||||
"LabelRSSFeedOpen": "RSS-стужка адкрыта",
|
||||
"LabelRSSFeedPreventIndexing": "Прадухіліць індэксацыю",
|
||||
"LabelRSSFeedSlug": "Ідэнтыфікатар RSS-стужкі",
|
||||
"LabelRSSFeedURL": "URL RSS-стужкі",
|
||||
@@ -392,6 +475,7 @@
|
||||
"LabelSettingsAudiobooksOnly": "Толькі аўдыякнігі",
|
||||
"LabelSettingsAudiobooksOnlyHelp": "Уключэнне гэтай налады будзе ігнараваць файлы электронных кніг, калі толькі яны не знаходзяцца ў тэчцы з аўдыякнігамі. У такім выпадку яны будуць пазначаны як дадатковыя электронныя кнігі.",
|
||||
"LabelSettingsBookshelfViewHelp": "Рэалістычны дызайн з драўлянымі паліцамі",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Аўтаматычна правяраць бібліятэку на змены",
|
||||
"LabelSettingsEnableWatcherHelp": "Адключае аўтаматычнае дадаванне/абнаўленне элементаў пры выяўленні змен у файлах. *Патрабуецца перазапуск сервера",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Дазволіць скрыптавы кантэнт у EPUB",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Дазволіць EPUB-файлам выконваць скрыпты. Рэкамендуецца пакінуць гэтую наладу выключанай, калі вы не давяраеце крыніцы EPUB-файлаў.",
|
||||
@@ -409,6 +493,11 @@
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Палка \"Працягнуць серыю\" на галоўнай старонцы паказвае першую не пачатую кнігу ў серыях, дзе завершана хаця б адна кніга і няма кніг у працэсе чытання. Уключэнне гэтай налады дазволіць працягваць серыю з самай апошняй завершанай кнігі замест першай не пачатай.",
|
||||
"LabelSettingsParseSubtitles": "Разабраць падзагалоўкі",
|
||||
"LabelSettingsParseSubtitlesHelp": "Выдзяляць падзагаловак з назваў тэчак аўдыякніг.<br>Падзагаловак павінен быць аддзелены знакам \" - \".<br>Напрыклад, \"Назва кнігі - Падзагаловак тут\" мае падзагаловак \"Падзагаловак тут\"",
|
||||
"LabelSettingsPreferMatchedMetadata": "Аддаваць перавагу супадаючым метададзеным",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Супадаючыя дадзеныя будуць замяняць дэталі элемента пры выкарыстанні функцыі Хуткі пошук. Па змаўчанні Хуткі пошук запаўняе толькі адсутныя дэталі.",
|
||||
"LabelSettingsStoreCoversWithItemHelp": "Па змаўчанні вокладкі захоўваюцца ў /metadata/items, уключэнне гэтай опцыі забяспечыць захоўванне вокладак у тэчцы элемента вашай бібліятэкі. Захоўвацца будзе толькі адзін файл з назвай «cover»",
|
||||
"LabelSettingsStoreMetadataWithItem": "Захоўваць метададзеныя разам з элементам",
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Па змаўчанні метададзеныя захоўваюцца ў /metadata/items. Уключэнне гэтай опцыі забяспечыць захоўванне файлаў метададзеных у тэчках элементаў вашай бібліятэкі",
|
||||
"LabelSettingsTimeFormat": "Фармат часу",
|
||||
"LabelShareDownloadableHelp": "Дазваляе карыстальнікам, якія маюць спасылку на доступ, спампаваць ZIP-файл элемента бібліятэкі.",
|
||||
"LabelShowAll": "Паказаць усё",
|
||||
@@ -457,11 +546,14 @@
|
||||
"LabelTimeRemaining": "Засталося {0}",
|
||||
"LabelTimeToShift": "Час зрушэння ў секундах",
|
||||
"LabelTitle": "Назва",
|
||||
"LabelToolsSplitM4bDescription": "Стварэнне MP3 з M4B, падзеленага па раздзелах, з убудаванымі метаданымі, вокладкай і раздзеламі.",
|
||||
"LabelToolsEmbedMetadata": "Убудаваць метададзеныя",
|
||||
"LabelToolsEmbedMetadataDescription": "Убудаваць метададзеныя ў аўдыёфайлы, уключаючы вокладку і раздзелы.",
|
||||
"LabelToolsMakeM4bDescription": "Стварыць аўдыёкнігу ў фармаце .M4B з убудаванымі метададзенымі, вокладкай і раздзеламі.",
|
||||
"LabelToolsSplitM4bDescription": "Стварэнне MP3 з M4B, падзеленага па раздзелах, з убудаванымі метададзенымі, вокладкай і раздзеламі.",
|
||||
"LabelTotalDuration": "Агульная працягласць",
|
||||
"LabelTotalTimeListened": "Агульны час праслухоўвання",
|
||||
"LabelTrackFromFilename": "Дарожка з імя файла",
|
||||
"LabelTrackFromMetadata": "Дарожка з метаданых",
|
||||
"LabelTrackFromMetadata": "Дарожка з метададзеных",
|
||||
"LabelTracks": "Дарожкі",
|
||||
"LabelTracksMultiTrack": "Шматдарожкавы",
|
||||
"LabelTracksNone": "Няма дарожак",
|
||||
@@ -510,19 +602,27 @@
|
||||
"MessageBackupsLocationPathEmpty": "Шлях да месцазнаходжання рэзервовых копій не можа быць пустым",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "Запоўніце ўключаныя палі дадзенымі з усіх элементаў. Палі з некалькімі значэннямі будуць аб'яднаны",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "Запоўніце ўключаныя палі падрабязнасцей карты дадзенымі з гэтага элемента",
|
||||
"MessageBatchQuickMatchDescription": "Хуткі пошук паспрабуе дадаць адсутныя вокладкі і метададзеныя для выбраных элементаў. Уключыце ніжэй выкладзеныя опцыі, каб дазволіць Хуткаму пошуку замяняць існуючыя вокладкі і/або метададзеныя.",
|
||||
"MessageBookshelfNoRSSFeeds": "Няма адкрытых RSS-стужак",
|
||||
"MessageChapterErrorStartGteDuration": "Няправільны час пачатку: ён павінен быць меншым за працягласць аўдыякнігі",
|
||||
"MessageChapterErrorStartLtPrev": "Няправільны час пачатку: ён павінен быць большым або роўным часу пачатку папярэдняга раздзела",
|
||||
"MessageConfirmCloseFeed": "Вы ўпэўнены, што жадаеце закрыць гэтую стужку?",
|
||||
"MessageConfirmDeleteMetadataProvider": "Ці ўпэўненыя вы, што жадаеце выдаліць карыстацкага пастаўшчыка метададзеных «{0}»?",
|
||||
"MessageConfirmEmbedMetadataInAudioFiles": "Ці ўпэўненыя вы, што жадаеце ўбудаваць метададзеныя ў {0} аўдыёфайлаў?",
|
||||
"MessageConfirmPurgeCache": "Ачышчэнне кэша выдаліць увесь каталог па адрасе <code>/metadata/cache</code>. <br /><br /> Ці сапраўды вы жадаеце выдаліць каталог кэша?",
|
||||
"MessageConfirmPurgeItemsCache": "Ачышчэнне кэша элементаў выдаліць увесь каталог па адрасе <code>/metadata/cache/items</code>. <br /> Вы ўпэўнены?",
|
||||
"MessageConfirmRemoveListeningSessions": "Вы ўпэўнены, што жадаеце выдаліць {0} сеансаў праслухоўвання?",
|
||||
"MessageConfirmRemoveMetadataFiles": "Ці ўпэўненыя вы, што жадаеце выдаліць усе файлы метададзеных{0} у тэчках элементаў вашай бібліятэкі?",
|
||||
"MessageConfirmRemovePlaylist": "Вы ўпэўненыя, што жадаеце выдаліць свой спіс прайгравання \"{0}\"?",
|
||||
"MessageConfirmSendEbookToDevice": "Вы ўпэўнены, што хочаце адправіць {0} электронную кнігу \"{1}\" на прыладу \"{2}\"?",
|
||||
"MessageDownloadingEpisode": "Спампоўка эпізоду",
|
||||
"MessageEmbedQueue": "У чарзе на ўбудаванне метададзеных (у чарзе {0})",
|
||||
"MessageEpisodesQueuedForDownload": "{0} эпізод(аў) у чарзе для спампоўкі",
|
||||
"MessageEreaderDevices": "Каб забяспечыць дастаўку электронных кніг, вам можа спатрэбіцца дадаць вышэйзгаданы адрас электроннай пошты як дазволенага адпраўніка для кожнай прылады, пералічанай ніжэй.",
|
||||
"MessageFeedURLWillBe": "URL стужкі будзе {0}",
|
||||
"MessageFetching": "Атрыманне...",
|
||||
"MessageLoading": "Загрузка...",
|
||||
"MessageLogsDescription": "Журналы захоўваюцца ў каталогу <code>/metadata/logs</code> у фармаце JSON. Журналы памылак захоўваюцца ў файле <code>/metadata/logs/crashlogs.txt</code>.",
|
||||
"MessageMapChapterTitles": "Супаставіць назвы раздзелаў з вашымі існуючымі раздзеламі аўдыякнігі без змянення часовых метак",
|
||||
"MessageMarkAsFinished": "Пазначыць як скончана",
|
||||
"MessageNoBookmarks": "Няма закладак",
|
||||
@@ -543,11 +643,17 @@
|
||||
"MessagePlaylistCreateFromCollection": "Стварыць спіс прайгравання з калекцыі",
|
||||
"MessagePodcastHasNoRSSFeedForMatching": "У падкаста няма URL RSS-стужкі для супадзення",
|
||||
"MessagePodcastSearchField": "Увядзіце пошукавы запыт або URL RSS-стужкі",
|
||||
"MessageQuickMatchDescription": "Запоўніць пустыя дэталі элемента і вокладку першым вынікам супадзення з «{0}». Не замяняе дэталі, калі опцыя «Аддаваць перавагу супадаючым метададзеным» на серверы не ўключана.",
|
||||
"MessageReportBugsAndContribute": "Паведамляйце пра памылкі, прапануйце новыя функцыі і ўдзельнічайце на",
|
||||
"MessageRestoreBackupWarning": "Аднаўленне рэзервовай копіі перазапіша ўсю базу даных, размешчаную ў /config, а таксама вокладкі ў /metadata/items і /metadata/authors. <br /><br /> Рэзервовыя копіі не змяняюць файлы ў вашых тэчках бібліятэкі. Калі вы ўключылі наладкі сервера для захоўвання воклак і метададзеных у тэчках бібліятэкі, гэтыя файлы не будуць захаваныя ў рэзервовых копіях і не зменяцца. <br /><br /> Усе кліенты, якія карыстаюцца вашым серверам, будуць аўтаматычна абноўлены.",
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Выконваць кожныя {0} у {1}",
|
||||
"MessageStartPlaybackAtTime": "Пачаць прайграванне для \"{0}\" з {1}?",
|
||||
"MessageTaskCanceledByUser": "Задача скасавана карыстальнікам",
|
||||
"MessageTaskDownloadingEpisodeDescription": "Спампоўка эпізоду \"{0}\"",
|
||||
"MessageTaskEmbeddingMetadata": "Убудаванне метададзеных",
|
||||
"MessageTaskEmbeddingMetadataDescription": "Убудаванне метададзеных у аўдыёкнігу «{0}»",
|
||||
"MessageTaskFailedToEmbedMetadataInFile": "Не ўдалося ўбудаваць метададзеныя ў файл «{0}»",
|
||||
"MessageTaskFailedToWriteMetadataFile": "Не ўдалося захаваць файл метададзеных",
|
||||
"MessageTaskOpmlImportDescription": "Стварэнне падкастаў з {0} RSS-стужак",
|
||||
"MessageTaskOpmlImportFeed": "Імпарт стужкі з OPML",
|
||||
"MessageTaskOpmlImportFeedDescription": "Імпартаванне RSS-стужкі \"{0}\"",
|
||||
@@ -602,6 +708,8 @@
|
||||
"ToastPlaylistCreateSuccess": "Спіс прайгравання створаны",
|
||||
"ToastPlaylistRemoveSuccess": "Спіс прайгравання выдалены",
|
||||
"ToastPlaylistUpdateSuccess": "Спіс прайгравання абноўлены",
|
||||
"ToastPodcastCreateFailed": "Не ўдалося стварыць падкаст",
|
||||
"ToastPodcastCreateSuccess": "Падкаст паспяхова створаны",
|
||||
"ToastPodcastGetFeedFailed": "Не ўдалося атрымаць стужку падкаста",
|
||||
"ToastPodcastNoEpisodesInFeed": "У RSS-стужцы не знойдзена эпізодаў",
|
||||
"ToastPodcastNoRssFeed": "У падкаста няма RSS-стужкі",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "Přidat",
|
||||
"ButtonAddApiKey": "Přidat API klíč",
|
||||
"ButtonAddChapters": "Přidat kapitoly",
|
||||
"ButtonAddDevice": "Přidat zařízení",
|
||||
"ButtonAddLibrary": "Přidat knihovnu",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "Vybrat složku",
|
||||
"ButtonChooseFiles": "Vybrat soubory",
|
||||
"ButtonClearFilter": "Vymazat filtr",
|
||||
"ButtonClose": "Zavřít",
|
||||
"ButtonCloseFeed": "Zavřít kanál",
|
||||
"ButtonCloseSession": "Zavřít otevřenou relaci",
|
||||
"ButtonCollections": "Kolekce",
|
||||
@@ -119,6 +121,7 @@
|
||||
"HeaderAccount": "Účet",
|
||||
"HeaderAddCustomMetadataProvider": "Přidat vlastního poskytovatele metadat",
|
||||
"HeaderAdvanced": "Pokročilé",
|
||||
"HeaderApiKeys": "API klíče",
|
||||
"HeaderAppriseNotificationSettings": "Nastavení oznámení Apprise",
|
||||
"HeaderAudioTracks": "Zvukové stopy",
|
||||
"HeaderAudiobookTools": "Nástroje pro správu souborů audioknih",
|
||||
@@ -162,6 +165,7 @@
|
||||
"HeaderMetadataOrderOfPrecedence": "Pořadí priorit metadat",
|
||||
"HeaderMetadataToEmbed": "Metadata k vložení",
|
||||
"HeaderNewAccount": "Nový účet",
|
||||
"HeaderNewApiKey": "Nový API klíč",
|
||||
"HeaderNewLibrary": "Nová knihovna",
|
||||
"HeaderNotificationCreate": "Vytvořit notifikaci",
|
||||
"HeaderNotificationUpdate": "Aktualizovat notifikaci",
|
||||
@@ -206,6 +210,7 @@
|
||||
"HeaderTableOfContents": "Obsah",
|
||||
"HeaderTools": "Nástroje",
|
||||
"HeaderUpdateAccount": "Aktualizovat účet",
|
||||
"HeaderUpdateApiKey": "Aktualizovat API klíč",
|
||||
"HeaderUpdateAuthor": "Aktualizovat autora",
|
||||
"HeaderUpdateDetails": "Aktualizovat podrobnosti",
|
||||
"HeaderUpdateLibrary": "Aktualizovat knihovnu",
|
||||
@@ -235,6 +240,10 @@
|
||||
"LabelAllUsersExcludingGuests": "Všichni uživatelé kromě hostů",
|
||||
"LabelAllUsersIncludingGuests": "Všichni uživatelé včetně hostů",
|
||||
"LabelAlreadyInYourLibrary": "Již ve vaší knihovně",
|
||||
"LabelApiKeyCreated": "API klíč \"{0}\" byl úspěšně vytvořen.",
|
||||
"LabelApiKeyCreatedDescription": "Zkopírujte si API klíč nyní, později již nebude možné jej zobrazit.",
|
||||
"LabelApiKeyUser": "Vydávat se za uživatele",
|
||||
"LabelApiKeyUserDescription": "Tento API klíč bude mít stejná oprávnění jako uživatel za něhož vystupuje. V protokolech to bude vypadat jako kdyby požadavky vytvářel přímo daný uživatel.",
|
||||
"LabelApiToken": "API Token",
|
||||
"LabelAppend": "Připojit",
|
||||
"LabelAudioBitrate": "Bitový tok zvuku (např. 128k)",
|
||||
@@ -346,6 +355,10 @@
|
||||
"LabelExample": "Příklad",
|
||||
"LabelExpandSeries": "Rozbalit série",
|
||||
"LabelExpandSubSeries": "Rozbalit podsérie",
|
||||
"LabelExpired": "Expirovaný",
|
||||
"LabelExpiresAt": "Expiruje v",
|
||||
"LabelExpiresInSeconds": "Expiruje za (sekundy)",
|
||||
"LabelExpiresNever": "Nikdy",
|
||||
"LabelExplicit": "Explicitně",
|
||||
"LabelExplicitChecked": "Explicitní (zaškrtnuto)",
|
||||
"LabelExplicitUnchecked": "Není explicitní (nezaškrtnuto)",
|
||||
@@ -455,6 +468,7 @@
|
||||
"LabelNewestEpisodes": "Nejnovější epizody",
|
||||
"LabelNextBackupDate": "Datum příští zálohy",
|
||||
"LabelNextScheduledRun": "Další naplánované spuštění",
|
||||
"LabelNoApiKeys": "Žádné API klíče",
|
||||
"LabelNoCustomMetadataProviders": "Žádní vlastní poskytovatelé metadat",
|
||||
"LabelNoEpisodesSelected": "Nebyly vybrány žádné epizody",
|
||||
"LabelNotFinished": "Nedokončeno",
|
||||
@@ -544,6 +558,7 @@
|
||||
"LabelSelectAll": "Vybrat vše",
|
||||
"LabelSelectAllEpisodes": "Vybrat všechny epizody",
|
||||
"LabelSelectEpisodesShowing": "Vyberte {0} epizody, které se zobrazují",
|
||||
"LabelSelectUser": "Vybrat uživatele",
|
||||
"LabelSelectUsers": "Vybrat uživatele",
|
||||
"LabelSendEbookToDevice": "Odeslat e-knihu do...",
|
||||
"LabelSequence": "Sekvence",
|
||||
@@ -708,7 +723,9 @@
|
||||
"MessageAddToPlayerQueue": "Přidat do fronty přehrávače",
|
||||
"MessageAppriseDescription": "Abyste mohli používat tuto funkci, musíte mít spuštěnou instanci <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> nebo API, které bude zpracovávat stejné požadavky. <br />Adresa URL API Apprise by měla být úplná URL cesta pro odeslání oznámení, např. pokud je vaše instance API obsluhována na adrese <code>http://192.168.1.1:8337</code> pak byste měli zadat <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Ujistěte se, že používáte ASIN ze správného regionu Audible a ne z Amazonu.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "Zastaralé API tokeny budou v budoucnu odstraněny. Použijte místo nich <a href=\"/config/api-keys\">API klíče</a>.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Po uložení restartujte server, aby se změny OIDC použily.",
|
||||
"MessageAuthenticationSecurityMessage": "Bezpečnost autentizace byla vylepšena. Všichni uživatelé se musí znovu přihlásit.",
|
||||
"MessageBackupsDescription": "Zálohy zahrnují uživatele, průběh uživatele, podrobnosti o položkách knihovny, nastavení serveru a obrázky uložené v <code>/metadata/items</code> a <code>/metadata/authors</code>. Zálohy <strong>ne</strong> zahrnují všechny soubory uložené ve složkách knihovny.",
|
||||
"MessageBackupsLocationEditNote": "Poznámka: Změna umístění záloh nepřesune ani nezmění existující zálohy",
|
||||
"MessageBackupsLocationNoEditNote": "Poznámka: Umístění záloh je nastavené z proměnných prostředí a nelze zde změnit.",
|
||||
@@ -730,6 +747,7 @@
|
||||
"MessageChaptersNotFound": "Kapitoly nenalezeny",
|
||||
"MessageCheckingCron": "Kontrola cronu...",
|
||||
"MessageConfirmCloseFeed": "Opravdu chcete zavřít tento kanál?",
|
||||
"MessageConfirmDeleteApiKey": "Opravdu chcete vymazat API klíč \"{0}\"?",
|
||||
"MessageConfirmDeleteBackup": "Opravdu chcete smazat zálohu pro {0}?",
|
||||
"MessageConfirmDeleteDevice": "Opravdu chcete vymazat zařízení e-reader \"{0}\"?",
|
||||
"MessageConfirmDeleteFile": "Tento krok smaže soubor ze souborového systému. Jsi si jisti?",
|
||||
@@ -1001,6 +1019,8 @@
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "Fronta stahování epizod je prázdná",
|
||||
"ToastEpisodeUpdateSuccess": "{0} epizod aktualizováno",
|
||||
"ToastErrorCannotShare": "Na tomto zařízení nelze nativně sdílet",
|
||||
"ToastFailedToCreate": "Nepodařilo se vytvořit",
|
||||
"ToastFailedToDelete": "Nepodařilo se odstranit",
|
||||
"ToastFailedToLoadData": "Nepodařilo se načíst data",
|
||||
"ToastFailedToMatch": "Nepodařilo se spárovat",
|
||||
"ToastFailedToShare": "Sdílení selhalo",
|
||||
@@ -1032,6 +1052,7 @@
|
||||
"ToastMustHaveAtLeastOnePath": "Musí mít minimálně jednu cestu",
|
||||
"ToastNameEmailRequired": "Jméno a email jsou vyžadovány",
|
||||
"ToastNameRequired": "Jméno je vyžadováno",
|
||||
"ToastNewApiKeyUserError": "Je nutné vybrat uživatele",
|
||||
"ToastNewEpisodesFound": "{0} nových epizod bylo nalezeno",
|
||||
"ToastNewUserCreatedFailed": "Chyba při vytváření účtu: \"{0}\"",
|
||||
"ToastNewUserCreatedSuccess": "Vytvořen nový účet",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "Tilføj",
|
||||
"ButtonAddApiKey": "Tilføj API-nøgle",
|
||||
"ButtonAddChapters": "Tilføj kapitler",
|
||||
"ButtonAddDevice": "Tilføj enhed",
|
||||
"ButtonAddLibrary": "Tilføj Bibliotek",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "Vælg en mappe",
|
||||
"ButtonChooseFiles": "Vælg filer",
|
||||
"ButtonClearFilter": "Ryd filter",
|
||||
"ButtonClose": "Luk",
|
||||
"ButtonCloseFeed": "Luk feed",
|
||||
"ButtonCloseSession": "Luk Åben Session",
|
||||
"ButtonCollections": "Samlinger",
|
||||
@@ -119,6 +121,7 @@
|
||||
"HeaderAccount": "Konto",
|
||||
"HeaderAddCustomMetadataProvider": "Tilføj Brugerdefineret Metadataudbyder",
|
||||
"HeaderAdvanced": "Avanceret",
|
||||
"HeaderApiKeys": "API-nøgler",
|
||||
"HeaderAppriseNotificationSettings": "Apprise Notifikationsindstillinger",
|
||||
"HeaderAudioTracks": "Lydspor",
|
||||
"HeaderAudiobookTools": "Audiobog Filhåndteringsværktøjer",
|
||||
@@ -162,6 +165,7 @@
|
||||
"HeaderMetadataOrderOfPrecedence": "Metadata-prioritet",
|
||||
"HeaderMetadataToEmbed": "Metadata til indlejring",
|
||||
"HeaderNewAccount": "Ny Konto",
|
||||
"HeaderNewApiKey": "Ny API-nøgle",
|
||||
"HeaderNewLibrary": "Nyt Bibliotek",
|
||||
"HeaderNotificationCreate": "Opret Notifikation",
|
||||
"HeaderNotificationUpdate": "Updater Notifikation",
|
||||
@@ -206,6 +210,7 @@
|
||||
"HeaderTableOfContents": "Indholdsfortegnelse",
|
||||
"HeaderTools": "Værktøjer",
|
||||
"HeaderUpdateAccount": "Opdater Konto",
|
||||
"HeaderUpdateApiKey": "Opdater API-nøgle",
|
||||
"HeaderUpdateAuthor": "Opdater Forfatter",
|
||||
"HeaderUpdateDetails": "Opdater Detaljer",
|
||||
"HeaderUpdateLibrary": "Opdater Bibliotek",
|
||||
@@ -235,6 +240,10 @@
|
||||
"LabelAllUsersExcludingGuests": "Alle bruger eksklusiv gæster",
|
||||
"LabelAllUsersIncludingGuests": "Alle bruger inklusiv gæster",
|
||||
"LabelAlreadyInYourLibrary": "Allerede i dit bibliotek",
|
||||
"LabelApiKeyCreated": "API-nøgle\"{0}\" oprettet succesfuldt.",
|
||||
"LabelApiKeyCreatedDescription": "Sørg for at kopiere API-nøglen nu, da du ikke vil kunne se den igen.",
|
||||
"LabelApiKeyUser": "Ret på vegne af brugeren",
|
||||
"LabelApiKeyUserDescription": "Denne API-nøgle vil have de samme tilladelser som den bruger, den handler på vegne af. Dette vil fremgå på samme måde i logfiler, som hvis brugeren foretog anmodningen.",
|
||||
"LabelApiToken": "API Token",
|
||||
"LabelAppend": "Tilføj",
|
||||
"LabelAudioBitrate": "Lydbitrate (f.eks. 128k)",
|
||||
@@ -287,7 +296,7 @@
|
||||
"LabelCover": "Omslag",
|
||||
"LabelCoverImageURL": "Omslagsbillede URL",
|
||||
"LabelCoverProvider": "Cover billede udbyder",
|
||||
"LabelCreatedAt": "Oprettet Kl.",
|
||||
"LabelCreatedAt": "Oprettet d.",
|
||||
"LabelCronExpression": "Cron Udtryk",
|
||||
"LabelCurrent": "Aktuel",
|
||||
"LabelCurrently": "Aktuelt:",
|
||||
@@ -346,6 +355,10 @@
|
||||
"LabelExample": "Eksempel",
|
||||
"LabelExpandSeries": "Udfold serie",
|
||||
"LabelExpandSubSeries": "Udfold underserie",
|
||||
"LabelExpired": "Udløbet",
|
||||
"LabelExpiresAt": "Udløbsdato",
|
||||
"LabelExpiresInSeconds": "Udløber om (seconds)",
|
||||
"LabelExpiresNever": "Aldrig",
|
||||
"LabelExplicit": "Eksplisit",
|
||||
"LabelExplicitChecked": "Eksplicit (markeret)",
|
||||
"LabelExplicitUnchecked": "Ikke eksplicit (ikke markeret)",
|
||||
@@ -455,6 +468,7 @@
|
||||
"LabelNewestEpisodes": "Nyeste episoder",
|
||||
"LabelNextBackupDate": "Næste sikkerhedskopi dato",
|
||||
"LabelNextScheduledRun": "Næste planlagte kørsel",
|
||||
"LabelNoApiKeys": "Ingen API-nøgler",
|
||||
"LabelNoCustomMetadataProviders": "Ingen brugerdefinerede metadata udbydere",
|
||||
"LabelNoEpisodesSelected": "Ingen episoder valgt",
|
||||
"LabelNotFinished": "Ikke færdig",
|
||||
@@ -544,6 +558,7 @@
|
||||
"LabelSelectAll": "Vælg alle",
|
||||
"LabelSelectAllEpisodes": "Vælg alle episoder",
|
||||
"LabelSelectEpisodesShowing": "Vælg {0} episoder vist",
|
||||
"LabelSelectUser": "Vælg bruger",
|
||||
"LabelSelectUsers": "Valgte brugere",
|
||||
"LabelSendEbookToDevice": "Send e-bog til...",
|
||||
"LabelSequence": "Sekvens",
|
||||
@@ -577,14 +592,14 @@
|
||||
"LabelSettingsLibraryMarkAsFinishedPercentComplete": "Procent gennemført er større end",
|
||||
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Tid tilbage er mindre end (sekunder)",
|
||||
"LabelSettingsLibraryMarkAsFinishedWhen": "Marker medie indhold som færdigt når",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Spring til tidligere bøger i Fortsæt serie",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Fortsæt Serien siden hylde viser de første bøger som ikke er startet i serier med mindst en bog som ikke er startet og ingen bøger i gang. Aktivering af denne indstilling vil fortsætte serien fra den sidst gennemførte bog modsat den først ikke startede bog.",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Spring tidligere bøger i Fortsæt serie over",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Fortsæt Serien siden viser den første bog som ikke er startet i serier med mindst en bog som ikke er startet og hvor ingen bøger i gang. Aktivering af denne indstilling vil fortsætte serien fra den sidst gennemførte bog i stedet for fra den første ikke startede bog.",
|
||||
"LabelSettingsParseSubtitles": "Fortolk undertitler",
|
||||
"LabelSettingsParseSubtitlesHelp": "Udtræk undertekster fra lydbogsmappenavne.<br>Undertitler skal adskilles af \" - \"<br>f.eks. \"Bogtitel - En undertitel her\" har undertitlen \"En undertitel her\"",
|
||||
"LabelSettingsPreferMatchedMetadata": "Foretræk matchede metadata",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Matchede data vil tilsidesætte elementdetaljer ved brug af Hurtig Match. Som standard udfylder Hurtig Match kun manglende detaljer.",
|
||||
"LabelSettingsSkipMatchingBooksWithASIN": "Spring over matchende bøger, der allerede har en ASIN",
|
||||
"LabelSettingsSkipMatchingBooksWithISBN": "Spring matchende bøger over, som allerede har et ISBN-nummer",
|
||||
"LabelSettingsSkipMatchingBooksWithASIN": "Ignorer matchende bøger, der allerede har en ASIN",
|
||||
"LabelSettingsSkipMatchingBooksWithISBN": "Ignorer matchende bøger, som allerede har et ISBN-nummer",
|
||||
"LabelSettingsSortingIgnorePrefixes": "Ignorer præfikser ved sortering",
|
||||
"LabelSettingsSortingIgnorePrefixesHelp": "f.eks. for præfikset \"the\" vil bogtitlen \"The Book Title\" blive sorteret som \"Book Title, The\"",
|
||||
"LabelSettingsSquareBookCovers": "Brug kvadratiske bogomslag",
|
||||
@@ -708,7 +723,9 @@
|
||||
"MessageAddToPlayerQueue": "Tilføj til afspilningskø",
|
||||
"MessageAppriseDescription": "For at bruge denne funktion skal du have en instans af <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> kørende eller en API, der håndterer de samme anmodninger. <br /> Apprise API-webadressen skal være den fulde URL-sti for at sende underretningen, f.eks. hvis din API-instans er tilgængelig på <code>http://192.168.1.1:8337</code>, så skal du bruge <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Sikr dig at du bruger ASIN fra den korrekte Audible region, ikke Amazon.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "Ældre API tokens vil blive fjernet i fremtiden. Brug <a href=\"/config/api-keys\">API-nøgler</a> i stedet.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Genstart sin server efter du har gemt for at bekræfte OIDC ændringer.",
|
||||
"MessageAuthenticationSecurityMessage": "Autentificeringen er blevet forbedret af sikkerhedsmæssige årsager. Alle brugere skal logge ind igen.",
|
||||
"MessageBackupsDescription": "Backups inkluderer brugere, brugerfremskridt, biblioteksvareoplysninger, serverindstillinger og billeder gemt i <code>/metadata/items</code> og <code>/metadata/authors</code>. Backups inkluderer <strong>ikke</strong> nogen filer gemt i dine biblioteksmapper.",
|
||||
"MessageBackupsLocationEditNote": "Note: Opdatering af backup sti vil ikke fjerne eller modificere eksisterende backups",
|
||||
"MessageBackupsLocationNoEditNote": "Note: Backup sti er sat igennem miljøvariabel og kan ikke ændres her.",
|
||||
@@ -730,6 +747,7 @@
|
||||
"MessageChaptersNotFound": "Kapitler ikke fundet",
|
||||
"MessageCheckingCron": "Tjekker cron...",
|
||||
"MessageConfirmCloseFeed": "Er du sikker på, at du vil lukke dette feed?",
|
||||
"MessageConfirmDeleteApiKey": "Er du sikker på at du vil slette API-nøglen \"{0}\"?",
|
||||
"MessageConfirmDeleteBackup": "Er du sikker på, at du vil slette backup for {0}?",
|
||||
"MessageConfirmDeleteDevice": "Er du sikker på at du vil fjerne elæser enhed \"{0}\"?",
|
||||
"MessageConfirmDeleteFile": "Dette vil slette filen fra dit filsystem. Er du sikker?",
|
||||
@@ -998,6 +1016,7 @@
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "Afsnit download kø renset",
|
||||
"ToastEpisodeUpdateSuccess": "{0} afsnit opdateret",
|
||||
"ToastErrorCannotShare": "Kan ikke dele på denne enhed",
|
||||
"ToastFailedToCreate": "Oprettelsen mislykkedes",
|
||||
"ToastFailedToLoadData": "Fejlede at indlæse data",
|
||||
"ToastFailedToMatch": "Fejlet match",
|
||||
"ToastFailedToShare": "Fejlet deling",
|
||||
@@ -1014,11 +1033,11 @@
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Mislykkedes markering som ikke afsluttet",
|
||||
"ToastItemMarkedAsNotFinishedSuccess": "Vare markeret som ikke afsluttet",
|
||||
"ToastItemUpdateSuccess": "Genstand opdateret",
|
||||
"ToastLibraryCreateFailed": "Mislykkedes oprettelse af bibliotek",
|
||||
"ToastLibraryCreateFailed": "Oprettelse af bibliotek mislykkedes",
|
||||
"ToastLibraryCreateSuccess": "Bibliotek \"{0}\" oprettet",
|
||||
"ToastLibraryDeleteFailed": "Mislykkedes sletning af bibliotek",
|
||||
"ToastLibraryDeleteFailed": "Sletning af bibliotek mislykkedes",
|
||||
"ToastLibraryDeleteSuccess": "Bibliotek slettet",
|
||||
"ToastLibraryScanFailedToStart": "Mislykkedes start af skanning",
|
||||
"ToastLibraryScanFailedToStart": "Start af skanning mislykkedes",
|
||||
"ToastLibraryScanStarted": "Biblioteksskanning startet",
|
||||
"ToastLibraryUpdateSuccess": "Bibliotek \"{0}\" opdateret",
|
||||
"ToastMatchAllAuthorsFailed": "Fejlede at matche alle forfattere",
|
||||
@@ -1097,7 +1116,7 @@
|
||||
"ToastUnlinkOpenIdFailed": "Fejlede i af afkoble bruger fra OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Bruger afkoblet fra OpenID",
|
||||
"ToastUploaderFilepathExistsError": "Filsti \"{0}\" findes allerede på serveren",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Genstand \"{0}\" benytter en undermappe af upload stien",
|
||||
"ToastUploaderItemExistsInSubdirectoryError": "Genstand \"{0}\" benytter en undermappe af upload stien.",
|
||||
"ToastUserDeleteFailed": "Mislykkedes sletning af bruger",
|
||||
"ToastUserDeleteSuccess": "Bruger slettet",
|
||||
"ToastUserPasswordChangeSuccess": "Password ændret",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"ButtonBatchEditPopulateMapDetails": "Kartendetails auffüllen",
|
||||
"ButtonBrowseForFolder": "Ordnersuche",
|
||||
"ButtonCancel": "Abbrechen",
|
||||
"ButtonCancelEncode": "Codierung abbrechen",
|
||||
"ButtonCancelEncode": "Konvertierung abbrechen",
|
||||
"ButtonChangeRootPassword": "Hauptpasswort ändern",
|
||||
"ButtonCheckAndDownloadNewEpisodes": "Überprüfe & lade neue Episoden herunter",
|
||||
"ButtonChooseAFolder": "Wähle einen Ordner",
|
||||
@@ -438,6 +438,7 @@
|
||||
"LabelLogLevelWarn": "Warnungen",
|
||||
"LabelLookForNewEpisodesAfterDate": "Suche nach neuen Episoden nach diesem Datum",
|
||||
"LabelLowestPriority": "Niedrigste Priorität",
|
||||
"LabelMatchConfidence": "Vertrauenswert",
|
||||
"LabelMatchExistingUsersBy": "Zuordnen existierender Benutzer mit",
|
||||
"LabelMatchExistingUsersByDescription": "Wird zum Verbinden vorhandener Benutzer verwendet. Sobald die Verbindung hergestellt ist, wird den Benutzern eine eindeutige ID vom SSO-Anbieter zugeordnet",
|
||||
"LabelMaxEpisodesToDownload": "Max. Anzahl an Episoden zum Herunterladen, 0 für unbegrenzte Episoden.",
|
||||
@@ -446,7 +447,7 @@
|
||||
"LabelMaxEpisodesToKeepHelp": "0 setzt keine Begrenzung. Wenn eine neue Episode automatisch heruntergeladen wird, wird die älteste Episode gelöscht, wenn du mehr als X Episoden gespeichert hast. Es wird nur eine Episode pro neuem Download gelöscht.",
|
||||
"LabelMediaPlayer": "Mediaplayer",
|
||||
"LabelMediaType": "Medientyp",
|
||||
"LabelMetaTag": "Meta Schlagwort",
|
||||
"LabelMetaTag": "Meta Tag",
|
||||
"LabelMetaTags": "Meta Tags",
|
||||
"LabelMetadataOrderOfPrecedenceDescription": "Höher priorisierte Quellen für Metadaten überschreiben Metadaten aus Quellen mit niedrigerer Priorität",
|
||||
"LabelMetadataProvider": "Metadatenanbieter",
|
||||
@@ -655,6 +656,7 @@
|
||||
"LabelTheme": "Farbschema",
|
||||
"LabelThemeDark": "Dunkel",
|
||||
"LabelThemeLight": "Hell",
|
||||
"LabelThemeSepia": "Sepia",
|
||||
"LabelTimeBase": "Basiszeit",
|
||||
"LabelTimeDurationXHours": "{0} Stunden",
|
||||
"LabelTimeDurationXMinutes": "{0} Minuten",
|
||||
@@ -723,6 +725,7 @@
|
||||
"MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen",
|
||||
"MessageAppriseDescription": "Um diese Funktion nutzen zu können, musst du eine Instanz von <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann. <br />Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter <code>http://192.168.1.1:8337</code> läuft, würdest du <code>http://192.168.1.1:8337/notify</code> eingeben.",
|
||||
"MessageAsinCheck": "Stellen Sie sicher, dass Sie die ASIN aus der richtigen Audible Region verwenden, nicht Amazon.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "Alte API tokens werden in Zukunft entfernt. Benutze stattdessen <a href=\"/config/api-keys\">API Keys</a>.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Nach dem Speichern muss der Server neugestartet werden um die OIDC Änderungen zu übernehmen.",
|
||||
"MessageAuthenticationSecurityMessage": "Die Anmeldung wurde abgesichert. Benutzersitzungen werden getrennt, alle Benutzer müssen sich erneut anmelden.",
|
||||
"MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in <code>/metadata/items</code> & <code>/metadata/authors</code> gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Medien-Ordnern) gespeichert sind.",
|
||||
|
||||
@@ -418,6 +418,7 @@
|
||||
"LabelLanguages": "Languages",
|
||||
"LabelLastBookAdded": "Last Book Added",
|
||||
"LabelLastBookUpdated": "Last Book Updated",
|
||||
"LabelLastProgressDate": "Last progress: {0}",
|
||||
"LabelLastSeen": "Last Seen",
|
||||
"LabelLastTime": "Last Time",
|
||||
"LabelLastUpdate": "Last Update",
|
||||
@@ -430,6 +431,7 @@
|
||||
"LabelLibraryFilterSublistEmpty": "No {0}",
|
||||
"LabelLibraryItem": "Library Item",
|
||||
"LabelLibraryName": "Library Name",
|
||||
"LabelLibrarySortByProgress": "Progress Updated",
|
||||
"LabelLimit": "Limit",
|
||||
"LabelLineSpacing": "Line spacing",
|
||||
"LabelListenAgain": "Listen Again",
|
||||
@@ -438,6 +440,7 @@
|
||||
"LabelLogLevelWarn": "Warn",
|
||||
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
||||
"LabelLowestPriority": "Lowest Priority",
|
||||
"LabelMatchConfidence": "Confidence",
|
||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
||||
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||
"LabelMaxEpisodesToDownload": "Max # of episodes to download. Use 0 for unlimited.",
|
||||
@@ -655,6 +658,7 @@
|
||||
"LabelTheme": "Theme",
|
||||
"LabelThemeDark": "Dark",
|
||||
"LabelThemeLight": "Light",
|
||||
"LabelThemeSepia": "Sepia",
|
||||
"LabelTimeBase": "Time Base",
|
||||
"LabelTimeDurationXHours": "{0} hours",
|
||||
"LabelTimeDurationXMinutes": "{0} minutes",
|
||||
@@ -801,6 +805,8 @@
|
||||
"MessageFeedURLWillBe": "Feed URL will be {0}",
|
||||
"MessageFetching": "Fetching...",
|
||||
"MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
|
||||
"MessageHeatmapListeningTimeTooltip": "<strong>{0} listening</strong> on {1}",
|
||||
"MessageHeatmapNoListeningSessions": "No listening sessions on {0}",
|
||||
"MessageImportantNotice": "Important Notice!",
|
||||
"MessageInsertChapterBelow": "Insert chapter below",
|
||||
"MessageInvalidAsin": "Invalid ASIN",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "Ajouter",
|
||||
"ButtonAddApiKey": "Ajouter une clé API",
|
||||
"ButtonAddChapters": "Ajouter des chapitres",
|
||||
"ButtonAddDevice": "Ajouter un appareil",
|
||||
"ButtonAddLibrary": "Ajouter une bibliothèque",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "Sélectionner un dossier",
|
||||
"ButtonChooseFiles": "Sélectionner des fichiers",
|
||||
"ButtonClearFilter": "Effacer le filtre",
|
||||
"ButtonClose": "Fermer",
|
||||
"ButtonCloseFeed": "Fermer le flux",
|
||||
"ButtonCloseSession": "Fermer la session",
|
||||
"ButtonCollections": "Collections",
|
||||
@@ -119,6 +121,7 @@
|
||||
"HeaderAccount": "Compte",
|
||||
"HeaderAddCustomMetadataProvider": "Ajouter un fournisseur de métadonnées personnalisé",
|
||||
"HeaderAdvanced": "Avancé",
|
||||
"HeaderApiKeys": "Clés API",
|
||||
"HeaderAppriseNotificationSettings": "Configuration des notifications Apprise",
|
||||
"HeaderAudioTracks": "Pistes audio",
|
||||
"HeaderAudiobookTools": "Outils de gestion de fichiers de livres audio",
|
||||
@@ -162,6 +165,7 @@
|
||||
"HeaderMetadataOrderOfPrecedence": "Ordre de priorité des métadonnées",
|
||||
"HeaderMetadataToEmbed": "Métadonnées à intégrer",
|
||||
"HeaderNewAccount": "Nouveau compte",
|
||||
"HeaderNewApiKey": "Nouvelle clé API",
|
||||
"HeaderNewLibrary": "Nouvelle bibliothèque",
|
||||
"HeaderNotificationCreate": "Créer une notification",
|
||||
"HeaderNotificationUpdate": "Mise à jour de la notification",
|
||||
@@ -177,6 +181,7 @@
|
||||
"HeaderPlaylist": "Liste de lecture",
|
||||
"HeaderPlaylistItems": "Éléments de la liste de lecture",
|
||||
"HeaderPodcastsToAdd": "Podcasts à ajouter",
|
||||
"HeaderPresets": "Préréglages",
|
||||
"HeaderPreviewCover": "Prévisualiser la couverture",
|
||||
"HeaderRSSFeedGeneral": "Détails du flux RSS",
|
||||
"HeaderRSSFeedIsOpen": "Le flux RSS est actif",
|
||||
@@ -205,6 +210,7 @@
|
||||
"HeaderTableOfContents": "Table des matières",
|
||||
"HeaderTools": "Outils",
|
||||
"HeaderUpdateAccount": "Mettre à jour le compte",
|
||||
"HeaderUpdateApiKey": "Mettre à jour la clé API",
|
||||
"HeaderUpdateAuthor": "Mettre à jour l’auteur",
|
||||
"HeaderUpdateDetails": "Mettre à jour les détails",
|
||||
"HeaderUpdateLibrary": "Mettre à jour la bibliothèque",
|
||||
@@ -234,6 +240,10 @@
|
||||
"LabelAllUsersExcludingGuests": "Tous les utilisateurs à l’exception des invités",
|
||||
"LabelAllUsersIncludingGuests": "Tous les utilisateurs, y compris les invités",
|
||||
"LabelAlreadyInYourLibrary": "Déjà dans la bibliothèque",
|
||||
"LabelApiKeyCreated": "La clé API « {0} » a été créée avec succès.",
|
||||
"LabelApiKeyCreatedDescription": "Assurez-vous de copier la clé API maintenant car vous ne pourrez plus la voir.",
|
||||
"LabelApiKeyUser": "Agir au nom de l’utilisateur",
|
||||
"LabelApiKeyUserDescription": "Cette clé API disposera des mêmes autorisations que l’utilisateur pour lequel elle agit. Elle apparaîtra dans les journaux comme si c’était l’utilisateur qui effectuait la requête.",
|
||||
"LabelApiToken": "Token API",
|
||||
"LabelAppend": "Ajouter",
|
||||
"LabelAudioBitrate": "Débit audio (par exemple 128k)",
|
||||
@@ -345,6 +355,10 @@
|
||||
"LabelExample": "Exemple",
|
||||
"LabelExpandSeries": "Développer la série",
|
||||
"LabelExpandSubSeries": "Développer les sous-séries",
|
||||
"LabelExpired": "Expiré",
|
||||
"LabelExpiresAt": "Expire à",
|
||||
"LabelExpiresInSeconds": "Expire dans (secondes)",
|
||||
"LabelExpiresNever": "Jamais",
|
||||
"LabelExplicit": "Restriction",
|
||||
"LabelExplicitChecked": "Explicite (vérifié)",
|
||||
"LabelExplicitUnchecked": "Non explicite (non vérifié)",
|
||||
@@ -454,6 +468,7 @@
|
||||
"LabelNewestEpisodes": "Épisodes récents",
|
||||
"LabelNextBackupDate": "Date de la prochaine sauvegarde",
|
||||
"LabelNextScheduledRun": "Prochain lancement prévu",
|
||||
"LabelNoApiKeys": "Aucune clé API",
|
||||
"LabelNoCustomMetadataProviders": "Aucun fournisseurs de métadonnées personnalisés",
|
||||
"LabelNoEpisodesSelected": "Aucun épisode sélectionné",
|
||||
"LabelNotFinished": "Non terminé",
|
||||
@@ -543,6 +558,7 @@
|
||||
"LabelSelectAll": "Tout sélectionner",
|
||||
"LabelSelectAllEpisodes": "Sélectionner tous les épisodes",
|
||||
"LabelSelectEpisodesShowing": "Sélectionner {0} épisode(s) en cours",
|
||||
"LabelSelectUser": "Sélectionner l’utilisateur",
|
||||
"LabelSelectUsers": "Sélectionner les utilisateurs",
|
||||
"LabelSendEbookToDevice": "Envoyer le livre numérique à…",
|
||||
"LabelSequence": "Séquence",
|
||||
@@ -707,7 +723,9 @@
|
||||
"MessageAddToPlayerQueue": "Ajouter en file d’attente",
|
||||
"MessageAppriseDescription": "Nécessite une instance d’<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes.<br />L’URL de l’API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Assurez-vous d’utiliser l’ASIN de la bonne région Audible, et non d’Amazon.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "Les jetons d’API hérités seront supprimés à l’avenir. Utilisez plutôt les <a href=\"/config/api-keys\">clés API</a>.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Redémarrez votre serveur après avoir enregistré pour appliquer les modifications OIDC.",
|
||||
"MessageAuthenticationSecurityMessage": "L’authentification a été améliorée pour plus de sécurité. Tous les utilisateurs doivent se reconnecter.",
|
||||
"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>n’incluent pas</strong> les fichiers stockés dans les dossiers de votre bibliothèque.",
|
||||
"MessageBackupsLocationEditNote": "Remarque : Mettre à jour l'emplacement de sauvegarde ne déplacera pas ou ne modifiera pas les sauvegardes existantes",
|
||||
"MessageBackupsLocationNoEditNote": "Remarque : l’emplacement de sauvegarde est défini via une variable d’environnement et ne peut pas être modifié ici.",
|
||||
@@ -729,6 +747,7 @@
|
||||
"MessageChaptersNotFound": "Chapitres non trouvés",
|
||||
"MessageCheckingCron": "Vérification du cron…",
|
||||
"MessageConfirmCloseFeed": "Êtes-vous sûr·e de vouloir fermer ce flux ?",
|
||||
"MessageConfirmDeleteApiKey": "Êtes-vous sûr de vouloir supprimer la clé API « {0} » ?",
|
||||
"MessageConfirmDeleteBackup": "Êtes-vous sûr·e de vouloir supprimer la sauvegarde de « {0} » ?",
|
||||
"MessageConfirmDeleteDevice": "Êtes-vous sûr·e de vouloir supprimer la liseuse « {0} » ?",
|
||||
"MessageConfirmDeleteFile": "Cela supprimera le fichier de votre système de fichiers. Êtes-vous sûr ?",
|
||||
@@ -756,6 +775,7 @@
|
||||
"MessageConfirmRemoveAuthor": "Êtes-vous sûr·e de vouloir supprimer l’auteur « {0} » ?",
|
||||
"MessageConfirmRemoveCollection": "Êtes-vous sûr·e de vouloir supprimer la collection « {0} » ?",
|
||||
"MessageConfirmRemoveEpisode": "Êtes-vous sûr·e de vouloir supprimer l’épisode « {0} » ?",
|
||||
"MessageConfirmRemoveEpisodeNote": "Remarque : cela ne supprime pas le fichier audio, sauf si vous activez « Supprimer définitivement le fichier »",
|
||||
"MessageConfirmRemoveEpisodes": "Êtes-vous sûr·e de vouloir supprimer {0} épisodes ?",
|
||||
"MessageConfirmRemoveListeningSessions": "Êtes-vous sûr·e de vouloir supprimer {0} sessions d’écoute ?",
|
||||
"MessageConfirmRemoveMetadataFiles": "Êtes-vous sûr·e de vouloir supprimer tous les fichiers « metatadata.{0} » des dossiers d’éléments de votre bibliothèque ?",
|
||||
@@ -917,6 +937,8 @@
|
||||
"NotificationOnBackupCompletedDescription": "Déclenché lorsqu’une sauvegarde est terminée",
|
||||
"NotificationOnBackupFailedDescription": "Déclenché lorsqu'une sauvegarde échoue",
|
||||
"NotificationOnEpisodeDownloadedDescription": "Déclenché lorsqu’un épisode de podcast est téléchargé automatiquement",
|
||||
"NotificationOnRSSFeedDisabledDescription": "Déclenché lorsque les téléchargements automatiques d’épisodes sont désactivés en raison d’un trop grand nombre de tentatives infructueuses",
|
||||
"NotificationOnRSSFeedFailedDescription": "Déclenché lorsque la demande de flux RSS échoue pour un téléchargement automatique d’épisode",
|
||||
"NotificationOnTestDescription": "Événement pour tester le système de notification",
|
||||
"PlaceholderNewCollection": "Nom de la nouvelle collection",
|
||||
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
|
||||
@@ -997,6 +1019,8 @@
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "File d’attente de téléchargement des épisodes effacée",
|
||||
"ToastEpisodeUpdateSuccess": "{0} épisodes mis à jour",
|
||||
"ToastErrorCannotShare": "Impossible de partager nativement sur cet appareil",
|
||||
"ToastFailedToCreate": "Échec de la création",
|
||||
"ToastFailedToDelete": "Échec de la suppression",
|
||||
"ToastFailedToLoadData": "Échec du chargement des données",
|
||||
"ToastFailedToMatch": "Échec de la correspondance",
|
||||
"ToastFailedToShare": "Échec du partage",
|
||||
@@ -1028,6 +1052,7 @@
|
||||
"ToastMustHaveAtLeastOnePath": "Doit avoir au moins un chemin",
|
||||
"ToastNameEmailRequired": "Le nom et le courriel sont requis",
|
||||
"ToastNameRequired": "Le nom est requis",
|
||||
"ToastNewApiKeyUserError": "Vous devez sélectionner un utilisateur",
|
||||
"ToastNewEpisodesFound": "{0} nouveaux épisodes trouvés",
|
||||
"ToastNewUserCreatedFailed": "La création du compte à échouée : « {0} »",
|
||||
"ToastNewUserCreatedSuccess": "Nouveau compte créé",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "הוסף",
|
||||
"ButtonAddApiKey": "הוסף מפתח ממשק תכנות (API)",
|
||||
"ButtonAddChapters": "הוסף פרקים",
|
||||
"ButtonAddDevice": "הוסף התקן",
|
||||
"ButtonAddLibrary": "הוסף ספרייה",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "בחר תיקייה",
|
||||
"ButtonChooseFiles": "בחר קבצים",
|
||||
"ButtonClearFilter": "נקה סינון",
|
||||
"ButtonClose": "סגור",
|
||||
"ButtonCloseFeed": "סגור ערוץ",
|
||||
"ButtonCloseSession": "סגור סשן פתוח",
|
||||
"ButtonCollections": "אוספים",
|
||||
|
||||
@@ -723,6 +723,7 @@
|
||||
"MessageAddToPlayerQueue": "Dodaj u redoslijed izvođenja",
|
||||
"MessageAppriseDescription": "Da biste se koristili ovom značajkom, treba vam instanca <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API-ja</a> ili API koji može rukovati istom vrstom zahtjeva.<br />The Adresa Apprise API-ja treba biti puna URL putanja za slanje obavijesti, npr. ako vam se API instanca poslužuje na adresi <code>http://192.168.1.1:8337</code> trebate upisati <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Upišite ASIN iz odgovarajuće Audibleove regije, ne s Amazonov.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "Starije API tokene ćemo ukloniti. Umjesto njih, koristite se <a href=\"/config/api-keys\">API ključevima</a> .",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Ponovno pokrenite poslužitelj da biste primijenili OIDC promjene.",
|
||||
"MessageAuthenticationSecurityMessage": "Provjera autentičnosti poboljšana je radi sigurnosti. Svi se korisnici moraju ponovno prijaviti.",
|
||||
"MessageBackupsDescription": "Sigurnosne kopije sadrže korisnike, korisnikov napredak medija, pojedinosti knjižničke građe, postavke poslužitelja i slike koje se spremaju u <code>/metadata/items</code> & <code>/metadata/authors</code>. Sigurnosne kopije ne sadrže niti jednu datoteku iz mapa knjižnice.",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "Hozzáadás",
|
||||
"ButtonAddApiKey": "API kulcs hozzáadása",
|
||||
"ButtonAddChapters": "Fejezetek hozzáadása",
|
||||
"ButtonAddDevice": "Eszköz hozzáadása",
|
||||
"ButtonAddLibrary": "Könyvtár hozzáadása",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "Válassz egy mappát",
|
||||
"ButtonChooseFiles": "Fájlok kiválasztása",
|
||||
"ButtonClearFilter": "Szűrő törlése",
|
||||
"ButtonClose": "Bezár",
|
||||
"ButtonCloseFeed": "Hírcsatorna bezárása",
|
||||
"ButtonCloseSession": "Nyitott munkamenet bezárása",
|
||||
"ButtonCollections": "Gyűjtemények",
|
||||
@@ -119,6 +121,7 @@
|
||||
"HeaderAccount": "Fiók",
|
||||
"HeaderAddCustomMetadataProvider": "Egyedi metaadat szolgáltató hozzáadása",
|
||||
"HeaderAdvanced": "Haladó",
|
||||
"HeaderApiKeys": "API kulcsok",
|
||||
"HeaderAppriseNotificationSettings": "Apprise értesítési beállítások",
|
||||
"HeaderAudioTracks": "Audiósávok",
|
||||
"HeaderAudiobookTools": "Hangoskönyv fájlkezelő eszközök",
|
||||
@@ -162,6 +165,7 @@
|
||||
"HeaderMetadataOrderOfPrecedence": "Metaadatok előnyben részesítési sorrendje",
|
||||
"HeaderMetadataToEmbed": "Beágyazandó metaadatok",
|
||||
"HeaderNewAccount": "Új fiók",
|
||||
"HeaderNewApiKey": "Új API kulcs",
|
||||
"HeaderNewLibrary": "Új könyvtár",
|
||||
"HeaderNotificationCreate": "Értesítés készítése",
|
||||
"HeaderNotificationUpdate": "Értesítés frissítése",
|
||||
@@ -206,6 +210,7 @@
|
||||
"HeaderTableOfContents": "Tartalomjegyzék",
|
||||
"HeaderTools": "Eszközök",
|
||||
"HeaderUpdateAccount": "Fiók frissítése",
|
||||
"HeaderUpdateApiKey": "API kulcs frissítése",
|
||||
"HeaderUpdateAuthor": "Szerző frissítése",
|
||||
"HeaderUpdateDetails": "Részletek frissítése",
|
||||
"HeaderUpdateLibrary": "Könyvtár frissítése",
|
||||
@@ -235,6 +240,10 @@
|
||||
"LabelAllUsersExcludingGuests": "Minden felhasználó, vendégek kivételével",
|
||||
"LabelAllUsersIncludingGuests": "Minden felhasználó, beleértve a vendégeket is",
|
||||
"LabelAlreadyInYourLibrary": "Már a könyvtárában van",
|
||||
"LabelApiKeyCreated": "\"{0}\" API kulcs sikeresen létrehozva.",
|
||||
"LabelApiKeyCreatedDescription": "Feltétlenül másolja le az API kulcsot, mert később már nem fogja látni.",
|
||||
"LabelApiKeyUser": "Felhasználó nevében eljárva",
|
||||
"LabelApiKeyUserDescription": "Ez az API-kulcs ugyanazokkal a jogosultságokkal rendelkezik, mint az a felhasználó, akinek a nevében működik. A naplófájlokban ez úgy jelenik meg, mintha a felhasználó maga küldte volna a kérést.",
|
||||
"LabelApiToken": "API Token",
|
||||
"LabelAppend": "Hozzáfűzés",
|
||||
"LabelAudioBitrate": "Audió bitráta (pl.128k)",
|
||||
@@ -264,7 +273,7 @@
|
||||
"LabelBonus": "Bónusz",
|
||||
"LabelBooks": "Könyvek",
|
||||
"LabelButtonText": "Gomb szövege",
|
||||
"LabelByAuthor": "{} által",
|
||||
"LabelByAuthor": "{0} által",
|
||||
"LabelChangePassword": "Jelszó megváltoztatása",
|
||||
"LabelChannels": "Csatornák",
|
||||
"LabelChapterCount": "{0} Fejezet",
|
||||
@@ -346,6 +355,10 @@
|
||||
"LabelExample": "Példa",
|
||||
"LabelExpandSeries": "Sorozat kinyitása",
|
||||
"LabelExpandSubSeries": "Alsorozat kinyitása",
|
||||
"LabelExpired": "Lejárt",
|
||||
"LabelExpiresAt": "Lejár",
|
||||
"LabelExpiresInSeconds": "Lejár (másodpercben)",
|
||||
"LabelExpiresNever": "Soha",
|
||||
"LabelExplicit": "Szókimondó",
|
||||
"LabelExplicitChecked": "Explicit (ellenőrizve)",
|
||||
"LabelExplicitUnchecked": "Nem explicit (nem ellenőrzött)",
|
||||
@@ -405,6 +418,7 @@
|
||||
"LabelLanguages": "Nyelvek",
|
||||
"LabelLastBookAdded": "Utolsó hozzáadott könyv",
|
||||
"LabelLastBookUpdated": "Utolsó frissített könyv",
|
||||
"LabelLastProgressDate": "Legutóbbi haladás: {0}",
|
||||
"LabelLastSeen": "Utolsó látogatás",
|
||||
"LabelLastTime": "Utolsó alkalom",
|
||||
"LabelLastUpdate": "Utolsó frissítés",
|
||||
@@ -417,6 +431,7 @@
|
||||
"LabelLibraryFilterSublistEmpty": "Nem {0}",
|
||||
"LabelLibraryItem": "Könyvtári elem",
|
||||
"LabelLibraryName": "Könyvtár neve",
|
||||
"LabelLibrarySortByProgress": "Haladás frissítve",
|
||||
"LabelLimit": "Korlát",
|
||||
"LabelLineSpacing": "Sorköz",
|
||||
"LabelListenAgain": "Újrahallgatás",
|
||||
@@ -425,6 +440,7 @@
|
||||
"LabelLogLevelWarn": "Figyelmeztetés",
|
||||
"LabelLookForNewEpisodesAfterDate": "Új epizódok keresése ezen a dátum után",
|
||||
"LabelLowestPriority": "Legalacsonyabb prioritás",
|
||||
"LabelMatchConfidence": "Bizalom",
|
||||
"LabelMatchExistingUsersBy": "Meglévő felhasználók egyeztetése",
|
||||
"LabelMatchExistingUsersByDescription": "Meglévő felhasználók összekapcsolására használt. Egyszer összekapcsolva, a felhasználók egyedülálló azonosítóval lesznek egyeztetve az Ön SSO szolgáltatójától",
|
||||
"LabelMaxEpisodesToDownload": "Letölthető epizódok maximális száma. Használja a 0-t a korlátlan letöltéshez.",
|
||||
@@ -455,6 +471,7 @@
|
||||
"LabelNewestEpisodes": "Legújabb epizódok",
|
||||
"LabelNextBackupDate": "Következő biztonsági másolat dátuma",
|
||||
"LabelNextScheduledRun": "Következő ütemezett futtatás",
|
||||
"LabelNoApiKeys": "Nincs API kulcs",
|
||||
"LabelNoCustomMetadataProviders": "Nincsenek egyedi metaadat szolgáltatók",
|
||||
"LabelNoEpisodesSelected": "Nincsenek kiválasztott epizódok",
|
||||
"LabelNotFinished": "Nem befejezett",
|
||||
@@ -544,6 +561,7 @@
|
||||
"LabelSelectAll": "Minden kiválasztása",
|
||||
"LabelSelectAllEpisodes": "Összes epizód kiválasztása",
|
||||
"LabelSelectEpisodesShowing": "Kiválasztás {0} megjelenített epizód",
|
||||
"LabelSelectUser": "Felhasználó kiválasztása",
|
||||
"LabelSelectUsers": "Felhasználók kiválasztása",
|
||||
"LabelSendEbookToDevice": "E-könyv küldése...",
|
||||
"LabelSequence": "Sorozat",
|
||||
@@ -640,6 +658,7 @@
|
||||
"LabelTheme": "Téma",
|
||||
"LabelThemeDark": "Sötét",
|
||||
"LabelThemeLight": "Világos",
|
||||
"LabelThemeSepia": "Szépia",
|
||||
"LabelTimeBase": "Időalap",
|
||||
"LabelTimeDurationXHours": "{0} óra",
|
||||
"LabelTimeDurationXMinutes": "{0} perc",
|
||||
@@ -708,7 +727,9 @@
|
||||
"MessageAddToPlayerQueue": "Hozzáadás a lejátszó sorhoz",
|
||||
"MessageAppriseDescription": "Ennek a funkció használatához futtatnia kell egy <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> példányt vagy egy olyan API-t, amely kezeli ezeket a kéréseket. <br />Az Apprise API URL-nek a teljes URL útvonalat kell tartalmaznia az értesítés elküldéséhez, például, ha az API példánya a <code>http://192.168.1.1:8337</code> címen szolgáltatva, akkor <code>http://192.168.1.1:8337/notify</code> értéket kell megadnia.",
|
||||
"MessageAsinCheck": "Győződjön meg róla, hogy az ASIN-t a megfelelő Audible régióból használja, nem az Amazonból.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "A régi API-tokenek a jövőben eltávolításra kerülnek. Helyette használja az <a href=\"/config/api-keys\">API-kulcsokat</a>.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "A mentés után indítsa újra a szervert az OIDC módosítások alkalmazásához.",
|
||||
"MessageAuthenticationSecurityMessage": "A biztonság érdekében a hitelesítés folyamatát továbbfejlesztettük. Minden felhasználónak újra be kell jelentkeznie.",
|
||||
"MessageBackupsDescription": "A biztonsági másolatok tartalmazzák a felhasználókat, a felhasználói haladást, a könyvtári elem részleteit, a szerver beállításait és a képeket, amelyek a <code>/metadata/items</code> és <code>/metadata/authors</code> mappákban vannak tárolva. A biztonsági másolatok <strong>nem</strong> tartalmazzák a könyvtári mappákban tárolt fájlokat.",
|
||||
"MessageBackupsLocationEditNote": "Megjegyzés: A biztonsági mentés helyének frissítése nem mozgatja vagy módosítja a meglévő biztonsági mentéseket",
|
||||
"MessageBackupsLocationNoEditNote": "Megjegyzés: A biztonsági mentés helye egy környezeti változóval van beállítva, és itt nem módosítható.",
|
||||
@@ -730,6 +751,7 @@
|
||||
"MessageChaptersNotFound": "Fejezetek nem találhatók",
|
||||
"MessageCheckingCron": "Cron ellenőrzése...",
|
||||
"MessageConfirmCloseFeed": "Biztosan be szeretné zárni ezt a hírcsatornát?",
|
||||
"MessageConfirmDeleteApiKey": "Biztosan törölni szeretné az \"{0}\" API kulcsot?",
|
||||
"MessageConfirmDeleteBackup": "Biztosan törölni szeretné a(z) {0} biztonsági másolatot?",
|
||||
"MessageConfirmDeleteDevice": "Biztos, hogy törölni szeretné a „{0}” e-olvasó eszközt?",
|
||||
"MessageConfirmDeleteFile": "Ez törölni fogja a fájlt a fájlrendszerből. Biztos benne?",
|
||||
@@ -1001,6 +1023,8 @@
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "Epizód letöltési várólista törölve",
|
||||
"ToastEpisodeUpdateSuccess": "{0} epizód frissítve",
|
||||
"ToastErrorCannotShare": "Ezen az eszközön nem lehet natívan megosztani",
|
||||
"ToastFailedToCreate": "Sikertelen létrehozás",
|
||||
"ToastFailedToDelete": "Sikertelen törlés",
|
||||
"ToastFailedToLoadData": "Sikertelen adatbetöltés",
|
||||
"ToastFailedToMatch": "Nem sikerült egyezőséget találni",
|
||||
"ToastFailedToShare": "Nem sikerült megosztani",
|
||||
@@ -1032,6 +1056,7 @@
|
||||
"ToastMustHaveAtLeastOnePath": "Legalább egy elérési útvonalnak kell lennie",
|
||||
"ToastNameEmailRequired": "Név és e-mail cím megadása kötelező",
|
||||
"ToastNameRequired": "A név megadása kötelező",
|
||||
"ToastNewApiKeyUserError": "Ki kell választani egy felhasználót",
|
||||
"ToastNewEpisodesFound": "{0} új epizód",
|
||||
"ToastNewUserCreatedFailed": "Nem sikerült a fiókot létrehozni: „{0}”",
|
||||
"ToastNewUserCreatedSuccess": "Új fiók létrehozva",
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"ButtonAdd": "Aggiungi",
|
||||
"ButtonAddApiKey": "Aggiungi chiave API",
|
||||
"ButtonAddChapters": "Aggiungi Capitoli",
|
||||
"ButtonAddDevice": "Aggiungi Dispositivo",
|
||||
"ButtonAddLibrary": "Aggiungi Libreria",
|
||||
"ButtonAddPodcasts": "Aggiungi Podcast",
|
||||
"ButtonAddUser": "Aggiungi User",
|
||||
"ButtonAddUser": "Aggiungi Utente",
|
||||
"ButtonAddYourFirstLibrary": "Aggiungi la tua prima libreria",
|
||||
"ButtonApply": "Applica",
|
||||
"ButtonApplyChapters": "Applica",
|
||||
"ButtonApplyChapters": "Applica Capitoli",
|
||||
"ButtonAuthors": "Autori",
|
||||
"ButtonBack": "Indietro",
|
||||
"ButtonBatchEditPopulateFromExisting": "Popola da esistente",
|
||||
"ButtonBatchEditPopulateMapDetails": "Inserisci i dettagli della mappa",
|
||||
"ButtonBrowseForFolder": "Per Cartella",
|
||||
"ButtonBrowseForFolder": "Sfoglia per Cartella",
|
||||
"ButtonCancel": "Annulla",
|
||||
"ButtonCancelEncode": "Ferma la codifica",
|
||||
"ButtonChangeRootPassword": "Cambia la Password di root",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "Seleziona la Cartella",
|
||||
"ButtonChooseFiles": "Seleziona i File",
|
||||
"ButtonClearFilter": "Elimina filtri",
|
||||
"ButtonClose": "Chiudi",
|
||||
"ButtonCloseFeed": "Chiudi flusso",
|
||||
"ButtonCloseSession": "Chiudi la sessione aperta",
|
||||
"ButtonCollections": "Raccolte",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "Toevoegen",
|
||||
"ButtonAddApiKey": "API Key toevoegen",
|
||||
"ButtonAddChapters": "Hoofdstukken toevoegen",
|
||||
"ButtonAddDevice": "Toestel toevoegen",
|
||||
"ButtonAddLibrary": "Bibliotheek toevoegen",
|
||||
@@ -10,7 +11,7 @@
|
||||
"ButtonApplyChapters": "Hoofdstukken toepassen",
|
||||
"ButtonAuthors": "Auteurs",
|
||||
"ButtonBack": "Terug",
|
||||
"ButtonBatchEditPopulateFromExisting": "Vullen vanuit bestaande",
|
||||
"ButtonBatchEditPopulateFromExisting": "Vul in met huidige",
|
||||
"ButtonBatchEditPopulateMapDetails": "Kaartgegevens invullen",
|
||||
"ButtonBrowseForFolder": "Bladeren naar map",
|
||||
"ButtonCancel": "Annuleren",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "Map kiezen",
|
||||
"ButtonChooseFiles": "Bestanden kiezen",
|
||||
"ButtonClearFilter": "Filter verwijderen",
|
||||
"ButtonClose": "Sluiten",
|
||||
"ButtonCloseFeed": "Feed sluiten",
|
||||
"ButtonCloseSession": "Sluit Sessie",
|
||||
"ButtonCollections": "Collecties",
|
||||
@@ -32,8 +34,8 @@
|
||||
"ButtonEditChapters": "Hoofdstukken wijzigen",
|
||||
"ButtonEditPodcast": "Podcast wijzigen",
|
||||
"ButtonEnable": "Aanzetten",
|
||||
"ButtonFireAndFail": "Fire and Fail",
|
||||
"ButtonFireOnTest": "Fire onTest event",
|
||||
"ButtonFireAndFail": "Uitvoeren en falen",
|
||||
"ButtonFireOnTest": "Test-Event uitvoeren",
|
||||
"ButtonForceReScan": "Forceer nieuwe scan",
|
||||
"ButtonFullPath": "Volledig pad",
|
||||
"ButtonHide": "Verberg",
|
||||
@@ -119,6 +121,7 @@
|
||||
"HeaderAccount": "Account",
|
||||
"HeaderAddCustomMetadataProvider": "Aangepaste Metadataprovider Toevoegen",
|
||||
"HeaderAdvanced": "Geavanceerd",
|
||||
"HeaderApiKeys": "API Key",
|
||||
"HeaderAppriseNotificationSettings": "Apprise-notificatie instellingen",
|
||||
"HeaderAudioTracks": "Audiotracks",
|
||||
"HeaderAudiobookTools": "Audioboekbestandbeheer tools",
|
||||
@@ -162,6 +165,7 @@
|
||||
"HeaderMetadataOrderOfPrecedence": "Metadata volgorde",
|
||||
"HeaderMetadataToEmbed": "In te sluiten metadata",
|
||||
"HeaderNewAccount": "Nieuwe account",
|
||||
"HeaderNewApiKey": "Nieuwe API Key",
|
||||
"HeaderNewLibrary": "Nieuwe bibliotheek",
|
||||
"HeaderNotificationCreate": "Notificatie Aanmaken",
|
||||
"HeaderNotificationUpdate": "Update Notificatie",
|
||||
@@ -206,6 +210,7 @@
|
||||
"HeaderTableOfContents": "Inhoudsopgave",
|
||||
"HeaderTools": "Gereedschap",
|
||||
"HeaderUpdateAccount": "Account bijwerken",
|
||||
"HeaderUpdateApiKey": "API Key updaten",
|
||||
"HeaderUpdateAuthor": "Auteur bijwerken",
|
||||
"HeaderUpdateDetails": "Details bijwerken",
|
||||
"HeaderUpdateLibrary": "Bibliotheek bijwerken",
|
||||
@@ -235,6 +240,10 @@
|
||||
"LabelAllUsersExcludingGuests": "Alle gebruikers exclusief gasten",
|
||||
"LabelAllUsersIncludingGuests": "Alle gebruikers inclusief gasten",
|
||||
"LabelAlreadyInYourLibrary": "Reeds in je bibliotheek",
|
||||
"LabelApiKeyCreated": "API Key \"{0}\" succesvol aangemaakt.",
|
||||
"LabelApiKeyCreatedDescription": "Zorg ervoor dat je de API key nu kopieert, je kan deze later niet meer bekijken.",
|
||||
"LabelApiKeyUser": "Uitvoeren namens de gebruiker",
|
||||
"LabelApiKeyUserDescription": "Deze API key krijgt dezelfde rechten als de gebruiker waar deze zich tot voordoet. In de logs zullen de requests ook op naam van de gebruiker staan.",
|
||||
"LabelApiToken": "API Token",
|
||||
"LabelAppend": "Achteraan toevoegen",
|
||||
"LabelAudioBitrate": "Audio Bitrate (b.v. 128k)",
|
||||
@@ -312,7 +321,7 @@
|
||||
"LabelDurationComparisonLonger": "({0} langer)",
|
||||
"LabelDurationComparisonShorter": "({0} korter)",
|
||||
"LabelDurationFound": "Gevonden duur:",
|
||||
"LabelEbook": "Ebook",
|
||||
"LabelEbook": "E-boek",
|
||||
"LabelEbooks": "Eboeken",
|
||||
"LabelEdit": "Wijzig",
|
||||
"LabelEmail": "Email",
|
||||
@@ -346,6 +355,10 @@
|
||||
"LabelExample": "Voorbeeld",
|
||||
"LabelExpandSeries": "Serie Uitvouwen",
|
||||
"LabelExpandSubSeries": "Subserie Uitvouwen",
|
||||
"LabelExpired": "Verlopen",
|
||||
"LabelExpiresAt": "Loopt af op",
|
||||
"LabelExpiresInSeconds": "Loopt af in (seconds) seconden",
|
||||
"LabelExpiresNever": "Nooit",
|
||||
"LabelExplicit": "Expliciet",
|
||||
"LabelExplicitChecked": "Expliciet (gechecked)",
|
||||
"LabelExplicitUnchecked": "Niet Expliciet (niet gechecked)",
|
||||
@@ -364,7 +377,7 @@
|
||||
"LabelFolder": "Map",
|
||||
"LabelFolders": "Mappen",
|
||||
"LabelFontBold": "Vetgedrukt",
|
||||
"LabelFontBoldness": "Font Boldness",
|
||||
"LabelFontBoldness": "Lettertype Dikte",
|
||||
"LabelFontFamily": "Lettertypefamilie",
|
||||
"LabelFontItalic": "Cursief",
|
||||
"LabelFontScale": "Lettertype schaal",
|
||||
@@ -372,7 +385,7 @@
|
||||
"LabelFormat": "Formaat",
|
||||
"LabelFull": "Vol",
|
||||
"LabelGenre": "Genre",
|
||||
"LabelGenres": "Genres",
|
||||
"LabelGenres": "Categorieën",
|
||||
"LabelHardDeleteFile": "Bestand permanent verwijderen",
|
||||
"LabelHasEbook": "Heeft Ebook",
|
||||
"LabelHasSupplementaryEbook": "Heeft aanvullend Ebook",
|
||||
@@ -405,6 +418,7 @@
|
||||
"LabelLanguages": "Talen",
|
||||
"LabelLastBookAdded": "Laatst toegevoegde boek",
|
||||
"LabelLastBookUpdated": "Laatst bijgewerkte boek",
|
||||
"LabelLastProgressDate": "Laatste vooruitgang: {0}",
|
||||
"LabelLastSeen": "Laatst gezien",
|
||||
"LabelLastTime": "Laatste keer",
|
||||
"LabelLastUpdate": "Laatste wijziging",
|
||||
@@ -417,6 +431,7 @@
|
||||
"LabelLibraryFilterSublistEmpty": "Nee {0}",
|
||||
"LabelLibraryItem": "Bibliotheekonderdeel",
|
||||
"LabelLibraryName": "Bibliotheeknaam",
|
||||
"LabelLibrarySortByProgress": "Voortuigang geüpdatet",
|
||||
"LabelLimit": "Limiet",
|
||||
"LabelLineSpacing": "Regelruimte",
|
||||
"LabelListenAgain": "Opnieuw Beluisteren",
|
||||
@@ -425,6 +440,7 @@
|
||||
"LabelLogLevelWarn": "Waarschuwing",
|
||||
"LabelLookForNewEpisodesAfterDate": "Zoek naar nieuwe afleveringen na deze datum",
|
||||
"LabelLowestPriority": "Laagste Prioriteit",
|
||||
"LabelMatchConfidence": "Vertrouwen",
|
||||
"LabelMatchExistingUsersBy": "Bestaande gebruikers matchen op",
|
||||
"LabelMatchExistingUsersByDescription": "Wordt gebruikt om bestaande gebruikers te verbinden. Zodra ze verbonden zijn, worden gebruikers gekoppeld aan een unieke id van uw SSO-provider",
|
||||
"LabelMaxEpisodesToDownload": "Maximale # afleveringen om te downloaden. Gebruik 0 voor ongelimiteerd.",
|
||||
@@ -455,6 +471,7 @@
|
||||
"LabelNewestEpisodes": "Nieuwste Afleveringen",
|
||||
"LabelNextBackupDate": "Volgende back-up datum",
|
||||
"LabelNextScheduledRun": "Volgende geplande run",
|
||||
"LabelNoApiKeys": "Geen API keys",
|
||||
"LabelNoCustomMetadataProviders": "Geen custom metadata bronnen",
|
||||
"LabelNoEpisodesSelected": "Geen afleveringen geselecteerd",
|
||||
"LabelNotFinished": "Niet Voltooid",
|
||||
@@ -514,7 +531,7 @@
|
||||
"LabelPublishers": "Uitgevers",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Aangepast e-mailadres eigenaar",
|
||||
"LabelRSSFeedCustomOwnerName": "Aangepaste naam eigenaar",
|
||||
"LabelRSSFeedOpen": "RSS Feed Open",
|
||||
"LabelRSSFeedOpen": "RSS Feed open",
|
||||
"LabelRSSFeedPreventIndexing": "Voorkom indexering",
|
||||
"LabelRSSFeedSlug": "RSS-feed slug",
|
||||
"LabelRSSFeedURL": "RSS-feed URL",
|
||||
@@ -544,6 +561,7 @@
|
||||
"LabelSelectAll": "Alles selecteren",
|
||||
"LabelSelectAllEpisodes": "Selecteer alle afleveringen",
|
||||
"LabelSelectEpisodesShowing": "Selecteer {0} afleveringen laten zien",
|
||||
"LabelSelectUser": "Gebruiker kiezen",
|
||||
"LabelSelectUsers": "Selecteer gebruikers",
|
||||
"LabelSendEbookToDevice": "Stuur ebook naar...",
|
||||
"LabelSequence": "Sequentie",
|
||||
@@ -636,10 +654,11 @@
|
||||
"LabelTextEditorBulletedList": "Opgesomde lijst",
|
||||
"LabelTextEditorLink": "Link",
|
||||
"LabelTextEditorNumberedList": "Genummerde lijst",
|
||||
"LabelTextEditorUnlink": "Unlink",
|
||||
"LabelTextEditorUnlink": "Ontkoppelen",
|
||||
"LabelTheme": "Thema",
|
||||
"LabelThemeDark": "Donker",
|
||||
"LabelThemeLight": "Licht",
|
||||
"LabelThemeSepia": "Sepia",
|
||||
"LabelTimeBase": "Tijdsbasis",
|
||||
"LabelTimeDurationXHours": "{0} Uren",
|
||||
"LabelTimeDurationXMinutes": "{0} minuten",
|
||||
@@ -708,7 +727,9 @@
|
||||
"MessageAddToPlayerQueue": "Toevoegen aan wachtrij",
|
||||
"MessageAppriseDescription": "Om deze functie te gebruiken heb je een draaiende instantie van <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> nodig of een api die dezelfde requests afhandelt. <br />De Apprise API Url moet het volledige URL-pad zijn om de notificatie te verzenden, b.v., als je API-instantie draait op <code>http://192.168.1.1:8337</code> dan zou je <code>http://192.168.1.1:8337/notify</code> gebruiken.",
|
||||
"MessageAsinCheck": "Zorg ervoor dat u de ASIN van de juiste Audible-regio gebruikt, niet die van Amazon.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "API tokens zijn verouderd en worden in de toekomst niet meer ondersteund. Gebruik inplaats daarvan <a href=\"/config/api-keys\">API keys</a> .",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Start uw server opnieuw op nadat u het opslaan hebt uitgevoerd, om de OIDC-wijzigingen toe te passen.",
|
||||
"MessageAuthenticationSecurityMessage": "Authenticatie is verbeterd omwille van veiligheid. Alle gebruikers moeten opnieuw inloggen.",
|
||||
"MessageBackupsDescription": "Back-ups omvatten gebruikers, gebruikers' voortgang, bibliotheekonderdeeldetails, serverinstellingen en afbeeldingen bewaard in <code>/metadata/items</code> & <code>/metadata/authors</code>. Back-ups <strong>bevatten niet</strong> de bestanden bewaard in je bibliotheekmappen.",
|
||||
"MessageBackupsLocationEditNote": "Let op: het bijwerken van de back-uplocatie zal bestaande back-ups niet verplaatsen of wijzigen",
|
||||
"MessageBackupsLocationNoEditNote": "Let op: De back-uplocatie wordt ingesteld via een omgevingsvariabele en kan hier niet worden gewijzigd.",
|
||||
@@ -730,6 +751,7 @@
|
||||
"MessageChaptersNotFound": "Hoofdstukken niet gevonden",
|
||||
"MessageCheckingCron": "Cron aan het checken...",
|
||||
"MessageConfirmCloseFeed": "Ben je zeker dat je deze feed wil sluiten?",
|
||||
"MessageConfirmDeleteApiKey": "Weet je zeker dat je deze API key \"{0}\" wil verwijderen?",
|
||||
"MessageConfirmDeleteBackup": "Weet je zeker dat je de backup voor {0} wil verwijderen?",
|
||||
"MessageConfirmDeleteDevice": "Ben je zeker dat je e-reader apparaat \"{0}\" wil verwijderen?",
|
||||
"MessageConfirmDeleteFile": "Dit verwijdert het bestand uit het bestandssysteem. Weet je het zeker?",
|
||||
@@ -762,7 +784,7 @@
|
||||
"MessageConfirmRemoveListeningSessions": "Weet je zeker dat je {0} luistersessies wilt verwijderen?",
|
||||
"MessageConfirmRemoveMetadataFiles": "Bent u zeker dat u alle metadata wil verwijderen. {0} bestanden in uw bibliotheel item folders?",
|
||||
"MessageConfirmRemoveNarrator": "Weet je zeker dat je verteller \"{0}\" wil verwijderen?",
|
||||
"MessageConfirmRemovePlaylist": "Weet je zeker dat je je afspeellijst \"{0}\" wil verwijderen?",
|
||||
"MessageConfirmRemovePlaylist": "Weet je zeker dat je afspeellijst \"{0}\" wil verwijderen?",
|
||||
"MessageConfirmRenameGenre": "Weet je zeker dat je genre \"{0}\" wil hernoemen naar \"{1}\" voor alle onderdelen?",
|
||||
"MessageConfirmRenameGenreMergeNote": "Opmerking: Dit genre bestaat al, dus zullen ze worden samengevoegd.",
|
||||
"MessageConfirmRenameGenreWarning": "Waarschuwing! Een gelijknamig genre met ander hoofdlettergebruik bestaat al: \"{0}\".",
|
||||
@@ -1001,6 +1023,8 @@
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "Aflevering download-wachtrij geleegt",
|
||||
"ToastEpisodeUpdateSuccess": "{0} afleveringen bijgewerkt",
|
||||
"ToastErrorCannotShare": "Kan niet native delen op dit apparaat",
|
||||
"ToastFailedToCreate": "Fout tijdens creëren",
|
||||
"ToastFailedToDelete": "Fout tijdens verwijderen",
|
||||
"ToastFailedToLoadData": "Data laden mislukt",
|
||||
"ToastFailedToMatch": "Match mislukt",
|
||||
"ToastFailedToShare": "Delen mislukt",
|
||||
@@ -1032,6 +1056,7 @@
|
||||
"ToastMustHaveAtLeastOnePath": "Moet ten minste een pad hebben",
|
||||
"ToastNameEmailRequired": "Naam en email zijn vereist",
|
||||
"ToastNameRequired": "Naam is vereist",
|
||||
"ToastNewApiKeyUserError": "Selecteer een gebruiker",
|
||||
"ToastNewEpisodesFound": "{0} nieuwe afleveringen gevonden",
|
||||
"ToastNewUserCreatedFailed": "Account: \"{0}\" aanmaken mislukt",
|
||||
"ToastNewUserCreatedSuccess": "Nieuw account aangemaakt",
|
||||
|
||||
@@ -334,9 +334,12 @@
|
||||
"LabelEncodingClearItemCache": "Pamiętaj o okresowym czyszczeniu pamięci podręcznej elementów.",
|
||||
"LabelEncodingFinishedM4B": "Ukończony plik M4B zostanie umieszczony w folderze audiobooka pod adresem:",
|
||||
"LabelEncodingInfoEmbedded": "Metadane zostaną osadzone w ścieżkach audio w folderze z audiobookiem.",
|
||||
"LabelEncodingWatcherDisabled": "Jeśli monitorowanie folderów jest wyłączone, należy ponownie przeskanować audiobooka.",
|
||||
"LabelEnd": "Zakończ",
|
||||
"LabelEndOfChapter": "Koniec rozdziału",
|
||||
"LabelEpisode": "Odcinek",
|
||||
"LabelEpisodeNotLinkedToRssFeed": "Odcinek nie jest powiązany z kanałem RSS",
|
||||
"LabelEpisodeNumber": "Odcinek #{0}",
|
||||
"LabelEpisodeTitle": "Tytuł odcinka",
|
||||
"LabelEpisodeType": "Typ odcinka",
|
||||
"LabelEpisodeUrlFromRssFeed": "Adres URL odcinka z kanału RSS",
|
||||
@@ -345,6 +348,8 @@
|
||||
"LabelExample": "Przykład",
|
||||
"LabelExpandSeries": "Rozwiń serie",
|
||||
"LabelExpandSubSeries": "Rozwiń podserie",
|
||||
"LabelExpiresInSeconds": "Wygasa za (sekund)",
|
||||
"LabelExpiresNever": "Nigdy",
|
||||
"LabelExplicit": "Nieprzyzwoite",
|
||||
"LabelExplicitChecked": "Nieprzyzwoite (sprawdzone)",
|
||||
"LabelExplicitUnchecked": "Przyzwoite (niesprawdzone)",
|
||||
@@ -404,6 +409,7 @@
|
||||
"LabelLanguages": "Języki",
|
||||
"LabelLastBookAdded": "Ostatnio dodana książka",
|
||||
"LabelLastBookUpdated": "Ostatnio modyfikowana książka",
|
||||
"LabelLastProgressDate": "Ostatni postęp: {0}",
|
||||
"LabelLastSeen": "Ostatnio widziany",
|
||||
"LabelLastTime": "Ostatni czas",
|
||||
"LabelLastUpdate": "Ostatnia aktualizacja",
|
||||
@@ -416,6 +422,7 @@
|
||||
"LabelLibraryFilterSublistEmpty": "Brak {0}",
|
||||
"LabelLibraryItem": "Element biblioteki",
|
||||
"LabelLibraryName": "Nazwa biblioteki",
|
||||
"LabelLibrarySortByProgress": "Postęp zaktualizowany",
|
||||
"LabelLimit": "Limit",
|
||||
"LabelLineSpacing": "Odstęp między wierszami",
|
||||
"LabelListenAgain": "Słuchaj ponownie",
|
||||
@@ -427,6 +434,7 @@
|
||||
"LabelMatchExistingUsersBy": "Dopasuje istniejących użytkowników poprzez",
|
||||
"LabelMatchExistingUsersByDescription": "Służy do łączenia istniejących użytkowników. Po połączeniu użytkownicy zostaną dopasowani za pomocą unikalnego identyfikatora od dostawcy SSO",
|
||||
"LabelMaxEpisodesToDownload": "Maksymalna liczba odcinków do pobrania. Użyj 0, aby wyłączyć ograniczenie.",
|
||||
"LabelMaxEpisodesToDownloadPerCheck": "Maksymalna liczba nowych odcinków do pobrania na jedno sprawdzenie",
|
||||
"LabelMaxEpisodesToKeep": "Maksymalna liczba odcinków do zachowania",
|
||||
"LabelMaxEpisodesToKeepHelp": "Wartość 0 wyłącza maksymalny limit. Po automatycznym pobraniu nowego odcinka, najstarszy odcinek zostanie usunięty, jeśli masz ich więcej niż X. Spowoduje to usunięcie tylko 1 odcinka na nowe pobieranie.",
|
||||
"LabelMediaPlayer": "Odtwarzacz",
|
||||
@@ -453,6 +461,7 @@
|
||||
"LabelNewestEpisodes": "Najnowsze odcinki",
|
||||
"LabelNextBackupDate": "Data kolejnej kopii zapasowej",
|
||||
"LabelNextScheduledRun": "Następne uruchomienie",
|
||||
"LabelNoApiKeys": "Brak kluczy API",
|
||||
"LabelNoCustomMetadataProviders": "Brak niestandardowych dostawców metadanych",
|
||||
"LabelNoEpisodesSelected": "Nie wybrano żadnych odcinków",
|
||||
"LabelNotFinished": "Nieukończone",
|
||||
@@ -469,6 +478,8 @@
|
||||
"LabelNotificationsMaxQueueSizeHelp": "Zdarzenia są ograniczone do 1 na sekundę. Zdarzenia będą ignorowane jeśli kolejka ma maksymalny rozmiar. Zapobiega to spamowaniu powiadomieniami.",
|
||||
"LabelNumberOfBooks": "Liczba książek",
|
||||
"LabelNumberOfEpisodes": "# Odcinków",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Nazwa deklaracji OpenID zawierającej zaawansowane uprawnienia do działań użytkownika w aplikacji, które będą miały zastosowanie do ról innych niż administracyjne (<b>jeśli skonfigurowano</b>). Jeśli deklaracja nie zostanie uwzględniona w odpowiedzi, dostęp do ABS zostanie zablokowany. Brak jednej opcji zostanie uznany za <code>fałsz</code>. Upewnij się, że deklaracja dostawcy tożsamości jest zgodna z oczekiwaną strukturą:",
|
||||
"LabelOpenIDClaims": "Pozostaw poniższe opcje puste, aby wyłączyć zaawansowane przypisywanie grup i uprawnień. Automatycznie zostanie przypisana grupa „Użytkownik”.",
|
||||
"LabelOpenRSSFeed": "Otwórz kanał RSS",
|
||||
"LabelOverwrite": "Nadpisz",
|
||||
"LabelPaginationPageXOfY": "Strona {0} z {1}",
|
||||
@@ -486,6 +497,7 @@
|
||||
"LabelPersonalYearReview": "Podsumowanie twojego roku ({0})",
|
||||
"LabelPhotoPathURL": "Scieżka/URL do zdjęcia",
|
||||
"LabelPlayMethod": "Metoda odtwarzania",
|
||||
"LabelPlaybackRateIncrementDecrement": "Zwiększenie/zmniejszenie szybkości odtwarzania",
|
||||
"LabelPlayerChapterNumberMarker": "{0} z {1}",
|
||||
"LabelPlaylists": "Listy odtwarzania",
|
||||
"LabelPodcast": "Podcast",
|
||||
@@ -502,10 +514,15 @@
|
||||
"LabelPubDate": "Data publikacji",
|
||||
"LabelPublishYear": "Rok publikacji",
|
||||
"LabelPublishedDate": "Opublikowano {0}",
|
||||
"LabelPublishedDecade": "Dekada publikacji",
|
||||
"LabelPublishedDecades": "Dekada publikacji",
|
||||
"LabelPublisher": "Wydawca",
|
||||
"LabelPublishers": "Wydawcy",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Email właściciela",
|
||||
"LabelRSSFeedCustomOwnerName": "Nazwa właściciela",
|
||||
"LabelRSSFeedOpen": "Otwarty Kanał RSS",
|
||||
"LabelRSSFeedPreventIndexing": "Zapobiegaj indeksowaniu",
|
||||
"LabelRSSFeedSlug": "Numer Kanału RSS",
|
||||
"LabelRSSFeedURL": "URL kanały RSS",
|
||||
"LabelRandomly": "Losowo",
|
||||
"LabelReAddSeriesToContinueListening": "Ponownie Dodaj Serię do sekcji Kontunuuj Odtwarzanie",
|
||||
@@ -520,6 +537,7 @@
|
||||
"LabelReleaseDate": "Data wydania",
|
||||
"LabelRemoveAllMetadataAbs": "Usuń wszystkie pliki metadata.abs",
|
||||
"LabelRemoveAllMetadataJson": "Usuń wszystkie pliki metadata.json",
|
||||
"LabelRemoveAudibleBranding": "Usuń Audible intro i outro z rozdziałów",
|
||||
"LabelRemoveCover": "Usuń okładkę",
|
||||
"LabelRemoveMetadataFile": "Usuń pliki metadanych z folderów biblioteki",
|
||||
"LabelRemoveMetadataFileHelp": "Usuń wszystkie pliki metadata.json i metadata.abs z {0} folderów.",
|
||||
@@ -532,6 +550,7 @@
|
||||
"LabelSelectAll": "Wybierz wszystko",
|
||||
"LabelSelectAllEpisodes": "Wybierz wszystkie odcinki",
|
||||
"LabelSelectEpisodesShowing": "Wybierz {0} wyświetlanych odcinków",
|
||||
"LabelSelectUser": "Wybierz użytkownika",
|
||||
"LabelSelectUsers": "Wybór użytkowników",
|
||||
"LabelSendEbookToDevice": "Wyślij ebook do...",
|
||||
"LabelSequence": "Kolejność",
|
||||
@@ -541,6 +560,7 @@
|
||||
"LabelServerYearReview": "Podsumowanie serwera w roku ({0})",
|
||||
"LabelSetEbookAsPrimary": "Ustaw jako pierwszy",
|
||||
"LabelSetEbookAsSupplementary": "Ustaw jako dodatkowy",
|
||||
"LabelSettingsAllowIframe": "Zezwól na osadzanie w ramce iframe",
|
||||
"LabelSettingsAudiobooksOnly": "Wyłącznie audiobooki",
|
||||
"LabelSettingsAudiobooksOnlyHelp": "Włączenie tej funkcji spowoduje ignorowanie plików ebooków, chyba że znajdują się wewnątrz folderu audiobooka kiedy to będą pokazywane jako dodatkowe ebooki",
|
||||
"LabelSettingsBookshelfViewHelp": "Widok półki z książkami",
|
||||
@@ -559,6 +579,8 @@
|
||||
"LabelSettingsHideSingleBookSeriesHelp": "Serie, które posiadają tylko jedną książkę, nie będą pokazywane na stronie z seriami i na stronie domowej z półkami.",
|
||||
"LabelSettingsHomePageBookshelfView": "Widok półki z książkami na stronie głównej",
|
||||
"LabelSettingsLibraryBookshelfView": "Widok półki z książkami na stronie biblioteki",
|
||||
"LabelSettingsLibraryMarkAsFinishedPercentComplete": "Procent ukończenia jest większy niż",
|
||||
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Pozostały czas jest mniejszy niż (sekund)",
|
||||
"LabelSettingsLibraryMarkAsFinishedWhen": "Oznacz element multimedialny jako ukończony, gdy",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Pomiń poprzednie książki przy kontynuacji serii",
|
||||
"LabelSettingsParseSubtitles": "Przetwarzaj podtytuły",
|
||||
|
||||
@@ -357,7 +357,7 @@
|
||||
"LabelExpandSubSeries": "Развернуть подсерию",
|
||||
"LabelExpired": "Истекший",
|
||||
"LabelExpiresAt": "Истекает в",
|
||||
"LabelExpiresInSeconds": "Истекает через (seconds)",
|
||||
"LabelExpiresInSeconds": "Истекает через (секунд)",
|
||||
"LabelExpiresNever": "Никогда",
|
||||
"LabelExplicit": "18+",
|
||||
"LabelExplicitChecked": "18+ (отмечено)",
|
||||
@@ -438,6 +438,7 @@
|
||||
"LabelLogLevelWarn": "Предупреждение",
|
||||
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
||||
"LabelLowestPriority": "Самый низкий приоритет",
|
||||
"LabelMatchConfidence": "Уверенность",
|
||||
"LabelMatchExistingUsersBy": "Сопоставление существующих пользователей по",
|
||||
"LabelMatchExistingUsersByDescription": "Используется для подключения существующих пользователей. После подключения пользователям будет присвоен уникальный идентификатор от поставщика единого входа",
|
||||
"LabelMaxEpisodesToDownload": "Максимальное количество эпизодов для загрузки. Используйте 0 для неограниченного количества.",
|
||||
@@ -655,7 +656,8 @@
|
||||
"LabelTheme": "Тема",
|
||||
"LabelThemeDark": "Темная",
|
||||
"LabelThemeLight": "Светлая",
|
||||
"LabelTimeBase": "Временная база",
|
||||
"LabelThemeSepia": "Сепия",
|
||||
"LabelTimeBase": "Основное время",
|
||||
"LabelTimeDurationXHours": "{0} часов",
|
||||
"LabelTimeDurationXMinutes": "{0} минут",
|
||||
"LabelTimeDurationXSeconds": "{0} секунд",
|
||||
@@ -723,6 +725,7 @@
|
||||
"MessageAddToPlayerQueue": "Добавить в очередь проигрывателя",
|
||||
"MessageAppriseDescription": "Для использования этой функции необходимо иметь запущенный экземпляр <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или api которое обрабатывает те же самые запросы. <br />URL-адрес API Apprise должен быть полным URL-адресом для отправки уведомления, т.е., если API запущено по адресу <code>http://192.168.1.1:8337</code> тогда нужно указать <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Убедитесь, что вы используете ASIN из правильной региональной зоны Audible, а не из Amazon.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "Устаревшие токены API в будущем будут удалены. Вместо них используйте <a href=\"/config/api-keys\">API-ключи</a>.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Перезапустите ваш сервер после сохранения для применения изменений в OIDC.",
|
||||
"MessageAuthenticationSecurityMessage": "В целях безопасности была улучшена аутентификация. Всем пользователям необходимо повторно войти в систему.",
|
||||
"MessageBackupsDescription": "Бэкап включает пользователей, прогресс пользователей, данные элементов библиотеки, настройки сервера и изображения хранящиеся в <code>/metadata/items</code> и <code>/metadata/authors</code>. Бэкапы <strong>НЕ</strong> сохраняют файлы из папок библиотек.",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "Dodaj",
|
||||
"ButtonAddApiKey": "Dodaj API ključ",
|
||||
"ButtonAddChapters": "Dodaj poglavja",
|
||||
"ButtonAddDevice": "Dodaj napravo",
|
||||
"ButtonAddLibrary": "Dodaj knjižnico",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "Izberite mapo",
|
||||
"ButtonChooseFiles": "Izberite datoteke",
|
||||
"ButtonClearFilter": "Počisti filter",
|
||||
"ButtonClose": "Zapri",
|
||||
"ButtonCloseFeed": "Zapri vir",
|
||||
"ButtonCloseSession": "Zapri odprto sejo",
|
||||
"ButtonCollections": "Zbirke",
|
||||
@@ -119,6 +121,7 @@
|
||||
"HeaderAccount": "Račun",
|
||||
"HeaderAddCustomMetadataProvider": "Dodaj ponudnika metapodatkov po meri",
|
||||
"HeaderAdvanced": "Napredno",
|
||||
"HeaderApiKeys": "API ključi",
|
||||
"HeaderAppriseNotificationSettings": "Nastavitve obvestil Apprise",
|
||||
"HeaderAudioTracks": "Zvočni posnetki",
|
||||
"HeaderAudiobookTools": "Orodja za upravljanje datotek zvočnih knjig",
|
||||
@@ -162,6 +165,7 @@
|
||||
"HeaderMetadataOrderOfPrecedence": "Vrstni red metapodatkov",
|
||||
"HeaderMetadataToEmbed": "Metapodatki za vdelavo",
|
||||
"HeaderNewAccount": "Nov račun",
|
||||
"HeaderNewApiKey": "Nov API ključ",
|
||||
"HeaderNewLibrary": "Nova knjižnica",
|
||||
"HeaderNotificationCreate": "Ustvari obvestilo",
|
||||
"HeaderNotificationUpdate": "Posodobi obvestilo",
|
||||
@@ -206,6 +210,7 @@
|
||||
"HeaderTableOfContents": "Kazalo",
|
||||
"HeaderTools": "Orodja",
|
||||
"HeaderUpdateAccount": "Posodobi račun",
|
||||
"HeaderUpdateApiKey": "Posodobi API ključ",
|
||||
"HeaderUpdateAuthor": "Posodobi avtorja",
|
||||
"HeaderUpdateDetails": "Posodobi podrobnosti",
|
||||
"HeaderUpdateLibrary": "Posodobi knjižnico",
|
||||
@@ -235,6 +240,10 @@
|
||||
"LabelAllUsersExcludingGuests": "Vsi uporabniki razen gosti",
|
||||
"LabelAllUsersIncludingGuests": "Vsi uporabniki vključno z gosti",
|
||||
"LabelAlreadyInYourLibrary": "Že v tvoji knjižnici",
|
||||
"LabelApiKeyCreated": "API ključ \"{0}\" je uspešno ustvarjen.",
|
||||
"LabelApiKeyCreatedDescription": "Ne pozabite takoj kopirati API ključ, saj ga kasneje ne boste mogli več videti.",
|
||||
"LabelApiKeyUser": "Izvedi v imenu uporabnika",
|
||||
"LabelApiKeyUserDescription": "Ta API ključ bo imel enaka dovoljenja kot uporabnik, v imenu katerega deluje. V dnevnikih bo to prikazano enako, kot če bi zahtevo oddal uporabnik.",
|
||||
"LabelApiToken": "API žeton",
|
||||
"LabelAppend": "Priloži",
|
||||
"LabelAudioBitrate": "Avdio bitna hitrost (npr. 128k)",
|
||||
@@ -346,6 +355,10 @@
|
||||
"LabelExample": "Primer",
|
||||
"LabelExpandSeries": "Razširi serije",
|
||||
"LabelExpandSubSeries": "Razširi podserije",
|
||||
"LabelExpired": "Potekel",
|
||||
"LabelExpiresAt": "Peteče ob",
|
||||
"LabelExpiresInSeconds": "Poteče čez (sekunde)",
|
||||
"LabelExpiresNever": "Nikoli",
|
||||
"LabelExplicit": "Eksplicitno",
|
||||
"LabelExplicitChecked": "Eksplicitno (omogočeno)",
|
||||
"LabelExplicitUnchecked": "Ne eksplicitno (onemogočeno)",
|
||||
@@ -405,6 +418,7 @@
|
||||
"LabelLanguages": "Jeziki",
|
||||
"LabelLastBookAdded": "Zadnja dodana knjiga",
|
||||
"LabelLastBookUpdated": "Zadnja posodobljena knjiga",
|
||||
"LabelLastProgressDate": "Zadnji napredek: {0}",
|
||||
"LabelLastSeen": "Nazadnje viden",
|
||||
"LabelLastTime": "Nazadnje",
|
||||
"LabelLastUpdate": "Zadnja posodobitev",
|
||||
@@ -417,6 +431,7 @@
|
||||
"LabelLibraryFilterSublistEmpty": "Ne {0}",
|
||||
"LabelLibraryItem": "Element knjižnice",
|
||||
"LabelLibraryName": "Ime knjižnice",
|
||||
"LabelLibrarySortByProgress": "Napredek posodobljen",
|
||||
"LabelLimit": "Omejitev",
|
||||
"LabelLineSpacing": "Vrstični razmak",
|
||||
"LabelListenAgain": "Poslušaj znova",
|
||||
@@ -425,6 +440,7 @@
|
||||
"LabelLogLevelWarn": "Opozoritve",
|
||||
"LabelLookForNewEpisodesAfterDate": "Poiščite nove epizode po tem datumu",
|
||||
"LabelLowestPriority": "Najnižja prioriteta",
|
||||
"LabelMatchConfidence": "Zaupanje",
|
||||
"LabelMatchExistingUsersBy": "Poveži obstoječe uporabnike po",
|
||||
"LabelMatchExistingUsersByDescription": "Uporablja se za povezovanje obstoječih uporabnikov. Ko se vzpostavi povezava, se bodo uporabniki ujemali z enoličnim ID-jem vašega ponudnika SSO",
|
||||
"LabelMaxEpisodesToDownload": "Največje število epizod za prenos. Uporabite 0 za neomejeno.",
|
||||
@@ -455,6 +471,7 @@
|
||||
"LabelNewestEpisodes": "Najnovejše epizode",
|
||||
"LabelNextBackupDate": "Naslednji datum varnostnega kopiranja",
|
||||
"LabelNextScheduledRun": "Naslednji načrtovani zagon",
|
||||
"LabelNoApiKeys": "Ni API ključev",
|
||||
"LabelNoCustomMetadataProviders": "Ni ponudnikov metapodatkov po meri",
|
||||
"LabelNoEpisodesSelected": "Izbrana ni nobena epizoda",
|
||||
"LabelNotFinished": "Ni dokončano",
|
||||
@@ -544,6 +561,7 @@
|
||||
"LabelSelectAll": "Izberite vse",
|
||||
"LabelSelectAllEpisodes": "Izberite vse epizode",
|
||||
"LabelSelectEpisodesShowing": "Izberi {0} prikazanih epizod",
|
||||
"LabelSelectUser": "Izberi uporabnika",
|
||||
"LabelSelectUsers": "Izberite uporabnike",
|
||||
"LabelSendEbookToDevice": "Pošlji eknjigo k...",
|
||||
"LabelSequence": "Zaporedje",
|
||||
@@ -640,6 +658,7 @@
|
||||
"LabelTheme": "Tema",
|
||||
"LabelThemeDark": "Temna",
|
||||
"LabelThemeLight": "Svetla",
|
||||
"LabelThemeSepia": "Sepija",
|
||||
"LabelTimeBase": "Osnovni čas",
|
||||
"LabelTimeDurationXHours": "{0} ur",
|
||||
"LabelTimeDurationXMinutes": "{0} minut",
|
||||
@@ -708,7 +727,9 @@
|
||||
"MessageAddToPlayerQueue": "Dodaj v čakalno vrsto predvajalnika",
|
||||
"MessageAppriseDescription": "Če želite uporabljati to funkcijo, morate imeti zagnano namestitev <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> ali API, ki bo obravnavala te iste zahteve. <br />Url API-ja Apprise mora biti celotna pot URL-ja za pošiljanje obvestila, npr. če je vaša namestitev API-ja postrežena na <code>http://192.168.1.1:8337</code>, bi morali vnesti <code >http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Prepričajte se, da uporabljate ASIN iz pravilne zvočne regije, ne iz Amazona.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "Zastareli API žetoni bodo v prihodnosti odstranjeni. Namesto tega uporabite <a href=\"/config/api-keys\">API ključe</a>.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Za uveljavitev OIDC sprememb, po shranjevanju znova zaženite strežnik.",
|
||||
"MessageAuthenticationSecurityMessage": "Zaradi varnosti je bila izboljšana avtentikacija. Vsi uporabniki se morajo ponovno prijaviti.",
|
||||
"MessageBackupsDescription": "Varnostne kopije vključujejo uporabnike, napredek uporabnikov, podrobnosti elementov knjižnice, nastavitve strežnika in slike, shranjene v <code>/metadata/items</code> & <code>/metadata/authors</code>. Varnostne kopije <strong>ne</strong> vključujejo datotek, shranjenih v mapah vaše knjižnice.",
|
||||
"MessageBackupsLocationEditNote": "Opomba: Posodabljanje lokacije varnostne kopije ne bo premaknilo ali spremenilo obstoječih varnostnih kopij",
|
||||
"MessageBackupsLocationNoEditNote": "Opomba: Lokacija varnostne kopije je nastavljena s spremenljivko okolja in je tu ni mogoče spremeniti.",
|
||||
@@ -730,6 +751,7 @@
|
||||
"MessageChaptersNotFound": "Poglavij ni bilo najdenih",
|
||||
"MessageCheckingCron": "Preverjam cron...",
|
||||
"MessageConfirmCloseFeed": "Ali ste prepričani, da želite zapreti ta vir?",
|
||||
"MessageConfirmDeleteApiKey": "Ali ste prepričani, da želite izbrisati API ključ \"{0}\"?",
|
||||
"MessageConfirmDeleteBackup": "Ali ste prepričani, da želite izbrisati varnostno kopijo za {0}?",
|
||||
"MessageConfirmDeleteDevice": "Ali ste prepričani, da želite izbrisati e-bralnik \"{0}\"?",
|
||||
"MessageConfirmDeleteFile": "To bo izbrisalo datoteko iz vašega datotečnega sistema. Ali ste prepričani?",
|
||||
@@ -1001,6 +1023,8 @@
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "Čakalna vrsta za prenos epizod je počiščena",
|
||||
"ToastEpisodeUpdateSuccess": "Število posodobljenih epizod: {0}",
|
||||
"ToastErrorCannotShare": "V tej napravi ni mogoče dati v skupno rabo",
|
||||
"ToastFailedToCreate": "Ustvarjanje ni bilo uspešno",
|
||||
"ToastFailedToDelete": "Brisanje ni bilo uspešno",
|
||||
"ToastFailedToLoadData": "Podatkov ni bilo mogoče naložiti",
|
||||
"ToastFailedToMatch": "Ujemanje ni uspelo",
|
||||
"ToastFailedToShare": "Skupna raba ni uspela",
|
||||
@@ -1032,6 +1056,7 @@
|
||||
"ToastMustHaveAtLeastOnePath": "Imeti mora vsaj eno pot",
|
||||
"ToastNameEmailRequired": "Ime in e-pošta sta obvezna",
|
||||
"ToastNameRequired": "Ime je obvezno",
|
||||
"ToastNewApiKeyUserError": "Morate izbrati uporabnika",
|
||||
"ToastNewEpisodesFound": "Število najdenih novih epizod: {0}",
|
||||
"ToastNewUserCreatedFailed": "Računa ni bilo mogoče ustvariti: \"{0}\"",
|
||||
"ToastNewUserCreatedSuccess": "Nov račun je bil ustvarjen",
|
||||
|
||||
@@ -436,6 +436,7 @@
|
||||
"LabelLogLevelWarn": "Varningar",
|
||||
"LabelLookForNewEpisodesAfterDate": "Sök efter nya avsnitt efter detta datum",
|
||||
"LabelLowestPriority": "Lägst prioritet",
|
||||
"LabelMatchConfidence": "Förtroende",
|
||||
"LabelMatchExistingUsersBy": "Matcha befintliga användare med",
|
||||
"LabelMatchExistingUsersByDescription": "Används för att koppla existerande användare. När kopplingen sker kommer användaren att matchas med ett unikt ID från SSO-leverantören.",
|
||||
"LabelMaxEpisodesToDownload": "Maximalt antal avsnitt att ladda ner (0 = obegränsat).",
|
||||
@@ -649,6 +650,7 @@
|
||||
"LabelTheme": "Utseende",
|
||||
"LabelThemeDark": "Mörkt",
|
||||
"LabelThemeLight": "Ljust",
|
||||
"LabelThemeSepia": "Sepia",
|
||||
"LabelTimeBase": "Tidsbas",
|
||||
"LabelTimeDurationXHours": "{0} timmar",
|
||||
"LabelTimeDurationXMinutes": "{0} minuter",
|
||||
@@ -714,6 +716,8 @@
|
||||
"LabelYourProgress": "Framsteg",
|
||||
"MessageAddToPlayerQueue": "Lägg till i spellistan",
|
||||
"MessageAppriseDescription": "För att använda den här funktionen behöver du ha en instans av <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> igång eller en API som hanterar dessa begäranden. <br />Apprise API-urlen bör vara hela URL-sökvägen för att skicka meddelandet, t.ex., om din API-instans är tillgänglig på <code>http://192.168.1.1:8337</code>, bör du ange <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "Legacy API-koder kommer att raderas i framtiden. Använd denna istället: <a href=\"/config/api-keys\">API Keys</a>.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Du måste starta om servern efter att du ändrat eller adderat OIDC (OpenID Connect).",
|
||||
"MessageAuthenticationSecurityMessage": "Identifieringen av användare har förbättrats av säkerhetsskäl. Alla användare måste därför logga in på nytt.",
|
||||
"MessageBackupsDescription": "Säkerhetskopior inkluderar användare, användarnas framsteg, biblioteksobjekt,<br>serverinställningar och bilder lagrade i <code>/metadata/items</code> & <code>/metadata/authors</code>.<br>De inkluderar <strong>INTE</strong> några filer lagrade i dina biblioteksmappar.",
|
||||
"MessageBackupsLocationEditNote": "OBS: När du ändrar plats för säkerhetskopiorna så flyttas INTE gamla säkerhetskopior dit",
|
||||
@@ -733,6 +737,7 @@
|
||||
"MessageChapterErrorStartGteDuration": "Ogiltig starttid, måste vara mindre än ljudbokens längd",
|
||||
"MessageChapterErrorStartLtPrev": "Ogiltig starttid, måste vara större än eller lika med föregående kapitlets starttid",
|
||||
"MessageChapterStartIsAfter": "Kapitlets start är efter din ljudboks slut",
|
||||
"MessageChaptersNotFound": "Inga kapitel kunde hittas",
|
||||
"MessageCheckingCron": "Kontrollerar cron...",
|
||||
"MessageConfirmCloseFeed": "Är du säker på att du vill stänga detta flöde?",
|
||||
"MessageConfirmDeleteApiKey": "Är du säker på att du vill radera API-nyckel \"{0}\"?",
|
||||
@@ -775,6 +780,7 @@
|
||||
"MessageConfirmRenameTagWarning": "VARNING! En liknande tagg med annat skrivsätt finns redan \"{0}\".",
|
||||
"MessageConfirmResetProgress": "Är du säker på att du vill nollställa ditt framsteg?",
|
||||
"MessageConfirmSendEbookToDevice": "Är du säker på att du vill skicka {0} e-bok \"{1}\" till enheten \"{2}\"?",
|
||||
"MessageConfirmUnlinkOpenId": "Är du säker på att du vill koppla bort denna användare från OpenID?",
|
||||
"MessageDaysListenedInTheLastYear": "{0} dagars lyssnande det senaste året",
|
||||
"MessageDownloadingEpisode": "Laddar ner avsnitt",
|
||||
"MessageDragFilesIntoTrackOrder": "Ändra ordningen genom att klicka och dra filerna till rätt plats",
|
||||
@@ -856,8 +862,10 @@
|
||||
"MessageScheduleRunEveryWeekdayAtTime": "Startar varje {0} klockan {1}",
|
||||
"MessageSearchResultsFor": "Sökresultat för",
|
||||
"MessageSelected": "{0} valda",
|
||||
"MessageSeriesSequenceCannotContainSpaces": "Ordningsnumret i en serie får inte innehålla ett mellanslag",
|
||||
"MessageServerCouldNotBeReached": "Servern kunde inte nås",
|
||||
"MessageSetChaptersFromTracksDescription": "Använd varje ljudfil som ett kapitel och ljudfilens namn som kapitlets rubrik",
|
||||
"MessageShareExpirationWillBe": "Giltig till kommer att bli <strong>{0}</strong>",
|
||||
"MessageShareExpiresIn": "Upphör om {0}",
|
||||
"MessageStartPlaybackAtTime": "Starta uppspelning av \"{0}\" vid tidpunkt {1}?",
|
||||
"MessageTaskAudioFileNotWritable": "Det går inte att skriva till ljudfilen \"{0}\"",
|
||||
@@ -935,6 +943,7 @@
|
||||
"StatsTotalDuration": "Med den totala längden…",
|
||||
"StatsYearInReview": "- SAMMANSTÄLLNING AV ÅRET",
|
||||
"ToastAccountUpdateSuccess": "Kontot har uppdaterats",
|
||||
"ToastAppriseUrlRequired": "En adress till Apprise måste anges",
|
||||
"ToastAsinRequired": "En ASIN-kod krävs",
|
||||
"ToastAuthorImageRemoveSuccess": "Författarens bild borttagen",
|
||||
"ToastAuthorNotFound": "Författaren \"{0}\" kunde inte identifieras",
|
||||
@@ -983,6 +992,8 @@
|
||||
"ToastEpisodeDownloadQueueClearFailed": "Misslyckades med att tömma kön",
|
||||
"ToastEpisodeDownloadQueueClearSuccess": "Kö för nedladdning av avsnitt har tömts",
|
||||
"ToastEpisodeUpdateSuccess": "{0} avsnitt uppdaterades",
|
||||
"ToastFailedToCreate": "Misslyckades med att addera",
|
||||
"ToastFailedToDelete": "Misslyckades med att radera",
|
||||
"ToastFailedToLoadData": "Misslyckades med att ladda data",
|
||||
"ToastFailedToMatch": "Misslyckades med att matcha",
|
||||
"ToastFailedToShare": "Misslyckades med att dela",
|
||||
@@ -1013,6 +1024,7 @@
|
||||
"ToastMetadataFilesRemovedSuccess": "{0} 'metadata.{1}' raderades",
|
||||
"ToastNameEmailRequired": "Ett namn och en e-postadress måste anges",
|
||||
"ToastNameRequired": "Ett namn måste anges",
|
||||
"ToastNewApiKeyUserError": "En användare måste väljas",
|
||||
"ToastNewEpisodesFound": "Hittade {0} nya avsnitt",
|
||||
"ToastNewUserCreatedFailed": "Misslyckades med att skapa kontot \"{0}\"",
|
||||
"ToastNewUserCreatedSuccess": "Ett nytt konto har skapats",
|
||||
@@ -1025,6 +1037,7 @@
|
||||
"ToastNoUpdatesNecessary": "Inga uppdateringar var nödvändiga",
|
||||
"ToastNotificationCreateFailed": "Misslyckades med att skapa meddelandet",
|
||||
"ToastNotificationDeleteFailed": "Misslyckades med att radera meddelandet",
|
||||
"ToastNotificationSettingsUpdateSuccess": "Inställningarna för meddelanden har ändrats",
|
||||
"ToastNotificationUpdateSuccess": "Meddelandet har uppdaterats",
|
||||
"ToastPlaylistCreateFailed": "Det gick inte att skapa spellistan",
|
||||
"ToastPlaylistCreateSuccess": "Spellistan skapad",
|
||||
@@ -1068,6 +1081,8 @@
|
||||
"ToastSortingPrefixesUpdateSuccess": "{0} begrepp för sortering har uppdateras",
|
||||
"ToastTitleRequired": "En titel måste anges",
|
||||
"ToastUnknownError": "Ett okänt fel inträffade",
|
||||
"ToastUnlinkOpenIdFailed": "Misslyckades med att koppla bort användaren från OpenID",
|
||||
"ToastUnlinkOpenIdSuccess": "Användaren har kopplats bort från OpenID",
|
||||
"ToastUploaderFilepathExistsError": "En fil med namnet \"{0}\" finns redan på servern",
|
||||
"ToastUserDeleteFailed": "Misslyckades med att ta bort användaren",
|
||||
"ToastUserDeleteSuccess": "Användaren borttagen",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "Ekle",
|
||||
"ButtonAddApiKey": "API Anahtarı Ekle",
|
||||
"ButtonAddChapters": "Bölüm Ekle",
|
||||
"ButtonAddDevice": "Cihaz Ekle",
|
||||
"ButtonAddLibrary": "Kütüphane Ekle",
|
||||
@@ -20,6 +21,7 @@
|
||||
"ButtonChooseAFolder": "Klasör seç",
|
||||
"ButtonChooseFiles": "Dosya seç",
|
||||
"ButtonClearFilter": "Filtreyi Temizle",
|
||||
"ButtonClose": "Kapat",
|
||||
"ButtonCloseFeed": "Akışı Kapat",
|
||||
"ButtonCloseSession": "Acık Oturumu Kapat",
|
||||
"ButtonCollections": "Koleksiyonlar",
|
||||
@@ -95,7 +97,17 @@
|
||||
"ButtonSearch": "Ara",
|
||||
"ButtonSelectFolderPath": "Klasör Yolunu Seç",
|
||||
"ButtonSeries": "Seriler",
|
||||
"ButtonShare": "Paylaş",
|
||||
"ButtonStats": "İstatistikler",
|
||||
"ButtonSubmit": "Gönder",
|
||||
"ButtonTest": "Dene",
|
||||
"ButtonUnlinkOpenId": "OpenID ilişiğini kaldır",
|
||||
"ButtonUpload": "Yükle",
|
||||
"ButtonUploadBackup": "Yedeği Yükle",
|
||||
"ButtonUploadCover": "Kapağı Yükle",
|
||||
"ButtonUploadOPMLFile": "OPML Dosyası Yükle",
|
||||
"ButtonUserDelete": "{0} kullanıcısını sil.",
|
||||
"ButtonUserEdit": "{0} kullanıcısını düzenle",
|
||||
"ButtonViewAll": "Tümünü Görüntüle",
|
||||
"ButtonYes": "Evet",
|
||||
"ErrorUploadFetchMetadataAPI": "Üst veriyi almakta hata",
|
||||
@@ -104,6 +116,7 @@
|
||||
"HeaderAccount": "Hesap",
|
||||
"HeaderAddCustomMetadataProvider": "Özel Üstveri Sağlayıcısı Ekle",
|
||||
"HeaderAdvanced": "Gelişmiş",
|
||||
"HeaderApiKeys": "API Anahtarları",
|
||||
"HeaderAppriseNotificationSettings": "Bildirim Ayarlarının Haberini Ver",
|
||||
"HeaderAudioTracks": "Ses Kanalları",
|
||||
"HeaderAudiobookTools": "Sesli Kitap Dosya Yönetim Araçları",
|
||||
@@ -111,13 +124,23 @@
|
||||
"HeaderBackups": "Yedeklemeler",
|
||||
"HeaderChangePassword": "Parolayı Değiştir",
|
||||
"HeaderChapters": "Bölümler",
|
||||
"HeaderChooseAFolder": "Klasör Seç",
|
||||
"HeaderCollection": "Koleksiyon",
|
||||
"HeaderCollectionItems": "Koleksiyon Öğeleri",
|
||||
"HeaderCover": "Kapak",
|
||||
"HeaderCurrentDownloads": "Geçerli İndirmeler",
|
||||
"HeaderCustomMessageOnLogin": "Girişteki Kişiselleştirilmiş Mesaj",
|
||||
"HeaderCustomMetadataProviders": "Kişiselleştirilmiş Metadata Sağlayıcıları",
|
||||
"HeaderDetails": "Detaylar",
|
||||
"HeaderDownloadQueue": "Kuyruktakileri İndir",
|
||||
"HeaderEbookFiles": "Ebook Dosyaları",
|
||||
"HeaderEmail": "Email",
|
||||
"HeaderEmailSettings": "Email Ayarları",
|
||||
"HeaderEpisodes": "Bölümler",
|
||||
"HeaderEreaderDevices": "Ekitap Cihazları",
|
||||
"HeaderEreaderSettings": "Ereader Ayarları",
|
||||
"HeaderFiles": "Dosyalar",
|
||||
"HeaderFindChapters": "Bölümleri Bul",
|
||||
"HeaderIgnoredFiles": "Görmezden Gelinen Dosyalar",
|
||||
"HeaderItemFiles": "Öğe Dosyaları",
|
||||
"HeaderItemMetadataUtils": "Öğe Üstveri Araçları",
|
||||
|
||||
@@ -418,6 +418,7 @@
|
||||
"LabelLanguages": "Мови",
|
||||
"LabelLastBookAdded": "Останню книгу додано",
|
||||
"LabelLastBookUpdated": "Останню книгу оновлено",
|
||||
"LabelLastProgressDate": "Останній прогрес: {0}",
|
||||
"LabelLastSeen": "Активність",
|
||||
"LabelLastTime": "Останній час",
|
||||
"LabelLastUpdate": "Останнє оновлення",
|
||||
@@ -430,6 +431,7 @@
|
||||
"LabelLibraryFilterSublistEmpty": "Ні {0}",
|
||||
"LabelLibraryItem": "Елемент бібліотеки",
|
||||
"LabelLibraryName": "Назва бібліотеки",
|
||||
"LabelLibrarySortByProgress": "Прогрес оновлено",
|
||||
"LabelLimit": "Обмеження",
|
||||
"LabelLineSpacing": "Відстань між рядками",
|
||||
"LabelListenAgain": "Слухати знову",
|
||||
@@ -438,6 +440,7 @@
|
||||
"LabelLogLevelWarn": "Увага",
|
||||
"LabelLookForNewEpisodesAfterDate": "Шукати нові епізоди після вказаної дати",
|
||||
"LabelLowestPriority": "Найнижчий пріоритет",
|
||||
"LabelMatchConfidence": "Впевненість",
|
||||
"LabelMatchExistingUsersBy": "Шукати наявних користувачів за",
|
||||
"LabelMatchExistingUsersByDescription": "Використовується для підключення наявних користувачів. Після підключення користувач отримає унікальний id від вашого сервісу SSO",
|
||||
"LabelMaxEpisodesToDownload": "Максимальна кількість епізодів для скачування. Використовуйте 0 для необмеженої кількості.",
|
||||
@@ -655,6 +658,7 @@
|
||||
"LabelTheme": "Тема",
|
||||
"LabelThemeDark": "Темна",
|
||||
"LabelThemeLight": "Світла",
|
||||
"LabelThemeSepia": "Сепія",
|
||||
"LabelTimeBase": "Шкала часу",
|
||||
"LabelTimeDurationXHours": "{0} години",
|
||||
"LabelTimeDurationXMinutes": "{0} хвилини",
|
||||
@@ -723,6 +727,7 @@
|
||||
"MessageAddToPlayerQueue": "Додати до черги відтворення",
|
||||
"MessageAppriseDescription": "Щоб скористатися цією функцією, вам потрібно мати запущену <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> або API, що оброблятиме ті ж запити. <br />Аби надсилати сповіщення, URL-адреса API Apprise мусить бути повною, наприклад, якщо ваш API розміщено за адресою <code>http://192.168.1.1:8337</code>, то необхідно вказати адресу <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "Переконайтесь, що ви використовуєте ASIN з правильної регіональної Audible зони, а не з Amazon.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "Застарілі токени API будуть видалені в майбутньому. Натомість використовуйте <a href=\"/config/api-keys\">Ключі API</a>.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "Перезавантажте сервер після збереження, щоб застосувати зміни OIDC.",
|
||||
"MessageAuthenticationSecurityMessage": "Автентифікацію покращено для безпеки. Усім користувачам потрібно повторно увійти в систему.",
|
||||
"MessageBackupsDescription": "Резервні копії містять користувачів, прогрес, подробиці елементів бібліотеки, налаштування сервера та зображення з <code>/metadata/items</code> та <code>/metadata/authors</code>. Резервні копії <strong>не</strong> містять жодних файлів з тек бібліотеки.",
|
||||
@@ -800,6 +805,8 @@
|
||||
"MessageFeedURLWillBe": "URL-адреса каналу буде {0}",
|
||||
"MessageFetching": "Отримання...",
|
||||
"MessageForceReScanDescription": "Просканує всі файли заново, як при першому скануванні. ID3-мітки, OPF-файли та текстові файли будуть проскановані як нові.",
|
||||
"MessageHeatmapListeningTimeTooltip": "<strong>{0} прослуховування</strong> на {1}",
|
||||
"MessageHeatmapNoListeningSessions": "Немає сеансів прослуховування на {0}",
|
||||
"MessageImportantNotice": "Важливе повідомлення!",
|
||||
"MessageInsertChapterBelow": "Введіть главу нижче",
|
||||
"MessageInvalidAsin": "Невірний ASIN",
|
||||
@@ -836,7 +843,7 @@
|
||||
"MessageNoItems": "Елементи відсутні",
|
||||
"MessageNoItemsFound": "Елементів не знайдено",
|
||||
"MessageNoListeningSessions": "Сеанси прослуховування відсутні",
|
||||
"MessageNoLogs": "Немає журналів",
|
||||
"MessageNoLogs": "Немає Журнали",
|
||||
"MessageNoMediaProgress": "Прогрес відсутній",
|
||||
"MessageNoNotifications": "Сповіщення відсутні",
|
||||
"MessageNoPodcastFeed": "Некоректний подкаст: немає каналу",
|
||||
|
||||
@@ -240,10 +240,10 @@
|
||||
"LabelAllUsersExcludingGuests": "除访客外的所有用户",
|
||||
"LabelAllUsersIncludingGuests": "包括访客的所有用户",
|
||||
"LabelAlreadyInYourLibrary": "已存在你的库中",
|
||||
"LabelApiKeyCreated": "API 密钥 \"{0}\" 创建成功。",
|
||||
"LabelApiKeyCreatedDescription": "请确保现在就复制 API 密钥,之后将无法再次查看。",
|
||||
"LabelApiKeyCreated": "API 密钥 \"{0}\" 创建成功.",
|
||||
"LabelApiKeyCreatedDescription": "请确保现在就复制 API 密钥, 之后将无法再次查看.",
|
||||
"LabelApiKeyUser": "代用户操作",
|
||||
"LabelApiKeyUserDescription": "此 API 密钥将具有与其代理的用户相同的权限。在日志中,其请求将被视为由该用户直接发出。",
|
||||
"LabelApiKeyUserDescription": "此 API 密钥将具有与其代理的用户相同的权限. 在日志中, 其请求将被视为由该用户直接发出.",
|
||||
"LabelApiToken": "API 令牌",
|
||||
"LabelAppend": "附加",
|
||||
"LabelAudioBitrate": "音频比特率 (例如: 128k)",
|
||||
@@ -329,7 +329,7 @@
|
||||
"LabelEmailSettingsRejectUnauthorized": "拒绝未经授权的证书",
|
||||
"LabelEmailSettingsRejectUnauthorizedHelp": "禁用SSL证书验证可能会使你的连接面临安全风险, 例如中间人攻击. 只有当你了解其中的含义并信任所连接的邮件服务器时, 才能禁用此选项.",
|
||||
"LabelEmailSettingsSecure": "安全",
|
||||
"LabelEmailSettingsSecureHelp": "开启此选项时,将始终通过TLS连接服务器。关闭此选项时,仅在服务器支持STARTTLS扩展时使用TLS。在大多数情况下,如果连接到端口465,请将此项设为开启。如果连接到端口587或25,请将此设置保持为关闭。(来自nodemailer.com/smtp/#authentication)",
|
||||
"LabelEmailSettingsSecureHelp": "开启此选项时, 将始终通过TLS连接服务器. 关闭此选项时, 仅在服务器支持STARTTLS扩展时使用TLS. 在大多数情况下, 如果连接到端口465, 请将此项设为开启. 如果连接到端口587或25, 请将此设置保持为关闭. (来自nodemailer.com/smtp/#authentication)",
|
||||
"LabelEmailSettingsTestAddress": "测试地址",
|
||||
"LabelEmbeddedCover": "嵌入封面",
|
||||
"LabelEnable": "启用",
|
||||
@@ -357,10 +357,10 @@
|
||||
"LabelExpandSubSeries": "展开子系列",
|
||||
"LabelExpired": "已过期",
|
||||
"LabelExpiresAt": "过期时间",
|
||||
"LabelExpiresInSeconds": "有效期(秒)",
|
||||
"LabelExpiresInSeconds": "有效期 (秒)",
|
||||
"LabelExpiresNever": "从不",
|
||||
"LabelExplicit": "含成人内容",
|
||||
"LabelExplicitChecked": "成人内容(已核实)",
|
||||
"LabelExplicitChecked": "成人内容 (已核实)",
|
||||
"LabelExplicitUnchecked": "无成人内容 (未核实)",
|
||||
"LabelExportOPML": "导出 OPML",
|
||||
"LabelFeedURL": "源 URL",
|
||||
@@ -418,6 +418,7 @@
|
||||
"LabelLanguages": "语言",
|
||||
"LabelLastBookAdded": "最后添加的书",
|
||||
"LabelLastBookUpdated": "最后更新的书",
|
||||
"LabelLastProgressDate": "上次阅读时间: {0}",
|
||||
"LabelLastSeen": "上次查看时间",
|
||||
"LabelLastTime": "最近一次",
|
||||
"LabelLastUpdate": "最近更新",
|
||||
@@ -430,6 +431,7 @@
|
||||
"LabelLibraryFilterSublistEmpty": "没有 {0}",
|
||||
"LabelLibraryItem": "媒体库项目",
|
||||
"LabelLibraryName": "媒体库名称",
|
||||
"LabelLibrarySortByProgress": "进度更新时间",
|
||||
"LabelLimit": "限制",
|
||||
"LabelLineSpacing": "行间距",
|
||||
"LabelListenAgain": "再次收听",
|
||||
@@ -438,6 +440,7 @@
|
||||
"LabelLogLevelWarn": "警告",
|
||||
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
||||
"LabelLowestPriority": "最低优先级",
|
||||
"LabelMatchConfidence": "置信度",
|
||||
"LabelMatchExistingUsersBy": "匹配现有用户",
|
||||
"LabelMatchExistingUsersByDescription": "用于连接现有用户. 连接后, 用户将通过 SSO 提供商提供的唯一 id 进行匹配",
|
||||
"LabelMaxEpisodesToDownload": "可下载的最大集数. 输入 0 表示无限制.",
|
||||
@@ -655,6 +658,7 @@
|
||||
"LabelTheme": "主题",
|
||||
"LabelThemeDark": "黑暗",
|
||||
"LabelThemeLight": "明亮",
|
||||
"LabelThemeSepia": "棕褐色",
|
||||
"LabelTimeBase": "时间基准",
|
||||
"LabelTimeDurationXHours": "{0} 小时",
|
||||
"LabelTimeDurationXMinutes": "{0} 分钟",
|
||||
@@ -723,14 +727,15 @@
|
||||
"MessageAddToPlayerQueue": "添加到播放队列",
|
||||
"MessageAppriseDescription": "要使用此功能,你需要运行一个 <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> 实例或一个可以处理这些相同请求的 API. <br />Apprise API Url 应该是发送通知的完整 URL 路径, 例如: 如果你的 API 实例运行在 <code>http://192.168.1.1:8337</code>, 那么你可以输入 <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageAsinCheck": "确保你使用的 ASIN 来自正确的 Audible 地区, 而不是亚马逊.",
|
||||
"MessageAuthenticationLegacyTokenWarning": "旧版 API 令牌将来会被移除. 请改用 <a href=\"/config/api-keys\">API 密钥</a>.",
|
||||
"MessageAuthenticationOIDCChangesRestart": "保存后重新启动服务器以应用 OIDC 更改.",
|
||||
"MessageAuthenticationSecurityMessage": "身份验证安全性已增强,所有用户都需要重新登录。",
|
||||
"MessageAuthenticationSecurityMessage": "身份验证安全性已增强, 所有用户都需要重新登录.",
|
||||
"MessageBackupsDescription": "备份包括用户, 用户进度, 媒体库项目详细信息, 服务器设置和图像, 存储在 <code>/metadata/items</code> & <code>/metadata/authors</code>. 备份不包括存储在你的媒体库文件夹中的任何文件.",
|
||||
"MessageBackupsLocationEditNote": "注意: 更新备份位置不会移动或修改现有备份",
|
||||
"MessageBackupsLocationNoEditNote": "注意: 备份位置是通过环境变量设置的, 不能在此处更改.",
|
||||
"MessageBackupsLocationPathEmpty": "备份位置路径不能为空",
|
||||
"MessageBatchEditPopulateMapDetailsAllHelp": "使用所有项目的数据填充已启用的字段. 具有多个值的字段将被合并",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "提取此项目的信息,填入上方所有勾选的编辑框中",
|
||||
"MessageBatchEditPopulateMapDetailsItemHelp": "提取此项目的信息, 填入上方所有勾选的编辑框中",
|
||||
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
|
||||
"MessageBookshelfNoCollections": "你尚未进行任何收藏",
|
||||
"MessageBookshelfNoCollectionsHelp": "收藏是公开的. 所有有权访问图书馆的用户都可以看到它们.",
|
||||
@@ -746,7 +751,7 @@
|
||||
"MessageChaptersNotFound": "未找到章节",
|
||||
"MessageCheckingCron": "检查计划任务...",
|
||||
"MessageConfirmCloseFeed": "你确定要关闭此订阅源吗?",
|
||||
"MessageConfirmDeleteApiKey": "你确定要删除 API 密钥 \"{0}\" 吗?",
|
||||
"MessageConfirmDeleteApiKey": "你确定要删除 API 密钥 \"{0}\" 吗?",
|
||||
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
||||
"MessageConfirmDeleteDevice": "你确定要删除电子阅读器设备 \"{0}\" 吗?",
|
||||
"MessageConfirmDeleteFile": "这将从文件系统中删除该文件. 你确定吗?",
|
||||
@@ -774,7 +779,7 @@
|
||||
"MessageConfirmRemoveAuthor": "你确定要删除作者 \"{0}\"?",
|
||||
"MessageConfirmRemoveCollection": "你确定要移除收藏 \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisode": "你确定要移除剧集 \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisodeNote": "注意:此操作不会删除音频文件,除非勾选“完全删除文件”选项",
|
||||
"MessageConfirmRemoveEpisodeNote": "注意: 此操作不会删除音频文件, 除非勾选 \"完全删除文件\" 选项",
|
||||
"MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
|
||||
"MessageConfirmRemoveListeningSessions": "你确定要移除 {0} 收听会话吗?",
|
||||
"MessageConfirmRemoveMetadataFiles": "你确实要删除库项目文件夹中的所有 metadata.{0} 文件吗?",
|
||||
@@ -800,6 +805,8 @@
|
||||
"MessageFeedURLWillBe": "源 URL 将改为 {0}",
|
||||
"MessageFetching": "正在获取...",
|
||||
"MessageForceReScanDescription": "将像重新扫描一样再次扫描所有文件. 音频文件 ID3 标签, OPF 文件和文本文件将被扫描为新文件.",
|
||||
"MessageHeatmapListeningTimeTooltip": "{1} <strong>收听了 {0}</strong>",
|
||||
"MessageHeatmapNoListeningSessions": "{0} 没有收听",
|
||||
"MessageImportantNotice": "重要通知!",
|
||||
"MessageInsertChapterBelow": "在下面插入章节",
|
||||
"MessageInvalidAsin": "无效的 ASIN",
|
||||
@@ -866,7 +873,7 @@
|
||||
"MessageRemoveEpisodes": "移除 {0} 剧集",
|
||||
"MessageRemoveFromPlayerQueue": "从播放队列中移除",
|
||||
"MessageRemoveUserWarning": "是否确实要永久删除用户 \"{0}\"?",
|
||||
"MessageReportBugsAndContribute": "反馈问题、建议功能或参与贡献,请访问",
|
||||
"MessageReportBugsAndContribute": "反馈问题, 建议功能或参与贡献, 请访问",
|
||||
"MessageResetChaptersConfirm": "你确定要重置章节并撤消你所做的更改吗?",
|
||||
"MessageRestoreBackupConfirm": "你确定要恢复创建的这个备份",
|
||||
"MessageRestoreBackupWarning": "恢复备份将覆盖位于 /config 的整个数据库并覆盖 /metadata/items & /metadata/authors 中的图像.<br /><br />备份不会修改媒体库文件夹中的任何文件. 如果你已启用服务器设置将封面和元数据存储在库文件夹中,则不会备份或覆盖这些内容.<br /><br />将自动刷新使用服务器的所有客户端.",
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.26.1",
|
||||
"version": "2.27.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.26.0",
|
||||
"version": "2.27.0",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf",
|
||||
"version": "2.26.1",
|
||||
"version": "2.27.0",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast server",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -280,7 +280,7 @@ class MeController {
|
||||
}
|
||||
|
||||
const { password, newPassword } = req.body
|
||||
if (!password || !newPassword || typeof password !== 'string' || typeof newPassword !== 'string') {
|
||||
if ((typeof password !== 'string' && password !== null) || (typeof newPassword !== 'string' && newPassword !== null)) {
|
||||
return res.status(400).send('Missing or invalid password or new password')
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ class MeController {
|
||||
if (updated) {
|
||||
await Database.updateSetting(Database.emailSettings)
|
||||
SocketAuthority.clientEmitter(req.user.id, 'ereader-devices-updated', {
|
||||
ereaderDevices: Database.emailSettings.ereaderDevices
|
||||
ereaderDevices: Database.emailSettings.getEReaderDevices(req.user)
|
||||
})
|
||||
}
|
||||
res.json({
|
||||
|
||||
@@ -288,7 +288,12 @@ class SessionController {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
|
||||
const audioTrack = playbackSession.audioTracks.find((t) => t.index === audioTrackIndex)
|
||||
let audioTrack = playbackSession.audioTracks.find((t) => toNumber(t.index, 1) === audioTrackIndex)
|
||||
|
||||
// Support clients passing 0 or 1 for podcast episode audio track index (handles old episodes pre-v2.21.0 having null index)
|
||||
if (!audioTrack && playbackSession.mediaType === 'podcast' && audioTrackIndex === 0) {
|
||||
audioTrack = playbackSession.audioTracks[0]
|
||||
}
|
||||
if (!audioTrack) {
|
||||
Logger.error(`[SessionController] Unable to find audio track with index=${audioTrackIndex}`)
|
||||
return res.sendStatus(404)
|
||||
|
||||
@@ -7,7 +7,7 @@ const FantLab = require('../providers/FantLab')
|
||||
const AudiobookCovers = require('../providers/AudiobookCovers')
|
||||
const CustomProviderAdapter = require('../providers/CustomProviderAdapter')
|
||||
const Logger = require('../Logger')
|
||||
const { levenshteinDistance, escapeRegExp } = require('../utils/index')
|
||||
const { levenshteinDistance, levenshteinSimilarity, escapeRegExp, isValidASIN } = require('../utils/index')
|
||||
const htmlSanitizer = require('../utils/htmlSanitizer')
|
||||
|
||||
class BookFinder {
|
||||
@@ -385,7 +385,11 @@ class BookFinder {
|
||||
|
||||
if (!title) return books
|
||||
|
||||
books = await this.runSearch(title, author, provider, asin, maxTitleDistance, maxAuthorDistance)
|
||||
const isTitleAsin = isValidASIN(title.toUpperCase())
|
||||
|
||||
let actualTitleQuery = title
|
||||
let actualAuthorQuery = author
|
||||
books = await this.runSearch(actualTitleQuery, actualAuthorQuery, provider, asin, maxTitleDistance, maxAuthorDistance)
|
||||
|
||||
if (!books.length && maxFuzzySearches > 0) {
|
||||
// Normalize title and author
|
||||
@@ -408,19 +412,26 @@ class BookFinder {
|
||||
for (const titlePart of titleParts) titleCandidates.add(titlePart)
|
||||
titleCandidates = titleCandidates.getCandidates()
|
||||
for (const titleCandidate of titleCandidates) {
|
||||
if (titleCandidate == title && authorCandidate == author) continue // We already tried this
|
||||
if (titleCandidate == actualTitleQuery && authorCandidate == actualAuthorQuery) continue // We already tried this
|
||||
if (++numFuzzySearches > maxFuzzySearches) break loop_author
|
||||
books = await this.runSearch(titleCandidate, authorCandidate, provider, asin, maxTitleDistance, maxAuthorDistance)
|
||||
actualTitleQuery = titleCandidate
|
||||
actualAuthorQuery = authorCandidate
|
||||
books = await this.runSearch(actualTitleQuery, actualAuthorQuery, provider, asin, maxTitleDistance, maxAuthorDistance)
|
||||
if (books.length) break loop_author
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (books.length) {
|
||||
const resultsHaveDuration = provider.startsWith('audible')
|
||||
if (resultsHaveDuration && libraryItem?.media?.duration) {
|
||||
const libraryItemDurationMinutes = libraryItem.media.duration / 60
|
||||
// If provider results have duration, sort by ascendinge duration difference from libraryItem
|
||||
const isAudibleProvider = provider.startsWith('audible')
|
||||
const libraryItemDurationMinutes = libraryItem?.media?.duration ? libraryItem.media.duration / 60 : null
|
||||
|
||||
books.forEach((book) => {
|
||||
if (typeof book !== 'object' || !isAudibleProvider) return
|
||||
book.matchConfidence = this.calculateMatchConfidence(book, libraryItemDurationMinutes, actualTitleQuery, actualAuthorQuery, isTitleAsin)
|
||||
})
|
||||
|
||||
if (isAudibleProvider && libraryItemDurationMinutes) {
|
||||
books.sort((a, b) => {
|
||||
const aDuration = a.duration || Number.POSITIVE_INFINITY
|
||||
const bDuration = b.duration || Number.POSITIVE_INFINITY
|
||||
@@ -433,6 +444,120 @@ class BookFinder {
|
||||
return books
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate match confidence score for a book
|
||||
* @param {Object} book - The book object to calculate confidence for
|
||||
* @param {number|null} libraryItemDurationMinutes - Duration of library item in minutes
|
||||
* @param {string} actualTitleQuery - Actual title query
|
||||
* @param {string} actualAuthorQuery - Actual author query
|
||||
* @param {boolean} isTitleAsin - Whether the title is an ASIN
|
||||
* @returns {number|null} - Match confidence score or null if not applicable
|
||||
*/
|
||||
calculateMatchConfidence(book, libraryItemDurationMinutes, actualTitleQuery, actualAuthorQuery, isTitleAsin) {
|
||||
// ASIN results are always a match
|
||||
if (isTitleAsin) return 1.0
|
||||
|
||||
let durationScore
|
||||
if (libraryItemDurationMinutes && typeof book.duration === 'number') {
|
||||
const durationDiff = Math.abs(book.duration - libraryItemDurationMinutes)
|
||||
// Duration scores:
|
||||
// diff | score
|
||||
// 0 | 1.0
|
||||
// 1 | 1.0
|
||||
// 2 | 0.9
|
||||
// 3 | 0.8
|
||||
// 4 | 0.7
|
||||
// 5 | 0.6
|
||||
// 6 | 0.48
|
||||
// 7 | 0.36
|
||||
// 8 | 0.24
|
||||
// 9 | 0.12
|
||||
// 10 | 0.0
|
||||
if (durationDiff <= 1) {
|
||||
// Covers durationDiff = 0 for score 1.0
|
||||
durationScore = 1.0
|
||||
} else if (durationDiff <= 5) {
|
||||
// (1, 5] - Score from 1.0 down to 0.6
|
||||
// Linearly interpolates between (1, 1.0) and (5, 0.6)
|
||||
// Equation: y = 1.0 - 0.08 * x
|
||||
durationScore = 1.1 - 0.1 * durationDiff
|
||||
} else if (durationDiff <= 10) {
|
||||
// (5, 10] - Score from 0.6 down to 0.0
|
||||
// Linearly interpolates between (5, 0.6) and (10, 0.0)
|
||||
// Equation: y = 1.2 - 0.12 * x
|
||||
durationScore = 1.2 - 0.12 * durationDiff
|
||||
} else {
|
||||
// durationDiff > 10 - Score is 0.0
|
||||
durationScore = 0.0
|
||||
}
|
||||
Logger.debug(`[BookFinder] Duration diff: ${durationDiff}, durationScore: ${durationScore}`)
|
||||
} else {
|
||||
// Default score if library item duration or book duration is not available
|
||||
durationScore = 0.1
|
||||
}
|
||||
|
||||
const calculateTitleScore = (titleQuery, book, keepSubtitle = false) => {
|
||||
const cleanTitle = cleanTitleForCompares(book.title || '', keepSubtitle)
|
||||
const cleanSubtitle = keepSubtitle && book.subtitle ? `: ${book.subtitle}` : ''
|
||||
const normBookTitle = `${cleanTitle}${cleanSubtitle}`
|
||||
const normTitleQuery = cleanTitleForCompares(titleQuery, keepSubtitle)
|
||||
const titleSimilarity = levenshteinSimilarity(normTitleQuery, normBookTitle)
|
||||
Logger.debug(`[BookFinder] keepSubtitle: ${keepSubtitle}, normBookTitle: ${normBookTitle}, normTitleQuery: ${normTitleQuery}, titleSimilarity: ${titleSimilarity}`)
|
||||
return titleSimilarity
|
||||
}
|
||||
const titleQueryHasSubtitle = hasSubtitle(actualTitleQuery)
|
||||
const titleScore = calculateTitleScore(actualTitleQuery, book, titleQueryHasSubtitle)
|
||||
|
||||
let authorScore
|
||||
const normAuthorQuery = cleanAuthorForCompares(actualAuthorQuery)
|
||||
const normBookAuthor = cleanAuthorForCompares(book.author || '')
|
||||
if (!normAuthorQuery) {
|
||||
// Original query had no author
|
||||
authorScore = 1.0 // Neutral score
|
||||
} else {
|
||||
// Original query HAS an author (cleanedQueryAuthorForScore is not empty)
|
||||
if (normBookAuthor) {
|
||||
const bookAuthorParts = normBookAuthor.split(',').map((name) => name.trim().toLowerCase())
|
||||
// Filter out empty parts that might result from ", ," or trailing/leading commas
|
||||
const validBookAuthorParts = bookAuthorParts.filter((p) => p.length > 0)
|
||||
|
||||
if (validBookAuthorParts.length === 0) {
|
||||
// Book author string was present but effectively empty (e.g. ",,")
|
||||
// Since cleanedQueryAuthorForScore is non-empty here, this is a mismatch.
|
||||
authorScore = 0.0
|
||||
} else {
|
||||
let maxPartScore = levenshteinSimilarity(normAuthorQuery, normBookAuthor)
|
||||
Logger.debug(`[BookFinder] normAuthorQuery: ${normAuthorQuery}, normBookAuthor: ${normBookAuthor}, similarity: ${maxPartScore}`)
|
||||
if (validBookAuthorParts.length > 1 || normBookAuthor.includes(',')) {
|
||||
validBookAuthorParts.forEach((part) => {
|
||||
// part is guaranteed to be non-empty here
|
||||
// cleanedQueryAuthorForScore is also guaranteed non-empty here.
|
||||
// levenshteinDistance lowercases by default, but part is already lowercased.
|
||||
const similarity = levenshteinSimilarity(normAuthorQuery, part)
|
||||
Logger.debug(`[BookFinder] normAuthorQuery: ${normAuthorQuery}, bookAuthorPart: ${part}, similarity: ${similarity}`)
|
||||
const currentPartScore = similarity
|
||||
maxPartScore = Math.max(maxPartScore, currentPartScore)
|
||||
})
|
||||
}
|
||||
authorScore = maxPartScore
|
||||
}
|
||||
} else {
|
||||
// Book has NO author (or not a string, or empty string)
|
||||
// Query has an author (cleanedQueryAuthorForScore is non-empty), book does not.
|
||||
authorScore = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
const W_DURATION = 0.7
|
||||
const W_TITLE = 0.2
|
||||
const W_AUTHOR = 0.1
|
||||
|
||||
Logger.debug(`[BookFinder] Duration score: ${durationScore}, Title score: ${titleScore}, Author score: ${authorScore}`)
|
||||
const confidence = W_DURATION * durationScore + W_TITLE * titleScore + W_AUTHOR * authorScore
|
||||
Logger.debug(`[BookFinder] Confidence: ${confidence}`)
|
||||
return Math.max(0, Math.min(1, confidence))
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for books
|
||||
*
|
||||
@@ -464,6 +589,7 @@ class BookFinder {
|
||||
} else {
|
||||
books = await this.getGoogleBooksResults(title, author)
|
||||
}
|
||||
|
||||
books.forEach((book) => {
|
||||
if (book.description) {
|
||||
book.description = htmlSanitizer.sanitize(book.description)
|
||||
@@ -505,6 +631,9 @@ class BookFinder {
|
||||
}
|
||||
module.exports = new BookFinder()
|
||||
|
||||
function hasSubtitle(title) {
|
||||
return title.includes(':') || title.includes(' - ')
|
||||
}
|
||||
function stripSubtitle(title) {
|
||||
if (title.includes(':')) {
|
||||
return title.split(':')[0].trim()
|
||||
@@ -523,12 +652,12 @@ function replaceAccentedChars(str) {
|
||||
}
|
||||
}
|
||||
|
||||
function cleanTitleForCompares(title) {
|
||||
function cleanTitleForCompares(title, keepSubtitle = false) {
|
||||
if (!title) return ''
|
||||
title = stripRedundantSpaces(title)
|
||||
|
||||
// Remove subtitle if there (i.e. "Cool Book: Coolest Ever" becomes "Cool Book")
|
||||
let stripped = stripSubtitle(title)
|
||||
let stripped = keepSubtitle ? title : stripSubtitle(title)
|
||||
|
||||
// Remove text in paranthesis (i.e. "Ender's Game (Ender's Saga)" becomes "Ender's Game")
|
||||
let cleaned = stripped.replace(/ *\([^)]*\) */g, '')
|
||||
|
||||
@@ -185,6 +185,7 @@ class PodcastEpisode extends Model {
|
||||
const track = structuredClone(this.audioFile)
|
||||
track.startOffset = 0
|
||||
track.title = this.audioFile.metadata.filename
|
||||
track.index = 1 // Podcast episodes only have one track
|
||||
track.contentUrl = `/api/items/${libraryItemId}/file/${track.ino}`
|
||||
return track
|
||||
}
|
||||
|
||||
@@ -34,6 +34,14 @@ const levenshteinDistance = (str1, str2, caseSensitive = false) => {
|
||||
}
|
||||
module.exports.levenshteinDistance = levenshteinDistance
|
||||
|
||||
const levenshteinSimilarity = (str1, str2, caseSensitive = false) => {
|
||||
const distance = levenshteinDistance(str1, str2, caseSensitive)
|
||||
const maxLength = Math.max(str1.length, str2.length)
|
||||
if (maxLength === 0) return 1
|
||||
return 1 - distance / maxLength
|
||||
}
|
||||
module.exports.levenshteinSimilarity = levenshteinSimilarity
|
||||
|
||||
module.exports.isObject = (val) => {
|
||||
return val !== null && typeof val === 'object'
|
||||
}
|
||||
|
||||
@@ -399,9 +399,6 @@ module.exports = {
|
||||
if (filterGroup !== 'series' && sortBy === 'sequence') {
|
||||
sortBy = 'media.metadata.title'
|
||||
}
|
||||
if (filterGroup !== 'progress' && sortBy === 'progress') {
|
||||
sortBy = 'media.metadata.title'
|
||||
}
|
||||
const includeRSSFeed = include.includes('rssfeed')
|
||||
const includeMediaItemShare = !!user?.isAdminOrUp && include.includes('share')
|
||||
|
||||
@@ -532,6 +529,18 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
// When sorting by progress but not filtering by progress, include media progresses
|
||||
if (filterGroup !== 'progress' && sortBy === 'progress') {
|
||||
bookIncludes.push({
|
||||
model: Database.mediaProgressModel,
|
||||
attributes: ['id', 'isFinished', 'currentTime', 'ebookProgress', 'updatedAt'],
|
||||
where: {
|
||||
userId: user.id
|
||||
},
|
||||
required: false
|
||||
})
|
||||
}
|
||||
|
||||
let { mediaWhere, replacements } = this.getMediaGroupQuery(filterGroup, filterValue)
|
||||
let bookWhere = Array.isArray(mediaWhere) ? mediaWhere : [mediaWhere]
|
||||
|
||||
|
||||
@@ -5,6 +5,12 @@ const bookFinder = require('../../../server/finders/BookFinder')
|
||||
const { LogLevel } = require('../../../server/utils/constants')
|
||||
const Logger = require('../../../server/Logger')
|
||||
Logger.setLogLevel(LogLevel.INFO)
|
||||
const { levenshteinDistance } = require('../../../server/utils/index')
|
||||
|
||||
// levenshteinDistance is needed for manual calculation of expected scores in tests.
|
||||
// Assuming it's accessible for testing purposes or we mock/replicate its basic behavior if needed.
|
||||
// For now, we'll assume bookFinder.search uses it internally correctly.
|
||||
// const { levenshteinDistance } = require('../../../server/utils/index') // Not used directly in test logic, but for reasoning.
|
||||
|
||||
describe('TitleCandidates', () => {
|
||||
describe('cleanAuthor non-empty', () => {
|
||||
@@ -326,31 +332,262 @@ describe('search', () => {
|
||||
const sorted = [{ duration: 1000 }, { duration: 500 }, { duration: 2000 }, { duration: 3000 }]
|
||||
|
||||
beforeEach(() => {
|
||||
runSearchStub.withArgs(t, a, provider).resolves(unsorted)
|
||||
runSearchStub.withArgs(t, a, provider).resolves(structuredClone(unsorted))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('returns results sorted by library item duration diff', async () => {
|
||||
expect(await bookFinder.search(libraryItem, provider, t, a)).to.deep.equal(sorted)
|
||||
const result = (await bookFinder.search(libraryItem, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||
expect(result).to.deep.equal(sorted)
|
||||
})
|
||||
|
||||
it('returns unsorted results if library item is null', async () => {
|
||||
expect(await bookFinder.search(null, provider, t, a)).to.deep.equal(unsorted)
|
||||
const result = (await bookFinder.search(null, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||
expect(result).to.deep.equal(unsorted)
|
||||
})
|
||||
|
||||
it('returns unsorted results if library item duration is undefined', async () => {
|
||||
expect(await bookFinder.search({ media: {} }, provider, t, a)).to.deep.equal(unsorted)
|
||||
const result = (await bookFinder.search({ media: {} }, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||
expect(result).to.deep.equal(unsorted)
|
||||
})
|
||||
|
||||
it('returns unsorted results if library item media is undefined', async () => {
|
||||
expect(await bookFinder.search({}, provider, t, a)).to.deep.equal(unsorted)
|
||||
const result = (await bookFinder.search({}, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||
expect(result).to.deep.equal(unsorted)
|
||||
})
|
||||
|
||||
it('should return a result last if it has no duration', async () => {
|
||||
const unsorted = [{}, { duration: 3000 }, { duration: 2000 }, { duration: 1000 }, { duration: 500 }]
|
||||
const sorted = [{ duration: 1000 }, { duration: 500 }, { duration: 2000 }, { duration: 3000 }, {}]
|
||||
runSearchStub.withArgs(t, a, provider).resolves(unsorted)
|
||||
runSearchStub.withArgs(t, a, provider).resolves(structuredClone(unsorted))
|
||||
const result = (await bookFinder.search(libraryItem, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||
expect(result).to.deep.equal(sorted)
|
||||
})
|
||||
})
|
||||
|
||||
expect(await bookFinder.search(libraryItem, provider, t, a)).to.deep.equal(sorted)
|
||||
describe('matchConfidence score', () => {
|
||||
const W_DURATION = 0.7
|
||||
const W_TITLE = 0.2
|
||||
const W_AUTHOR = 0.1
|
||||
const DEFAULT_DURATION_SCORE_MISSING_INFO = 0.1
|
||||
|
||||
const libraryItemPerfectDuration = { media: { duration: 600 } } // 10 minutes
|
||||
|
||||
// Helper to calculate expected title/author score based on Levenshtein
|
||||
// Assumes queryPart and bookPart are already "cleaned" for length calculation consistency with BookFinder.js
|
||||
const calculateStringMatchScore = (cleanedQueryPart, cleanedBookPart) => {
|
||||
if (!cleanedQueryPart) return cleanedBookPart ? 0 : 1 // query empty: 1 if book empty, else 0
|
||||
if (!cleanedBookPart) return 0 // query non-empty, book empty: 0
|
||||
|
||||
// Use the imported levenshteinDistance. It defaults to case-insensitive, which is what we want.
|
||||
const distance = levenshteinDistance(cleanedQueryPart, cleanedBookPart)
|
||||
return Math.max(0, 1 - distance / Math.max(cleanedQueryPart.length, cleanedBookPart.length))
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
runSearchStub.resolves([])
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
describe('for audible provider', () => {
|
||||
const provider = 'audible'
|
||||
|
||||
it('should be 1.0 for perfect duration, title, and author match', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
// durationScore = 1.0 (diff 0 <= 1 min)
|
||||
// titleScore = 1.0 (exact match)
|
||||
// authorScore = 1.0 (exact match)
|
||||
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should correctly score a large duration mismatch', async () => {
|
||||
const bookResults = [{ duration: 21, title: 'The Great Novel', author: 'John Doe' }] // 21 min, diff = 11 min
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
// durationScore = 0.0
|
||||
// titleScore = 1.0
|
||||
// authorScore = 1.0
|
||||
const expectedConfidence = W_DURATION * 0.0 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should correctly score a medium duration mismatch', async () => {
|
||||
const bookResults = [{ duration: 16, title: 'The Great Novel', author: 'John Doe' }] // 16 min, diff = 6 min
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
// durationScore = 1.2 - 6 * 0.12 = 0.48
|
||||
// titleScore = 1.0
|
||||
// authorScore = 1.0
|
||||
const expectedConfidence = W_DURATION * 0.48 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should correctly score a minor duration mismatch', async () => {
|
||||
const bookResults = [{ duration: 14, title: 'The Great Novel', author: 'John Doe' }] // 14 min, diff = 4 min
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
// durationScore = 1.1 - 4 * 0.1 = 0.7
|
||||
// titleScore = 1.0
|
||||
// authorScore = 1.0
|
||||
const expectedConfidence = W_DURATION * 0.7 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should correctly score a tiny duration mismatch', async () => {
|
||||
const bookResults = [{ duration: 11, title: 'The Great Novel', author: 'John Doe' }] // 11 min, diff = 1 min
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
// durationScore = 1.0
|
||||
// titleScore = 1.0
|
||||
// authorScore = 1.0
|
||||
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should use default duration score if libraryItem duration is missing', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search({ media: {} }, provider, 'The Great Novel', 'John Doe')
|
||||
// durationScore = DEFAULT_DURATION_SCORE_MISSING_INFO (0.2)
|
||||
const expectedConfidence = W_DURATION * DEFAULT_DURATION_SCORE_MISSING_INFO + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should use default duration score if book duration is missing', async () => {
|
||||
const bookResults = [{ title: 'The Great Novel', author: 'John Doe' }] // No duration in book
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
// durationScore = DEFAULT_DURATION_SCORE_MISSING_INFO (0.2)
|
||||
const expectedConfidence = W_DURATION * DEFAULT_DURATION_SCORE_MISSING_INFO + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should correctly score a partial title match', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
// Query: 'Novel Ex', Book: 'Novel'
|
||||
// cleanTitleForCompares('Novel Ex') -> 'novel ex' (length 8)
|
||||
// cleanTitleForCompares('Novel') -> 'novel' (length 5)
|
||||
// levenshteinDistance('novel ex', 'novel') = 3
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'Novel Ex', 'John Doe')
|
||||
const expectedTitleScore = calculateStringMatchScore('novel ex', 'novel') // 1 - (3/8) = 0.625
|
||||
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * expectedTitleScore + W_AUTHOR * 1.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should correctly score a partial author match (comma-separated)', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'Jane Smith, Jon Doee' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
// Query: 'Jon Doe', Book part: 'Jon Doee'
|
||||
// cleanAuthorForCompares('Jon Doe') -> 'jon doe' (length 7)
|
||||
// book author part (already lowercased) -> 'jon doee' (length 8)
|
||||
// levenshteinDistance('jon doe', 'jon doee') = 1
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'Jon Doe')
|
||||
// For the author part 'jon doee':
|
||||
const expectedAuthorPartScore = calculateStringMatchScore('jon doe', 'jon doee') // 1 - (1/7)
|
||||
// Assuming 'jane smith' gives a lower or 0 score, max score will be from 'jon doee'
|
||||
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * expectedAuthorPartScore
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should give authorScore 0 if query has author but book does not', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: null }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
// authorScore = 0.0
|
||||
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * 0.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should give authorScore 1.0 if query has no author', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', '') // Empty author
|
||||
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||
})
|
||||
|
||||
it('handles book author string that is only commas correctly (score 0)', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: ',, ,, ,' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
// cleanedQueryAuthorForScore = "john doe"
|
||||
// book.author leads to validBookAuthorParts being empty.
|
||||
// authorScore = 0.0
|
||||
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * 0.0
|
||||
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||
})
|
||||
|
||||
it('should return 1.0 for ASIN results', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'B000F28ZJ4', null)
|
||||
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||
})
|
||||
|
||||
it('should return 1.0 when author matches one of the book authors', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe, Jane Smith' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||
})
|
||||
|
||||
it('should return 1.0 when author query and multiple book authors are the same', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe, Jane Smith' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe, Jane Smith')
|
||||
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||
})
|
||||
|
||||
it('should correctly score against a book with a subtitle when the query has a subtitle', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', subtitle: 'A Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel: A Novel', 'John Doe')
|
||||
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||
})
|
||||
|
||||
it('should correctly score against a book with a subtitle when the query does not have a subtitle', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', subtitle: 'A Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||
})
|
||||
|
||||
describe('after fuzzy searches', () => {
|
||||
it('should return 1.0 for a title candidate match', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves([])
|
||||
runSearchStub.withArgs('the great novel', 'john doe').resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel - A Novel', 'John Doe')
|
||||
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||
})
|
||||
|
||||
it('should return 1.0 for an author candidate match', async () => {
|
||||
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves([])
|
||||
runSearchStub.withArgs('the great novel', 'john doe').resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe, Jane Smith')
|
||||
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('for non-audible provider (e.g., google)', () => {
|
||||
const provider = 'google'
|
||||
it('should have not have matchConfidence', async () => {
|
||||
const bookResults = [{ title: 'The Great Novel', author: 'John Doe' }]
|
||||
runSearchStub.resolves(bookResults)
|
||||
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||
expect(results[0]).to.not.have.property('matchConfidence')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user