Compare commits

...

72 Commits

Author SHA1 Message Date
advplyr
3337b3af18 Version bump v2.27.0 2025-08-02 17:53:27 -05:00
advplyr
835d2c7f36 Merge pull request #4535 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-08-02 18:52:04 -04:00
FiendFEARing
03f91099e0 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1135 of 1135 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-08-02 15:02:00 +02:00
Максим Горпиніч
c04afd0787 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1135 of 1135 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-02 15:01:59 +02:00
Grzegorz Orlowski
b03bd79f5d Translated using Weblate (Polish)
Currently translated at 77.6% (881 of 1135 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-08-02 15:01:57 +02:00
Troj@
79b4042e8e Translated using Weblate (Belarusian)
Currently translated at 63.6% (721 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2025-08-01 02:51:15 +02:00
FiendFEARing
8f718ef91c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-08-01 02:51:15 +02:00
Pepijn
4053b20623 Translated using Weblate (Dutch)
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-01 02:51:15 +02:00
Remco Schrijver
c4d654635f Translated using Weblate (Dutch)
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-01 02:51:15 +02:00
enosh
ef5d0ffa48 Translated using Weblate (Hebrew)
Currently translated at 74.7% (847 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/he/
2025-08-01 02:51:15 +02:00
Troj@
6a826cdb36 Translated using Weblate (Belarusian)
Currently translated at 58.1% (659 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2025-08-01 02:51:15 +02:00
thehijacker
1d837f5f21 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-08-01 02:51:15 +02:00
Максим Горпиніч
80873b379c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-01 02:51:15 +02:00
Remco Schrijver
82a8f8f126 Translated using Weblate (Dutch)
Currently translated at 98.6% (1118 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-01 02:51:15 +02:00
Kabika82
4725a466da Translated using Weblate (Hungarian)
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-08-01 02:51:15 +02:00
Jannik
031edc870c Translated using Weblate (German)
Currently translated at 99.8% (1131 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-01 02:51:15 +02:00
Jan-Eric Myhrgren
625e2445b5 Translated using Weblate (Swedish)
Currently translated at 96.2% (1089 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-01 02:51:15 +02:00
max grakov
1640af2f1c Translated using Weblate (Russian)
Currently translated at 100.0% (1131 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-08-01 02:51:15 +02:00
Remco Schrijver
c76f76cc27 Translated using Weblate (Dutch)
Currently translated at 98.4% (1113 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-01 02:51:15 +02:00
ugyes
74af212293 Translated using Weblate (Hungarian)
Currently translated at 100.0% (1131 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-08-01 02:51:15 +02:00
Vito0912
e04efb9c6a Translated using Weblate (German)
Currently translated at 99.9% (1130 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-01 02:51:14 +02:00
B0rax
ee17e7a555 Translated using Weblate (German)
Currently translated at 99.9% (1130 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-01 02:51:14 +02:00
FiendFEARing
694a852c07 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1131 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-08-01 02:51:14 +02:00
Максим Горпиніч
18068bb261 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1131 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-01 02:51:14 +02:00
Mikkel Dupont Olesen
71257f6c6c Translated using Weblate (Danish)
Currently translated at 99.4% (1124 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-08-01 02:51:14 +02:00
advplyr
4d70929d2e Add locale strings for user stats heatmap #4550 2025-07-31 19:51:05 -05:00
advplyr
578e9559e4 Merge pull request #4551 from chriscam85/master
Including total durations into the de-branding from #4226 as warning message is always present currently
2025-07-31 20:23:47 -04:00
advplyr
894ea0b80a Update chapter data log 2025-07-31 19:19:11 -05:00
Chris Campanile
e54571f011 Including total durations into the de-branding from #4226 as warning message is always present currently 2025-07-31 16:48:05 -07:00
advplyr
32da0f1224 Merge pull request #4542 from advplyr/progress_updated_sort
Add book library sort by progress updated #1215
2025-07-28 15:13:40 -04:00
advplyr
2054accdc9 Update library sort dropdown to use max height available 2025-07-28 15:07:57 -04:00
advplyr
7d8b857c77 Add book library sort by progress updated #1215 2025-07-28 14:58:28 -04:00
advplyr
0107cb4782 UI/UX fix x overflow for sessions tables on mobile 2025-07-26 09:45:44 -05:00
advplyr
f273eee807 Merge pull request #4534 from michaeldvinci/sepia-theme
Add 'sepia' theme to EpubReader
2025-07-25 17:34:34 -05:00
advplyr
4af21b079a Fix epub toc search input colors for themes 2025-07-25 17:31:59 -05:00
Michael Vinci
c9eaf2db2d Add 'sepia' theme to EpubReader 2025-07-25 17:01:16 -05:00
advplyr
a5fb0d9cdb Merge pull request #4530 from advplyr/fix_ereader_socket_event
Fix ereader update socket event sending all devices #4529
2025-07-24 17:33:42 -05:00
advplyr
53c80d9798 Merge pull request #4528 from FelixSche/master
Update SideRail.vue
2025-07-24 17:32:44 -05:00
advplyr
832165716b Fix ereader update socket event sending all devices #4529 2025-07-24 17:29:08 -05:00
Felix
d9f2d8bf1d Update SideRail.vue
Changed cursor at version to pointer
2025-07-24 13:57:26 +02:00
advplyr
a7a3a56509 Version bump v2.26.3 2025-07-23 17:18:51 -05:00
advplyr
4082fadf90 Merge pull request #4525 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-07-23 17:17:51 -05:00
FiendFEARing
93160b83bf Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1130 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-23 13:06:32 +02:00
Максим Горпиніч
472240f994 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1130 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-07-23 13:06:31 +02:00
Dmitry
c3f0fb8e5e Translated using Weblate (Russian)
Currently translated at 100.0% (1130 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-07-23 13:06:30 +02:00
Daniel Schosser
b156ebeb9f Translated using Weblate (German)
Currently translated at 99.9% (1129 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-23 13:06:29 +02:00
advplyr
e4c775c847 Merge pull request #4523 from advplyr/fix_change_empty_root_password
Update change password to support null or empty string passwords #4522
2025-07-22 17:01:07 -05:00
advplyr
45e8e72759 Update change password to support null or empty string passwords #4522 2025-07-22 15:17:00 -05:00
advplyr
0ae7340889 Merge pull request #4520 from advplyr/fix_podcast_session_track_index
Fix podcast episode track index null in playback session
2025-07-22 15:04:22 -05:00
advplyr
8c38987d92 Fix podcast episode track index null in playback session 2025-07-22 14:44:36 -05:00
advplyr
878f0787ba Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2025-07-21 17:07:12 -05:00
advplyr
880d85eaef Version bump v2.26.2 2025-07-21 17:07:06 -05:00
advplyr
f7aaebc1de Merge pull request #4508 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-07-21 17:01:15 -05:00
Charlie
d96ebbe82d Translated using Weblate (French)
Currently translated at 100.0% (1129 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2025-07-22 00:00:51 +02:00
kuci-JK
70d67156f0 Translated using Weblate (Czech)
Currently translated at 99.2% (1121 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2025-07-22 00:00:51 +02:00
Serhat Gülaştı
f293b317be Translated using Weblate (Turkish)
Currently translated at 28.3% (320 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/tr/
2025-07-22 00:00:51 +02:00
Simple16
1f23794f88 Translated using Weblate (Russian)
Currently translated at 99.9% (1128 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-07-22 00:00:51 +02:00
FiendFEARing
e6bfd118f6 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1129 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-22 00:00:51 +02:00
SunSpring
1166400ab1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1129 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-22 00:00:51 +02:00
Максим Горпиніч
55f0ac871b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1129 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-07-22 00:00:51 +02:00
Angelo Prandelli
3584f6e24f Translated using Weblate (Italian)
Currently translated at 98.2% (1109 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-07-22 00:00:51 +02:00
biuklija
23bf2594c8 Translated using Weblate (Croatian)
Currently translated at 100.0% (1129 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-07-22 00:00:51 +02:00
advplyr
8fb460ce05 Merge pull request #4319 from mikiher/audible-confidence-score
Audible confidence score
2025-07-21 17:00:44 -05:00
advplyr
8c4bbfd6a2 Add match confidence as a badge on match book card 2025-07-21 16:52:21 -05:00
advplyr
742961e0b8 Merge pull request #4510 from advplyr/fix_set_token
Fix set token on page load #4509
2025-07-18 17:11:09 -05:00
mikiher
e9a705587a Merge branch 'advplyr:master' into audible-confidence-score 2025-07-13 10:13:00 +03:00
mikiher
bf6d81b333 Merge branch 'advplyr:master' into audible-confidence-score 2025-07-09 09:04:52 +03:00
mikiher
9c44fc0d01 Merge branch 'advplyr:master' into audible-confidence-score 2025-06-26 18:09:13 +03:00
mikiher
5017e7ce9e Merge branch 'advplyr:master' into audible-confidence-score 2025-06-16 10:26:58 +03:00
mikiher
de25763a74 Add match confidence display to BookMatchCard 2025-05-21 11:16:46 +03:00
mikiher
a894ceb9cf Match confidence calculation for audible results 2025-05-21 10:25:42 +03:00
mikiher
387e58a714 Add levenshteinSimilarity function to utils 2025-05-21 09:57:44 +03:00
40 changed files with 988 additions and 202 deletions

View File

@@ -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>

View File

@@ -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">

View File

@@ -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

View File

@@ -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>

View File

@@ -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`

View File

@@ -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'

View File

@@ -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()

View File

@@ -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",

View File

@@ -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",

View File

@@ -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

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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']

View File

@@ -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-стужкі",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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.",

View File

@@ -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",

View File

@@ -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 lauteur",
"HeaderUpdateDetails": "Mettre à jour les détails",
"HeaderUpdateLibrary": "Mettre à jour la bibliothèque",
@@ -234,6 +240,10 @@
"LabelAllUsersExcludingGuests": "Tous les utilisateurs à lexception 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 lutilisateur",
"LabelApiKeyUserDescription": "Cette clé API disposera des mêmes autorisations que lutilisateur pour lequel elle agit. Elle apparaîtra dans les journaux comme si cétait lutilisateur 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 lutilisateur",
"LabelSelectUsers": "Sélectionner les utilisateurs",
"LabelSendEbookToDevice": "Envoyer le livre numérique à…",
"LabelSequence": "Séquence",
@@ -707,7 +723,9 @@
"MessageAddToPlayerQueue": "Ajouter en file dattente",
"MessageAppriseDescription": "Nécessite une instance d<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes.<br />LURL de lAPI Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
"MessageAsinCheck": "Assurez-vous dutiliser lASIN de la bonne région Audible, et non dAmazon.",
"MessageAuthenticationLegacyTokenWarning": "Les jetons dAPI hérités seront supprimés à lavenir. 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": "Lauthentification 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>nincluent 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: lemplacement de sauvegarde est défini via une variable denvironnement 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 lauteur « {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é lorsquune sauvegarde est terminée",
"NotificationOnBackupFailedDescription": "Déclenché lorsqu'une sauvegarde échoue",
"NotificationOnEpisodeDownloadedDescription": "Déclenché lorsquun é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 dun 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 dattente 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éé",

View File

@@ -1,5 +1,6 @@
{
"ButtonAdd": "הוסף",
"ButtonAddApiKey": "הוסף מפתח ממשק תכנות (API)",
"ButtonAddChapters": "הוסף פרקים",
"ButtonAddDevice": "הוסף התקן",
"ButtonAddLibrary": "הוסף ספרייה",
@@ -20,6 +21,7 @@
"ButtonChooseAFolder": "בחר תיקייה",
"ButtonChooseFiles": "בחר קבצים",
"ButtonClearFilter": "נקה סינון",
"ButtonClose": "סגור",
"ButtonCloseFeed": "סגור ערוץ",
"ButtonCloseSession": "סגור סשן פתוח",
"ButtonCollections": "אוספים",

View File

@@ -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.",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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> сохраняют файлы из папок библиотек.",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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ı",

View File

@@ -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": "Некоректний подкаст: немає каналу",

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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({

View File

@@ -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)

View File

@@ -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, '')

View File

@@ -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
}

View File

@@ -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'
}

View File

@@ -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]

View File

@@ -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')
})
})
})
})