mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-18 13:52:02 +02:00
Compare commits
54 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a89a24e48e | ||
|
a968aca304 | ||
|
fd4932cdbb | ||
|
dcaca43817 | ||
|
0eed4e82f9 | ||
|
2ed2328401 | ||
|
8b260c8bc6 | ||
|
7dcb9b98a0 | ||
|
311ac7104e | ||
|
2c45b28d48 | ||
|
b53613f82c | ||
|
751371abb8 | ||
|
6365c02875 | ||
|
fb3834156b | ||
|
c03f3f722d | ||
|
a06f48ca29 | ||
|
9d79552dda | ||
|
ed98614b6f | ||
|
09dd2cc79c | ||
|
e87237048a | ||
|
d71968fd80 | ||
|
f83c605ae1 | ||
|
4325f470dd | ||
|
800ecf8e82 | ||
|
5cb143d50b | ||
|
798c73c66c | ||
|
0fa7c46274 | ||
|
c2d420ec70 | ||
|
152daf7bf3 | ||
|
8d99249e50 | ||
|
c6724ba353 | ||
|
a519d44666 | ||
|
7e8bf977cc | ||
|
4018be6330 | ||
|
99a3867ce9 | ||
|
2116f60133 | ||
|
794f0ef42a | ||
|
3e423839a1 | ||
|
2773c8c4a9 | ||
|
e510174f12 | ||
|
08c9e8d47d | ||
|
1908ec3df5 | ||
|
df3878d4ca | ||
|
1097de6f1f | ||
|
e408070b19 | ||
|
af67c2e86f | ||
|
6a52d2a968 | ||
|
5ef632a7eb | ||
|
77d7a50b99 | ||
|
9da0be6d36 | ||
|
c41bdb951c | ||
|
54815ea9c7 | ||
|
679ffed0ea | ||
|
09397cf3de |
@ -3,24 +3,18 @@
|
|||||||
<div class="flex md:hidden h-10 items-center">
|
<div class="flex md:hidden h-10 items-center">
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}`" class="grow h-full flex justify-center items-center" :class="isHomePage ? 'bg-primary/80' : 'bg-primary/40'">
|
<nuxt-link :to="`/library/${currentLibraryId}`" class="grow h-full flex justify-center items-center" :class="isHomePage ? 'bg-primary/80' : 'bg-primary/40'">
|
||||||
<p v-if="isHomePage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonHome }}</p>
|
<p v-if="isHomePage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonHome }}</p>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<span v-else class="material-symbols text-lg">home</span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
|
||||||
</svg>
|
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf`" class="grow h-full flex justify-center items-center" :class="isLibraryPage ? 'bg-primary/80' : 'bg-primary/40'">
|
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf`" class="grow h-full flex justify-center items-center" :class="isLibraryPage ? 'bg-primary/80' : 'bg-primary/40'">
|
||||||
<p v-if="isLibraryPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonLibrary }}</p>
|
<p v-if="isLibraryPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonLibrary }}</p>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<span v-else class="material-symbols text-lg">import_contacts</span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
|
||||||
</svg>
|
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<nuxt-link v-if="isPodcastLibrary" :to="`/library/${currentLibraryId}/podcast/latest`" class="grow h-full flex justify-center items-center" :class="isPodcastLatestPage ? 'bg-primary/80' : 'bg-primary/40'">
|
<nuxt-link v-if="isPodcastLibrary" :to="`/library/${currentLibraryId}/podcast/latest`" class="grow h-full flex justify-center items-center" :class="isPodcastLatestPage ? 'bg-primary/80' : 'bg-primary/40'">
|
||||||
<p class="text-sm">{{ $strings.ButtonLatest }}</p>
|
<p class="text-sm">{{ $strings.ButtonLatest }}</p>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/series`" class="grow h-full flex justify-center items-center" :class="isSeriesPage ? 'bg-primary/80' : 'bg-primary/40'">
|
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/series`" class="grow h-full flex justify-center items-center" :class="isSeriesPage ? 'bg-primary/80' : 'bg-primary/40'">
|
||||||
<p v-if="isSeriesPage" class="text-sm">{{ $strings.ButtonSeries }}</p>
|
<p v-if="isSeriesPage" class="text-sm">{{ $strings.ButtonSeries }}</p>
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<span v-else class="material-symbols text-lg">view_column</span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
|
|
||||||
</svg>
|
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="grow h-full flex justify-center items-center" :class="isPlaylistsPage ? 'bg-primary/80' : 'bg-primary/40'">
|
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="grow h-full flex justify-center items-center" :class="isPlaylistsPage ? 'bg-primary/80' : 'bg-primary/40'">
|
||||||
<p v-if="isPlaylistsPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonPlaylists }}</p>
|
<p v-if="isPlaylistsPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonPlaylists }}</p>
|
||||||
@ -32,12 +26,7 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/authors`" class="grow h-full flex justify-center items-center" :class="isAuthorsPage ? 'bg-primary/80' : 'bg-primary/40'">
|
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/authors`" class="grow h-full flex justify-center items-center" :class="isAuthorsPage ? 'bg-primary/80' : 'bg-primary/40'">
|
||||||
<p v-if="isAuthorsPage" class="text-sm">{{ $strings.ButtonAuthors }}</p>
|
<p v-if="isAuthorsPage" class="text-sm">{{ $strings.ButtonAuthors }}</p>
|
||||||
<svg v-else class="w-5 h-5" viewBox="0 0 24 24">
|
<span v-else class="material-symbols text-lg">groups</span>
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="grow h-full flex justify-center items-center" :class="isPodcastSearchPage ? 'bg-primary/80' : 'bg-primary/40'">
|
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="grow h-full flex justify-center items-center" :class="isPodcastSearchPage ? 'bg-primary/80' : 'bg-primary/40'">
|
||||||
<p class="text-sm">{{ $strings.ButtonAdd }}</p>
|
<p class="text-sm">{{ $strings.ButtonAdd }}</p>
|
||||||
|
@ -5,9 +5,7 @@
|
|||||||
|
|
||||||
<div id="siderail-buttons-container" role="navigation" aria-label="Library Navigation" :class="{ 'player-open': streamLibraryItem }" class="w-full overflow-y-auto overflow-x-hidden">
|
<div id="siderail-buttons-container" role="navigation" aria-label="Library Navigation" :class="{ 'player-open': streamLibraryItem }" class="w-full overflow-y-auto overflow-x-hidden">
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="homePage ? 'bg-primary/80' : 'bg-bg/60'">
|
<nuxt-link :to="`/library/${currentLibraryId}`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="homePage ? 'bg-primary/80' : 'bg-bg/60'">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<span class="material-symbols text-2xl">home</span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonHome }}</p>
|
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonHome }}</p>
|
||||||
|
|
||||||
@ -23,9 +21,7 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="showLibrary ? 'bg-primary/80' : 'bg-bg/60'">
|
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="showLibrary ? 'bg-primary/80' : 'bg-bg/60'">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<span class="material-symbols text-2xl">import_contacts</span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLibrary }}</p>
|
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLibrary }}</p>
|
||||||
|
|
||||||
@ -33,9 +29,7 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/series`" class="w-full h-20 flex flex-col items-center justify-center text-white/80 border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="isSeriesPage ? 'bg-primary/80' : 'bg-bg/60'">
|
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/series`" class="w-full h-20 flex flex-col items-center justify-center text-white/80 border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="isSeriesPage ? 'bg-primary/80' : 'bg-bg/60'">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<span class="material-symbols text-2xl">view_column</span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSeries }}</p>
|
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSeries }}</p>
|
||||||
|
|
||||||
@ -59,12 +53,7 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/authors`" class="w-full h-20 flex flex-col items-center justify-center text-white/80 border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="isAuthorsPage ? 'bg-primary/80' : 'bg-bg/60'">
|
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/authors`" class="w-full h-20 flex flex-col items-center justify-center text-white/80 border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="isAuthorsPage ? 'bg-primary/80' : 'bg-bg/60'">
|
||||||
<svg class="w-6 h-6" viewBox="0 0 24 24">
|
<span class="material-symbols text-2xl">groups</span>
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonAuthors }}</p>
|
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonAuthors }}</p>
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap justify-center mt-6">
|
<div class="flex flex-wrap justify-center mt-6">
|
||||||
<div class="flex p-2">
|
<div class="flex p-2">
|
||||||
<svg class="h-14 w-14" viewBox="0 0 24 24">
|
<span class="material-symbols text-5xl py-1">newsstand</span>
|
||||||
<path fill="currentColor" d="M9 3V18H12V3H9M12 5L16 18L19 17L15 4L12 5M5 5V18H8V5H5M3 19V21H21V19H3Z" />
|
|
||||||
</svg>
|
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalItems) }}</p>
|
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalItems) }}</p>
|
||||||
<p class="text-xs md:text-sm text-white/80">{{ $strings.LabelStatsItemsInLibrary }}</p>
|
<p class="text-xs md:text-sm text-white/80">{{ $strings.LabelStatsItemsInLibrary }}</p>
|
||||||
@ -19,9 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isBookLibrary" class="flex p-2">
|
<div v-if="isBookLibrary" class="flex p-2">
|
||||||
<svg class="h-14 w-14" viewBox="0 0 24 24">
|
<span class="material-symbols text-5xl py-1">person</span>
|
||||||
<path fill="currentColor" d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z" />
|
|
||||||
</svg>
|
|
||||||
<div class="px-1">
|
<div class="px-1">
|
||||||
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalAuthors) }}</p>
|
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalAuthors) }}</p>
|
||||||
<p class="text-xs md:text-sm text-white/80">{{ $strings.LabelStatsAuthors }}</p>
|
<p class="text-xs md:text-sm text-white/80">{{ $strings.LabelStatsAuthors }}</p>
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<button :aria-label="isRead ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" class="icon-btn rounded-md flex items-center justify-center h-9 w-9 relative" :class="borderless ? '' : 'bg-primary border border-gray-600'" @click="clickBtn">
|
<button :aria-label="isRead ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" class="icon-btn rounded-md flex items-center justify-center h-9 w-9 relative" :class="borderless ? '' : 'bg-primary border border-gray-600'" @click="clickBtn">
|
||||||
<div class="w-5 h-5 text-white relative">
|
<div class="w-5 h-5 relative">
|
||||||
<svg v-if="isRead" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(63, 181, 68)">
|
<span v-if="isRead" class="material-symbols fill text-xl text-success">beenhere</span>
|
||||||
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z" />
|
<span v-else class="material-symbols text-xl text-white">beenhere</span>
|
||||||
</svg>
|
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-7 19.6l-7-4.66V3h14v12.93l-7 4.67zm-2.01-7.42l-2.58-2.59L6 12l4 4 8-8-1.42-1.42z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,40 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<ui-tooltip :text="$strings.LabelExplicit" direction="top">
|
<ui-tooltip :text="$strings.LabelExplicit" direction="top">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="12px" height="12px" viewBox="0 0 512 512" class="ml-1">
|
<span class="material-symbols fill text-sm ml-1 !block">explicit</span>
|
||||||
<path
|
|
||||||
fill="white"
|
|
||||||
d="M 89.00,40.12
|
|
||||||
C 89.00,40.12 127.00,40.12 127.00,40.12
|
|
||||||
127.00,40.12 198.00,40.12 198.00,40.12
|
|
||||||
198.00,40.12 416.00,40.12 416.00,40.12
|
|
||||||
446.58,40.05 472.95,66.42 473.00,97.00
|
|
||||||
473.00,97.00 473.00,303.00 473.00,303.00
|
|
||||||
473.00,303.00 473.00,418.00 473.00,418.00
|
|
||||||
472.65,447.55 445.06,472.95 416.00,473.00
|
|
||||||
416.00,473.00 210.00,473.00 210.00,473.00
|
|
||||||
210.00,473.00 95.00,473.00 95.00,473.00
|
|
||||||
65.45,472.65 40.05,445.06 40.00,416.00
|
|
||||||
40.00,416.00 40.00,136.00 40.00,136.00
|
|
||||||
40.00,136.00 40.00,109.00 40.00,109.00
|
|
||||||
40.00,109.00 40.00,96.00 40.00,96.00
|
|
||||||
40.07,81.58 46.89,67.14 57.01,57.01
|
|
||||||
61.17,52.86 64.86,50.13 70.00,47.31
|
|
||||||
77.25,43.33 81.02,42.18 89.00,40.12 Z
|
|
||||||
M 337.00,121.00
|
|
||||||
C 337.00,121.00 175.00,121.00 175.00,121.00
|
|
||||||
175.00,121.00 175.00,392.00 175.00,392.00
|
|
||||||
175.00,392.00 337.00,392.00 337.00,392.00
|
|
||||||
337.00,392.00 337.00,349.00 337.00,349.00
|
|
||||||
337.00,349.00 226.00,349.00 226.00,349.00
|
|
||||||
226.00,349.00 226.00,274.00 226.00,274.00
|
|
||||||
226.00,274.00 332.00,274.00 332.00,274.00
|
|
||||||
332.00,274.00 332.00,232.00 332.00,232.00
|
|
||||||
332.00,232.00 226.00,232.00 226.00,232.00
|
|
||||||
226.00,232.00 226.00,164.00 226.00,164.00
|
|
||||||
226.00,164.00 337.00,164.00 337.00,164.00
|
|
||||||
337.00,164.00 337.00,121.00 337.00,121.00 Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</ui-tooltip>
|
</ui-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ export default {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('User has no more accessible libraries')
|
console.error('User has no more accessible libraries')
|
||||||
this.$store.commit('libraries/setCurrentLibrary', null)
|
this.$store.commit('libraries/setCurrentLibrary', { id: null })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.27.0",
|
"version": "2.28.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.27.0",
|
"version": "2.28.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.27.0",
|
"version": "2.28.0",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
@ -53,51 +53,101 @@
|
|||||||
|
|
||||||
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
|
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
|
||||||
<div class="w-8 min-w-8 md:w-12 md:min-w-12"></div>
|
<div class="w-8 min-w-8 md:w-12 md:min-w-12"></div>
|
||||||
<div class="w-24 min-w-24 md:w-32 md:min-w-32 px-2">{{ $strings.LabelStart }}</div>
|
<div class="w-38 min-w-38 md:w-40 md:min-w-40 px-1 pl-8">{{ $strings.LabelStart }}</div>
|
||||||
<div class="grow px-2">{{ $strings.LabelTitle }}</div>
|
<div class="grow px-1 min-w-54">{{ $strings.LabelTitle }}</div>
|
||||||
|
<div class="w-7 min-w-7 px-1 flex items-center justify-center">
|
||||||
|
<ui-tooltip :text="allChaptersLocked ? $strings.TooltipUnlockAllChapters : $strings.TooltipLockAllChapters" direction="bottom">
|
||||||
|
<button class="w-7 h-7 rounded-full flex items-center justify-center cursor-pointer transition-colors duration-150" :class="allChaptersLocked ? 'text-orange-400 hover:text-orange-300' : 'text-gray-300 hover:text-white'" @click="toggleAllChaptersLock">
|
||||||
|
<span class="material-symbols text-xl">{{ allChaptersLocked ? 'lock' : 'lock_open' }}</span>
|
||||||
|
</button>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
<div class="w-32"></div>
|
<div class="w-32"></div>
|
||||||
</div>
|
</div>
|
||||||
<template v-for="chapter in newChapters">
|
<div v-for="chapter in newChapters" :key="chapter.id" class="flex py-1">
|
||||||
<div :key="chapter.id" class="flex py-1">
|
<div class="w-8 min-w-8 md:w-12 md:min-w-12">#{{ chapter.id + 1 }}</div>
|
||||||
<div class="w-8 min-w-8 md:w-12 md:min-w-12">#{{ chapter.id + 1 }}</div>
|
<div class="w-38 min-w-38 md:w-40 md:min-w-40 px-1">
|
||||||
<div class="w-24 min-w-24 md:w-32 md:min-w-32 px-1">
|
<div class="flex items-center gap-1">
|
||||||
<ui-text-input v-if="showSecondInputs" v-model="chapter.start" type="number" class="text-xs" @change="checkChapters" />
|
<ui-tooltip :text="$strings.TooltipSubtractOneSecond" direction="bottom">
|
||||||
<ui-time-picker v-else class="text-xs" v-model="chapter.start" :show-three-digit-hour="mediaDuration >= 360000" @change="checkChapters" />
|
<button
|
||||||
</div>
|
class="w-6 h-6 rounded-full flex items-center justify-center text-gray-300 hover:text-white transform hover:scale-110 duration-150 flex-shrink-0"
|
||||||
<div class="grow px-1">
|
:class="{ 'opacity-50 cursor-not-allowed': chapter.id === 0 && chapter.start - timeIncrementAmount < 0 }"
|
||||||
<ui-text-input v-model="chapter.title" @change="checkChapters" class="text-xs min-w-52" />
|
@click="incrementChapterTime(chapter, -timeIncrementAmount)"
|
||||||
</div>
|
:disabled="chapter.id === 0 && chapter.start - timeIncrementAmount < 0"
|
||||||
<div class="w-32 min-w-32 px-2 py-1">
|
>
|
||||||
<div class="flex items-center">
|
<span class="material-symbols text-sm">remove</span>
|
||||||
<ui-tooltip :text="$strings.MessageRemoveChapter" direction="bottom">
|
</button>
|
||||||
<button v-if="newChapters.length > 1" class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-error transform hover:scale-110 duration-150" @click="removeChapter(chapter)">
|
</ui-tooltip>
|
||||||
<span class="material-symbols text-base">remove</span>
|
|
||||||
</button>
|
|
||||||
</ui-tooltip>
|
|
||||||
|
|
||||||
<ui-tooltip :text="$strings.MessageInsertChapterBelow" direction="bottom">
|
<div class="flex-1 min-w-0">
|
||||||
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-success transform hover:scale-110 duration-150" @click="addChapter(chapter)">
|
<ui-text-input v-if="showSecondInputs" v-model="chapter.start" type="number" class="text-xs" @change="checkChapters" />
|
||||||
<span class="material-symbols text-lg">add</span>
|
<ui-time-picker v-else class="text-xs" v-model="chapter.start" :show-three-digit-hour="mediaDuration >= 360000" @change="checkChapters" />
|
||||||
</button>
|
|
||||||
</ui-tooltip>
|
|
||||||
|
|
||||||
<ui-tooltip :text="selectedChapterId === chapter.id && isPlayingChapter ? $strings.MessagePauseChapter : $strings.MessagePlayChapter" direction="bottom">
|
|
||||||
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-white transform hover:scale-110 duration-150" @click="playChapter(chapter)">
|
|
||||||
<widgets-loading-spinner v-if="selectedChapterId === chapter.id && isLoadingChapter" />
|
|
||||||
<span v-else-if="selectedChapterId === chapter.id && isPlayingChapter" class="material-symbols text-base">pause</span>
|
|
||||||
<span v-else class="material-symbols text-base">play_arrow</span>
|
|
||||||
</button>
|
|
||||||
</ui-tooltip>
|
|
||||||
|
|
||||||
<ui-tooltip v-if="chapter.error" :text="chapter.error" direction="left">
|
|
||||||
<button class="w-7 h-7 rounded-full flex items-center justify-center text-error">
|
|
||||||
<span class="material-symbols text-lg">error_outline</span>
|
|
||||||
</button>
|
|
||||||
</ui-tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ui-tooltip :text="$strings.TooltipAddOneSecond" direction="bottom">
|
||||||
|
<button class="w-6 h-6 rounded-full flex items-center justify-center text-gray-300 hover:text-white transform hover:scale-110 duration-150 flex-shrink-0" :class="{ 'opacity-50 cursor-not-allowed': chapter.start + timeIncrementAmount >= mediaDuration }" @click="incrementChapterTime(chapter, timeIncrementAmount)" :disabled="chapter.start + timeIncrementAmount >= mediaDuration">
|
||||||
|
<span class="material-symbols text-sm">add</span>
|
||||||
|
</button>
|
||||||
|
</ui-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="grow px-1">
|
||||||
|
<ui-text-input v-model="chapter.title" @change="checkChapters" class="text-xs min-w-52" />
|
||||||
|
</div>
|
||||||
|
<div class="w-7 min-w-7 px-1 py-1">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<ui-tooltip :text="lockedChapters.has(chapter.id) ? $strings.TooltipUnlockChapter : $strings.TooltipLockChapter" direction="bottom">
|
||||||
|
<button class="w-7 h-7 rounded-full flex items-center justify-center transform hover:scale-110 duration-150 flex-shrink-0" :class="lockedChapters.has(chapter.id) ? 'text-orange-400 hover:text-orange-300' : 'text-gray-300 hover:text-white'" @click="toggleChapterLock(chapter, $event)">
|
||||||
|
<span class="material-symbols text-base">{{ lockedChapters.has(chapter.id) ? 'lock' : 'lock_open' }}</span>
|
||||||
|
</button>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-32 min-w-32 px-2 py-1">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<ui-tooltip :text="$strings.MessageRemoveChapter" direction="bottom">
|
||||||
|
<button v-if="newChapters.length > 1" class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-error transform hover:scale-110 duration-150" @click="removeChapter(chapter)">
|
||||||
|
<span class="material-symbols text-base">delete</span>
|
||||||
|
</button>
|
||||||
|
</ui-tooltip>
|
||||||
|
|
||||||
|
<ui-tooltip :text="$strings.MessageInsertChapterBelow" direction="bottom">
|
||||||
|
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-success transform hover:scale-110 duration-150" @click="addChapter(chapter)">
|
||||||
|
<span class="material-symbols text-lg">add_row_below</span>
|
||||||
|
</button>
|
||||||
|
</ui-tooltip>
|
||||||
|
<ui-tooltip :text="selectedChapterId === chapter.id && isPlayingChapter ? $strings.MessagePauseChapter : $strings.MessagePlayChapter" direction="bottom">
|
||||||
|
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-white transform hover:scale-110 duration-150" @click="playChapter(chapter)">
|
||||||
|
<widgets-loading-spinner v-if="selectedChapterId === chapter.id && isLoadingChapter" />
|
||||||
|
<span v-else-if="selectedChapterId === chapter.id && isPlayingChapter" class="material-symbols text-base">pause</span>
|
||||||
|
<span v-else class="material-symbols text-base">play_arrow</span>
|
||||||
|
</button>
|
||||||
|
</ui-tooltip>
|
||||||
|
<ui-tooltip v-if="selectedChapterId === chapter.id && (isPlayingChapter || isLoadingChapter)" :text="$strings.TooltipAdjustChapterStart" direction="bottom">
|
||||||
|
<div class="ml-2 text-xs text-gray-300 font-mono min-w-10 cursor-pointer hover:text-white transition-colors duration-150" @click="adjustChapterStartTime(chapter)">{{ elapsedTime }}s</div>
|
||||||
|
</ui-tooltip>
|
||||||
|
<ui-tooltip v-if="chapter.error" :text="chapter.error" direction="left">
|
||||||
|
<button class="w-7 h-7 rounded-full flex items-center justify-center text-error">
|
||||||
|
<span class="material-symbols text-lg">error_outline</span>
|
||||||
|
</button>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center mt-4 mb-2">
|
||||||
|
<div class="w-8 min-w-8 md:w-12 md:min-w-12"></div>
|
||||||
|
<div class="w-38 min-w-38 md:w-40 md:min-w-40 px-1"></div>
|
||||||
|
<div class="flex items-center gap-2 grow px-1">
|
||||||
|
<ui-text-input v-model="bulkChapterInput" :placeholder="$strings.PlaceholderBulkChapterInput" class="text-xs grow min-w-52" @keyup.enter="handleBulkChapterAdd" />
|
||||||
|
</div>
|
||||||
|
<div class="w-39 min-w-39 px-1 py-1">
|
||||||
|
<ui-tooltip :text="$strings.TooltipAddChapters" direction="bottom" class="inline-block align-middle">
|
||||||
|
<button class="w-5 h-5 rounded-full flex items-center justify-center text-gray-300 hover:text-success transform hover:scale-110 duration-150 flex-shrink-0" :aria-label="$strings.TooltipAddChapters" :class="{ 'opacity-50 cursor-not-allowed': !bulkChapterInput.trim() }" :disabled="!bulkChapterInput.trim()" @click="handleBulkChapterAdd">
|
||||||
|
<span class="material-symbols text-lg">add</span>
|
||||||
|
</button>
|
||||||
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full max-w-xl py-4 px-2">
|
<div class="w-full max-w-xl py-4 px-2">
|
||||||
@ -114,19 +164,15 @@
|
|||||||
<div class="w-20">{{ $strings.LabelDuration }}</div>
|
<div class="w-20">{{ $strings.LabelDuration }}</div>
|
||||||
<div class="w-20 hidden md:block text-center">{{ $strings.HeaderChapters }}</div>
|
<div class="w-20 hidden md:block text-center">{{ $strings.HeaderChapters }}</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-for="track in audioTracks">
|
<div v-for="track in audioTracks" :key="track.ino" class="flex items-center py-2" :class="currentTrackIndex === track.index && isPlayingChapter ? 'bg-success/10' : ''">
|
||||||
<div :key="track.ino" class="flex items-center py-2" :class="currentTrackIndex === track.index && isPlayingChapter ? 'bg-success/10' : ''">
|
<div class="grow max-w-[calc(100%-80px)] pr-2">
|
||||||
<div class="grow max-w-[calc(100%-80px)] pr-2">
|
<p class="text-xs truncate max-w-sm">{{ track.metadata.filename }}</p>
|
||||||
<p class="text-xs truncate max-w-sm">{{ track.metadata.filename }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="w-20" style="min-width: 80px">
|
|
||||||
<p class="text-xs font-mono text-gray-200">{{ $secondsToTimestamp(Math.round(track.duration), false, true) }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="w-20 hidden md:flex justify-center" style="min-width: 80px">
|
|
||||||
<span v-if="(track.chapters || []).length" class="material-symbols text-success text-sm">check</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="w-20" style="min-width: 80px">
|
||||||
|
<p class="text-xs font-mono text-gray-200">{{ $secondsToTimestamp(Math.round(track.duration), false, true) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-20 hidden md:flex justify-center" style="min-width: 80px"><span v-if="(track.chapters || []).length" class="material-symbols text-success text-sm">check</span></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -134,6 +180,7 @@
|
|||||||
<ui-loading-indicator />
|
<ui-loading-indicator />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- audible chapter lookup modal -->
|
||||||
<modals-modal v-model="showFindChaptersModal" name="edit-book" :width="500" :processing="findingChapters">
|
<modals-modal v-model="showFindChaptersModal" name="edit-book" :width="500" :processing="findingChapters">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
|
||||||
@ -159,12 +206,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="w-full p-4">
|
<div v-else class="w-full p-4">
|
||||||
<div class="flex justify-between mb-4">
|
<div class="flex mb-4">
|
||||||
|
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-white flex-shrink-0" :aria-label="$strings.ButtonBack" @click="resetChapterLookupData">
|
||||||
|
<span class="material-symbols text-lg">arrow_back</span>
|
||||||
|
</button>
|
||||||
<p>
|
<p>
|
||||||
{{ $strings.LabelDurationFound }} <span class="font-semibold">{{ $secondsToTimestamp(chapterData.runtimeLengthSec) }}</span
|
{{ $strings.LabelDurationFound }} <span class="font-semibold">{{ $secondsToTimestamp(chapterData.runtimeLengthSec) }}</span>
|
||||||
><br />
|
<br />
|
||||||
<span class="font-semibold" :class="{ 'text-warning': chapters.length !== chapterData.chapters.length }">{{ chapterData.chapters.length }}</span> {{ $strings.LabelChaptersFound }}
|
<span class="font-semibold" :class="{ 'text-warning': chapters.length !== chapterData.chapters.length }">{{ chapterData.chapters.length }}</span> {{ $strings.LabelChaptersFound }}
|
||||||
</p>
|
</p>
|
||||||
|
<div class="grow" />
|
||||||
<p>
|
<p>
|
||||||
{{ $strings.LabelYourAudiobookDuration }}: <span class="font-semibold">{{ $secondsToTimestamp(mediaDurationRounded) }}</span
|
{{ $strings.LabelYourAudiobookDuration }}: <span class="font-semibold">{{ $secondsToTimestamp(mediaDurationRounded) }}</span
|
||||||
><br />
|
><br />
|
||||||
@ -198,17 +249,49 @@
|
|||||||
<p class="pl-2">{{ $strings.MessageChapterStartIsAfter }}</p>
|
<p class="pl-2">{{ $strings.MessageChapterStartIsAfter }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center pt-2">
|
<div class="flex items-center pt-2 justify-between">
|
||||||
<ui-btn small color="bg-primary" class="mr-1" @click="applyChapterNamesOnly">{{ $strings.ButtonMapChapterTitles }}</ui-btn>
|
<div class="flex items-center gap-2">
|
||||||
<ui-tooltip :text="$strings.MessageMapChapterTitles" direction="top" class="flex items-center">
|
<ui-btn small color="bg-primary" @click="applyChapterNamesOnly">{{ $strings.ButtonMapChapterTitles }}</ui-btn>
|
||||||
<span class="material-symbols text-xl text-gray-200">info</span>
|
<ui-tooltip :text="$strings.MessageMapChapterTitles" direction="top" class="flex items-center">
|
||||||
</ui-tooltip>
|
<span class="material-symbols text-xl text-gray-200">info</span>
|
||||||
<div class="grow" />
|
</ui-tooltip>
|
||||||
|
</div>
|
||||||
<ui-btn small color="bg-success" @click="applyChapterData">{{ $strings.ButtonApplyChapters }}</ui-btn>
|
<ui-btn small color="bg-success" @click="applyChapterData">{{ $strings.ButtonApplyChapters }}</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
|
|
||||||
|
<!-- create bulk chapters modal -->
|
||||||
|
<modals-modal v-model="showBulkChapterModal" name="bulk-chapters" :width="400">
|
||||||
|
<template #outer>
|
||||||
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
|
||||||
|
<p class="text-3xl text-white truncate pointer-events-none">{{ $strings.HeaderBulkChapterModal }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="w-full h-full max-h-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative p-6">
|
||||||
|
<div class="flex flex-col space-y-8">
|
||||||
|
<p class="text-base">{{ $strings.MessageBulkChapterPattern }}</p>
|
||||||
|
|
||||||
|
<div v-if="detectedPattern" class="text-sm text-gray-400 bg-gray-800 p-2 rounded">
|
||||||
|
<strong>{{ $strings.LabelDetectedPattern }}</strong> "{{ detectedPattern.before }}{{ formatNumberWithPadding(detectedPattern.startingNumber, detectedPattern) }}{{ detectedPattern.after }}"
|
||||||
|
<br />
|
||||||
|
<strong>{{ $strings.LabelNextChapters }}</strong>
|
||||||
|
"{{ detectedPattern.before }}{{ formatNumberWithPadding(detectedPattern.startingNumber + 1, detectedPattern) }}{{ detectedPattern.after }}", "{{ detectedPattern.before }}{{ formatNumberWithPadding(detectedPattern.startingNumber + 2, detectedPattern) }}{{ detectedPattern.after }}", etc.
|
||||||
|
</div>
|
||||||
|
<div class="flex px-1 items-center">
|
||||||
|
<label class="text-base font-medium">{{ $strings.LabelNumberOfChapters }}</label>
|
||||||
|
<div class="grow" />
|
||||||
|
<ui-text-input v-model="bulkChapterCount" type="number" min="1" max="50" class="w-14" :style="{ height: `2em` }" @keyup.enter="addBulkChapters" />
|
||||||
|
</div>
|
||||||
|
<div class="flex px-1 items-center">
|
||||||
|
<ui-btn small @click="showBulkChapterModal = false">{{ $strings.ButtonCancel }}</ui-btn>
|
||||||
|
<div class="grow" />
|
||||||
|
<ui-btn small color="bg-success" @click="addBulkChapters">{{ $strings.ButtonAddChapters }}</ui-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</modals-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -265,7 +348,17 @@ export default {
|
|||||||
removeBranding: false,
|
removeBranding: false,
|
||||||
showSecondInputs: false,
|
showSecondInputs: false,
|
||||||
audibleRegions: ['US', 'CA', 'UK', 'AU', 'FR', 'DE', 'JP', 'IT', 'IN', 'ES'],
|
audibleRegions: ['US', 'CA', 'UK', 'AU', 'FR', 'DE', 'JP', 'IT', 'IN', 'ES'],
|
||||||
hasChanges: false
|
hasChanges: false,
|
||||||
|
timeIncrementAmount: 1,
|
||||||
|
elapsedTime: 0,
|
||||||
|
playStartTime: null,
|
||||||
|
elapsedTimeInterval: null,
|
||||||
|
lockedChapters: new Set(),
|
||||||
|
lastSelectedLockIndex: null,
|
||||||
|
bulkChapterInput: '',
|
||||||
|
showBulkChapterModal: false,
|
||||||
|
bulkChapterCount: 1,
|
||||||
|
detectedPattern: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -304,9 +397,18 @@ export default {
|
|||||||
},
|
},
|
||||||
selectedChapterId() {
|
selectedChapterId() {
|
||||||
return this.selectedChapter ? this.selectedChapter.id : null
|
return this.selectedChapter ? this.selectedChapter.id : null
|
||||||
|
},
|
||||||
|
allChaptersLocked() {
|
||||||
|
return this.newChapters.length > 0 && this.newChapters.every((chapter) => this.lockedChapters.has(chapter.id))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
formatNumberWithPadding(number, pattern) {
|
||||||
|
if (!pattern || !pattern.hasLeadingZeros || !pattern.originalPadding) {
|
||||||
|
return number.toString()
|
||||||
|
}
|
||||||
|
return number.toString().padStart(pattern.originalPadding, '0')
|
||||||
|
},
|
||||||
setChaptersFromTracks() {
|
setChaptersFromTracks() {
|
||||||
let currentStartTime = 0
|
let currentStartTime = 0
|
||||||
let index = 0
|
let index = 0
|
||||||
@ -321,7 +423,7 @@ export default {
|
|||||||
currentStartTime += track.duration
|
currentStartTime += track.duration
|
||||||
}
|
}
|
||||||
this.newChapters = chapters
|
this.newChapters = chapters
|
||||||
|
this.lockedChapters = new Set()
|
||||||
this.checkChapters()
|
this.checkChapters()
|
||||||
},
|
},
|
||||||
toggleRemoveBranding() {
|
toggleRemoveBranding() {
|
||||||
@ -334,19 +436,22 @@ export default {
|
|||||||
|
|
||||||
const amount = Number(this.shiftAmount)
|
const amount = Number(this.shiftAmount)
|
||||||
|
|
||||||
const lastChapter = this.newChapters[this.newChapters.length - 1]
|
// Check if any unlocked chapters would be affected negatively
|
||||||
if (lastChapter.start + amount > this.mediaDurationRounded) {
|
const unlockedChapters = this.newChapters.filter((chap) => !this.lockedChapters.has(chap.id))
|
||||||
this.$toast.error(this.$strings.ToastChaptersInvalidShiftAmountLast)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.newChapters[1].start + amount <= 0) {
|
if (unlockedChapters.length === 0) {
|
||||||
this.$toast.error(this.$strings.ToastChaptersInvalidShiftAmountStart)
|
this.$toast.warning(this.$strings.ToastChaptersAllLocked)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < this.newChapters.length; i++) {
|
for (let i = 0; i < this.newChapters.length; i++) {
|
||||||
const chap = this.newChapters[i]
|
const chap = this.newChapters[i]
|
||||||
|
|
||||||
|
// Skip locked chapters
|
||||||
|
if (this.lockedChapters.has(chap.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
chap.end = Math.min(chap.end + amount, this.mediaDuration)
|
chap.end = Math.min(chap.end + amount, this.mediaDuration)
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
chap.start = Math.max(0, chap.start + amount)
|
chap.start = Math.max(0, chap.start + amount)
|
||||||
@ -354,6 +459,83 @@ export default {
|
|||||||
}
|
}
|
||||||
this.checkChapters()
|
this.checkChapters()
|
||||||
},
|
},
|
||||||
|
incrementChapterTime(chapter, amount) {
|
||||||
|
if (chapter.id === 0 && chapter.start + amount < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (chapter.start + amount >= this.mediaDuration) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chapter.start = Math.max(0, chapter.start + amount)
|
||||||
|
this.checkChapters()
|
||||||
|
},
|
||||||
|
adjustChapterStartTime(chapter) {
|
||||||
|
const newStartTime = chapter.start + this.elapsedTime
|
||||||
|
chapter.start = newStartTime
|
||||||
|
this.checkChapters()
|
||||||
|
this.$toast.success(this.$strings.ToastChapterStartTimeAdjusted.replace('{0}', this.elapsedTime))
|
||||||
|
|
||||||
|
this.destroyAudioEl()
|
||||||
|
},
|
||||||
|
startElapsedTimeTracking() {
|
||||||
|
this.elapsedTime = 0
|
||||||
|
this.playStartTime = Date.now()
|
||||||
|
this.elapsedTimeInterval = setInterval(() => {
|
||||||
|
this.elapsedTime = Math.floor((Date.now() - this.playStartTime) / 1000)
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
stopElapsedTimeTracking() {
|
||||||
|
if (this.elapsedTimeInterval) {
|
||||||
|
clearInterval(this.elapsedTimeInterval)
|
||||||
|
this.elapsedTimeInterval = null
|
||||||
|
}
|
||||||
|
this.elapsedTime = 0
|
||||||
|
this.playStartTime = null
|
||||||
|
},
|
||||||
|
toggleChapterLock(chapter, event) {
|
||||||
|
const chapterId = chapter.id
|
||||||
|
|
||||||
|
if (event.shiftKey && this.lastSelectedLockIndex !== null) {
|
||||||
|
const startIndex = Math.min(this.lastSelectedLockIndex, chapterId)
|
||||||
|
const endIndex = Math.max(this.lastSelectedLockIndex, chapterId)
|
||||||
|
const shouldLock = !this.lockedChapters.has(chapterId)
|
||||||
|
|
||||||
|
for (let i = startIndex; i <= endIndex; i++) {
|
||||||
|
if (shouldLock) {
|
||||||
|
this.lockedChapters.add(i)
|
||||||
|
} else {
|
||||||
|
this.lockedChapters.delete(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.lockedChapters.has(chapterId)) {
|
||||||
|
this.lockedChapters.delete(chapterId)
|
||||||
|
} else {
|
||||||
|
this.lockedChapters.add(chapterId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastSelectedLockIndex = chapterId
|
||||||
|
this.lockedChapters = new Set(this.lockedChapters)
|
||||||
|
},
|
||||||
|
lockAllChapters() {
|
||||||
|
this.newChapters.forEach((chapter) => {
|
||||||
|
this.lockedChapters.add(chapter.id)
|
||||||
|
})
|
||||||
|
this.lockedChapters = new Set(this.lockedChapters)
|
||||||
|
},
|
||||||
|
unlockAllChapters() {
|
||||||
|
this.lockedChapters.clear()
|
||||||
|
this.lockedChapters = new Set(this.lockedChapters)
|
||||||
|
},
|
||||||
|
toggleAllChaptersLock() {
|
||||||
|
if (this.allChaptersLocked) {
|
||||||
|
this.unlockAllChapters()
|
||||||
|
} else {
|
||||||
|
this.lockAllChapters()
|
||||||
|
}
|
||||||
|
},
|
||||||
editItem() {
|
editItem() {
|
||||||
this.$store.commit('showEditModal', this.libraryItem)
|
this.$store.commit('showEditModal', this.libraryItem)
|
||||||
},
|
},
|
||||||
@ -368,6 +550,10 @@ export default {
|
|||||||
this.checkChapters()
|
this.checkChapters()
|
||||||
},
|
},
|
||||||
removeChapter(chapter) {
|
removeChapter(chapter) {
|
||||||
|
if (this.lockedChapters.has(chapter.id)) {
|
||||||
|
this.$toast.warning(this.$strings.ToastChapterLocked)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.newChapters = this.newChapters.filter((ch) => ch.id !== chapter.id)
|
this.newChapters = this.newChapters.filter((ch) => ch.id !== chapter.id)
|
||||||
this.checkChapters()
|
this.checkChapters()
|
||||||
},
|
},
|
||||||
@ -451,6 +637,7 @@ export default {
|
|||||||
console.log('Audio playing')
|
console.log('Audio playing')
|
||||||
this.isLoadingChapter = false
|
this.isLoadingChapter = false
|
||||||
this.isPlayingChapter = true
|
this.isPlayingChapter = true
|
||||||
|
this.startElapsedTimeTracking()
|
||||||
})
|
})
|
||||||
audioEl.addEventListener('ended', () => {
|
audioEl.addEventListener('ended', () => {
|
||||||
console.log('Audio ended')
|
console.log('Audio ended')
|
||||||
@ -473,6 +660,10 @@ export default {
|
|||||||
this.selectedChapter = null
|
this.selectedChapter = null
|
||||||
this.isPlayingChapter = false
|
this.isPlayingChapter = false
|
||||||
this.isLoadingChapter = false
|
this.isLoadingChapter = false
|
||||||
|
this.stopElapsedTimeTracking()
|
||||||
|
},
|
||||||
|
resetChapterLookupData() {
|
||||||
|
this.chapterData = null
|
||||||
},
|
},
|
||||||
saveChapters() {
|
saveChapters() {
|
||||||
this.checkChapters()
|
this.checkChapters()
|
||||||
@ -523,7 +714,7 @@ export default {
|
|||||||
},
|
},
|
||||||
applyChapterNamesOnly() {
|
applyChapterNamesOnly() {
|
||||||
this.newChapters.forEach((chapter, index) => {
|
this.newChapters.forEach((chapter, index) => {
|
||||||
if (this.chapterData.chapters[index]) {
|
if (this.chapterData.chapters[index] && !this.lockedChapters.has(chapter.id)) {
|
||||||
chapter.title = this.chapterData.chapters[index].title
|
chapter.title = this.chapterData.chapters[index].title
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -535,7 +726,7 @@ export default {
|
|||||||
},
|
},
|
||||||
applyChapterData() {
|
applyChapterData() {
|
||||||
let index = 0
|
let index = 0
|
||||||
this.newChapters = this.chapterData.chapters
|
const audibleChapters = this.chapterData.chapters
|
||||||
.filter((chap) => chap.startOffsetSec < this.mediaDuration)
|
.filter((chap) => chap.startOffsetSec < this.mediaDuration)
|
||||||
.map((chap) => {
|
.map((chap) => {
|
||||||
return {
|
return {
|
||||||
@ -545,6 +736,21 @@ export default {
|
|||||||
title: chap.title
|
title: chap.title
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const merged = []
|
||||||
|
let audibleIdx = 0
|
||||||
|
for (let i = 0; i < Math.max(this.newChapters.length, audibleChapters.length); i++) {
|
||||||
|
const isLocked = this.lockedChapters.has(i)
|
||||||
|
if (isLocked && this.newChapters[i]) {
|
||||||
|
merged.push({ ...this.newChapters[i], id: i })
|
||||||
|
} else if (audibleChapters[audibleIdx]) {
|
||||||
|
merged.push({ ...audibleChapters[audibleIdx], id: i })
|
||||||
|
audibleIdx++
|
||||||
|
} else if (this.newChapters[i]) {
|
||||||
|
merged.push({ ...this.newChapters[i], id: i })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.newChapters = merged
|
||||||
this.showFindChaptersModal = false
|
this.showFindChaptersModal = false
|
||||||
this.chapterData = null
|
this.chapterData = null
|
||||||
|
|
||||||
@ -643,6 +849,7 @@ export default {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
this.lockedChapters = new Set()
|
||||||
this.checkChapters()
|
this.checkChapters()
|
||||||
},
|
},
|
||||||
removeAllChaptersClick() {
|
removeAllChaptersClick() {
|
||||||
@ -684,6 +891,91 @@ export default {
|
|||||||
this.saving = false
|
this.saving = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
handleBulkChapterAdd() {
|
||||||
|
const input = this.bulkChapterInput.trim()
|
||||||
|
if (!input) return
|
||||||
|
|
||||||
|
const numberMatch = input.match(/(\d+)/)
|
||||||
|
|
||||||
|
if (numberMatch) {
|
||||||
|
// Extract the base pattern and number, preserving zero-padding
|
||||||
|
const originalNumberString = numberMatch[1]
|
||||||
|
const foundNumber = parseInt(originalNumberString)
|
||||||
|
const numberIndex = numberMatch.index
|
||||||
|
const beforeNumber = input.substring(0, numberIndex)
|
||||||
|
const afterNumber = input.substring(numberIndex + originalNumberString.length)
|
||||||
|
|
||||||
|
this.detectedPattern = {
|
||||||
|
before: beforeNumber,
|
||||||
|
after: afterNumber,
|
||||||
|
startingNumber: foundNumber,
|
||||||
|
originalPadding: originalNumberString.length,
|
||||||
|
hasLeadingZeros: originalNumberString.length > 1 && originalNumberString.startsWith('0')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bulkChapterCount = 1
|
||||||
|
this.showBulkChapterModal = true
|
||||||
|
} else {
|
||||||
|
this.addSingleChapterFromInput(input)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addSingleChapterFromInput(title) {
|
||||||
|
// Find the last chapter to determine where to add the new one
|
||||||
|
const lastChapter = this.newChapters[this.newChapters.length - 1]
|
||||||
|
const newStart = lastChapter ? lastChapter.end : 0
|
||||||
|
const newEnd = Math.min(newStart + 300, this.mediaDuration)
|
||||||
|
|
||||||
|
const newChapter = {
|
||||||
|
id: this.newChapters.length,
|
||||||
|
start: newStart,
|
||||||
|
end: newEnd,
|
||||||
|
title: title
|
||||||
|
}
|
||||||
|
|
||||||
|
this.newChapters.push(newChapter)
|
||||||
|
this.bulkChapterInput = ''
|
||||||
|
this.checkChapters()
|
||||||
|
},
|
||||||
|
|
||||||
|
addBulkChapters() {
|
||||||
|
const count = parseInt(this.bulkChapterCount)
|
||||||
|
if (!count || count < 1 || count > 150) {
|
||||||
|
this.$toast.error(this.$strings.ToastBulkChapterInvalidCount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { before, after, startingNumber, originalPadding, hasLeadingZeros } = this.detectedPattern
|
||||||
|
const lastChapter = this.newChapters[this.newChapters.length - 1]
|
||||||
|
const baseStart = lastChapter ? lastChapter.start + 1 : 0
|
||||||
|
|
||||||
|
// Add multiple chapters with the detected pattern
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const chapterNumber = startingNumber + i
|
||||||
|
let formattedNumber = chapterNumber.toString()
|
||||||
|
|
||||||
|
// Apply zero-padding if the original had leading zeros
|
||||||
|
if (hasLeadingZeros && originalPadding > 1) {
|
||||||
|
formattedNumber = chapterNumber.toString().padStart(originalPadding, '0')
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStart = baseStart + i
|
||||||
|
const newEnd = Math.min(newStart + i + i, this.mediaDuration)
|
||||||
|
|
||||||
|
const newChapter = {
|
||||||
|
id: this.newChapters.length,
|
||||||
|
start: newStart,
|
||||||
|
end: newEnd,
|
||||||
|
title: `${before}${formattedNumber}${after}`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.newChapters.push(newChapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bulkChapterInput = ''
|
||||||
|
this.showBulkChapterModal = false
|
||||||
|
this.detectedPattern = null
|
||||||
|
this.checkChapters()
|
||||||
|
},
|
||||||
libraryItemUpdated(libraryItem) {
|
libraryItemUpdated(libraryItem) {
|
||||||
if (libraryItem.id === this.libraryItem.id) {
|
if (libraryItem.id === this.libraryItem.id) {
|
||||||
if (!!libraryItem.media.metadata.asin && this.mediaMetadata.asin !== libraryItem.media.metadata.asin) {
|
if (!!libraryItem.media.metadata.asin && this.mediaMetadata.asin !== libraryItem.media.metadata.asin) {
|
||||||
|
@ -131,35 +131,26 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grow py-2">
|
<div class="grow py-2">
|
||||||
<ui-dropdown :label="$strings.LabelSettingsDateFormat" v-model="newServerSettings.dateFormat" :items="dateFormats" small class="max-w-52" @input="(val) => updateSettingsKey('dateFormat', val)" />
|
<ui-dropdown :label="$strings.LabelSettingsDateFormat" v-model="newServerSettings.dateFormat" :items="dateFormats" small class="max-w-72" @input="(val) => updateSettingsKey('dateFormat', val)" />
|
||||||
<p class="text-xs ml-1 text-white/60">{{ $strings.LabelExample }}: {{ dateExample }}</p>
|
<p class="text-xs ml-1 text-white/60">{{ $strings.LabelExample }}: {{ dateExample }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grow py-2">
|
<div class="grow py-2">
|
||||||
<ui-dropdown :label="$strings.LabelSettingsTimeFormat" v-model="newServerSettings.timeFormat" :items="timeFormats" small class="max-w-52" @input="(val) => updateSettingsKey('timeFormat', val)" />
|
<ui-dropdown :label="$strings.LabelSettingsTimeFormat" v-model="newServerSettings.timeFormat" :items="timeFormats" small class="max-w-72" @input="(val) => updateSettingsKey('timeFormat', val)" />
|
||||||
<p class="text-xs ml-1 text-white/60">{{ $strings.LabelExample }}: {{ timeExample }}</p>
|
<p class="text-xs ml-1 text-white/60">{{ $strings.LabelExample }}: {{ timeExample }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
<ui-dropdown :label="$strings.LabelLanguageDefaultServer" ref="langDropdown" v-model="newServerSettings.language" :items="$languageCodeOptions" small class="max-w-52" @input="updateServerLanguage" />
|
<ui-dropdown :label="$strings.LabelLanguageDefaultServer" ref="langDropdown" v-model="newServerSettings.language" :items="$languageCodeOptions" small class="max-w-72" @input="updateServerLanguage" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- old experimental features -->
|
<div class="pt-4">
|
||||||
<!-- <div class="pt-4">
|
<h2 class="font-semibold">{{ $strings.HeaderSettingsSecurity }}</h2>
|
||||||
<h2 class="font-semibold">{{ $strings.HeaderSettingsExperimental }}</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center py-2">
|
<div class="py-2">
|
||||||
<ui-toggle-switch labeledBy="settings-experimental-features" v-model="showExperimentalFeatures" />
|
<ui-multi-select v-model="newServerSettings.allowedOrigins" :items="newServerSettings.allowedOrigins" :label="$strings.LabelCorsAllowed" class="max-w-72" @input="updateCorsOrigins" />
|
||||||
<ui-tooltip :text="$strings.LabelSettingsExperimentalFeaturesHelp">
|
</div>
|
||||||
<p class="pl-4">
|
|
||||||
<span id="settings-experimental-features">{{ $strings.LabelSettingsExperimentalFeatures }}</span>
|
|
||||||
<a :aria-label="$strings.LabelSettingsExperimentalFeaturesHelp" href="https://github.com/advplyr/audiobookshelf/discussions/75" target="_blank">
|
|
||||||
<span class="material-symbols icon-text">info</span>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</ui-tooltip>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</app-settings-content>
|
</app-settings-content>
|
||||||
@ -323,6 +314,27 @@ export default {
|
|||||||
updateServerLanguage(val) {
|
updateServerLanguage(val) {
|
||||||
this.updateSettingsKey('language', val)
|
this.updateSettingsKey('language', val)
|
||||||
},
|
},
|
||||||
|
updateCorsOrigins(val) {
|
||||||
|
const validOrigins = []
|
||||||
|
const invalidOrigins = []
|
||||||
|
|
||||||
|
val.forEach((origin) => {
|
||||||
|
const trimmedOrigin = origin.trim().toLowerCase()
|
||||||
|
try {
|
||||||
|
new URL(trimmedOrigin)
|
||||||
|
validOrigins.push(trimmedOrigin)
|
||||||
|
} catch {
|
||||||
|
invalidOrigins.push(trimmedOrigin)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (invalidOrigins.length > 0) {
|
||||||
|
this.$toast.error(this.$strings.ToastInvalidUrls)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.newServerSettings.allowedOrigins = validOrigins
|
||||||
|
this.updateSettingsKey('allowedOrigins', validOrigins)
|
||||||
|
},
|
||||||
updateSettingsKey(key, val) {
|
updateSettingsKey(key, val) {
|
||||||
if (key === 'scannerDisableWatcher') {
|
if (key === 'scannerDisableWatcher') {
|
||||||
this.newServerSettings.scannerDisableWatcher = val
|
this.newServerSettings.scannerDisableWatcher = val
|
||||||
@ -352,6 +364,7 @@ export default {
|
|||||||
initServerSettings() {
|
initServerSettings() {
|
||||||
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
|
||||||
this.newServerSettings.sortingPrefixes = [...(this.newServerSettings.sortingPrefixes || [])]
|
this.newServerSettings.sortingPrefixes = [...(this.newServerSettings.sortingPrefixes || [])]
|
||||||
|
this.newServerSettings.allowedOrigins = [...(this.newServerSettings.allowedOrigins || [])]
|
||||||
this.scannerEnableWatcher = !this.newServerSettings.scannerDisableWatcher
|
this.scannerEnableWatcher = !this.newServerSettings.scannerDisableWatcher
|
||||||
|
|
||||||
this.homepageUseBookshelfView = this.newServerSettings.homeBookshelfView != this.$constants.BookshelfView.DETAIL
|
this.homepageUseBookshelfView = this.newServerSettings.homeBookshelfView != this.$constants.BookshelfView.DETAIL
|
||||||
|
@ -189,7 +189,7 @@ export default {
|
|||||||
require('@/plugins/chromecast.js').default(this)
|
require('@/plugins/chromecast.js').default(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
|
this.$store.commit('libraries/setCurrentLibrary', { id: userDefaultLibraryId })
|
||||||
this.$store.commit('user/setUser', user)
|
this.$store.commit('user/setUser', user)
|
||||||
// Access token only returned from login, not authorize
|
// Access token only returned from login, not authorize
|
||||||
if (user.accessToken) {
|
if (user.accessToken) {
|
||||||
|
@ -133,7 +133,7 @@ export const actions = {
|
|||||||
commit('setNumUserPlaylists', numUserPlaylists)
|
commit('setNumUserPlaylists', numUserPlaylists)
|
||||||
commit('scanners/setCustomMetadataProviders', customMetadataProviders, { root: true })
|
commit('scanners/setCustomMetadataProviders', customMetadataProviders, { root: true })
|
||||||
|
|
||||||
commit('setCurrentLibrary', libraryId)
|
commit('setCurrentLibrary', { id: libraryId })
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -182,8 +182,8 @@ export const mutations = {
|
|||||||
setLibraryIssues(state, val) {
|
setLibraryIssues(state, val) {
|
||||||
state.issues = val
|
state.issues = val
|
||||||
},
|
},
|
||||||
setCurrentLibrary(state, val) {
|
setCurrentLibrary(state, { id }) {
|
||||||
state.currentLibraryId = val
|
state.currentLibraryId = id
|
||||||
},
|
},
|
||||||
set(state, libraries) {
|
set(state, libraries) {
|
||||||
state.libraries = libraries
|
state.libraries = libraries
|
||||||
|
@ -152,7 +152,6 @@ export const actions = {
|
|||||||
.$post('/auth/refresh')
|
.$post('/auth/refresh')
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
const newAccessToken = response.user.accessToken
|
const newAccessToken = response.user.accessToken
|
||||||
commit('setUser', response.user)
|
|
||||||
commit('setAccessToken', newAccessToken)
|
commit('setAccessToken', newAccessToken)
|
||||||
// Emit event used to re-authenticate socket in default.vue since $root is not available here
|
// Emit event used to re-authenticate socket in default.vue since $root is not available here
|
||||||
if (this.$eventBus) {
|
if (this.$eventBus) {
|
||||||
|
@ -240,7 +240,7 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Усе карыстальнікі, акрамя гасцей",
|
"LabelAllUsersExcludingGuests": "Усе карыстальнікі, акрамя гасцей",
|
||||||
"LabelAllUsersIncludingGuests": "Усе карыстальнікі, уключаючы гасцей",
|
"LabelAllUsersIncludingGuests": "Усе карыстальнікі, уключаючы гасцей",
|
||||||
"LabelAlreadyInYourLibrary": "Ужо ў вашай бібліятэцы",
|
"LabelAlreadyInYourLibrary": "Ужо ў вашай бібліятэцы",
|
||||||
"LabelApiKeyCreated": "API-ключ «{0}» паспяхова створаны.",
|
"LabelApiKeyCreated": "API-ключ \"{0}\" паспяхова створаны.",
|
||||||
"LabelApiKeyCreatedDescription": "Пераканайцеся, што вы скапіявалі API-ключ зараз, бо паўторна яго ўбачыць не атрымаецца.",
|
"LabelApiKeyCreatedDescription": "Пераканайцеся, што вы скапіявалі API-ключ зараз, бо паўторна яго ўбачыць не атрымаецца.",
|
||||||
"LabelApiKeyUser": "Дзейнічаць ад імя карыстальніка",
|
"LabelApiKeyUser": "Дзейнічаць ад імя карыстальніка",
|
||||||
"LabelApiKeyUserDescription": "Гэты API-ключ будзе мець тыя ж правы, што і карыстальнік, ад імя якога ён дзейнічае. У журналах гэта будзе выглядаць так, быццам запыт робіць сам карыстальнік.",
|
"LabelApiKeyUserDescription": "Гэты API-ключ будзе мець тыя ж правы, што і карыстальнік, ад імя якога ён дзейнічае. У журналах гэта будзе выглядаць так, быццам запыт робіць сам карыстальнік.",
|
||||||
@ -309,7 +309,7 @@
|
|||||||
"LabelDevice": "Прылада",
|
"LabelDevice": "Прылада",
|
||||||
"LabelDeviceInfo": "Інфармацыя пра прыладу",
|
"LabelDeviceInfo": "Інфармацыя пра прыладу",
|
||||||
"LabelDeviceIsAvailableTo": "Прылада даступная для...",
|
"LabelDeviceIsAvailableTo": "Прылада даступная для...",
|
||||||
"LabelDirectory": "Дырэкторыя",
|
"LabelDirectory": "Каталог",
|
||||||
"LabelDiscFromFilename": "Дыск з імя файла",
|
"LabelDiscFromFilename": "Дыск з імя файла",
|
||||||
"LabelDiscFromMetadata": "Дыск па метададзеных",
|
"LabelDiscFromMetadata": "Дыск па метададзеных",
|
||||||
"LabelDiscover": "Знайсці",
|
"LabelDiscover": "Знайсці",
|
||||||
@ -327,7 +327,7 @@
|
|||||||
"LabelEmail": "Электронная пошта",
|
"LabelEmail": "Электронная пошта",
|
||||||
"LabelEmailSettingsFromAddress": "Адрас адпраўніка",
|
"LabelEmailSettingsFromAddress": "Адрас адпраўніка",
|
||||||
"LabelEmailSettingsRejectUnauthorized": "Адхіляць неаўтарызаваныя сертыфікаты",
|
"LabelEmailSettingsRejectUnauthorized": "Адхіляць неаўтарызаваныя сертыфікаты",
|
||||||
"LabelEmailSettingsRejectUnauthorizedHelp": "Адключэнне праверкі SSL-сертыфіката можа зрабіць ваша злучэнне ўразлівым перад пагрозамі бяспекі, такімі як атакі «чалавек пасярэдзіне». Адключайце гэтую опцыю толькі калі цалкам разумееце наступствы і ўпэўнены ў надзейнасці паштовага сервера.",
|
"LabelEmailSettingsRejectUnauthorizedHelp": "Адключэнне праверкі SSL-сертыфіката можа зрабіць ваша злучэнне ўразлівым перад пагрозамі бяспекі, такімі як атакі \"чалавек пасярэдзіне\". Адключайце гэтую опцыю толькі калі цалкам разумееце наступствы і ўпэўнены ў надзейнасці паштовага сервера.",
|
||||||
"LabelEmailSettingsSecure": "Бяспечныя",
|
"LabelEmailSettingsSecure": "Бяспечныя",
|
||||||
"LabelEmailSettingsSecureHelp": "Калі ўключана, злучэнне будзе выкарыстоўваць TLS пры падключэнні да сервера. Калі выключана, TLS будзе выкарыстоўвацца толькі ў выпадку падтрымкі пашырэння STARTTLS на серверы. У большасці выпадкаў усталюйце значэнне true пры падключэнні да порта 465. Для партоў 587 або 25 не ўключайце яго. (інфармацыя з nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "Калі ўключана, злучэнне будзе выкарыстоўваць TLS пры падключэнні да сервера. Калі выключана, TLS будзе выкарыстоўвацца толькі ў выпадку падтрымкі пашырэння STARTTLS на серверы. У большасці выпадкаў усталюйце значэнне true пры падключэнні да порта 465. Для партоў 587 або 25 не ўключайце яго. (інфармацыя з nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmailSettingsTestAddress": "Тэставы адрас",
|
"LabelEmailSettingsTestAddress": "Тэставы адрас",
|
||||||
@ -338,6 +338,7 @@
|
|||||||
"LabelEncodingClearItemCache": "Пераканайцеся, што перыядычна ачышчаеце кэш элементаў.",
|
"LabelEncodingClearItemCache": "Пераканайцеся, што перыядычна ачышчаеце кэш элементаў.",
|
||||||
"LabelEncodingFinishedM4B": "Гатовы файл M4B будзе змешчаны ў вашу тэчку з аўдыякнігамі па адрасе:",
|
"LabelEncodingFinishedM4B": "Гатовы файл M4B будзе змешчаны ў вашу тэчку з аўдыякнігамі па адрасе:",
|
||||||
"LabelEncodingInfoEmbedded": "Метададзеныя будуць убудаваны ў аўдыядарожкі ўнутры вашай тэчкі з аўдыякнігамі.",
|
"LabelEncodingInfoEmbedded": "Метададзеныя будуць убудаваны ў аўдыядарожкі ўнутры вашай тэчкі з аўдыякнігамі.",
|
||||||
|
"LabelEncodingStartedNavigation": "Пасля запуску задачы вы можаце перайсці на іншую старонку.",
|
||||||
"LabelEncodingTimeWarning": "Кадаванне можа заняць да 30 хвілін.",
|
"LabelEncodingTimeWarning": "Кадаванне можа заняць да 30 хвілін.",
|
||||||
"LabelEnd": "Канец",
|
"LabelEnd": "Канец",
|
||||||
"LabelEndOfChapter": "Канец раздзела",
|
"LabelEndOfChapter": "Канец раздзела",
|
||||||
@ -495,7 +496,7 @@
|
|||||||
"LabelSettingsParseSubtitlesHelp": "Выдзяляць падзагаловак з назваў тэчак аўдыякніг.<br>Падзагаловак павінен быць аддзелены знакам \" - \".<br>Напрыклад, \"Назва кнігі - Падзагаловак тут\" мае падзагаловак \"Падзагаловак тут\"",
|
"LabelSettingsParseSubtitlesHelp": "Выдзяляць падзагаловак з назваў тэчак аўдыякніг.<br>Падзагаловак павінен быць аддзелены знакам \" - \".<br>Напрыклад, \"Назва кнігі - Падзагаловак тут\" мае падзагаловак \"Падзагаловак тут\"",
|
||||||
"LabelSettingsPreferMatchedMetadata": "Аддаваць перавагу супадаючым метададзеным",
|
"LabelSettingsPreferMatchedMetadata": "Аддаваць перавагу супадаючым метададзеным",
|
||||||
"LabelSettingsPreferMatchedMetadataHelp": "Супадаючыя дадзеныя будуць замяняць дэталі элемента пры выкарыстанні функцыі Хуткі пошук. Па змаўчанні Хуткі пошук запаўняе толькі адсутныя дэталі.",
|
"LabelSettingsPreferMatchedMetadataHelp": "Супадаючыя дадзеныя будуць замяняць дэталі элемента пры выкарыстанні функцыі Хуткі пошук. Па змаўчанні Хуткі пошук запаўняе толькі адсутныя дэталі.",
|
||||||
"LabelSettingsStoreCoversWithItemHelp": "Па змаўчанні вокладкі захоўваюцца ў /metadata/items, уключэнне гэтай опцыі забяспечыць захоўванне вокладак у тэчцы элемента вашай бібліятэкі. Захоўвацца будзе толькі адзін файл з назвай «cover»",
|
"LabelSettingsStoreCoversWithItemHelp": "Па змаўчанні вокладкі захоўваюцца ў /metadata/items, уключэнне гэтай опцыі забяспечыць захоўванне вокладак у тэчцы элемента вашай бібліятэкі. Захоўвацца будзе толькі адзін файл з назвай \"cover\"",
|
||||||
"LabelSettingsStoreMetadataWithItem": "Захоўваць метададзеныя разам з элементам",
|
"LabelSettingsStoreMetadataWithItem": "Захоўваць метададзеныя разам з элементам",
|
||||||
"LabelSettingsStoreMetadataWithItemHelp": "Па змаўчанні метададзеныя захоўваюцца ў /metadata/items. Уключэнне гэтай опцыі забяспечыць захоўванне файлаў метададзеных у тэчках элементаў вашай бібліятэкі",
|
"LabelSettingsStoreMetadataWithItemHelp": "Па змаўчанні метададзеныя захоўваюцца ў /metadata/items. Уключэнне гэтай опцыі забяспечыць захоўванне файлаў метададзеных у тэчках элементаў вашай бібліятэкі",
|
||||||
"LabelSettingsTimeFormat": "Фармат часу",
|
"LabelSettingsTimeFormat": "Фармат часу",
|
||||||
@ -527,7 +528,7 @@
|
|||||||
"LabelTags": "Меткі",
|
"LabelTags": "Меткі",
|
||||||
"LabelTagsAccessibleToUser": "Меткі, даступныя карыстальніку",
|
"LabelTagsAccessibleToUser": "Меткі, даступныя карыстальніку",
|
||||||
"LabelTagsNotAccessibleToUser": "Меткі, недаступныя карыстальніку",
|
"LabelTagsNotAccessibleToUser": "Меткі, недаступныя карыстальніку",
|
||||||
"LabelTasks": "Выконваюцца задачы",
|
"LabelTasks": "Запушчаныя задачы",
|
||||||
"LabelTextEditorBulletedList": "Маркіраваны спіс",
|
"LabelTextEditorBulletedList": "Маркіраваны спіс",
|
||||||
"LabelTextEditorLink": "Спасылка",
|
"LabelTextEditorLink": "Спасылка",
|
||||||
"LabelTextEditorNumberedList": "Нумараваны спіс",
|
"LabelTextEditorNumberedList": "Нумараваны спіс",
|
||||||
@ -607,7 +608,7 @@
|
|||||||
"MessageChapterErrorStartGteDuration": "Няправільны час пачатку: ён павінен быць меншым за працягласць аўдыякнігі",
|
"MessageChapterErrorStartGteDuration": "Няправільны час пачатку: ён павінен быць меншым за працягласць аўдыякнігі",
|
||||||
"MessageChapterErrorStartLtPrev": "Няправільны час пачатку: ён павінен быць большым або роўным часу пачатку папярэдняга раздзела",
|
"MessageChapterErrorStartLtPrev": "Няправільны час пачатку: ён павінен быць большым або роўным часу пачатку папярэдняга раздзела",
|
||||||
"MessageConfirmCloseFeed": "Вы ўпэўнены, што жадаеце закрыць гэтую стужку?",
|
"MessageConfirmCloseFeed": "Вы ўпэўнены, што жадаеце закрыць гэтую стужку?",
|
||||||
"MessageConfirmDeleteMetadataProvider": "Ці ўпэўненыя вы, што жадаеце выдаліць карыстацкага пастаўшчыка метададзеных «{0}»?",
|
"MessageConfirmDeleteMetadataProvider": "Ці ўпэўненыя вы, што жадаеце выдаліць карыстацкага пастаўшчыка метададзеных \"{0}\"?",
|
||||||
"MessageConfirmEmbedMetadataInAudioFiles": "Ці ўпэўненыя вы, што жадаеце ўбудаваць метададзеныя ў {0} аўдыёфайлаў?",
|
"MessageConfirmEmbedMetadataInAudioFiles": "Ці ўпэўненыя вы, што жадаеце ўбудаваць метададзеныя ў {0} аўдыёфайлаў?",
|
||||||
"MessageConfirmPurgeCache": "Ачышчэнне кэша выдаліць увесь каталог па адрасе <code>/metadata/cache</code>. <br /><br /> Ці сапраўды вы жадаеце выдаліць каталог кэша?",
|
"MessageConfirmPurgeCache": "Ачышчэнне кэша выдаліць увесь каталог па адрасе <code>/metadata/cache</code>. <br /><br /> Ці сапраўды вы жадаеце выдаліць каталог кэша?",
|
||||||
"MessageConfirmPurgeItemsCache": "Ачышчэнне кэша элементаў выдаліць увесь каталог па адрасе <code>/metadata/cache/items</code>. <br /> Вы ўпэўнены?",
|
"MessageConfirmPurgeItemsCache": "Ачышчэнне кэша элементаў выдаліць увесь каталог па адрасе <code>/metadata/cache/items</code>. <br /> Вы ўпэўнены?",
|
||||||
@ -636,6 +637,7 @@
|
|||||||
"MessageNoMediaProgress": "Няма прагрэсу медыя",
|
"MessageNoMediaProgress": "Няма прагрэсу медыя",
|
||||||
"MessageNoPodcastFeed": "Няправільны падкаст: Няма стужкі",
|
"MessageNoPodcastFeed": "Няправільны падкаст: Няма стужкі",
|
||||||
"MessageNoPodcastsFound": "Падкасты не знойдзены",
|
"MessageNoPodcastsFound": "Падкасты не знойдзены",
|
||||||
|
"MessageNoTasksRunning": "Няма запушчаных задач",
|
||||||
"MessageNoUpdatesWereNecessary": "Абнаўленні не патрабаваліся",
|
"MessageNoUpdatesWereNecessary": "Абнаўленні не патрабаваліся",
|
||||||
"MessageNoUserPlaylists": "У вас няма спісаў прайгравання",
|
"MessageNoUserPlaylists": "У вас няма спісаў прайгравання",
|
||||||
"MessageNoUserPlaylistsHelp": "Спісы прайгравання прыватныя. Толькі карыстальнік, які іх стварыў, можа іх бачыць.",
|
"MessageNoUserPlaylistsHelp": "Спісы прайгравання прыватныя. Толькі карыстальнік, які іх стварыў, можа іх бачыць.",
|
||||||
@ -643,17 +645,28 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Стварыць спіс прайгравання з калекцыі",
|
"MessagePlaylistCreateFromCollection": "Стварыць спіс прайгравання з калекцыі",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "У падкаста няма URL RSS-стужкі для супадзення",
|
"MessagePodcastHasNoRSSFeedForMatching": "У падкаста няма URL RSS-стужкі для супадзення",
|
||||||
"MessagePodcastSearchField": "Увядзіце пошукавы запыт або URL RSS-стужкі",
|
"MessagePodcastSearchField": "Увядзіце пошукавы запыт або URL RSS-стужкі",
|
||||||
"MessageQuickMatchDescription": "Запоўніць пустыя дэталі элемента і вокладку першым вынікам супадзення з «{0}». Не замяняе дэталі, калі опцыя «Аддаваць перавагу супадаючым метададзеным» на серверы не ўключана.",
|
"MessageQuickMatchDescription": "Запоўніць пустыя дэталі элемента і вокладку першым вынікам супадзення з '{0}'. Не замяняе дэталі, калі опцыя «Аддаваць перавагу супадаючым метададзеным» на серверы не ўключана.",
|
||||||
"MessageReportBugsAndContribute": "Паведамляйце пра памылкі, прапануйце новыя функцыі і ўдзельнічайце на",
|
"MessageReportBugsAndContribute": "Паведамляйце пра памылкі, прапануйце новыя функцыі і ўдзельнічайце на",
|
||||||
"MessageRestoreBackupWarning": "Аднаўленне рэзервовай копіі перазапіша ўсю базу даных, размешчаную ў /config, а таксама вокладкі ў /metadata/items і /metadata/authors. <br /><br /> Рэзервовыя копіі не змяняюць файлы ў вашых тэчках бібліятэкі. Калі вы ўключылі наладкі сервера для захоўвання воклак і метададзеных у тэчках бібліятэкі, гэтыя файлы не будуць захаваныя ў рэзервовых копіях і не зменяцца. <br /><br /> Усе кліенты, якія карыстаюцца вашым серверам, будуць аўтаматычна абноўлены.",
|
"MessageRestoreBackupWarning": "Аднаўленне рэзервовай копіі перазапіша ўсю базу даных, размешчаную ў /config, а таксама вокладкі ў /metadata/items і /metadata/authors. <br /><br /> Рэзервовыя копіі не змяняюць файлы ў вашых тэчках бібліятэкі. Калі вы ўключылі наладкі сервера для захоўвання воклак і метададзеных у тэчках бібліятэкі, гэтыя файлы не будуць захаваныя ў рэзервовых копіях і не зменяцца. <br /><br /> Усе кліенты, якія карыстаюцца вашым серверам, будуць аўтаматычна абноўлены.",
|
||||||
"MessageScheduleRunEveryWeekdayAtTime": "Выконваць кожныя {0} у {1}",
|
"MessageScheduleRunEveryWeekdayAtTime": "Выконваць кожныя {0} у {1}",
|
||||||
"MessageStartPlaybackAtTime": "Пачаць прайграванне для \"{0}\" з {1}?",
|
"MessageStartPlaybackAtTime": "Пачаць прайграванне для \"{0}\" з {1}?",
|
||||||
|
"MessageTaskAudioFileNotWritable": "Аўдыёфайл \"{0}\" недаступны для запісу",
|
||||||
"MessageTaskCanceledByUser": "Задача скасавана карыстальнікам",
|
"MessageTaskCanceledByUser": "Задача скасавана карыстальнікам",
|
||||||
"MessageTaskDownloadingEpisodeDescription": "Спампоўка эпізоду \"{0}\"",
|
"MessageTaskDownloadingEpisodeDescription": "Спампоўка эпізоду \"{0}\"",
|
||||||
"MessageTaskEmbeddingMetadata": "Убудаванне метададзеных",
|
"MessageTaskEmbeddingMetadata": "Убудаванне метададзеных",
|
||||||
"MessageTaskEmbeddingMetadataDescription": "Убудаванне метададзеных у аўдыёкнігу «{0}»",
|
"MessageTaskEmbeddingMetadataDescription": "Убудаванне метададзеных у аўдыёкнігу \"{0}\"",
|
||||||
"MessageTaskFailedToEmbedMetadataInFile": "Не ўдалося ўбудаваць метададзеныя ў файл «{0}»",
|
"MessageTaskEncodingM4b": "Кадаванне M4B",
|
||||||
|
"MessageTaskEncodingM4bDescription": "Кадаванне аўдыякнігі \"{0}\" у адзін файл m4b",
|
||||||
|
"MessageTaskFailed": "Не ўдалося",
|
||||||
|
"MessageTaskFailedToBackupAudioFile": "Не ўдалося зрабіць рэзервовую копію аўдыёфайла \"{0}\"",
|
||||||
|
"MessageTaskFailedToCreateCacheDirectory": "Не ўдалося стварыць каталог кэша",
|
||||||
|
"MessageTaskFailedToEmbedMetadataInFile": "Не ўдалося ўбудаваць метададзеныя ў файл \"{0}\"",
|
||||||
|
"MessageTaskFailedToMergeAudioFiles": "Не ўдалося аб’яднаць аўдыёфайлы",
|
||||||
|
"MessageTaskFailedToMoveM4bFile": "Не ўдалося перамясціць файл m4b",
|
||||||
"MessageTaskFailedToWriteMetadataFile": "Не ўдалося захаваць файл метададзеных",
|
"MessageTaskFailedToWriteMetadataFile": "Не ўдалося захаваць файл метададзеных",
|
||||||
|
"MessageTaskMatchingBooksInLibrary": "Пошук супадзенняў кніг у бібліятэцы \"{0}\"",
|
||||||
|
"MessageTaskNoFilesToScan": "Няма файлаў для сканавання",
|
||||||
|
"MessageTaskOpmlImport": "Імпарт OPML",
|
||||||
"MessageTaskOpmlImportDescription": "Стварэнне падкастаў з {0} RSS-стужак",
|
"MessageTaskOpmlImportDescription": "Стварэнне падкастаў з {0} RSS-стужак",
|
||||||
"MessageTaskOpmlImportFeed": "Імпарт стужкі з OPML",
|
"MessageTaskOpmlImportFeed": "Імпарт стужкі з OPML",
|
||||||
"MessageTaskOpmlImportFeedDescription": "Імпартаванне RSS-стужкі \"{0}\"",
|
"MessageTaskOpmlImportFeedDescription": "Імпартаванне RSS-стужкі \"{0}\"",
|
||||||
@ -662,6 +675,7 @@
|
|||||||
"MessageTaskOpmlImportFeedPodcastExists": "Падкаст ужо існуе па гэтым шляху",
|
"MessageTaskOpmlImportFeedPodcastExists": "Падкаст ужо існуе па гэтым шляху",
|
||||||
"MessageTaskOpmlImportFeedPodcastFailed": "Не ўдалося стварыць падкаст",
|
"MessageTaskOpmlImportFeedPodcastFailed": "Не ўдалося стварыць падкаст",
|
||||||
"MessageTaskOpmlParseNoneFound": "У OPML-файле не знойдзена стужак",
|
"MessageTaskOpmlParseNoneFound": "У OPML-файле не знойдзена стужак",
|
||||||
|
"MessageTaskTargetDirectoryNotWritable": "Мэтавы каталог недаступны для запісу",
|
||||||
"NoteChapterEditorTimes": "Заўвага: Час пачатку першага раздзела павінен заставацца 0:00, а час пачатку апошняга раздзела не можа перавышаць працягласць гэтай аўдыякнігі.",
|
"NoteChapterEditorTimes": "Заўвага: Час пачатку першага раздзела павінен заставацца 0:00, а час пачатку апошняга раздзела не можа перавышаць працягласць гэтай аўдыякнігі.",
|
||||||
"NoteRSSFeedPodcastAppsHttps": "Папярэджанне: большасць праграм для падкастаў патрабуюць, каб URL RSS-стужкі выкарыстоўваў HTTPS",
|
"NoteRSSFeedPodcastAppsHttps": "Папярэджанне: большасць праграм для падкастаў патрабуюць, каб URL RSS-стужкі выкарыстоўваў HTTPS",
|
||||||
"NoteRSSFeedPodcastAppsPubDate": "Папярэджанне: адзін ці больш вашых эпізодаў не маюць даты публікацыі. Некаторыя праграмы для падкастаў патрабуюць гэтага.",
|
"NoteRSSFeedPodcastAppsPubDate": "Папярэджанне: адзін ці больш вашых эпізодаў не маюць даты публікацыі. Некаторыя праграмы для падкастаў патрабуюць гэтага.",
|
||||||
@ -718,6 +732,7 @@
|
|||||||
"ToastSendEbookToDeviceFailed": "Не ўдалося адправіць электронную кнігу на прыладу",
|
"ToastSendEbookToDeviceFailed": "Не ўдалося адправіць электронную кнігу на прыладу",
|
||||||
"ToastSendEbookToDeviceSuccess": "Электронная кніга адпраўлена на прыладу \"{0}\"",
|
"ToastSendEbookToDeviceSuccess": "Электронная кніга адпраўлена на прыладу \"{0}\"",
|
||||||
"ToastSleepTimerDone": "Таймер сну скончыўся... Хр-р-р",
|
"ToastSleepTimerDone": "Таймер сну скончыўся... Хр-р-р",
|
||||||
|
"ToastUploaderItemExistsInSubdirectoryError": "Элемент \"{0}\" выкарыстоўвае падкаталог шляху загрузкі.",
|
||||||
"ToastUserPasswordMustChange": "Новы пароль не можа супадаць са старым",
|
"ToastUserPasswordMustChange": "Новы пароль не можа супадаць са старым",
|
||||||
"ToastUserRootRequireName": "Неабходна ўвесці імя карыстальніка адміністратара"
|
"ToastUserRootRequireName": "Неабходна ўвесці імя карыстальніка адміністратара"
|
||||||
}
|
}
|
||||||
|
@ -199,6 +199,7 @@
|
|||||||
"HeaderSettingsExperimental": "Experimentelle Funktionen",
|
"HeaderSettingsExperimental": "Experimentelle Funktionen",
|
||||||
"HeaderSettingsGeneral": "Allgemein",
|
"HeaderSettingsGeneral": "Allgemein",
|
||||||
"HeaderSettingsScanner": "Scanner",
|
"HeaderSettingsScanner": "Scanner",
|
||||||
|
"HeaderSettingsSecurity": "Sicherheit",
|
||||||
"HeaderSettingsWebClient": "Web-Client",
|
"HeaderSettingsWebClient": "Web-Client",
|
||||||
"HeaderSleepTimer": "Sleep-Timer",
|
"HeaderSleepTimer": "Sleep-Timer",
|
||||||
"HeaderStatsLargestItems": "Größte Medien",
|
"HeaderStatsLargestItems": "Größte Medien",
|
||||||
@ -293,6 +294,7 @@
|
|||||||
"LabelContinueListening": "Weiterhören",
|
"LabelContinueListening": "Weiterhören",
|
||||||
"LabelContinueReading": "Weiterlesen",
|
"LabelContinueReading": "Weiterlesen",
|
||||||
"LabelContinueSeries": "Serien fortsetzen",
|
"LabelContinueSeries": "Serien fortsetzen",
|
||||||
|
"LabelCorsAllowed": "Erlaubte CORS Quellen",
|
||||||
"LabelCover": "Titelbild",
|
"LabelCover": "Titelbild",
|
||||||
"LabelCoverImageURL": "URL des Titelbildes",
|
"LabelCoverImageURL": "URL des Titelbildes",
|
||||||
"LabelCoverProvider": "Titelbildanbieter",
|
"LabelCoverProvider": "Titelbildanbieter",
|
||||||
@ -418,6 +420,7 @@
|
|||||||
"LabelLanguages": "Sprachen",
|
"LabelLanguages": "Sprachen",
|
||||||
"LabelLastBookAdded": "Zuletzt hinzugefügtes Buch",
|
"LabelLastBookAdded": "Zuletzt hinzugefügtes Buch",
|
||||||
"LabelLastBookUpdated": "Zuletzt aktualisiertes Buch",
|
"LabelLastBookUpdated": "Zuletzt aktualisiertes Buch",
|
||||||
|
"LabelLastProgressDate": "Letzter Fortschritt: {0}",
|
||||||
"LabelLastSeen": "Zuletzt gesehen",
|
"LabelLastSeen": "Zuletzt gesehen",
|
||||||
"LabelLastTime": "Letztes Mal",
|
"LabelLastTime": "Letztes Mal",
|
||||||
"LabelLastUpdate": "Letzte Aktualisierung",
|
"LabelLastUpdate": "Letzte Aktualisierung",
|
||||||
@ -430,6 +433,7 @@
|
|||||||
"LabelLibraryFilterSublistEmpty": "Keine {0}",
|
"LabelLibraryFilterSublistEmpty": "Keine {0}",
|
||||||
"LabelLibraryItem": "Bibliothekseintrag",
|
"LabelLibraryItem": "Bibliothekseintrag",
|
||||||
"LabelLibraryName": "Bibliotheksname",
|
"LabelLibraryName": "Bibliotheksname",
|
||||||
|
"LabelLibrarySortByProgress": "Fortschritt aktualisiert",
|
||||||
"LabelLimit": "Begrenzung",
|
"LabelLimit": "Begrenzung",
|
||||||
"LabelLineSpacing": "Zeilenabstand",
|
"LabelLineSpacing": "Zeilenabstand",
|
||||||
"LabelListenAgain": "Erneut anhören",
|
"LabelListenAgain": "Erneut anhören",
|
||||||
@ -529,7 +533,7 @@
|
|||||||
"LabelPublishers": "Herausgeber",
|
"LabelPublishers": "Herausgeber",
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Benutzerdefinierte Eigentümer-E-Mail",
|
"LabelRSSFeedCustomOwnerEmail": "Benutzerdefinierte Eigentümer-E-Mail",
|
||||||
"LabelRSSFeedCustomOwnerName": "Benutzerdefinierter Name des Eigentümers",
|
"LabelRSSFeedCustomOwnerName": "Benutzerdefinierter Name des Eigentümers",
|
||||||
"LabelRSSFeedOpen": "RSS Feed offen",
|
"LabelRSSFeedOpen": "RSS-Feed offen",
|
||||||
"LabelRSSFeedPreventIndexing": "Indizierung verhindern",
|
"LabelRSSFeedPreventIndexing": "Indizierung verhindern",
|
||||||
"LabelRSSFeedSlug": "RSS-Feed-Schlagwort",
|
"LabelRSSFeedSlug": "RSS-Feed-Schlagwort",
|
||||||
"LabelRSSFeedURL": "RSS-Feed-URL",
|
"LabelRSSFeedURL": "RSS-Feed-URL",
|
||||||
@ -803,6 +807,7 @@
|
|||||||
"MessageFeedURLWillBe": "Feed-URL wird {0} sein",
|
"MessageFeedURLWillBe": "Feed-URL wird {0} sein",
|
||||||
"MessageFetching": "Wird abgerufen …",
|
"MessageFetching": "Wird abgerufen …",
|
||||||
"MessageForceReScanDescription": "Durchsucht alle Dateien erneut, wie bei einem frischen Scan. ID3-Tags von Audiodateien, OPF-Dateien und Textdateien werden neu durchsucht.",
|
"MessageForceReScanDescription": "Durchsucht alle Dateien erneut, wie bei einem frischen Scan. ID3-Tags von Audiodateien, OPF-Dateien und Textdateien werden neu durchsucht.",
|
||||||
|
"MessageHeatmapNoListeningSessions": "Keine Hörsitzungen am {0}",
|
||||||
"MessageImportantNotice": "Wichtiger Hinweis!",
|
"MessageImportantNotice": "Wichtiger Hinweis!",
|
||||||
"MessageInsertChapterBelow": "Kapitel unten einfügen",
|
"MessageInsertChapterBelow": "Kapitel unten einfügen",
|
||||||
"MessageInvalidAsin": "Ungültige ASIN",
|
"MessageInvalidAsin": "Ungültige ASIN",
|
||||||
@ -1030,6 +1035,7 @@
|
|||||||
"ToastInvalidImageUrl": "Ungültiger Bild URL",
|
"ToastInvalidImageUrl": "Ungültiger Bild URL",
|
||||||
"ToastInvalidMaxEpisodesToDownload": "Ungültige Max. Anzahl an Episoden zum Herunterladen",
|
"ToastInvalidMaxEpisodesToDownload": "Ungültige Max. Anzahl an Episoden zum Herunterladen",
|
||||||
"ToastInvalidUrl": "Ungültiger URL",
|
"ToastInvalidUrl": "Ungültiger URL",
|
||||||
|
"ToastInvalidUrls": "Eine oder mehrere URLs sind in einem falschen Format",
|
||||||
"ToastItemCoverUpdateSuccess": "Titelbild aktualisiert",
|
"ToastItemCoverUpdateSuccess": "Titelbild aktualisiert",
|
||||||
"ToastItemDeletedFailed": "Fehler beim löschen des Artikels",
|
"ToastItemDeletedFailed": "Fehler beim löschen des Artikels",
|
||||||
"ToastItemDeletedSuccess": "Artikel gelöscht",
|
"ToastItemDeletedSuccess": "Artikel gelöscht",
|
||||||
|
@ -127,6 +127,7 @@
|
|||||||
"HeaderAudiobookTools": "Audiobook File Management Tools",
|
"HeaderAudiobookTools": "Audiobook File Management Tools",
|
||||||
"HeaderAuthentication": "Authentication",
|
"HeaderAuthentication": "Authentication",
|
||||||
"HeaderBackups": "Backups",
|
"HeaderBackups": "Backups",
|
||||||
|
"HeaderBulkChapterModal": "Add Multiple Chapters",
|
||||||
"HeaderChangePassword": "Change Password",
|
"HeaderChangePassword": "Change Password",
|
||||||
"HeaderChapters": "Chapters",
|
"HeaderChapters": "Chapters",
|
||||||
"HeaderChooseAFolder": "Choose a Folder",
|
"HeaderChooseAFolder": "Choose a Folder",
|
||||||
@ -199,6 +200,7 @@
|
|||||||
"HeaderSettingsExperimental": "Experimental Features",
|
"HeaderSettingsExperimental": "Experimental Features",
|
||||||
"HeaderSettingsGeneral": "General",
|
"HeaderSettingsGeneral": "General",
|
||||||
"HeaderSettingsScanner": "Scanner",
|
"HeaderSettingsScanner": "Scanner",
|
||||||
|
"HeaderSettingsSecurity": "Security",
|
||||||
"HeaderSettingsWebClient": "Web Client",
|
"HeaderSettingsWebClient": "Web Client",
|
||||||
"HeaderSleepTimer": "Sleep Timer",
|
"HeaderSleepTimer": "Sleep Timer",
|
||||||
"HeaderStatsLargestItems": "Largest Items",
|
"HeaderStatsLargestItems": "Largest Items",
|
||||||
@ -293,6 +295,7 @@
|
|||||||
"LabelContinueListening": "Continue Listening",
|
"LabelContinueListening": "Continue Listening",
|
||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Continue Reading",
|
||||||
"LabelContinueSeries": "Continue Series",
|
"LabelContinueSeries": "Continue Series",
|
||||||
|
"LabelCorsAllowed": "Allowed CORS Origins",
|
||||||
"LabelCover": "Cover",
|
"LabelCover": "Cover",
|
||||||
"LabelCoverImageURL": "Cover Image URL",
|
"LabelCoverImageURL": "Cover Image URL",
|
||||||
"LabelCoverProvider": "Cover Provider",
|
"LabelCoverProvider": "Cover Provider",
|
||||||
@ -306,6 +309,7 @@
|
|||||||
"LabelDeleteFromFileSystemCheckbox": "Delete from file system (uncheck to only remove from database)",
|
"LabelDeleteFromFileSystemCheckbox": "Delete from file system (uncheck to only remove from database)",
|
||||||
"LabelDescription": "Description",
|
"LabelDescription": "Description",
|
||||||
"LabelDeselectAll": "Deselect All",
|
"LabelDeselectAll": "Deselect All",
|
||||||
|
"LabelDetectedPattern": "Detected pattern:",
|
||||||
"LabelDevice": "Device",
|
"LabelDevice": "Device",
|
||||||
"LabelDeviceInfo": "Device Info",
|
"LabelDeviceInfo": "Device Info",
|
||||||
"LabelDeviceIsAvailableTo": "Device is available to...",
|
"LabelDeviceIsAvailableTo": "Device is available to...",
|
||||||
@ -470,6 +474,7 @@
|
|||||||
"LabelNewestAuthors": "Newest Authors",
|
"LabelNewestAuthors": "Newest Authors",
|
||||||
"LabelNewestEpisodes": "Newest Episodes",
|
"LabelNewestEpisodes": "Newest Episodes",
|
||||||
"LabelNextBackupDate": "Next backup date",
|
"LabelNextBackupDate": "Next backup date",
|
||||||
|
"LabelNextChapters": "Next chapters will be:",
|
||||||
"LabelNextScheduledRun": "Next scheduled run",
|
"LabelNextScheduledRun": "Next scheduled run",
|
||||||
"LabelNoApiKeys": "No API keys",
|
"LabelNoApiKeys": "No API keys",
|
||||||
"LabelNoCustomMetadataProviders": "No custom metadata providers",
|
"LabelNoCustomMetadataProviders": "No custom metadata providers",
|
||||||
@ -487,6 +492,7 @@
|
|||||||
"LabelNotificationsMaxQueueSize": "Max queue size for notification events",
|
"LabelNotificationsMaxQueueSize": "Max queue size for notification events",
|
||||||
"LabelNotificationsMaxQueueSizeHelp": "Events are limited to firing 1 per second. Events will be ignored if the queue is at max size. This prevents notification spamming.",
|
"LabelNotificationsMaxQueueSizeHelp": "Events are limited to firing 1 per second. Events will be ignored if the queue is at max size. This prevents notification spamming.",
|
||||||
"LabelNumberOfBooks": "Number of Books",
|
"LabelNumberOfBooks": "Number of Books",
|
||||||
|
"LabelNumberOfChapters": "Number of chapters:",
|
||||||
"LabelNumberOfEpisodes": "# of Episodes",
|
"LabelNumberOfEpisodes": "# of Episodes",
|
||||||
"LabelOpenIDAdvancedPermsClaimDescription": "Name of the OpenID claim that contains advanced permissions for user actions within the application which will apply to non-admin roles (<b>if configured</b>). If the claim is missing from the response, access to ABS will be denied. If a single option is missing, it will be treated as <code>false</code>. Ensure the identity provider's claim matches the expected structure:",
|
"LabelOpenIDAdvancedPermsClaimDescription": "Name of the OpenID claim that contains advanced permissions for user actions within the application which will apply to non-admin roles (<b>if configured</b>). If the claim is missing from the response, access to ABS will be denied. If a single option is missing, it will be treated as <code>false</code>. Ensure the identity provider's claim matches the expected structure:",
|
||||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||||
@ -743,6 +749,7 @@
|
|||||||
"MessageBookshelfNoResultsForFilter": "No results for filter \"{0}: {1}\"",
|
"MessageBookshelfNoResultsForFilter": "No results for filter \"{0}: {1}\"",
|
||||||
"MessageBookshelfNoResultsForQuery": "No results for query",
|
"MessageBookshelfNoResultsForQuery": "No results for query",
|
||||||
"MessageBookshelfNoSeries": "You have no series",
|
"MessageBookshelfNoSeries": "You have no series",
|
||||||
|
"MessageBulkChapterPattern": "How many chapters would you like to add with this numbering pattern?",
|
||||||
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
|
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
|
||||||
"MessageChapterErrorFirstNotZero": "First chapter must start at 0",
|
"MessageChapterErrorFirstNotZero": "First chapter must start at 0",
|
||||||
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
|
||||||
@ -946,6 +953,7 @@
|
|||||||
"NotificationOnRSSFeedDisabledDescription": "Triggered when automatic episode downloads are disabled due to too many failed attempts",
|
"NotificationOnRSSFeedDisabledDescription": "Triggered when automatic episode downloads are disabled due to too many failed attempts",
|
||||||
"NotificationOnRSSFeedFailedDescription": "Triggered when the RSS feed request fails for an automatic episode download",
|
"NotificationOnRSSFeedFailedDescription": "Triggered when the RSS feed request fails for an automatic episode download",
|
||||||
"NotificationOnTestDescription": "Event for testing the notification system",
|
"NotificationOnTestDescription": "Event for testing the notification system",
|
||||||
|
"PlaceholderBulkChapterInput": "Enter chapter title or use numbering (e.g., 'Episode 1', 'Chapter 10', '1.')",
|
||||||
"PlaceholderNewCollection": "New collection name",
|
"PlaceholderNewCollection": "New collection name",
|
||||||
"PlaceholderNewFolderPath": "New folder path",
|
"PlaceholderNewFolderPath": "New folder path",
|
||||||
"PlaceholderNewPlaylist": "New playlist name",
|
"PlaceholderNewPlaylist": "New playlist name",
|
||||||
@ -999,8 +1007,12 @@
|
|||||||
"ToastBookmarkCreateFailed": "Failed to create bookmark",
|
"ToastBookmarkCreateFailed": "Failed to create bookmark",
|
||||||
"ToastBookmarkCreateSuccess": "Bookmark added",
|
"ToastBookmarkCreateSuccess": "Bookmark added",
|
||||||
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
||||||
|
"ToastBulkChapterInvalidCount": "Enter a number between 1 and 150",
|
||||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||||
|
"ToastChapterLocked": "Chapter is locked.",
|
||||||
|
"ToastChapterStartTimeAdjusted": "Chapter start time adjusted by {0} seconds",
|
||||||
|
"ToastChaptersAllLocked": "All chapters are locked. Unlock some chapters to shift their times.",
|
||||||
"ToastChaptersHaveErrors": "Chapters have errors",
|
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||||
"ToastChaptersInvalidShiftAmountLast": "Invalid shift amount. The last chapter start time would extend beyond the duration of this audiobook.",
|
"ToastChaptersInvalidShiftAmountLast": "Invalid shift amount. The last chapter start time would extend beyond the duration of this audiobook.",
|
||||||
"ToastChaptersInvalidShiftAmountStart": "Invalid shift amount. The first chapter would have zero or negative length and would be overwritten by the second chapter. Increase the start duration of second chapter.",
|
"ToastChaptersInvalidShiftAmountStart": "Invalid shift amount. The first chapter would have zero or negative length and would be overwritten by the second chapter. Increase the start duration of second chapter.",
|
||||||
@ -1034,6 +1046,7 @@
|
|||||||
"ToastInvalidImageUrl": "Invalid image URL",
|
"ToastInvalidImageUrl": "Invalid image URL",
|
||||||
"ToastInvalidMaxEpisodesToDownload": "Invalid max episodes to download",
|
"ToastInvalidMaxEpisodesToDownload": "Invalid max episodes to download",
|
||||||
"ToastInvalidUrl": "Invalid URL",
|
"ToastInvalidUrl": "Invalid URL",
|
||||||
|
"ToastInvalidUrls": "One or more URLs are invalid",
|
||||||
"ToastItemCoverUpdateSuccess": "Item cover updated",
|
"ToastItemCoverUpdateSuccess": "Item cover updated",
|
||||||
"ToastItemDeletedFailed": "Failed to delete item",
|
"ToastItemDeletedFailed": "Failed to delete item",
|
||||||
"ToastItemDeletedSuccess": "Deleted item",
|
"ToastItemDeletedSuccess": "Deleted item",
|
||||||
@ -1133,5 +1146,13 @@
|
|||||||
"ToastUserPasswordChangeSuccess": "Password changed successfully",
|
"ToastUserPasswordChangeSuccess": "Password changed successfully",
|
||||||
"ToastUserPasswordMismatch": "Passwords do not match",
|
"ToastUserPasswordMismatch": "Passwords do not match",
|
||||||
"ToastUserPasswordMustChange": "New password cannot match old password",
|
"ToastUserPasswordMustChange": "New password cannot match old password",
|
||||||
"ToastUserRootRequireName": "Must enter a root username"
|
"ToastUserRootRequireName": "Must enter a root username",
|
||||||
|
"TooltipAddChapters": "Add chapter(s)",
|
||||||
|
"TooltipAddOneSecond": "Add 1 second",
|
||||||
|
"TooltipAdjustChapterStart": "Click to adjust start time",
|
||||||
|
"TooltipLockAllChapters": "Lock all chapters",
|
||||||
|
"TooltipLockChapter": "Lock chapter (Shift+click for range)",
|
||||||
|
"TooltipSubtractOneSecond": "Subtract 1 second",
|
||||||
|
"TooltipUnlockAllChapters": "Unlock all chapters",
|
||||||
|
"TooltipUnlockChapter": "Unlock chapter (Shift+click for range)"
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,7 @@
|
|||||||
"HeaderAudiobookTools": "Herramientas de Gestión de Archivos de Audiolibro",
|
"HeaderAudiobookTools": "Herramientas de Gestión de Archivos de Audiolibro",
|
||||||
"HeaderAuthentication": "Autenticación",
|
"HeaderAuthentication": "Autenticación",
|
||||||
"HeaderBackups": "Respaldos",
|
"HeaderBackups": "Respaldos",
|
||||||
|
"HeaderBulkChapterModal": "Añadir Múltiples Capítulos",
|
||||||
"HeaderChangePassword": "Cambiar contraseña",
|
"HeaderChangePassword": "Cambiar contraseña",
|
||||||
"HeaderChapters": "Capítulos",
|
"HeaderChapters": "Capítulos",
|
||||||
"HeaderChooseAFolder": "Escoger una Carpeta",
|
"HeaderChooseAFolder": "Escoger una Carpeta",
|
||||||
@ -297,6 +298,7 @@
|
|||||||
"LabelDeleteFromFileSystemCheckbox": "Eliminar del sistema de archivos (desmarque para quitar de la base de datos solamente)",
|
"LabelDeleteFromFileSystemCheckbox": "Eliminar del sistema de archivos (desmarque para quitar de la base de datos solamente)",
|
||||||
"LabelDescription": "Descripción",
|
"LabelDescription": "Descripción",
|
||||||
"LabelDeselectAll": "Deseleccionar Todos",
|
"LabelDeselectAll": "Deseleccionar Todos",
|
||||||
|
"LabelDetectedPattern": "Patrón detectado:",
|
||||||
"LabelDevice": "Dispositivo",
|
"LabelDevice": "Dispositivo",
|
||||||
"LabelDeviceInfo": "Información del dispositivo",
|
"LabelDeviceInfo": "Información del dispositivo",
|
||||||
"LabelDeviceIsAvailableTo": "El dispositivo está disponible para...",
|
"LabelDeviceIsAvailableTo": "El dispositivo está disponible para...",
|
||||||
@ -454,6 +456,7 @@
|
|||||||
"LabelNewestAuthors": "Autores más nuevos",
|
"LabelNewestAuthors": "Autores más nuevos",
|
||||||
"LabelNewestEpisodes": "Episodios más nuevos",
|
"LabelNewestEpisodes": "Episodios más nuevos",
|
||||||
"LabelNextBackupDate": "Fecha del siguiente respaldo",
|
"LabelNextBackupDate": "Fecha del siguiente respaldo",
|
||||||
|
"LabelNextChapters": "Los próximos capítulos serán:",
|
||||||
"LabelNextScheduledRun": "Próxima ejecución programada",
|
"LabelNextScheduledRun": "Próxima ejecución programada",
|
||||||
"LabelNoCustomMetadataProviders": "Sin proveedores de metadatos personalizados",
|
"LabelNoCustomMetadataProviders": "Sin proveedores de metadatos personalizados",
|
||||||
"LabelNoEpisodesSelected": "Ningún Episodio Seleccionado",
|
"LabelNoEpisodesSelected": "Ningún Episodio Seleccionado",
|
||||||
@ -470,6 +473,7 @@
|
|||||||
"LabelNotificationsMaxQueueSize": "Tamaño máximo de la cola de notificaciones",
|
"LabelNotificationsMaxQueueSize": "Tamaño máximo de la cola de notificaciones",
|
||||||
"LabelNotificationsMaxQueueSizeHelp": "Las notificaciones están limitadas a 1 por segundo. Las notificaciones serán ignoradas si llegan al numero máximo de cola para prevenir spam de eventos.",
|
"LabelNotificationsMaxQueueSizeHelp": "Las notificaciones están limitadas a 1 por segundo. Las notificaciones serán ignoradas si llegan al numero máximo de cola para prevenir spam de eventos.",
|
||||||
"LabelNumberOfBooks": "Número de libros",
|
"LabelNumberOfBooks": "Número de libros",
|
||||||
|
"LabelNumberOfChapters": "Número de capítulos:",
|
||||||
"LabelNumberOfEpisodes": "N.º de episodios",
|
"LabelNumberOfEpisodes": "N.º de episodios",
|
||||||
"LabelOpenIDAdvancedPermsClaimDescription": "Nombre de la notificación de OpenID que contiene permisos avanzados para acciones de usuario dentro de la aplicación que se aplicarán a roles que no sean de administrador (<b>si están configurados</b>). Si el reclamo no aparece en la respuesta, se denegará el acceso a ABS. Si falta una sola opción, se tratará como <code>falsa</code>. Asegúrese de que la notificación del proveedor de identidades coincida con la estructura esperada:",
|
"LabelOpenIDAdvancedPermsClaimDescription": "Nombre de la notificación de OpenID que contiene permisos avanzados para acciones de usuario dentro de la aplicación que se aplicarán a roles que no sean de administrador (<b>si están configurados</b>). Si el reclamo no aparece en la respuesta, se denegará el acceso a ABS. Si falta una sola opción, se tratará como <code>falsa</code>. Asegúrese de que la notificación del proveedor de identidades coincida con la estructura esperada:",
|
||||||
"LabelOpenIDClaims": "Deje las siguientes opciones vacías para desactivar la asignación avanzada de grupos y permisos, lo que asignaría de manera automática al grupo «Usuario».",
|
"LabelOpenIDClaims": "Deje las siguientes opciones vacías para desactivar la asignación avanzada de grupos y permisos, lo que asignaría de manera automática al grupo «Usuario».",
|
||||||
@ -722,6 +726,7 @@
|
|||||||
"MessageBookshelfNoResultsForFilter": "El filtro «{0}: {1}» no produjo ningún resultado",
|
"MessageBookshelfNoResultsForFilter": "El filtro «{0}: {1}» no produjo ningún resultado",
|
||||||
"MessageBookshelfNoResultsForQuery": "No hay resultados para la consulta",
|
"MessageBookshelfNoResultsForQuery": "No hay resultados para la consulta",
|
||||||
"MessageBookshelfNoSeries": "No tiene ninguna serie",
|
"MessageBookshelfNoSeries": "No tiene ninguna serie",
|
||||||
|
"MessageBulkChapterPattern": "¿Cuántos capítulos desea añadir con este patrón de numeración?",
|
||||||
"MessageChapterEndIsAfter": "El final del capítulo es después del final de tu audiolibro",
|
"MessageChapterEndIsAfter": "El final del capítulo es después del final de tu audiolibro",
|
||||||
"MessageChapterErrorFirstNotZero": "El primer capítulo debe iniciar en 0",
|
"MessageChapterErrorFirstNotZero": "El primer capítulo debe iniciar en 0",
|
||||||
"MessageChapterErrorStartGteDuration": "El tiempo de inicio no es válido: debe ser inferior a la duración del audiolibro",
|
"MessageChapterErrorStartGteDuration": "El tiempo de inicio no es válido: debe ser inferior a la duración del audiolibro",
|
||||||
@ -919,6 +924,7 @@
|
|||||||
"NotificationOnBackupFailedDescription": "Se activa cuando falla una copia de seguridad",
|
"NotificationOnBackupFailedDescription": "Se activa cuando falla una copia de seguridad",
|
||||||
"NotificationOnEpisodeDownloadedDescription": "Se activa cuando se descarga automáticamente un episodio de un podcast",
|
"NotificationOnEpisodeDownloadedDescription": "Se activa cuando se descarga automáticamente un episodio de un podcast",
|
||||||
"NotificationOnTestDescription": "Evento para probar el sistema de notificaciones",
|
"NotificationOnTestDescription": "Evento para probar el sistema de notificaciones",
|
||||||
|
"PlaceholderBulkChapterInput": "Ingrese título de capítulo o use numeración (ej. 'Episodio 1', 'Capítulo 10', '1.')",
|
||||||
"PlaceholderNewCollection": "Nuevo nombre de la colección",
|
"PlaceholderNewCollection": "Nuevo nombre de la colección",
|
||||||
"PlaceholderNewFolderPath": "Nueva ruta de carpeta",
|
"PlaceholderNewFolderPath": "Nueva ruta de carpeta",
|
||||||
"PlaceholderNewPlaylist": "Nuevo nombre de la lista de reproducción",
|
"PlaceholderNewPlaylist": "Nuevo nombre de la lista de reproducción",
|
||||||
@ -972,8 +978,10 @@
|
|||||||
"ToastBookmarkCreateFailed": "No se pudo crear el marcador",
|
"ToastBookmarkCreateFailed": "No se pudo crear el marcador",
|
||||||
"ToastBookmarkCreateSuccess": "Marcador añadido",
|
"ToastBookmarkCreateSuccess": "Marcador añadido",
|
||||||
"ToastBookmarkRemoveSuccess": "Marcador eliminado",
|
"ToastBookmarkRemoveSuccess": "Marcador eliminado",
|
||||||
|
"ToastBulkChapterInvalidCount": "Por favor ingrese un número válido entre 1 y 150",
|
||||||
"ToastCachePurgeFailed": "No se pudo purgar la antememoria",
|
"ToastCachePurgeFailed": "No se pudo purgar la antememoria",
|
||||||
"ToastCachePurgeSuccess": "Se purgó la antememoria correctamente",
|
"ToastCachePurgeSuccess": "Se purgó la antememoria correctamente",
|
||||||
|
"ToastChaptersAllLocked": "Todos los capítulos están bloqueados. Desbloquee algunos capítulos para cambiar sus tiempos.",
|
||||||
"ToastChaptersHaveErrors": "Los capítulos tienen errores",
|
"ToastChaptersHaveErrors": "Los capítulos tienen errores",
|
||||||
"ToastChaptersInvalidShiftAmountLast": "Cantidad de desplazamiento no válida. La hora de inicio del último capítulo se extendería más allá de la duración de este audiolibro.",
|
"ToastChaptersInvalidShiftAmountLast": "Cantidad de desplazamiento no válida. La hora de inicio del último capítulo se extendería más allá de la duración de este audiolibro.",
|
||||||
"ToastChaptersInvalidShiftAmountStart": "Cantidad de desplazamiento no válida. El primer capítulo tendría una duración cero o negativa y lo sobrescribiría el segundo capítulo. Aumente la duración inicial del segundo capítulo.",
|
"ToastChaptersInvalidShiftAmountStart": "Cantidad de desplazamiento no válida. El primer capítulo tendría una duración cero o negativa y lo sobrescribiría el segundo capítulo. Aumente la duración inicial del segundo capítulo.",
|
||||||
@ -1103,5 +1111,12 @@
|
|||||||
"ToastUserPasswordChangeSuccess": "Contraseña modificada correctamente",
|
"ToastUserPasswordChangeSuccess": "Contraseña modificada correctamente",
|
||||||
"ToastUserPasswordMismatch": "No coinciden las contraseñas",
|
"ToastUserPasswordMismatch": "No coinciden las contraseñas",
|
||||||
"ToastUserPasswordMustChange": "La nueva contraseña no puede ser igual que la anterior",
|
"ToastUserPasswordMustChange": "La nueva contraseña no puede ser igual que la anterior",
|
||||||
"ToastUserRootRequireName": "Debe introducir un nombre de usuario administrativo"
|
"ToastUserRootRequireName": "Debe introducir un nombre de usuario administrativo",
|
||||||
|
"TooltipAddChapters": "Añadir capítulo(s)",
|
||||||
|
"TooltipAddOneSecond": "Añadir 1 segundo",
|
||||||
|
"TooltipLockAllChapters": "Bloquear todos los capítulos",
|
||||||
|
"TooltipLockChapter": "Bloquear capítulo (Mayús+clic para rango)",
|
||||||
|
"TooltipSubtractOneSecond": "Restar 1 segundo",
|
||||||
|
"TooltipUnlockAllChapters": "Desbloquear todos los capítulos",
|
||||||
|
"TooltipUnlockChapter": "Desbloquear capítulo (Mayús+clic para rango)"
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"ButtonAuthors": "Autorid",
|
"ButtonAuthors": "Autorid",
|
||||||
"ButtonBack": "Tagasi",
|
"ButtonBack": "Tagasi",
|
||||||
"ButtonBrowseForFolder": "Sirvi kausta",
|
"ButtonBrowseForFolder": "Sirvi kausta",
|
||||||
"ButtonCancel": "Tühista",
|
"ButtonCancel": "Katkesta",
|
||||||
"ButtonCancelEncode": "Tühista kodeerimine",
|
"ButtonCancelEncode": "Tühista kodeerimine",
|
||||||
"ButtonChangeRootPassword": "Muuda põhiparooli",
|
"ButtonChangeRootPassword": "Muuda põhiparooli",
|
||||||
"ButtonCheckAndDownloadNewEpisodes": "Kontrolli ja laadi alla uued episoodid",
|
"ButtonCheckAndDownloadNewEpisodes": "Kontrolli ja laadi alla uued episoodid",
|
||||||
@ -20,9 +20,9 @@
|
|||||||
"ButtonClearFilter": "Tühista filter",
|
"ButtonClearFilter": "Tühista filter",
|
||||||
"ButtonCloseFeed": "Sulge voog",
|
"ButtonCloseFeed": "Sulge voog",
|
||||||
"ButtonCloseSession": "Sulge avatud sessioon",
|
"ButtonCloseSession": "Sulge avatud sessioon",
|
||||||
"ButtonCollections": "Kogud",
|
"ButtonCollections": "Kollektsioonid",
|
||||||
"ButtonConfigureScanner": "Konfigureeri skanner",
|
"ButtonConfigureScanner": "Konfigureeri skanner",
|
||||||
"ButtonCreate": "Loo",
|
"ButtonCreate": "Loo uus",
|
||||||
"ButtonCreateBackup": "Loo varundus",
|
"ButtonCreateBackup": "Loo varundus",
|
||||||
"ButtonDelete": "Kustuta",
|
"ButtonDelete": "Kustuta",
|
||||||
"ButtonDownloadQueue": "Järjekord",
|
"ButtonDownloadQueue": "Järjekord",
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"ButtonIssues": "Probleemid",
|
"ButtonIssues": "Probleemid",
|
||||||
"ButtonJumpBackward": "Hüppa tagasi",
|
"ButtonJumpBackward": "Hüppa tagasi",
|
||||||
"ButtonJumpForward": "Hüppa edasi",
|
"ButtonJumpForward": "Hüppa edasi",
|
||||||
"ButtonLatest": "Uusim",
|
"ButtonLatest": "Viimased",
|
||||||
"ButtonLibrary": "Raamatukogu",
|
"ButtonLibrary": "Raamatukogu",
|
||||||
"ButtonLogout": "Logi välja",
|
"ButtonLogout": "Logi välja",
|
||||||
"ButtonLookup": "Otsi",
|
"ButtonLookup": "Otsi",
|
||||||
@ -52,11 +52,11 @@
|
|||||||
"ButtonOk": "Ok",
|
"ButtonOk": "Ok",
|
||||||
"ButtonOpenFeed": "Ava voog",
|
"ButtonOpenFeed": "Ava voog",
|
||||||
"ButtonOpenManager": "Ava haldur",
|
"ButtonOpenManager": "Ava haldur",
|
||||||
"ButtonPause": "Peata",
|
"ButtonPause": "Paus",
|
||||||
"ButtonPlay": "Mängi",
|
"ButtonPlay": "Play",
|
||||||
"ButtonPlayAll": "Mängi kõik",
|
"ButtonPlayAll": "Mängi kõik",
|
||||||
"ButtonPlaying": "Mängib",
|
"ButtonPlaying": "Mängib",
|
||||||
"ButtonPlaylists": "Esitusloendid",
|
"ButtonPlaylists": "Playlist",
|
||||||
"ButtonPrevious": "Eelmine",
|
"ButtonPrevious": "Eelmine",
|
||||||
"ButtonPreviousChapter": "Eelmine peatükk",
|
"ButtonPreviousChapter": "Eelmine peatükk",
|
||||||
"ButtonPurgeAllCache": "Tühjenda kogu vahemälu",
|
"ButtonPurgeAllCache": "Tühjenda kogu vahemälu",
|
||||||
@ -69,7 +69,7 @@
|
|||||||
"ButtonReadLess": "Loe vähem",
|
"ButtonReadLess": "Loe vähem",
|
||||||
"ButtonReadMore": "Loe rohkem",
|
"ButtonReadMore": "Loe rohkem",
|
||||||
"ButtonRefresh": "Värskenda",
|
"ButtonRefresh": "Värskenda",
|
||||||
"ButtonRemove": "Eemalda",
|
"ButtonRemove": "Kustuta",
|
||||||
"ButtonRemoveAll": "Eemalda kõik",
|
"ButtonRemoveAll": "Eemalda kõik",
|
||||||
"ButtonRemoveAllLibraryItems": "Eemalda kõik raamatukogu esemed",
|
"ButtonRemoveAllLibraryItems": "Eemalda kõik raamatukogu esemed",
|
||||||
"ButtonRemoveFromContinueListening": "Eemalda jätkake kuulamisest",
|
"ButtonRemoveFromContinueListening": "Eemalda jätkake kuulamisest",
|
||||||
@ -120,12 +120,12 @@
|
|||||||
"HeaderCustomMetadataProviders": "Kohandatud metaandmete pakkujad",
|
"HeaderCustomMetadataProviders": "Kohandatud metaandmete pakkujad",
|
||||||
"HeaderDetails": "Detailid",
|
"HeaderDetails": "Detailid",
|
||||||
"HeaderDownloadQueue": "Allalaadimise järjekord",
|
"HeaderDownloadQueue": "Allalaadimise järjekord",
|
||||||
"HeaderEbookFiles": "E-raamatute failid",
|
"HeaderEbookFiles": "E-raamatu failid",
|
||||||
"HeaderEmail": "E-post",
|
"HeaderEmail": "E-post",
|
||||||
"HeaderEmailSettings": "E-posti seaded",
|
"HeaderEmailSettings": "E-posti seaded",
|
||||||
"HeaderEpisodes": "Episoodid",
|
"HeaderEpisodes": "Episoodid",
|
||||||
"HeaderEreaderDevices": "E-lugerite seadmed",
|
"HeaderEreaderDevices": "E-lugerite seadmed",
|
||||||
"HeaderEreaderSettings": "E-lugerite seadistused",
|
"HeaderEreaderSettings": "E-lugeja sätted",
|
||||||
"HeaderFiles": "Failid",
|
"HeaderFiles": "Failid",
|
||||||
"HeaderFindChapters": "Leia peatükid",
|
"HeaderFindChapters": "Leia peatükid",
|
||||||
"HeaderIgnoredFiles": "Ignoreeritud failid",
|
"HeaderIgnoredFiles": "Ignoreeritud failid",
|
||||||
@ -155,8 +155,8 @@
|
|||||||
"HeaderPasswordAuthentication": "Parooli autentimine",
|
"HeaderPasswordAuthentication": "Parooli autentimine",
|
||||||
"HeaderPermissions": "Õigused",
|
"HeaderPermissions": "Õigused",
|
||||||
"HeaderPlayerQueue": "Mängija järjekord",
|
"HeaderPlayerQueue": "Mängija järjekord",
|
||||||
"HeaderPlaylist": "Mänguloend",
|
"HeaderPlaylist": "Playlist",
|
||||||
"HeaderPlaylistItems": "Mänguloendi esemed",
|
"HeaderPlaylistItems": "Playlisti esemed",
|
||||||
"HeaderPodcastsToAdd": "Lisatavad podcastid",
|
"HeaderPodcastsToAdd": "Lisatavad podcastid",
|
||||||
"HeaderPreviewCover": "Eelvaate kaas",
|
"HeaderPreviewCover": "Eelvaate kaas",
|
||||||
"HeaderRSSFeedGeneral": "RSS-i üksikasjad",
|
"HeaderRSSFeedGeneral": "RSS-i üksikasjad",
|
||||||
@ -174,7 +174,7 @@
|
|||||||
"HeaderSettingsExperimental": "Katsetusfunktsioonid",
|
"HeaderSettingsExperimental": "Katsetusfunktsioonid",
|
||||||
"HeaderSettingsGeneral": "Üldised",
|
"HeaderSettingsGeneral": "Üldised",
|
||||||
"HeaderSettingsScanner": "Skanner",
|
"HeaderSettingsScanner": "Skanner",
|
||||||
"HeaderSleepTimer": "Uinaku taimer",
|
"HeaderSleepTimer": "Unetaimer",
|
||||||
"HeaderStatsLargestItems": "Suurimad esemed",
|
"HeaderStatsLargestItems": "Suurimad esemed",
|
||||||
"HeaderStatsLongestItems": "Kõige pikemad esemed (tunnid)",
|
"HeaderStatsLongestItems": "Kõige pikemad esemed (tunnid)",
|
||||||
"HeaderStatsMinutesListeningChart": "Kuulamise minutid (viimased 7 päeva)",
|
"HeaderStatsMinutesListeningChart": "Kuulamise minutid (viimased 7 päeva)",
|
||||||
@ -197,9 +197,10 @@
|
|||||||
"LabelActivity": "Tegevus",
|
"LabelActivity": "Tegevus",
|
||||||
"LabelAddToCollection": "Lisa kogusse",
|
"LabelAddToCollection": "Lisa kogusse",
|
||||||
"LabelAddToCollectionBatch": "Lisa {0} raamatut kogusse",
|
"LabelAddToCollectionBatch": "Lisa {0} raamatut kogusse",
|
||||||
"LabelAddToPlaylist": "Lisa mänguloendisse",
|
"LabelAddToPlaylist": "Lisa playlisti",
|
||||||
"LabelAddToPlaylistBatch": "Lisa {0} eset mänguloendisse",
|
"LabelAddToPlaylistBatch": "Lisa {0} eset mänguloendisse",
|
||||||
"LabelAddedAt": "Lisatud",
|
"LabelAddedAt": "Lisatud",
|
||||||
|
"LabelAddedDate": "Lisatud {0}",
|
||||||
"LabelAdminUsersOnly": "Ainult administraatorid",
|
"LabelAdminUsersOnly": "Ainult administraatorid",
|
||||||
"LabelAll": "Kõik",
|
"LabelAll": "Kõik",
|
||||||
"LabelAllUsers": "Kõik kasutajad",
|
"LabelAllUsers": "Kõik kasutajad",
|
||||||
@ -208,10 +209,10 @@
|
|||||||
"LabelAlreadyInYourLibrary": "Juba teie raamatukogus",
|
"LabelAlreadyInYourLibrary": "Juba teie raamatukogus",
|
||||||
"LabelAppend": "Lisa",
|
"LabelAppend": "Lisa",
|
||||||
"LabelAuthor": "Autor",
|
"LabelAuthor": "Autor",
|
||||||
"LabelAuthorFirstLast": "Autor (Eesnimi Perekonnanimi)",
|
"LabelAuthorFirstLast": "Autor (eesnimi perekonnanimi)",
|
||||||
"LabelAuthorLastFirst": "Autor (Perekonnanimi, Eesnimi)",
|
"LabelAuthorLastFirst": "Autor (perekonnanimi, eesnimi)",
|
||||||
"LabelAuthors": "Autorid",
|
"LabelAuthors": "Autorid",
|
||||||
"LabelAutoDownloadEpisodes": "Automaatne episoodide allalaadimine",
|
"LabelAutoDownloadEpisodes": "Episoodide automaatne allalaadimine",
|
||||||
"LabelAutoFetchMetadata": "Automaatne metaandmete hankimine",
|
"LabelAutoFetchMetadata": "Automaatne metaandmete hankimine",
|
||||||
"LabelAutoFetchMetadataHelp": "Toob tiitli, autori ja seeria metaandmed üleslaadimise hõlbustamiseks. Lisametaandmed võivad pärast üleslaadimist vajada vastavust.",
|
"LabelAutoFetchMetadataHelp": "Toob tiitli, autori ja seeria metaandmed üleslaadimise hõlbustamiseks. Lisametaandmed võivad pärast üleslaadimist vajada vastavust.",
|
||||||
"LabelAutoLaunch": "Automaatne käivitamine",
|
"LabelAutoLaunch": "Automaatne käivitamine",
|
||||||
@ -265,7 +266,7 @@
|
|||||||
"LabelDiscover": "Avasta",
|
"LabelDiscover": "Avasta",
|
||||||
"LabelDownload": "Lae alla",
|
"LabelDownload": "Lae alla",
|
||||||
"LabelDownloadNEpisodes": "Lae alla {0} episoodi",
|
"LabelDownloadNEpisodes": "Lae alla {0} episoodi",
|
||||||
"LabelDuration": "Kestus",
|
"LabelDuration": "Kestvus",
|
||||||
"LabelDurationFound": "Leitud kestus:",
|
"LabelDurationFound": "Leitud kestus:",
|
||||||
"LabelEbook": "E-raamat",
|
"LabelEbook": "E-raamat",
|
||||||
"LabelEbooks": "E-raamatud",
|
"LabelEbooks": "E-raamatud",
|
||||||
@ -278,6 +279,7 @@
|
|||||||
"LabelEmbeddedCover": "Manustatud kaas",
|
"LabelEmbeddedCover": "Manustatud kaas",
|
||||||
"LabelEnable": "Luba",
|
"LabelEnable": "Luba",
|
||||||
"LabelEnd": "Lõpp",
|
"LabelEnd": "Lõpp",
|
||||||
|
"LabelEndOfChapter": "Peatükki lõpp",
|
||||||
"LabelEpisode": "Episood",
|
"LabelEpisode": "Episood",
|
||||||
"LabelEpisodeTitle": "Episoodi pealkiri",
|
"LabelEpisodeTitle": "Episoodi pealkiri",
|
||||||
"LabelEpisodeType": "Episoodi tüüp",
|
"LabelEpisodeType": "Episoodi tüüp",
|
||||||
@ -288,13 +290,14 @@
|
|||||||
"LabelFile": "Fail",
|
"LabelFile": "Fail",
|
||||||
"LabelFileBirthtime": "Faili sünniaeg",
|
"LabelFileBirthtime": "Faili sünniaeg",
|
||||||
"LabelFileModified": "Faili muudetud",
|
"LabelFileModified": "Faili muudetud",
|
||||||
"LabelFilename": "Failinimi",
|
"LabelFilename": "Faili nimi",
|
||||||
"LabelFilterByUser": "Filtri alusel kasutaja järgi",
|
"LabelFilterByUser": "Filtri alusel kasutaja järgi",
|
||||||
"LabelFindEpisodes": "Otsi episoodid",
|
"LabelFindEpisodes": "Otsi episoodid",
|
||||||
"LabelFinished": "Lõpetatud",
|
"LabelFinished": "Lõpetatud",
|
||||||
"LabelFolder": "Kaust",
|
"LabelFolder": "Kaust",
|
||||||
"LabelFolders": "Kataloogid",
|
"LabelFolders": "Kataloogid",
|
||||||
"LabelFontBold": "Paks",
|
"LabelFontBold": "Paks",
|
||||||
|
"LabelFontBoldness": "Fondi paksus",
|
||||||
"LabelFontFamily": "Fondi pere",
|
"LabelFontFamily": "Fondi pere",
|
||||||
"LabelFontItalic": "Kaldkiri",
|
"LabelFontItalic": "Kaldkiri",
|
||||||
"LabelFontScale": "Fondi suurus",
|
"LabelFontScale": "Fondi suurus",
|
||||||
@ -303,7 +306,7 @@
|
|||||||
"LabelGenre": "Žanr",
|
"LabelGenre": "Žanr",
|
||||||
"LabelGenres": "Žanrid",
|
"LabelGenres": "Žanrid",
|
||||||
"LabelHardDeleteFile": "Faili lõplik kustutamine",
|
"LabelHardDeleteFile": "Faili lõplik kustutamine",
|
||||||
"LabelHasEbook": "On e-raamat",
|
"LabelHasEbook": "E-raamat olemas",
|
||||||
"LabelHasSupplementaryEbook": "On täiendav e-raamat",
|
"LabelHasSupplementaryEbook": "On täiendav e-raamat",
|
||||||
"LabelHighestPriority": "Kõrgeim prioriteet",
|
"LabelHighestPriority": "Kõrgeim prioriteet",
|
||||||
"LabelHour": "Tund",
|
"LabelHour": "Tund",
|
||||||
@ -311,7 +314,7 @@
|
|||||||
"LabelImageURLFromTheWeb": "Pildi URL veebist",
|
"LabelImageURLFromTheWeb": "Pildi URL veebist",
|
||||||
"LabelInProgress": "Pooleli",
|
"LabelInProgress": "Pooleli",
|
||||||
"LabelIncludeInTracklist": "Kaasa jälgimisloendis",
|
"LabelIncludeInTracklist": "Kaasa jälgimisloendis",
|
||||||
"LabelIncomplete": "Puudulik",
|
"LabelIncomplete": "Lõpetamata",
|
||||||
"LabelInterval": "Intervall",
|
"LabelInterval": "Intervall",
|
||||||
"LabelIntervalCustomDailyWeekly": "Kohandatud päevane/nädalane",
|
"LabelIntervalCustomDailyWeekly": "Kohandatud päevane/nädalane",
|
||||||
"LabelIntervalEvery12Hours": "Iga 12 tunni tagant",
|
"LabelIntervalEvery12Hours": "Iga 12 tunni tagant",
|
||||||
@ -365,12 +368,12 @@
|
|||||||
"LabelNarrators": "Jutustajad",
|
"LabelNarrators": "Jutustajad",
|
||||||
"LabelNew": "Uus",
|
"LabelNew": "Uus",
|
||||||
"LabelNewPassword": "Uus parool",
|
"LabelNewPassword": "Uus parool",
|
||||||
"LabelNewestAuthors": "Uusimad autorid",
|
"LabelNewestAuthors": "Uuemad autorid",
|
||||||
"LabelNewestEpisodes": "Uusimad episoodid",
|
"LabelNewestEpisodes": "Uuemad episoodid",
|
||||||
"LabelNextBackupDate": "Järgmine varukoopia kuupäev",
|
"LabelNextBackupDate": "Järgmine varukoopia kuupäev",
|
||||||
"LabelNextScheduledRun": "Järgmine ajakava järgmine",
|
"LabelNextScheduledRun": "Järgmine ajakava järgmine",
|
||||||
"LabelNoEpisodesSelected": "Episoodid pole valitud",
|
"LabelNoEpisodesSelected": "Episoodid pole valitud",
|
||||||
"LabelNotFinished": "Ei ole lõpetatud",
|
"LabelNotFinished": "Lõpetamata",
|
||||||
"LabelNotStarted": "Pole alustatud",
|
"LabelNotStarted": "Pole alustatud",
|
||||||
"LabelNotes": "Märkused",
|
"LabelNotes": "Märkused",
|
||||||
"LabelNotificationAppriseURL": "Apprise URL-id",
|
"LabelNotificationAppriseURL": "Apprise URL-id",
|
||||||
@ -383,7 +386,7 @@
|
|||||||
"LabelNotificationsMaxQueueSize": "Teavituste sündmuste maksimaalne järjekorra suurus",
|
"LabelNotificationsMaxQueueSize": "Teavituste sündmuste maksimaalne järjekorra suurus",
|
||||||
"LabelNotificationsMaxQueueSizeHelp": "Sündmused on piiratud 1 sekundiga. Sündmusi ignoreeritakse, kui järjekord on maksimumsuuruses. See takistab teavituste rämpsposti.",
|
"LabelNotificationsMaxQueueSizeHelp": "Sündmused on piiratud 1 sekundiga. Sündmusi ignoreeritakse, kui järjekord on maksimumsuuruses. See takistab teavituste rämpsposti.",
|
||||||
"LabelNumberOfBooks": "Raamatute arv",
|
"LabelNumberOfBooks": "Raamatute arv",
|
||||||
"LabelNumberOfEpisodes": "Episoodide arv",
|
"LabelNumberOfEpisodes": "# episoode",
|
||||||
"LabelOpenRSSFeed": "Ava RSS voog",
|
"LabelOpenRSSFeed": "Ava RSS voog",
|
||||||
"LabelOverwrite": "Kirjuta üle",
|
"LabelOverwrite": "Kirjuta üle",
|
||||||
"LabelPassword": "Parool",
|
"LabelPassword": "Parool",
|
||||||
@ -398,16 +401,18 @@
|
|||||||
"LabelPhotoPathURL": "Foto tee/URL",
|
"LabelPhotoPathURL": "Foto tee/URL",
|
||||||
"LabelPlayMethod": "Esitusmeetod",
|
"LabelPlayMethod": "Esitusmeetod",
|
||||||
"LabelPlaylists": "Mänguloendid",
|
"LabelPlaylists": "Mänguloendid",
|
||||||
|
"LabelPodcast": "Podcast",
|
||||||
"LabelPodcastSearchRegion": "Podcasti otsingu piirkond",
|
"LabelPodcastSearchRegion": "Podcasti otsingu piirkond",
|
||||||
"LabelPodcastType": "Podcasti tüüp",
|
"LabelPodcastType": "Podcasti tüüp",
|
||||||
"LabelPodcasts": "Podcastid",
|
"LabelPodcasts": "Podcastid",
|
||||||
"LabelPrefixesToIgnore": "Eiramiseks eesliited (tõstutundetu)",
|
"LabelPrefixesToIgnore": "Eiramiseks eesliited (tõstutundetu)",
|
||||||
"LabelPreventIndexing": "Vältige oma voogu indekseerimist iTunes'i ja Google podcasti kataloogides",
|
"LabelPreventIndexing": "Vältige oma voogu indekseerimist iTunes'i ja Google podcasti kataloogides",
|
||||||
"LabelPrimaryEbook": "Esmane e-raamat",
|
"LabelPrimaryEbook": "Esmane e-raamat",
|
||||||
"LabelProgress": "Edenemine",
|
"LabelProgress": "Progress",
|
||||||
"LabelProvider": "Pakkuja",
|
"LabelProvider": "Pakkuja",
|
||||||
"LabelPubDate": "Avaldamise kuupäev",
|
"LabelPubDate": "Publitseerimise kuupäev",
|
||||||
"LabelPublishYear": "Aasta avaldamine",
|
"LabelPublishYear": "Publitseerimise aasta",
|
||||||
|
"LabelPublishedDate": "Publitseeritud {0}",
|
||||||
"LabelPublisher": "Kirjastaja",
|
"LabelPublisher": "Kirjastaja",
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Kohandatud omaniku e-post",
|
"LabelRSSFeedCustomOwnerEmail": "Kohandatud omaniku e-post",
|
||||||
"LabelRSSFeedCustomOwnerName": "Kohandatud omaniku nimi",
|
"LabelRSSFeedCustomOwnerName": "Kohandatud omaniku nimi",
|
||||||
@ -415,7 +420,8 @@
|
|||||||
"LabelRSSFeedPreventIndexing": "Vältige indekseerimist",
|
"LabelRSSFeedPreventIndexing": "Vältige indekseerimist",
|
||||||
"LabelRSSFeedSlug": "RSS voog Slug",
|
"LabelRSSFeedSlug": "RSS voog Slug",
|
||||||
"LabelRSSFeedURL": "RSS voog URL",
|
"LabelRSSFeedURL": "RSS voog URL",
|
||||||
"LabelRead": "Lugenud",
|
"LabelRandomly": "Juhuslikus järjekorras",
|
||||||
|
"LabelRead": "Loetud läbi",
|
||||||
"LabelReadAgain": "Loe uuesti",
|
"LabelReadAgain": "Loe uuesti",
|
||||||
"LabelReadEbookWithoutProgress": "Lugege e-raamatut ilma edenemist säilitamata",
|
"LabelReadEbookWithoutProgress": "Lugege e-raamatut ilma edenemist säilitamata",
|
||||||
"LabelRecentSeries": "Hiljutised seeriad",
|
"LabelRecentSeries": "Hiljutised seeriad",
|
||||||
@ -469,9 +475,9 @@
|
|||||||
"LabelSettingsStoreMetadataWithItem": "Salvesta metaandmed üksusega",
|
"LabelSettingsStoreMetadataWithItem": "Salvesta metaandmed üksusega",
|
||||||
"LabelSettingsStoreMetadataWithItemHelp": "Vaikimisi salvestatakse metaandmed /metadata/items kausta. Selle seadistuse lubamine salvestab metaandmed teie raamatukogu üksuse kaustadesse",
|
"LabelSettingsStoreMetadataWithItemHelp": "Vaikimisi salvestatakse metaandmed /metadata/items kausta. Selle seadistuse lubamine salvestab metaandmed teie raamatukogu üksuse kaustadesse",
|
||||||
"LabelSettingsTimeFormat": "Kellaaja vorming",
|
"LabelSettingsTimeFormat": "Kellaaja vorming",
|
||||||
"LabelShowAll": "Näita kõiki",
|
"LabelShowAll": "Näita kõik",
|
||||||
"LabelSize": "Suurus",
|
"LabelSize": "Suurus",
|
||||||
"LabelSleepTimer": "Uinaku taimer",
|
"LabelSleepTimer": "Unetaimer",
|
||||||
"LabelStart": "Alusta",
|
"LabelStart": "Alusta",
|
||||||
"LabelStartTime": "Alustamise aeg",
|
"LabelStartTime": "Alustamise aeg",
|
||||||
"LabelStarted": "Alustatud",
|
"LabelStarted": "Alustatud",
|
||||||
@ -480,13 +486,13 @@
|
|||||||
"LabelStatsAuthors": "Autorid",
|
"LabelStatsAuthors": "Autorid",
|
||||||
"LabelStatsBestDay": "Parim päev",
|
"LabelStatsBestDay": "Parim päev",
|
||||||
"LabelStatsDailyAverage": "Päevane keskmine",
|
"LabelStatsDailyAverage": "Päevane keskmine",
|
||||||
"LabelStatsDays": "Päevad",
|
"LabelStatsDays": "Päevi",
|
||||||
"LabelStatsDaysListened": "Kuulatud päevad",
|
"LabelStatsDaysListened": "Kuulatud päevad",
|
||||||
"LabelStatsHours": "Tunnid",
|
"LabelStatsHours": "Tunnid",
|
||||||
"LabelStatsInARow": "järjest",
|
"LabelStatsInARow": "järjest",
|
||||||
"LabelStatsItemsFinished": "Lõpetatud üksused",
|
"LabelStatsItemsFinished": "Lõpetatud üksused",
|
||||||
"LabelStatsItemsInLibrary": "Üksused raamatukogus",
|
"LabelStatsItemsInLibrary": "Üksused raamatukogus",
|
||||||
"LabelStatsMinutes": "minutit",
|
"LabelStatsMinutes": "minuteid",
|
||||||
"LabelStatsMinutesListening": "Kuulamise minutid",
|
"LabelStatsMinutesListening": "Kuulamise minutid",
|
||||||
"LabelStatsOverallDays": "Kokku päevad",
|
"LabelStatsOverallDays": "Kokku päevad",
|
||||||
"LabelStatsOverallHours": "Kokku tunnid",
|
"LabelStatsOverallHours": "Kokku tunnid",
|
||||||
@ -502,7 +508,7 @@
|
|||||||
"LabelTextEditorNumberedList": "Numberloend",
|
"LabelTextEditorNumberedList": "Numberloend",
|
||||||
"LabelTextEditorUnlink": "Eemalda link",
|
"LabelTextEditorUnlink": "Eemalda link",
|
||||||
"LabelTheme": "Teema",
|
"LabelTheme": "Teema",
|
||||||
"LabelThemeDark": "Tume",
|
"LabelThemeDark": "Pime",
|
||||||
"LabelThemeLight": "Hele",
|
"LabelThemeLight": "Hele",
|
||||||
"LabelTimeBase": "Aja alus",
|
"LabelTimeBase": "Aja alus",
|
||||||
"LabelTimeListened": "Kuulatud aeg",
|
"LabelTimeListened": "Kuulatud aeg",
|
||||||
@ -527,7 +533,7 @@
|
|||||||
"LabelType": "Tüüp",
|
"LabelType": "Tüüp",
|
||||||
"LabelUnabridged": "Täismahus",
|
"LabelUnabridged": "Täismahus",
|
||||||
"LabelUndo": "Võta tagasi",
|
"LabelUndo": "Võta tagasi",
|
||||||
"LabelUnknown": "Tundmatu",
|
"LabelUnknown": "Teadmata",
|
||||||
"LabelUpdateCover": "Uuenda kaant",
|
"LabelUpdateCover": "Uuenda kaant",
|
||||||
"LabelUpdateCoverHelp": "Luba üle kirjutamine olemasolevate kaante jaoks valitud raamatutele, kui leitakse sobivus",
|
"LabelUpdateCoverHelp": "Luba üle kirjutamine olemasolevate kaante jaoks valitud raamatutele, kui leitakse sobivus",
|
||||||
"LabelUpdateDetails": "Uuenda üksikasju",
|
"LabelUpdateDetails": "Uuenda üksikasju",
|
||||||
|
@ -127,6 +127,7 @@
|
|||||||
"HeaderAudiobookTools": "Outils de gestion de fichiers de livres audio",
|
"HeaderAudiobookTools": "Outils de gestion de fichiers de livres audio",
|
||||||
"HeaderAuthentication": "Authentification",
|
"HeaderAuthentication": "Authentification",
|
||||||
"HeaderBackups": "Sauvegardes",
|
"HeaderBackups": "Sauvegardes",
|
||||||
|
"HeaderBulkChapterModal": "Ajouter Plusieurs Chapitres",
|
||||||
"HeaderChangePassword": "Modifier le mot de passe",
|
"HeaderChangePassword": "Modifier le mot de passe",
|
||||||
"HeaderChapters": "Chapitres",
|
"HeaderChapters": "Chapitres",
|
||||||
"HeaderChooseAFolder": "Sélectionner un dossier",
|
"HeaderChooseAFolder": "Sélectionner un dossier",
|
||||||
@ -199,6 +200,7 @@
|
|||||||
"HeaderSettingsExperimental": "Fonctionnalités expérimentales",
|
"HeaderSettingsExperimental": "Fonctionnalités expérimentales",
|
||||||
"HeaderSettingsGeneral": "Général",
|
"HeaderSettingsGeneral": "Général",
|
||||||
"HeaderSettingsScanner": "Analyseur",
|
"HeaderSettingsScanner": "Analyseur",
|
||||||
|
"HeaderSettingsSecurity": "Sécurité",
|
||||||
"HeaderSettingsWebClient": "Client Web",
|
"HeaderSettingsWebClient": "Client Web",
|
||||||
"HeaderSleepTimer": "Minuterie",
|
"HeaderSleepTimer": "Minuterie",
|
||||||
"HeaderStatsLargestItems": "Éléments les plus grands",
|
"HeaderStatsLargestItems": "Éléments les plus grands",
|
||||||
@ -206,7 +208,7 @@
|
|||||||
"HeaderStatsMinutesListeningChart": "Minutes d’écoute (7 derniers jours)",
|
"HeaderStatsMinutesListeningChart": "Minutes d’écoute (7 derniers jours)",
|
||||||
"HeaderStatsRecentSessions": "Sessions récentes",
|
"HeaderStatsRecentSessions": "Sessions récentes",
|
||||||
"HeaderStatsTop10Authors": "Top 10 Auteurs",
|
"HeaderStatsTop10Authors": "Top 10 Auteurs",
|
||||||
"HeaderStatsTop5Genres": "Top 5 Genres",
|
"HeaderStatsTop5Genres": "Top 5 des genres",
|
||||||
"HeaderTableOfContents": "Table des matières",
|
"HeaderTableOfContents": "Table des matières",
|
||||||
"HeaderTools": "Outils",
|
"HeaderTools": "Outils",
|
||||||
"HeaderUpdateAccount": "Mettre à jour le compte",
|
"HeaderUpdateAccount": "Mettre à jour le compte",
|
||||||
@ -293,6 +295,7 @@
|
|||||||
"LabelContinueListening": "Continuer l'écoute",
|
"LabelContinueListening": "Continuer l'écoute",
|
||||||
"LabelContinueReading": "Continuer la lecture",
|
"LabelContinueReading": "Continuer la lecture",
|
||||||
"LabelContinueSeries": "Continuer les séries",
|
"LabelContinueSeries": "Continuer les séries",
|
||||||
|
"LabelCorsAllowed": "Origines autorisées pour les requêtes CORS",
|
||||||
"LabelCover": "Couverture",
|
"LabelCover": "Couverture",
|
||||||
"LabelCoverImageURL": "URL vers l’image de couverture",
|
"LabelCoverImageURL": "URL vers l’image de couverture",
|
||||||
"LabelCoverProvider": "Source des couvertures",
|
"LabelCoverProvider": "Source des couvertures",
|
||||||
@ -306,6 +309,7 @@
|
|||||||
"LabelDeleteFromFileSystemCheckbox": "Supprimer du système de fichiers (décocher pour ne supprimer que de la base de données)",
|
"LabelDeleteFromFileSystemCheckbox": "Supprimer du système de fichiers (décocher pour ne supprimer que de la base de données)",
|
||||||
"LabelDescription": "Description",
|
"LabelDescription": "Description",
|
||||||
"LabelDeselectAll": "Tout déselectionner",
|
"LabelDeselectAll": "Tout déselectionner",
|
||||||
|
"LabelDetectedPattern": "Motif détecté :",
|
||||||
"LabelDevice": "Appareil",
|
"LabelDevice": "Appareil",
|
||||||
"LabelDeviceInfo": "Détail de l’appareil",
|
"LabelDeviceInfo": "Détail de l’appareil",
|
||||||
"LabelDeviceIsAvailableTo": "L’appareil est disponible pour…",
|
"LabelDeviceIsAvailableTo": "L’appareil est disponible pour…",
|
||||||
@ -359,7 +363,7 @@
|
|||||||
"LabelExpiresAt": "Expire à",
|
"LabelExpiresAt": "Expire à",
|
||||||
"LabelExpiresInSeconds": "Expire dans (secondes)",
|
"LabelExpiresInSeconds": "Expire dans (secondes)",
|
||||||
"LabelExpiresNever": "Jamais",
|
"LabelExpiresNever": "Jamais",
|
||||||
"LabelExplicit": "Restriction",
|
"LabelExplicit": "Contenu explicite",
|
||||||
"LabelExplicitChecked": "Explicite (vérifié)",
|
"LabelExplicitChecked": "Explicite (vérifié)",
|
||||||
"LabelExplicitUnchecked": "Non explicite (non vérifié)",
|
"LabelExplicitUnchecked": "Non explicite (non vérifié)",
|
||||||
"LabelExportOPML": "Exporter OPML",
|
"LabelExportOPML": "Exporter OPML",
|
||||||
@ -418,6 +422,7 @@
|
|||||||
"LabelLanguages": "Langues",
|
"LabelLanguages": "Langues",
|
||||||
"LabelLastBookAdded": "Dernier livre ajouté",
|
"LabelLastBookAdded": "Dernier livre ajouté",
|
||||||
"LabelLastBookUpdated": "Dernier livre mis à jour",
|
"LabelLastBookUpdated": "Dernier livre mis à jour",
|
||||||
|
"LabelLastProgressDate": "Dernière position : {0}",
|
||||||
"LabelLastSeen": "Vu dernièrement",
|
"LabelLastSeen": "Vu dernièrement",
|
||||||
"LabelLastTime": "Progression",
|
"LabelLastTime": "Progression",
|
||||||
"LabelLastUpdate": "Dernière mise à jour",
|
"LabelLastUpdate": "Dernière mise à jour",
|
||||||
@ -430,14 +435,16 @@
|
|||||||
"LabelLibraryFilterSublistEmpty": "Aucun {0}",
|
"LabelLibraryFilterSublistEmpty": "Aucun {0}",
|
||||||
"LabelLibraryItem": "Élément de bibliothèque",
|
"LabelLibraryItem": "Élément de bibliothèque",
|
||||||
"LabelLibraryName": "Nom de la bibliothèque",
|
"LabelLibraryName": "Nom de la bibliothèque",
|
||||||
|
"LabelLibrarySortByProgress": "Progression mise à jour",
|
||||||
"LabelLimit": "Limite",
|
"LabelLimit": "Limite",
|
||||||
"LabelLineSpacing": "Espacement des lignes",
|
"LabelLineSpacing": "Espacement des lignes",
|
||||||
"LabelListenAgain": "Écouter à nouveau",
|
"LabelListenAgain": "Écouter à nouveau",
|
||||||
"LabelLogLevelDebug": "Débogage",
|
"LabelLogLevelDebug": "Débogage",
|
||||||
"LabelLogLevelInfo": "Info",
|
"LabelLogLevelInfo": "Info",
|
||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Attention",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Rechercher les nouveaux épisodes après cette date",
|
"LabelLookForNewEpisodesAfterDate": "Rechercher les nouveaux épisodes après cette date",
|
||||||
"LabelLowestPriority": "Priorité la plus basse",
|
"LabelLowestPriority": "Priorité la plus basse",
|
||||||
|
"LabelMatchConfidence": "Confiance",
|
||||||
"LabelMatchExistingUsersBy": "Correspondance avec les utilisateurs existants",
|
"LabelMatchExistingUsersBy": "Correspondance avec les utilisateurs existants",
|
||||||
"LabelMatchExistingUsersByDescription": "Utilisé pour connecter les utilisateurs existants. Une fois connectés, les utilisateurs seront associés à un identifiant unique provenant de votre fournisseur SSO",
|
"LabelMatchExistingUsersByDescription": "Utilisé pour connecter les utilisateurs existants. Une fois connectés, les utilisateurs seront associés à un identifiant unique provenant de votre fournisseur SSO",
|
||||||
"LabelMaxEpisodesToDownload": "Nombre maximum d’épisodes à télécharger. 0 pour illimité.",
|
"LabelMaxEpisodesToDownload": "Nombre maximum d’épisodes à télécharger. 0 pour illimité.",
|
||||||
@ -467,6 +474,7 @@
|
|||||||
"LabelNewestAuthors": "Auteurs récents",
|
"LabelNewestAuthors": "Auteurs récents",
|
||||||
"LabelNewestEpisodes": "Épisodes récents",
|
"LabelNewestEpisodes": "Épisodes récents",
|
||||||
"LabelNextBackupDate": "Date de la prochaine sauvegarde",
|
"LabelNextBackupDate": "Date de la prochaine sauvegarde",
|
||||||
|
"LabelNextChapters": "Les prochains chapitres seront :",
|
||||||
"LabelNextScheduledRun": "Prochain lancement prévu",
|
"LabelNextScheduledRun": "Prochain lancement prévu",
|
||||||
"LabelNoApiKeys": "Aucune clé API",
|
"LabelNoApiKeys": "Aucune clé API",
|
||||||
"LabelNoCustomMetadataProviders": "Aucun fournisseurs de métadonnées personnalisés",
|
"LabelNoCustomMetadataProviders": "Aucun fournisseurs de métadonnées personnalisés",
|
||||||
@ -484,6 +492,7 @@
|
|||||||
"LabelNotificationsMaxQueueSize": "Nombres de notifications maximum à mettre en attente",
|
"LabelNotificationsMaxQueueSize": "Nombres de notifications maximum à mettre en attente",
|
||||||
"LabelNotificationsMaxQueueSizeHelp": "La limite de notification est de un évènement par seconde. Les notifications seront ignorées si la file d’attente est à son maximum. Cela empêche un flot trop important.",
|
"LabelNotificationsMaxQueueSizeHelp": "La limite de notification est de un évènement par seconde. Les notifications seront ignorées si la file d’attente est à son maximum. Cela empêche un flot trop important.",
|
||||||
"LabelNumberOfBooks": "Nombre de livres",
|
"LabelNumberOfBooks": "Nombre de livres",
|
||||||
|
"LabelNumberOfChapters": "Nombre de chapitres :",
|
||||||
"LabelNumberOfEpisodes": "Nombre d'épisodes",
|
"LabelNumberOfEpisodes": "Nombre d'épisodes",
|
||||||
"LabelOpenIDAdvancedPermsClaimDescription": "Nom de la demande OpenID qui contient des autorisations avancées pour les actions de l’utilisateur dans l’application, qui s’appliqueront à des rôles autres que celui d’administrateur (<b>s’il est configuré</b>). Si la demande est absente de la réponse, l’accès à ABS sera refusé. Si une seule option est manquante, elle sera considérée comme <code>false</code>. Assurez-vous que la demande du fournisseur d’identité correspond à la structure attendue :",
|
"LabelOpenIDAdvancedPermsClaimDescription": "Nom de la demande OpenID qui contient des autorisations avancées pour les actions de l’utilisateur dans l’application, qui s’appliqueront à des rôles autres que celui d’administrateur (<b>s’il est configuré</b>). Si la demande est absente de la réponse, l’accès à ABS sera refusé. Si une seule option est manquante, elle sera considérée comme <code>false</code>. Assurez-vous que la demande du fournisseur d’identité correspond à la structure attendue :",
|
||||||
"LabelOpenIDClaims": "Laissez les options suivantes vides pour désactiver l’attribution avancée de groupes et d’autorisations, en attribuant alors automatiquement le groupe « Utilisateur ».",
|
"LabelOpenIDClaims": "Laissez les options suivantes vides pour désactiver l’attribution avancée de groupes et d’autorisations, en attribuant alors automatiquement le groupe « Utilisateur ».",
|
||||||
@ -614,7 +623,7 @@
|
|||||||
"LabelShareOpen": "Ouvrir le partage",
|
"LabelShareOpen": "Ouvrir le partage",
|
||||||
"LabelShareURL": "Partager l’URL",
|
"LabelShareURL": "Partager l’URL",
|
||||||
"LabelShowAll": "Tout afficher",
|
"LabelShowAll": "Tout afficher",
|
||||||
"LabelShowSeconds": "Afficher les seondes",
|
"LabelShowSeconds": "Afficher les secondes",
|
||||||
"LabelShowSubtitles": "Afficher les sous-titres",
|
"LabelShowSubtitles": "Afficher les sous-titres",
|
||||||
"LabelSize": "Taille",
|
"LabelSize": "Taille",
|
||||||
"LabelSleepTimer": "Minuterie de mise en veille",
|
"LabelSleepTimer": "Minuterie de mise en veille",
|
||||||
@ -655,6 +664,7 @@
|
|||||||
"LabelTheme": "Thème",
|
"LabelTheme": "Thème",
|
||||||
"LabelThemeDark": "Sombre",
|
"LabelThemeDark": "Sombre",
|
||||||
"LabelThemeLight": "Clair",
|
"LabelThemeLight": "Clair",
|
||||||
|
"LabelThemeSepia": "Sépia",
|
||||||
"LabelTimeBase": "Base de temps",
|
"LabelTimeBase": "Base de temps",
|
||||||
"LabelTimeDurationXHours": "{0} heures",
|
"LabelTimeDurationXHours": "{0} heures",
|
||||||
"LabelTimeDurationXMinutes": "{0} minutes",
|
"LabelTimeDurationXMinutes": "{0} minutes",
|
||||||
@ -739,6 +749,7 @@
|
|||||||
"MessageBookshelfNoResultsForFilter": "Aucun résultat pour le filtre « {0} : {1} »",
|
"MessageBookshelfNoResultsForFilter": "Aucun résultat pour le filtre « {0} : {1} »",
|
||||||
"MessageBookshelfNoResultsForQuery": "Aucun résultat pour la requête",
|
"MessageBookshelfNoResultsForQuery": "Aucun résultat pour la requête",
|
||||||
"MessageBookshelfNoSeries": "Vous n’avez aucune série",
|
"MessageBookshelfNoSeries": "Vous n’avez aucune série",
|
||||||
|
"MessageBulkChapterPattern": "Combien de chapitres souhaitez-vous ajouter avec ce motif de numérotation ?",
|
||||||
"MessageChapterEndIsAfter": "La fin du chapitre se situe après la fin de votre livre audio",
|
"MessageChapterEndIsAfter": "La fin du chapitre se situe après la fin de votre livre audio",
|
||||||
"MessageChapterErrorFirstNotZero": "Le premier capitre doit débuter à 0",
|
"MessageChapterErrorFirstNotZero": "Le premier capitre doit débuter à 0",
|
||||||
"MessageChapterErrorStartGteDuration": "Horodatage invalide car il doit débuter avant la fin du livre",
|
"MessageChapterErrorStartGteDuration": "Horodatage invalide car il doit débuter avant la fin du livre",
|
||||||
@ -801,6 +812,8 @@
|
|||||||
"MessageFeedURLWillBe": "L’URL du flux sera {0}",
|
"MessageFeedURLWillBe": "L’URL du flux sera {0}",
|
||||||
"MessageFetching": "Récupération…",
|
"MessageFetching": "Récupération…",
|
||||||
"MessageForceReScanDescription": "analysera de nouveau tous les fichiers. Les étiquettes ID3 des fichiers audio, les fichiers OPF et les fichiers texte seront analysés comme s’ils étaient nouveaux.",
|
"MessageForceReScanDescription": "analysera de nouveau tous les fichiers. Les étiquettes ID3 des fichiers audio, les fichiers OPF et les fichiers texte seront analysés comme s’ils étaient nouveaux.",
|
||||||
|
"MessageHeatmapListeningTimeTooltip": "<strong>{0} À l’écoute</strong> sur {1}",
|
||||||
|
"MessageHeatmapNoListeningSessions": "Aucune session en cours sur {0}",
|
||||||
"MessageImportantNotice": "Information importante !",
|
"MessageImportantNotice": "Information importante !",
|
||||||
"MessageInsertChapterBelow": "Insérer le chapitre ci-dessous",
|
"MessageInsertChapterBelow": "Insérer le chapitre ci-dessous",
|
||||||
"MessageInvalidAsin": "ASIN invalide",
|
"MessageInvalidAsin": "ASIN invalide",
|
||||||
@ -837,7 +850,7 @@
|
|||||||
"MessageNoItems": "Aucun élément",
|
"MessageNoItems": "Aucun élément",
|
||||||
"MessageNoItemsFound": "Aucun élément trouvé",
|
"MessageNoItemsFound": "Aucun élément trouvé",
|
||||||
"MessageNoListeningSessions": "Aucune session d’écoute en cours",
|
"MessageNoListeningSessions": "Aucune session d’écoute en cours",
|
||||||
"MessageNoLogs": "Aucun journaux",
|
"MessageNoLogs": "Aucun journal",
|
||||||
"MessageNoMediaProgress": "Aucun média en cours",
|
"MessageNoMediaProgress": "Aucun média en cours",
|
||||||
"MessageNoNotifications": "Aucune notification",
|
"MessageNoNotifications": "Aucune notification",
|
||||||
"MessageNoPodcastFeed": "Podcast invalide : pas de flux",
|
"MessageNoPodcastFeed": "Podcast invalide : pas de flux",
|
||||||
@ -940,11 +953,12 @@
|
|||||||
"NotificationOnRSSFeedDisabledDescription": "Déclenché lorsque les téléchargements automatiques d’épisodes sont désactivés en raison d’un trop grand nombre de tentatives infructueuses",
|
"NotificationOnRSSFeedDisabledDescription": "Déclenché lorsque les téléchargements automatiques d’épisodes sont désactivés en raison d’un trop grand nombre de tentatives infructueuses",
|
||||||
"NotificationOnRSSFeedFailedDescription": "Déclenché lorsque la demande de flux RSS échoue pour un téléchargement automatique d’épisode",
|
"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",
|
"NotificationOnTestDescription": "Événement pour tester le système de notification",
|
||||||
|
"PlaceholderBulkChapterInput": "Entrez le titre du chapitre ou utilisez la numérotation (ex. 'Épisode 1', 'Chapitre 10', '1.')",
|
||||||
"PlaceholderNewCollection": "Nom de la nouvelle collection",
|
"PlaceholderNewCollection": "Nom de la nouvelle collection",
|
||||||
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
|
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
|
||||||
"PlaceholderNewPlaylist": "Nouveau nom de liste de lecture",
|
"PlaceholderNewPlaylist": "Nouveau nom de liste de lecture",
|
||||||
"PlaceholderSearch": "Recherche…",
|
"PlaceholderSearch": "Recherche…",
|
||||||
"PlaceholderSearchEpisode": "Recherche d’épisode…",
|
"PlaceholderSearchEpisode": "Rechercher un épisode…",
|
||||||
"StatsAuthorsAdded": "auteurs ajoutés",
|
"StatsAuthorsAdded": "auteurs ajoutés",
|
||||||
"StatsBooksAdded": "livres ajoutés",
|
"StatsBooksAdded": "livres ajoutés",
|
||||||
"StatsBooksAdditional": "Les ajouts comprennent…",
|
"StatsBooksAdditional": "Les ajouts comprennent…",
|
||||||
@ -993,8 +1007,10 @@
|
|||||||
"ToastBookmarkCreateFailed": "Échec de la création de signet",
|
"ToastBookmarkCreateFailed": "Échec de la création de signet",
|
||||||
"ToastBookmarkCreateSuccess": "Signet ajouté",
|
"ToastBookmarkCreateSuccess": "Signet ajouté",
|
||||||
"ToastBookmarkRemoveSuccess": "Signet supprimé",
|
"ToastBookmarkRemoveSuccess": "Signet supprimé",
|
||||||
|
"ToastBulkChapterInvalidCount": "Veuillez entrer un nombre valide entre 1 et 150",
|
||||||
"ToastCachePurgeFailed": "Échec de la purge du cache",
|
"ToastCachePurgeFailed": "Échec de la purge du cache",
|
||||||
"ToastCachePurgeSuccess": "Cache purgé avec succès",
|
"ToastCachePurgeSuccess": "Cache purgé avec succès",
|
||||||
|
"ToastChaptersAllLocked": "Tous les chapitres sont verrouillés. Déverrouillez certains chapitres pour décaler leurs temps.",
|
||||||
"ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs",
|
"ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs",
|
||||||
"ToastChaptersInvalidShiftAmountLast": "Durée de décalage non valide. L’heure de début du dernier chapitre pourrait dépasser la durée de ce livre audio.",
|
"ToastChaptersInvalidShiftAmountLast": "Durée de décalage non valide. L’heure de début du dernier chapitre pourrait dépasser la durée de ce livre audio.",
|
||||||
"ToastChaptersInvalidShiftAmountStart": "Durée de décalage non valide. Le premier chapitre aurait une longueur nulle ou négative et serait écrasé par le second. Augmentez la durée de début du second chapitre.",
|
"ToastChaptersInvalidShiftAmountStart": "Durée de décalage non valide. Le premier chapitre aurait une longueur nulle ou négative et serait écrasé par le second. Augmentez la durée de début du second chapitre.",
|
||||||
@ -1028,6 +1044,7 @@
|
|||||||
"ToastInvalidImageUrl": "URL de l'image invalide",
|
"ToastInvalidImageUrl": "URL de l'image invalide",
|
||||||
"ToastInvalidMaxEpisodesToDownload": "Nombre maximum d’épisodes à télécharger non valide",
|
"ToastInvalidMaxEpisodesToDownload": "Nombre maximum d’épisodes à télécharger non valide",
|
||||||
"ToastInvalidUrl": "URL invalide",
|
"ToastInvalidUrl": "URL invalide",
|
||||||
|
"ToastInvalidUrls": "Une ou plusieurs URL sont invalides",
|
||||||
"ToastItemCoverUpdateSuccess": "Couverture mise à jour",
|
"ToastItemCoverUpdateSuccess": "Couverture mise à jour",
|
||||||
"ToastItemDeletedFailed": "La suppression de l'élément à échouée",
|
"ToastItemDeletedFailed": "La suppression de l'élément à échouée",
|
||||||
"ToastItemDeletedSuccess": "Élément supprimé",
|
"ToastItemDeletedSuccess": "Élément supprimé",
|
||||||
@ -1127,5 +1144,12 @@
|
|||||||
"ToastUserPasswordChangeSuccess": "Mot de passe modifié avec succès",
|
"ToastUserPasswordChangeSuccess": "Mot de passe modifié avec succès",
|
||||||
"ToastUserPasswordMismatch": "Les mots de passe ne correspondent pas",
|
"ToastUserPasswordMismatch": "Les mots de passe ne correspondent pas",
|
||||||
"ToastUserPasswordMustChange": "Le nouveau mot de passe ne peut pas être identique à l’ancien",
|
"ToastUserPasswordMustChange": "Le nouveau mot de passe ne peut pas être identique à l’ancien",
|
||||||
"ToastUserRootRequireName": "Vous devez entrer un nom d’utilisateur root"
|
"ToastUserRootRequireName": "Vous devez entrer un nom d’utilisateur root",
|
||||||
|
"TooltipAddChapters": "Ajouter chapitre(s)",
|
||||||
|
"TooltipAddOneSecond": "Ajouter 1 seconde",
|
||||||
|
"TooltipLockAllChapters": "Verrouiller tous les chapitres",
|
||||||
|
"TooltipLockChapter": "Verrouiller le chapitre (Maj+clic pour plage)",
|
||||||
|
"TooltipSubtractOneSecond": "Soustraire 1 seconde",
|
||||||
|
"TooltipUnlockAllChapters": "Déverrouiller tous les chapitres",
|
||||||
|
"TooltipUnlockChapter": "Déverrouiller le chapitre (Maj+clic pour plage)"
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "जोड़ें",
|
"ButtonAdd": "जोड़ें",
|
||||||
|
"ButtonAddApiKey": "एपीआई कुंजी जोड़ें",
|
||||||
"ButtonAddChapters": "अध्याय जोड़ें",
|
"ButtonAddChapters": "अध्याय जोड़ें",
|
||||||
|
"ButtonAddDevice": "उपकरण जोड़ें",
|
||||||
|
"ButtonAddLibrary": "संग्रह जोड़ें",
|
||||||
"ButtonAddPodcasts": "पॉडकास्ट जोड़ें",
|
"ButtonAddPodcasts": "पॉडकास्ट जोड़ें",
|
||||||
|
"ButtonAddUser": "उपयोगकर्ता जोड़ें",
|
||||||
"ButtonAddYourFirstLibrary": "अपनी पहली पुस्तकालय जोड़ें",
|
"ButtonAddYourFirstLibrary": "अपनी पहली पुस्तकालय जोड़ें",
|
||||||
"ButtonApply": "लागू करें",
|
"ButtonApply": "लागू करें",
|
||||||
"ButtonApplyChapters": "अध्यायों में परिवर्तन लागू करें",
|
"ButtonApplyChapters": "अध्यायों में परिवर्तन लागू करें",
|
||||||
"ButtonAuthors": "लेखक",
|
"ButtonAuthors": "लेखक",
|
||||||
"ButtonBack": "पीछे",
|
"ButtonBack": "पीछे",
|
||||||
|
"ButtonBatchEditPopulateFromExisting": "मौजूदा से आबाद करें",
|
||||||
|
"ButtonBatchEditPopulateMapDetails": "मानचित्र विवरण भरें",
|
||||||
"ButtonBrowseForFolder": "फ़ोल्डर खोजें",
|
"ButtonBrowseForFolder": "फ़ोल्डर खोजें",
|
||||||
"ButtonCancel": "रद्द करें",
|
"ButtonCancel": "रद्द करें",
|
||||||
"ButtonCancelEncode": "एनकोड रद्द करें",
|
"ButtonCancelEncode": "एनकोड रद्द करें",
|
||||||
@ -15,7 +21,9 @@
|
|||||||
"ButtonChooseAFolder": "एक फ़ोल्डर चुनें",
|
"ButtonChooseAFolder": "एक फ़ोल्डर चुनें",
|
||||||
"ButtonChooseFiles": "फ़ाइलें चुनें",
|
"ButtonChooseFiles": "फ़ाइलें चुनें",
|
||||||
"ButtonClearFilter": "लागू फ़िल्टर साफ़ करें",
|
"ButtonClearFilter": "लागू फ़िल्टर साफ़ करें",
|
||||||
|
"ButtonClose": "बंद करें",
|
||||||
"ButtonCloseFeed": "फ़ीड बंद करें",
|
"ButtonCloseFeed": "फ़ीड बंद करें",
|
||||||
|
"ButtonCloseSession": "वर्तमान सत्र बंद करें",
|
||||||
"ButtonCollections": "संग्रह",
|
"ButtonCollections": "संग्रह",
|
||||||
"ButtonConfigureScanner": "स्कैनर सेटिंग्स बदलें",
|
"ButtonConfigureScanner": "स्कैनर सेटिंग्स बदलें",
|
||||||
"ButtonCreate": "बनाएं",
|
"ButtonCreate": "बनाएं",
|
||||||
@ -25,6 +33,7 @@
|
|||||||
"ButtonEdit": "संपादित करें",
|
"ButtonEdit": "संपादित करें",
|
||||||
"ButtonEditChapters": "अध्याय संपादित करें",
|
"ButtonEditChapters": "अध्याय संपादित करें",
|
||||||
"ButtonEditPodcast": "पॉडकास्ट संपादित करें",
|
"ButtonEditPodcast": "पॉडकास्ट संपादित करें",
|
||||||
|
"ButtonEnable": "सक्षम करें",
|
||||||
"ButtonForceReScan": "बलपूर्वक पुन: स्कैन करें",
|
"ButtonForceReScan": "बलपूर्वक पुन: स्कैन करें",
|
||||||
"ButtonFullPath": "पूर्ण पथ",
|
"ButtonFullPath": "पूर्ण पथ",
|
||||||
"ButtonHide": "छुपाएं",
|
"ButtonHide": "छुपाएं",
|
||||||
|
@ -199,6 +199,7 @@
|
|||||||
"HeaderSettingsExperimental": "Eksperimentalne značajke",
|
"HeaderSettingsExperimental": "Eksperimentalne značajke",
|
||||||
"HeaderSettingsGeneral": "Općenito",
|
"HeaderSettingsGeneral": "Općenito",
|
||||||
"HeaderSettingsScanner": "Skener",
|
"HeaderSettingsScanner": "Skener",
|
||||||
|
"HeaderSettingsSecurity": "Sigurnost",
|
||||||
"HeaderSettingsWebClient": "Web klijent",
|
"HeaderSettingsWebClient": "Web klijent",
|
||||||
"HeaderSleepTimer": "Timer za spavanje",
|
"HeaderSleepTimer": "Timer za spavanje",
|
||||||
"HeaderStatsLargestItems": "Najveće stavke",
|
"HeaderStatsLargestItems": "Najveće stavke",
|
||||||
@ -293,6 +294,7 @@
|
|||||||
"LabelContinueListening": "Nastavi slušati",
|
"LabelContinueListening": "Nastavi slušati",
|
||||||
"LabelContinueReading": "Nastavi čitati",
|
"LabelContinueReading": "Nastavi čitati",
|
||||||
"LabelContinueSeries": "Nastavi serijal",
|
"LabelContinueSeries": "Nastavi serijal",
|
||||||
|
"LabelCorsAllowed": "Dozvoljena CORS ishodišta",
|
||||||
"LabelCover": "Naslovnica",
|
"LabelCover": "Naslovnica",
|
||||||
"LabelCoverImageURL": "URL naslovnice",
|
"LabelCoverImageURL": "URL naslovnice",
|
||||||
"LabelCoverProvider": "Pružatelj naslovnica",
|
"LabelCoverProvider": "Pružatelj naslovnica",
|
||||||
@ -418,6 +420,7 @@
|
|||||||
"LabelLanguages": "Jezici",
|
"LabelLanguages": "Jezici",
|
||||||
"LabelLastBookAdded": "Zadnja dodana knjiga",
|
"LabelLastBookAdded": "Zadnja dodana knjiga",
|
||||||
"LabelLastBookUpdated": "Zadnja ažurirana knjiga",
|
"LabelLastBookUpdated": "Zadnja ažurirana knjiga",
|
||||||
|
"LabelLastProgressDate": "Zadnji napredak: {0}",
|
||||||
"LabelLastSeen": "Zadnji puta viđen",
|
"LabelLastSeen": "Zadnji puta viđen",
|
||||||
"LabelLastTime": "Zadnje doslušano vrijeme",
|
"LabelLastTime": "Zadnje doslušano vrijeme",
|
||||||
"LabelLastUpdate": "Zadnje ažuriranje",
|
"LabelLastUpdate": "Zadnje ažuriranje",
|
||||||
@ -430,6 +433,7 @@
|
|||||||
"LabelLibraryFilterSublistEmpty": "Br {0}",
|
"LabelLibraryFilterSublistEmpty": "Br {0}",
|
||||||
"LabelLibraryItem": "Stavka knjižnice",
|
"LabelLibraryItem": "Stavka knjižnice",
|
||||||
"LabelLibraryName": "Ime knjižnice",
|
"LabelLibraryName": "Ime knjižnice",
|
||||||
|
"LabelLibrarySortByProgress": "Napredak ažuriran",
|
||||||
"LabelLimit": "Ograničenje",
|
"LabelLimit": "Ograničenje",
|
||||||
"LabelLineSpacing": "Razmak između redaka",
|
"LabelLineSpacing": "Razmak između redaka",
|
||||||
"LabelListenAgain": "Ponovno poslušaj",
|
"LabelListenAgain": "Ponovno poslušaj",
|
||||||
@ -438,6 +442,7 @@
|
|||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Traži nove nastavke nakon ovog datuma",
|
"LabelLookForNewEpisodesAfterDate": "Traži nove nastavke nakon ovog datuma",
|
||||||
"LabelLowestPriority": "Najniži prioritet",
|
"LabelLowestPriority": "Najniži prioritet",
|
||||||
|
"LabelMatchConfidence": "Pouzdanost",
|
||||||
"LabelMatchExistingUsersBy": "Prepoznaj postojeće korisnike pomoću",
|
"LabelMatchExistingUsersBy": "Prepoznaj postojeće korisnike pomoću",
|
||||||
"LabelMatchExistingUsersByDescription": "Rabi se za povezivanje postojećih korisnika. Nakon što se spoje, korisnike se prepoznaje temeljem jedinstvene oznake vašeg pružatelja SSO usluga",
|
"LabelMatchExistingUsersByDescription": "Rabi se za povezivanje postojećih korisnika. Nakon što se spoje, korisnike se prepoznaje temeljem jedinstvene oznake vašeg pružatelja SSO usluga",
|
||||||
"LabelMaxEpisodesToDownload": "Najveći broj nastavaka za preuzimanje. 0 za neograničeno.",
|
"LabelMaxEpisodesToDownload": "Najveći broj nastavaka za preuzimanje. 0 za neograničeno.",
|
||||||
@ -655,6 +660,7 @@
|
|||||||
"LabelTheme": "Tema",
|
"LabelTheme": "Tema",
|
||||||
"LabelThemeDark": "Tamna",
|
"LabelThemeDark": "Tamna",
|
||||||
"LabelThemeLight": "Svijetla",
|
"LabelThemeLight": "Svijetla",
|
||||||
|
"LabelThemeSepia": "Sepija",
|
||||||
"LabelTimeBase": "Baza vremena",
|
"LabelTimeBase": "Baza vremena",
|
||||||
"LabelTimeDurationXHours": "{0} sati",
|
"LabelTimeDurationXHours": "{0} sati",
|
||||||
"LabelTimeDurationXMinutes": "{0} minuta",
|
"LabelTimeDurationXMinutes": "{0} minuta",
|
||||||
@ -801,6 +807,8 @@
|
|||||||
"MessageFeedURLWillBe": "URL izvora bit će {0}",
|
"MessageFeedURLWillBe": "URL izvora bit će {0}",
|
||||||
"MessageFetching": "Dohvaćam...",
|
"MessageFetching": "Dohvaćam...",
|
||||||
"MessageForceReScanDescription": "će ponovno skenirati sve datoteke kao nove datoteke. ID3 tagovi zvučnih datoteka, OPF datoteke i tekstualne datoteke skenirat će se kao da su nove.",
|
"MessageForceReScanDescription": "će ponovno skenirati sve datoteke kao nove datoteke. ID3 tagovi zvučnih datoteka, OPF datoteke i tekstualne datoteke skenirat će se kao da su nove.",
|
||||||
|
"MessageHeatmapListeningTimeTooltip": "<strong>{0} sluša</strong> na {1}",
|
||||||
|
"MessageHeatmapNoListeningSessions": "Nema sesija slušanja na {0}",
|
||||||
"MessageImportantNotice": "Važna obavijest!",
|
"MessageImportantNotice": "Važna obavijest!",
|
||||||
"MessageInsertChapterBelow": "Unesi poglavlje ispod",
|
"MessageInsertChapterBelow": "Unesi poglavlje ispod",
|
||||||
"MessageInvalidAsin": "Nevažeći ASIN",
|
"MessageInvalidAsin": "Nevažeći ASIN",
|
||||||
@ -1028,6 +1036,7 @@
|
|||||||
"ToastInvalidImageUrl": "Neispravan URL slike",
|
"ToastInvalidImageUrl": "Neispravan URL slike",
|
||||||
"ToastInvalidMaxEpisodesToDownload": "Neispravan unos maksimalnog broja nastavaka",
|
"ToastInvalidMaxEpisodesToDownload": "Neispravan unos maksimalnog broja nastavaka",
|
||||||
"ToastInvalidUrl": "Neispravan URL",
|
"ToastInvalidUrl": "Neispravan URL",
|
||||||
|
"ToastInvalidUrls": "Jedan ili više URL-ova nisu ispravni",
|
||||||
"ToastItemCoverUpdateSuccess": "Naslovnica stavke ažurirana",
|
"ToastItemCoverUpdateSuccess": "Naslovnica stavke ažurirana",
|
||||||
"ToastItemDeletedFailed": "Brisanje stavke nije uspjelo",
|
"ToastItemDeletedFailed": "Brisanje stavke nije uspjelo",
|
||||||
"ToastItemDeletedSuccess": "Stavka je izbrisana",
|
"ToastItemDeletedSuccess": "Stavka je izbrisana",
|
||||||
|
@ -1,15 +1,65 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "追加",
|
"ButtonAdd": "追加",
|
||||||
|
"ButtonAddApiKey": "APIキーの追加",
|
||||||
"ButtonAddChapters": "チャプターの追加",
|
"ButtonAddChapters": "チャプターの追加",
|
||||||
|
"ButtonAddDevice": "端末の追加",
|
||||||
|
"ButtonAddLibrary": "ライブラリーの追加",
|
||||||
|
"ButtonAddPodcasts": "ポッドキャストの追加",
|
||||||
|
"ButtonAddUser": "ユーザーの追加",
|
||||||
|
"ButtonAddYourFirstLibrary": "最初のライブラリーを追加",
|
||||||
|
"ButtonApply": "確定",
|
||||||
|
"ButtonApplyChapters": "チャプターを確定する",
|
||||||
|
"ButtonAuthors": "作者",
|
||||||
|
"ButtonBack": "戻る",
|
||||||
"ButtonCancel": "キャンセル",
|
"ButtonCancel": "キャンセル",
|
||||||
|
"ButtonChangeRootPassword": "Rootのパスワードを変更する",
|
||||||
|
"ButtonChooseAFolder": "フォルダーを選ぶ",
|
||||||
|
"ButtonChooseFiles": "ファイルを選ぶ",
|
||||||
|
"ButtonClearFilter": "絞り込みを解除",
|
||||||
|
"ButtonClose": "閉じる",
|
||||||
|
"ButtonCollections": "コレクション",
|
||||||
|
"ButtonCreate": "作成",
|
||||||
|
"ButtonCreateBackup": "バックアップを作成する",
|
||||||
|
"ButtonDelete": "削除",
|
||||||
|
"ButtonDownloadQueue": "次に再生",
|
||||||
|
"ButtonEdit": "編集",
|
||||||
|
"ButtonEditChapters": "チャプターの編集",
|
||||||
|
"ButtonEditPodcast": "ポッドキャストの編集",
|
||||||
|
"ButtonEnable": "オンにする",
|
||||||
|
"ButtonHide": "非表示",
|
||||||
|
"ButtonHome": "ホーム",
|
||||||
|
"ButtonJumpBackward": "巻き戻し",
|
||||||
|
"ButtonJumpForward": "早送り",
|
||||||
|
"ButtonLibrary": "ライブラリー",
|
||||||
|
"ButtonLogout": "ログアウト",
|
||||||
"ButtonOk": "はい",
|
"ButtonOk": "はい",
|
||||||
"ButtonPlay": "プレイ",
|
"ButtonPlay": "プレイ",
|
||||||
"ButtonPlaying": "プレイ中",
|
"ButtonPlaying": "プレイ中",
|
||||||
"ButtonPrevious": "先",
|
"ButtonPrevious": "先",
|
||||||
|
"ButtonQueueAddItem": "次に再生する",
|
||||||
|
"ButtonQueueRemoveItem": "次に再生から削除",
|
||||||
|
"ButtonReScan": "再スキャン",
|
||||||
"ButtonRead": "野村",
|
"ButtonRead": "野村",
|
||||||
|
"ButtonReadLess": "閉じる",
|
||||||
|
"ButtonReadMore": "もっと見る",
|
||||||
|
"ButtonRefresh": "再読み込み",
|
||||||
|
"ButtonRemove": "削除",
|
||||||
|
"ButtonRemoveAll": "全て削除",
|
||||||
|
"ButtonRemoveAllLibraryItems": "ライブラリーの項目を全て削除",
|
||||||
|
"ButtonReset": "元に戻す",
|
||||||
|
"ButtonResetToDefault": "デフォルトに戻す",
|
||||||
|
"ButtonRestore": "復元",
|
||||||
|
"ButtonSave": "保存",
|
||||||
|
"ButtonSaveAndClose": "保存して閉じる",
|
||||||
|
"ButtonScan": "スキャン",
|
||||||
|
"ButtonScanLibrary": "ライブラリーをスキャン",
|
||||||
|
"ButtonScrollLeft": "左にスクロール",
|
||||||
|
"ButtonScrollRight": "右にスクロール",
|
||||||
|
"ButtonSearch": "検索",
|
||||||
"ButtonYes": "はい",
|
"ButtonYes": "はい",
|
||||||
"HeaderPlayerSettings": "プレーヤーの設定",
|
"HeaderPlayerSettings": "プレーヤーの設定",
|
||||||
"LabelBooks": "ほん",
|
"LabelBooks": "ほん",
|
||||||
|
"LabelContinueListening": "続きから聞く",
|
||||||
"LabelLanguage": "言語",
|
"LabelLanguage": "言語",
|
||||||
"LabelLanguages": "言語",
|
"LabelLanguages": "言語",
|
||||||
"LabelName": "名",
|
"LabelName": "名",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Legg til",
|
"ButtonAdd": "Legg til",
|
||||||
|
"ButtonAddApiKey": "Legg til API-nøkkel",
|
||||||
"ButtonAddChapters": "Legg til kapittel",
|
"ButtonAddChapters": "Legg til kapittel",
|
||||||
"ButtonAddDevice": "Legg til enhet",
|
"ButtonAddDevice": "Legg til enhet",
|
||||||
"ButtonAddLibrary": "Legg til bibliotek",
|
"ButtonAddLibrary": "Legg til bibliotek",
|
||||||
@ -10,6 +11,8 @@
|
|||||||
"ButtonApplyChapters": "Bruk kapittel",
|
"ButtonApplyChapters": "Bruk kapittel",
|
||||||
"ButtonAuthors": "Forfattere",
|
"ButtonAuthors": "Forfattere",
|
||||||
"ButtonBack": "Tilbake",
|
"ButtonBack": "Tilbake",
|
||||||
|
"ButtonBatchEditPopulateFromExisting": "Opprett fra eksisterende",
|
||||||
|
"ButtonBatchEditPopulateMapDetails": "Legg til detaljer",
|
||||||
"ButtonBrowseForFolder": "Bla gjennom mappe",
|
"ButtonBrowseForFolder": "Bla gjennom mappe",
|
||||||
"ButtonCancel": "Avbryt",
|
"ButtonCancel": "Avbryt",
|
||||||
"ButtonCancelEncode": "Avbryt konvertering",
|
"ButtonCancelEncode": "Avbryt konvertering",
|
||||||
@ -18,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Velg mappe",
|
"ButtonChooseAFolder": "Velg mappe",
|
||||||
"ButtonChooseFiles": "Velg filer",
|
"ButtonChooseFiles": "Velg filer",
|
||||||
"ButtonClearFilter": "Fjern filter",
|
"ButtonClearFilter": "Fjern filter",
|
||||||
|
"ButtonClose": "Lukk",
|
||||||
"ButtonCloseFeed": "Lukk Feed",
|
"ButtonCloseFeed": "Lukk Feed",
|
||||||
"ButtonCloseSession": "Lukk åpen økt",
|
"ButtonCloseSession": "Lukk åpen økt",
|
||||||
"ButtonCollections": "Samlinger",
|
"ButtonCollections": "Samlinger",
|
||||||
@ -117,6 +121,7 @@
|
|||||||
"HeaderAccount": "Konto",
|
"HeaderAccount": "Konto",
|
||||||
"HeaderAddCustomMetadataProvider": "Legg til egendefinert metadata tilbyder",
|
"HeaderAddCustomMetadataProvider": "Legg til egendefinert metadata tilbyder",
|
||||||
"HeaderAdvanced": "Avansert",
|
"HeaderAdvanced": "Avansert",
|
||||||
|
"HeaderApiKeys": "API-nøkler",
|
||||||
"HeaderAppriseNotificationSettings": "Apprise varslingsinstillinger",
|
"HeaderAppriseNotificationSettings": "Apprise varslingsinstillinger",
|
||||||
"HeaderAudioTracks": "Lydspor",
|
"HeaderAudioTracks": "Lydspor",
|
||||||
"HeaderAudiobookTools": "Lydbok Filbehandlingsverktøy",
|
"HeaderAudiobookTools": "Lydbok Filbehandlingsverktøy",
|
||||||
@ -160,6 +165,7 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "Prioriteringsrekkefølge for metadata",
|
"HeaderMetadataOrderOfPrecedence": "Prioriteringsrekkefølge for metadata",
|
||||||
"HeaderMetadataToEmbed": "Metadata å bake inn",
|
"HeaderMetadataToEmbed": "Metadata å bake inn",
|
||||||
"HeaderNewAccount": "Ny konto",
|
"HeaderNewAccount": "Ny konto",
|
||||||
|
"HeaderNewApiKey": "Ny API-nøkkel",
|
||||||
"HeaderNewLibrary": "Ny bibliotek",
|
"HeaderNewLibrary": "Ny bibliotek",
|
||||||
"HeaderNotificationCreate": "Opprett varsling",
|
"HeaderNotificationCreate": "Opprett varsling",
|
||||||
"HeaderNotificationUpdate": "Oppdater varsling",
|
"HeaderNotificationUpdate": "Oppdater varsling",
|
||||||
@ -193,6 +199,7 @@
|
|||||||
"HeaderSettingsExperimental": "Eksperimentelle funksjoner",
|
"HeaderSettingsExperimental": "Eksperimentelle funksjoner",
|
||||||
"HeaderSettingsGeneral": "Generell",
|
"HeaderSettingsGeneral": "Generell",
|
||||||
"HeaderSettingsScanner": "Skanner",
|
"HeaderSettingsScanner": "Skanner",
|
||||||
|
"HeaderSettingsSecurity": "Sikkerhet",
|
||||||
"HeaderSettingsWebClient": "Webklient",
|
"HeaderSettingsWebClient": "Webklient",
|
||||||
"HeaderSleepTimer": "Sove timer",
|
"HeaderSleepTimer": "Sove timer",
|
||||||
"HeaderStatsLargestItems": "Største enheter",
|
"HeaderStatsLargestItems": "Største enheter",
|
||||||
@ -204,6 +211,7 @@
|
|||||||
"HeaderTableOfContents": "Innholdsfortegnelse",
|
"HeaderTableOfContents": "Innholdsfortegnelse",
|
||||||
"HeaderTools": "Verktøy",
|
"HeaderTools": "Verktøy",
|
||||||
"HeaderUpdateAccount": "Oppdater konto",
|
"HeaderUpdateAccount": "Oppdater konto",
|
||||||
|
"HeaderUpdateApiKey": "Oppdater API-nøkkel",
|
||||||
"HeaderUpdateAuthor": "Oppdater forfatter",
|
"HeaderUpdateAuthor": "Oppdater forfatter",
|
||||||
"HeaderUpdateDetails": "Oppdater detaljer",
|
"HeaderUpdateDetails": "Oppdater detaljer",
|
||||||
"HeaderUpdateLibrary": "Oppdater bibliotek",
|
"HeaderUpdateLibrary": "Oppdater bibliotek",
|
||||||
@ -233,6 +241,10 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Alle brukere bortsett fra gjester",
|
"LabelAllUsersExcludingGuests": "Alle brukere bortsett fra gjester",
|
||||||
"LabelAllUsersIncludingGuests": "Alle brukere inkludert gjester",
|
"LabelAllUsersIncludingGuests": "Alle brukere inkludert gjester",
|
||||||
"LabelAlreadyInYourLibrary": "Allerede i biblioteket",
|
"LabelAlreadyInYourLibrary": "Allerede i biblioteket",
|
||||||
|
"LabelApiKeyCreated": "API-nøkkel \"{0}\" ble opprettet.",
|
||||||
|
"LabelApiKeyCreatedDescription": "Husk å kopiere API-nøkkelen nå siden du ikke kan se den igjen senere.",
|
||||||
|
"LabelApiKeyUser": "Handle på vegne av bruker",
|
||||||
|
"LabelApiKeyUserDescription": "Denne API-nøkkelen vil ha de samme tillatelsene som brukeren den handler på vegne av. I loggene vil dette se ut som om brukeren selv foretok forespørselen.",
|
||||||
"LabelApiToken": "API token",
|
"LabelApiToken": "API token",
|
||||||
"LabelAppend": "Legge til",
|
"LabelAppend": "Legge til",
|
||||||
"LabelAudioBitrate": "Bitrate for lyd (f.eks. 128k)",
|
"LabelAudioBitrate": "Bitrate for lyd (f.eks. 128k)",
|
||||||
@ -252,7 +264,7 @@
|
|||||||
"LabelBackToUser": "Tilbake til bruker",
|
"LabelBackToUser": "Tilbake til bruker",
|
||||||
"LabelBackupAudioFiles": "Sikkerhetskopier lydfiler",
|
"LabelBackupAudioFiles": "Sikkerhetskopier lydfiler",
|
||||||
"LabelBackupLocation": "Mappe for sikkerhetskopiering",
|
"LabelBackupLocation": "Mappe for sikkerhetskopiering",
|
||||||
"LabelBackupsEnableAutomaticBackups": "Aktiver automatisk sikkerhetskopi",
|
"LabelBackupsEnableAutomaticBackups": "Automatiske sikkerhetskopier",
|
||||||
"LabelBackupsEnableAutomaticBackupsHelp": "Sikkerhetskopier lagret under /metadata/backups",
|
"LabelBackupsEnableAutomaticBackupsHelp": "Sikkerhetskopier lagret under /metadata/backups",
|
||||||
"LabelBackupsMaxBackupSize": "Maksimal størrelse for sikkerhetskopi (i GB) (0 for ubegrenset)",
|
"LabelBackupsMaxBackupSize": "Maksimal størrelse for sikkerhetskopi (i GB) (0 for ubegrenset)",
|
||||||
"LabelBackupsMaxBackupSizeHelp": "For å forhindre feilkonfigurasjon, vil sikkerhetskopier mislykkes hvis de oveskride konfigurert størrelse.",
|
"LabelBackupsMaxBackupSizeHelp": "For å forhindre feilkonfigurasjon, vil sikkerhetskopier mislykkes hvis de oveskride konfigurert størrelse.",
|
||||||
@ -282,6 +294,7 @@
|
|||||||
"LabelContinueListening": "Fortsett lytting",
|
"LabelContinueListening": "Fortsett lytting",
|
||||||
"LabelContinueReading": "Fortsett lesing",
|
"LabelContinueReading": "Fortsett lesing",
|
||||||
"LabelContinueSeries": "Fortsett serier",
|
"LabelContinueSeries": "Fortsett serier",
|
||||||
|
"LabelCorsAllowed": "Tillate CORS-opprinnelser",
|
||||||
"LabelCover": "Omslag",
|
"LabelCover": "Omslag",
|
||||||
"LabelCoverImageURL": "Omslagsbilde URL",
|
"LabelCoverImageURL": "Omslagsbilde URL",
|
||||||
"LabelCoverProvider": "Tilbyder av omslagsbilde",
|
"LabelCoverProvider": "Tilbyder av omslagsbilde",
|
||||||
@ -344,6 +357,10 @@
|
|||||||
"LabelExample": "Eksempel",
|
"LabelExample": "Eksempel",
|
||||||
"LabelExpandSeries": "Vis serie",
|
"LabelExpandSeries": "Vis serie",
|
||||||
"LabelExpandSubSeries": "Vis underserie",
|
"LabelExpandSubSeries": "Vis underserie",
|
||||||
|
"LabelExpired": "Utløpt",
|
||||||
|
"LabelExpiresAt": "Utløper",
|
||||||
|
"LabelExpiresInSeconds": "Utløper om (sekunder)",
|
||||||
|
"LabelExpiresNever": "Aldri",
|
||||||
"LabelExplicit": "Eksplisitt",
|
"LabelExplicit": "Eksplisitt",
|
||||||
"LabelExplicitChecked": "Eksplisitt (avhuket)",
|
"LabelExplicitChecked": "Eksplisitt (avhuket)",
|
||||||
"LabelExplicitUnchecked": "Ikke eksplisitt (ikke avhuket)",
|
"LabelExplicitUnchecked": "Ikke eksplisitt (ikke avhuket)",
|
||||||
@ -373,7 +390,7 @@
|
|||||||
"LabelGenres": "Sjangre",
|
"LabelGenres": "Sjangre",
|
||||||
"LabelHardDeleteFile": "Tving sletting av fil",
|
"LabelHardDeleteFile": "Tving sletting av fil",
|
||||||
"LabelHasEbook": "Har e-bok",
|
"LabelHasEbook": "Har e-bok",
|
||||||
"LabelHasSupplementaryEbook": "Har komplimentær e-bok",
|
"LabelHasSupplementaryEbook": "Har supplerende e-bok",
|
||||||
"LabelHideSubtitles": "Skjul undertitler",
|
"LabelHideSubtitles": "Skjul undertitler",
|
||||||
"LabelHighestPriority": "Høyeste prioritet",
|
"LabelHighestPriority": "Høyeste prioritet",
|
||||||
"LabelHost": "Tjener",
|
"LabelHost": "Tjener",
|
||||||
@ -403,10 +420,11 @@
|
|||||||
"LabelLanguages": "Språk",
|
"LabelLanguages": "Språk",
|
||||||
"LabelLastBookAdded": "Siste bok lagt til",
|
"LabelLastBookAdded": "Siste bok lagt til",
|
||||||
"LabelLastBookUpdated": "Siste bok oppdatert",
|
"LabelLastBookUpdated": "Siste bok oppdatert",
|
||||||
|
"LabelLastProgressDate": "Siste fremgang: {0}",
|
||||||
"LabelLastSeen": "Sist sett",
|
"LabelLastSeen": "Sist sett",
|
||||||
"LabelLastTime": "Siste tid",
|
"LabelLastTime": "Siste tid",
|
||||||
"LabelLastUpdate": "Siste oppdatering",
|
"LabelLastUpdate": "Siste oppdatering",
|
||||||
"LabelLayout": "Oppsett",
|
"LabelLayout": "Utseende",
|
||||||
"LabelLayoutSinglePage": "Enkeltside",
|
"LabelLayoutSinglePage": "Enkeltside",
|
||||||
"LabelLayoutSplitPage": "Del side",
|
"LabelLayoutSplitPage": "Del side",
|
||||||
"LabelLess": "Mindre",
|
"LabelLess": "Mindre",
|
||||||
@ -415,6 +433,7 @@
|
|||||||
"LabelLibraryFilterSublistEmpty": "Ingen {0}",
|
"LabelLibraryFilterSublistEmpty": "Ingen {0}",
|
||||||
"LabelLibraryItem": "Bibliotek enhet",
|
"LabelLibraryItem": "Bibliotek enhet",
|
||||||
"LabelLibraryName": "Bibliotek navn",
|
"LabelLibraryName": "Bibliotek navn",
|
||||||
|
"LabelLibrarySortByProgress": "Fremgang oppdatert",
|
||||||
"LabelLimit": "Begrensning",
|
"LabelLimit": "Begrensning",
|
||||||
"LabelLineSpacing": "Linjemellomrom",
|
"LabelLineSpacing": "Linjemellomrom",
|
||||||
"LabelListenAgain": "Lytt igjen",
|
"LabelListenAgain": "Lytt igjen",
|
||||||
@ -468,7 +487,7 @@
|
|||||||
"LabelNotificationsMaxQueueSize": "Maksimalt antall varslinger i kø",
|
"LabelNotificationsMaxQueueSize": "Maksimalt antall varslinger i kø",
|
||||||
"LabelNotificationsMaxQueueSizeHelp": "Hendelser er begrenset til avfyre én gang per sekund. Hendelser blir ignorert om køen er full. Dette forhindrer overflod av varslinger.",
|
"LabelNotificationsMaxQueueSizeHelp": "Hendelser er begrenset til avfyre én gang per sekund. Hendelser blir ignorert om køen er full. Dette forhindrer overflod av varslinger.",
|
||||||
"LabelNumberOfBooks": "Antall bøker",
|
"LabelNumberOfBooks": "Antall bøker",
|
||||||
"LabelNumberOfEpisodes": "Antall episoder",
|
"LabelNumberOfEpisodes": "# episoder",
|
||||||
"LabelOpenIDAdvancedPermsClaimDescription": "Navnet på OpenID claim'et som inneholder avanserte tilganger for brukerhandlinger i applikasjonen som vil brukes for ikke-administratorroller (<b>hvis konfigurert</b>). Hvis claim'et mangler fra responsen, nektes tilgang til ABS. Hvis en enkelt opsjon mangler, blir behandlet som <code>false</code>. Påse at identitetstilbyderens claim stemmer overens med den forventede strukturen:",
|
"LabelOpenIDAdvancedPermsClaimDescription": "Navnet på OpenID claim'et som inneholder avanserte tilganger for brukerhandlinger i applikasjonen som vil brukes for ikke-administratorroller (<b>hvis konfigurert</b>). Hvis claim'et mangler fra responsen, nektes tilgang til ABS. Hvis en enkelt opsjon mangler, blir behandlet som <code>false</code>. Påse at identitetstilbyderens claim stemmer overens med den forventede strukturen:",
|
||||||
"LabelOpenIDClaims": "La følge valg være tomme for å slå av avanserte gruppe og tillatelser. Gruppen \"Bruker\" vil da også automatisk legges til.",
|
"LabelOpenIDClaims": "La følge valg være tomme for å slå av avanserte gruppe og tillatelser. Gruppen \"Bruker\" vil da også automatisk legges til.",
|
||||||
"LabelOpenRSSFeed": "Åpne RSS Feed",
|
"LabelOpenRSSFeed": "Åpne RSS Feed",
|
||||||
@ -510,11 +529,11 @@
|
|||||||
"LabelPublishers": "Utgivere",
|
"LabelPublishers": "Utgivere",
|
||||||
"LabelRSSFeedCustomOwnerEmail": "Tilpasset eier e-post",
|
"LabelRSSFeedCustomOwnerEmail": "Tilpasset eier e-post",
|
||||||
"LabelRSSFeedCustomOwnerName": "Tilpasset eier Navn",
|
"LabelRSSFeedCustomOwnerName": "Tilpasset eier Navn",
|
||||||
"LabelRSSFeedOpen": "RSS Feed åpne",
|
"LabelRSSFeedOpen": "RSS-strøm åpen",
|
||||||
"LabelRSSFeedPreventIndexing": "Forhindre indeksering",
|
"LabelRSSFeedPreventIndexing": "Forhindre indeksering",
|
||||||
"LabelRSSFeedSlug": "RSS-feed ID",
|
"LabelRSSFeedSlug": "RSS-feed ID",
|
||||||
"LabelRSSFeedURL": "RSS-feed URL",
|
"LabelRSSFeedURL": "RSS-feed URL",
|
||||||
"LabelRandomly": "Tilfeldig",
|
"LabelRandomly": "Tilfeldighet",
|
||||||
"LabelReAddSeriesToContinueListening": "Legg til igjen til \"Fortsett å lytte\"",
|
"LabelReAddSeriesToContinueListening": "Legg til igjen til \"Fortsett å lytte\"",
|
||||||
"LabelRead": "Les",
|
"LabelRead": "Les",
|
||||||
"LabelReadAgain": "Les igjen",
|
"LabelReadAgain": "Les igjen",
|
||||||
@ -624,7 +643,7 @@
|
|||||||
"LabelStatsWeekListening": "Uker lyttet",
|
"LabelStatsWeekListening": "Uker lyttet",
|
||||||
"LabelSubtitle": "Undertittel",
|
"LabelSubtitle": "Undertittel",
|
||||||
"LabelSupportedFileTypes": "Støttede filtyper",
|
"LabelSupportedFileTypes": "Støttede filtyper",
|
||||||
"LabelTag": "Tag",
|
"LabelTag": "Merke",
|
||||||
"LabelTags": "Tagger",
|
"LabelTags": "Tagger",
|
||||||
"LabelTagsAccessibleToUser": "Tagger tilgjengelig for bruker",
|
"LabelTagsAccessibleToUser": "Tagger tilgjengelig for bruker",
|
||||||
"LabelTagsNotAccessibleToUser": "Tagger ikke tilgjengelig for bruker",
|
"LabelTagsNotAccessibleToUser": "Tagger ikke tilgjengelig for bruker",
|
||||||
@ -829,7 +848,7 @@
|
|||||||
"MessagePlaylistCreateFromCollection": "Lag spilleliste fra samling",
|
"MessagePlaylistCreateFromCollection": "Lag spilleliste fra samling",
|
||||||
"MessagePleaseWait": "Vennligst vent...",
|
"MessagePleaseWait": "Vennligst vent...",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast har ingen RSS feed url til bruk av sammenligning",
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast har ingen RSS feed url til bruk av sammenligning",
|
||||||
"MessagePodcastSearchField": "Skriv inn søkeord eller RSS-feed URL",
|
"MessagePodcastSearchField": "Skriv inn søkeord eller URL til en RSS-strøm",
|
||||||
"MessageQuickEmbedInProgress": "Hurtiginnbygging pågår",
|
"MessageQuickEmbedInProgress": "Hurtiginnbygging pågår",
|
||||||
"MessageQuickEmbedQueue": "Kø for hurtiginnbygging ({0} i kø)",
|
"MessageQuickEmbedQueue": "Kø for hurtiginnbygging ({0} i kø)",
|
||||||
"MessageQuickMatchAllEpisodes": "Kjapp matching av alle episoder",
|
"MessageQuickMatchAllEpisodes": "Kjapp matching av alle episoder",
|
||||||
|
@ -241,6 +241,7 @@
|
|||||||
"LabelAllUsersIncludingGuests": "Wszyscy użytkownicy, łącznie z gośćmi",
|
"LabelAllUsersIncludingGuests": "Wszyscy użytkownicy, łącznie z gośćmi",
|
||||||
"LabelAlreadyInYourLibrary": "Już istnieje w twojej bibliotece",
|
"LabelAlreadyInYourLibrary": "Już istnieje w twojej bibliotece",
|
||||||
"LabelApiKeyCreated": "Klucz API \"{0}\" został pomyślnie utworzony.",
|
"LabelApiKeyCreated": "Klucz API \"{0}\" został pomyślnie utworzony.",
|
||||||
|
"LabelApiKeyCreatedDescription": "Pamiętaj o skopiowaniu klucza API, ponieważ nie będziesz już mógł go zobaczyć.",
|
||||||
"LabelApiToken": "API Token",
|
"LabelApiToken": "API Token",
|
||||||
"LabelAppend": "Dołącz",
|
"LabelAppend": "Dołącz",
|
||||||
"LabelAudioBitrate": "Audio Bitrate (np. 128k)",
|
"LabelAudioBitrate": "Audio Bitrate (np. 128k)",
|
||||||
@ -312,6 +313,7 @@
|
|||||||
"LabelDiscover": "Odkrywaj",
|
"LabelDiscover": "Odkrywaj",
|
||||||
"LabelDownload": "Pobierz",
|
"LabelDownload": "Pobierz",
|
||||||
"LabelDownloadNEpisodes": "Ściąganie {0} odcinków",
|
"LabelDownloadNEpisodes": "Ściąganie {0} odcinków",
|
||||||
|
"LabelDownloadable": "Do pobrania",
|
||||||
"LabelDuration": "Czas trwania",
|
"LabelDuration": "Czas trwania",
|
||||||
"LabelDurationComparisonExactMatch": "(dokładne dopasowanie)",
|
"LabelDurationComparisonExactMatch": "(dokładne dopasowanie)",
|
||||||
"LabelDurationComparisonLonger": "({0} dłużej)",
|
"LabelDurationComparisonLonger": "({0} dłużej)",
|
||||||
@ -334,6 +336,9 @@
|
|||||||
"LabelEncodingClearItemCache": "Pamiętaj o okresowym czyszczeniu pamięci podręcznej elementów.",
|
"LabelEncodingClearItemCache": "Pamiętaj o okresowym czyszczeniu pamięci podręcznej elementów.",
|
||||||
"LabelEncodingFinishedM4B": "Ukończony plik M4B zostanie umieszczony w folderze audiobooka pod adresem:",
|
"LabelEncodingFinishedM4B": "Ukończony plik M4B zostanie umieszczony w folderze audiobooka pod adresem:",
|
||||||
"LabelEncodingInfoEmbedded": "Metadane zostaną osadzone w ścieżkach audio w folderze z audiobookiem.",
|
"LabelEncodingInfoEmbedded": "Metadane zostaną osadzone w ścieżkach audio w folderze z audiobookiem.",
|
||||||
|
"LabelEncodingStartedNavigation": "Po uruchomieniu zadania możesz opuścić tę stronę.",
|
||||||
|
"LabelEncodingTimeWarning": "Konwersja może potrwać do 30 minut.",
|
||||||
|
"LabelEncodingWarningAdvancedSettings": "Ostrzeżenie: Nie aktualizuj tych ustawień, jeśli nie jesteś zaznajomiony ze sposobem działania ffmpeg i opcji konwersji.",
|
||||||
"LabelEncodingWatcherDisabled": "Jeśli monitorowanie folderów jest wyłączone, należy ponownie przeskanować audiobooka.",
|
"LabelEncodingWatcherDisabled": "Jeśli monitorowanie folderów jest wyłączone, należy ponownie przeskanować audiobooka.",
|
||||||
"LabelEnd": "Zakończ",
|
"LabelEnd": "Zakończ",
|
||||||
"LabelEndOfChapter": "Koniec rozdziału",
|
"LabelEndOfChapter": "Koniec rozdziału",
|
||||||
@ -583,8 +588,9 @@
|
|||||||
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Pozostały czas jest mniejszy niż (sekund)",
|
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Pozostały czas jest mniejszy niż (sekund)",
|
||||||
"LabelSettingsLibraryMarkAsFinishedWhen": "Oznacz element multimedialny jako ukończony, gdy",
|
"LabelSettingsLibraryMarkAsFinishedWhen": "Oznacz element multimedialny jako ukończony, gdy",
|
||||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Pomiń poprzednie książki przy kontynuacji serii",
|
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Pomiń poprzednie książki przy kontynuacji serii",
|
||||||
|
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Strona „Kontynuuj serię” wyświetla pierwszą nierozpoczętą książkę z serii, w której ukończono co najmniej jedną książkę i żadnej nie rozpoczęto. Włączając to ustawienie, będziesz kontynuować serię po przeczytaniu ostatniej książki, a nie od pierwszej nierozpoczętej książki z serii.",
|
||||||
"LabelSettingsParseSubtitles": "Przetwarzaj podtytuły",
|
"LabelSettingsParseSubtitles": "Przetwarzaj podtytuły",
|
||||||
"LabelSettingsParseSubtitlesHelp": "Opcja pozwala na pobranie podtytułu z nazwy folderu z audiobookiem. <br>Podtytuł musi być rozdzielony za pomocą separatora \" - \"<br>Przykład: \"Book Title - A Subtitle Here\" podtytuł \"A Subtitle Here\"",
|
"LabelSettingsParseSubtitlesHelp": "Opcja pozwala na pobranie podtytułu z nazwy folderu z audiobookiem. <br>Podtytuł musi być rozdzielony za pomocą separatora \" - \"<br>Przykład: \"Tytuł książki - Podtytuł\" podtytuł \"Podtytuł\"",
|
||||||
"LabelSettingsPreferMatchedMetadata": "Preferowanie dopasowanych metadanych",
|
"LabelSettingsPreferMatchedMetadata": "Preferowanie dopasowanych metadanych",
|
||||||
"LabelSettingsPreferMatchedMetadataHelp": "Dopasowane dane będą miały pierwszeństwo nad szczegółami pozycji podczas używania Szybkiego dopasowania. Domyślnie Szybkie dopasowanie uzupełnia tylko brakujące szczegóły.",
|
"LabelSettingsPreferMatchedMetadataHelp": "Dopasowane dane będą miały pierwszeństwo nad szczegółami pozycji podczas używania Szybkiego dopasowania. Domyślnie Szybkie dopasowanie uzupełnia tylko brakujące szczegóły.",
|
||||||
"LabelSettingsSkipMatchingBooksWithASIN": "Pomiń dopasowanie książek, które już mają ASIN",
|
"LabelSettingsSkipMatchingBooksWithASIN": "Pomiń dopasowanie książek, które już mają ASIN",
|
||||||
@ -696,46 +702,87 @@
|
|||||||
"LabelYourPlaylists": "Twoje playlisty",
|
"LabelYourPlaylists": "Twoje playlisty",
|
||||||
"LabelYourProgress": "Twój postęp",
|
"LabelYourProgress": "Twój postęp",
|
||||||
"MessageAppriseDescription": "Aby użyć tej funkcji, konieczne jest posiadanie instancji <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> albo innego rozwiązania, które obsługuje schemat zapytań Apprise. <br />URL do interfejsu API powinno być całkowitą ścieżką, np., jeśli Twoje API do powiadomień jest dostępne pod adresem <code>http://192.168.1.1:8337</code> to wpisany tutaj URL powinien mieć postać: <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Aby użyć tej funkcji, konieczne jest posiadanie instancji <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> albo innego rozwiązania, które obsługuje schemat zapytań Apprise. <br />URL do interfejsu API powinno być całkowitą ścieżką, np., jeśli Twoje API do powiadomień jest dostępne pod adresem <code>http://192.168.1.1:8337</code> to wpisany tutaj URL powinien mieć postać: <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Starsze tokeny API zostaną w przyszłości usunięte. Zamiast nich należy używać <a href=\"/config/api-keys\">kluczy API</a>.",
|
||||||
|
"MessageAuthenticationSecurityMessage": "Uwierzytelnianie zostało ulepszone ze względów bezpieczeństwa. Wszyscy użytkownicy muszą się ponownie zalogować.",
|
||||||
"MessageBackupsDescription": "Kopie zapasowe obejmują użytkowników, postępy użytkowników, szczegóły pozycji biblioteki, ustawienia serwera i obrazy przechowywane w <code>/metadata/items</code> & <code>/metadata/authors</code>. Kopie zapasowe nie obejmują żadnych plików przechowywanych w folderach biblioteki.",
|
"MessageBackupsDescription": "Kopie zapasowe obejmują użytkowników, postępy użytkowników, szczegóły pozycji biblioteki, ustawienia serwera i obrazy przechowywane w <code>/metadata/items</code> & <code>/metadata/authors</code>. Kopie zapasowe nie obejmują żadnych plików przechowywanych w folderach biblioteki.",
|
||||||
"MessageBackupsLocationEditNote": "Uwaga: Zmiana lokalizacji kopii zapasowej nie przenosi ani nie modyfikuje istniejących kopii zapasowych",
|
"MessageBackupsLocationEditNote": "Uwaga: Zmiana lokalizacji kopii zapasowej nie przenosi ani nie modyfikuje istniejących kopii zapasowych",
|
||||||
"MessageBackupsLocationNoEditNote": "Uwaga: Lokalizacja kopii zapasowej jest ustawiona poprzez zmienną środowiskową i nie może być tutaj zmieniona.",
|
"MessageBackupsLocationNoEditNote": "Uwaga: Lokalizacja kopii zapasowej jest ustawiona poprzez zmienną środowiskową i nie może być tutaj zmieniona.",
|
||||||
"MessageBackupsLocationPathEmpty": "Ścieżka do kopii zapasowej nie może być pusta",
|
"MessageBackupsLocationPathEmpty": "Ścieżka do kopii zapasowej nie może być pusta",
|
||||||
|
"MessageBatchEditPopulateMapDetailsAllHelp": "Wypełnij włączone pola danymi ze wszystkich elementów. Pola z wieloma wartościami zostaną scalone.",
|
||||||
"MessageBatchQuickMatchDescription": "Quick Match będzie próbował dodać brakujące okładki i metadane dla wybranych elementów. Włącz poniższe opcje, aby umożliwić Quick Match nadpisanie istniejących okładek i/lub metadanych.",
|
"MessageBatchQuickMatchDescription": "Quick Match będzie próbował dodać brakujące okładki i metadane dla wybranych elementów. Włącz poniższe opcje, aby umożliwić Quick Match nadpisanie istniejących okładek i/lub metadanych.",
|
||||||
"MessageBookshelfNoCollections": "Nie posiadasz jeszcze żadnych kolekcji",
|
"MessageBookshelfNoCollections": "Nie posiadasz jeszcze żadnych kolekcji",
|
||||||
|
"MessageBookshelfNoCollectionsHelp": "Kolekcje są publiczne. Wszyscy użytkownicy mający dostęp do biblioteki mogą je zobaczyć.",
|
||||||
"MessageBookshelfNoRSSFeeds": "Nie posiadasz żadnych otwartych feedów RSS",
|
"MessageBookshelfNoRSSFeeds": "Nie posiadasz żadnych otwartych feedów RSS",
|
||||||
"MessageBookshelfNoResultsForFilter": "Nie znaleziono żadnych pozycji przy aktualnym filtrowaniu \"{0}: {1}\"",
|
"MessageBookshelfNoResultsForFilter": "Nie znaleziono żadnych pozycji przy aktualnym filtrowaniu \"{0}: {1}\"",
|
||||||
"MessageBookshelfNoResultsForQuery": "Brak wyników zapytania",
|
"MessageBookshelfNoResultsForQuery": "Brak wyników zapytania",
|
||||||
"MessageBookshelfNoSeries": "Nie masz jeszcze żadnych serii",
|
"MessageBookshelfNoSeries": "Nie masz jeszcze żadnych serii",
|
||||||
"MessageChapterEndIsAfter": "Koniec rozdziału następuje po zakończeniu audiobooka",
|
"MessageChapterEndIsAfter": "Koniec rozdziału następuje po zakończeniu audiobooka",
|
||||||
"MessageChapterErrorFirstNotZero": "Pierwszy rozdział musi rozpoczynać się na 0",
|
"MessageChapterErrorFirstNotZero": "Pierwszy rozdział musi rozpoczynać się na 0",
|
||||||
|
"MessageChapterErrorStartGteDuration": "Nieprawidłowy czas rozpoczęcia, musi być krótszy niż długość audiobooka",
|
||||||
|
"MessageChapterErrorStartLtPrev": "Nieprawidłowy czas rozpoczęcia, musi być większy lub taki sam, jak czas rozpoczęcia poprzedniego rozdziału.",
|
||||||
"MessageChapterStartIsAfter": "Początek rozdziału następuje po zakończeniu audiobooka",
|
"MessageChapterStartIsAfter": "Początek rozdziału następuje po zakończeniu audiobooka",
|
||||||
|
"MessageChaptersNotFound": "Nie znaleziono rozdziałów",
|
||||||
"MessageCheckingCron": "Sprawdzanie cron...",
|
"MessageCheckingCron": "Sprawdzanie cron...",
|
||||||
|
"MessageConfirmCloseFeed": "Czy na pewno chcesz zamknąć ten kanał?",
|
||||||
|
"MessageConfirmDeleteApiKey": "Czy na pewno chcesz usunąć klucz API \"{0}\"?",
|
||||||
"MessageConfirmDeleteBackup": "Czy na pewno chcesz usunąć kopię zapasową dla {0}?",
|
"MessageConfirmDeleteBackup": "Czy na pewno chcesz usunąć kopię zapasową dla {0}?",
|
||||||
|
"MessageConfirmDeleteDevice": "Czy na pewno chcesz usunąć czytnik e-booków \"{0}\"?",
|
||||||
"MessageConfirmDeleteFile": "Ta operacja usunie plik z twojego dysku. Jesteś pewien?",
|
"MessageConfirmDeleteFile": "Ta operacja usunie plik z twojego dysku. Jesteś pewien?",
|
||||||
"MessageConfirmDeleteLibrary": "Czy na pewno chcesz trwale usunąć bibliotekę \"{0}\"?",
|
"MessageConfirmDeleteLibrary": "Czy na pewno chcesz trwale usunąć bibliotekę \"{0}\"?",
|
||||||
"MessageConfirmDeleteLibraryItem": "Ta operacja usunie pozycję biblioteki z bazy danych i z dysku. Czy jesteś pewien?",
|
"MessageConfirmDeleteLibraryItem": "Ta operacja usunie pozycję biblioteki z bazy danych i z dysku. Czy jesteś pewien?",
|
||||||
|
"MessageConfirmDeleteLibraryItems": "{0} element(ów) zostanie teraz usuniętych z bazy danych i systemu plików. Czy jesteś pewien?",
|
||||||
|
"MessageConfirmDeleteMetadataProvider": "Czy na pewno chcesz usunąć niestandardowego dostawcę metadanych: \"{0}\"?",
|
||||||
|
"MessageConfirmDeleteNotification": "Czy na pewno chcesz usunąć to powiadomienie?",
|
||||||
"MessageConfirmDeleteSession": "Czy na pewno chcesz usunąć tę sesję?",
|
"MessageConfirmDeleteSession": "Czy na pewno chcesz usunąć tę sesję?",
|
||||||
|
"MessageConfirmEmbedMetadataInAudioFiles": "Czy na pewno chcesz osadzić metadane w {0} plikach audio?",
|
||||||
"MessageConfirmForceReScan": "Czy na pewno chcesz wymusić ponowne skanowanie?",
|
"MessageConfirmForceReScan": "Czy na pewno chcesz wymusić ponowne skanowanie?",
|
||||||
"MessageConfirmMarkAllEpisodesFinished": "Czy na pewno chcesz oznaczyć wszystkie odcinki jako ukończone?",
|
"MessageConfirmMarkAllEpisodesFinished": "Czy na pewno chcesz oznaczyć wszystkie odcinki jako ukończone?",
|
||||||
"MessageConfirmMarkAllEpisodesNotFinished": "Czy na pewno chcesz oznaczyć wszystkie odcinki jako nieukończone?",
|
"MessageConfirmMarkAllEpisodesNotFinished": "Czy na pewno chcesz oznaczyć wszystkie odcinki jako nieukończone?",
|
||||||
|
"MessageConfirmMarkItemFinished": "Czy na pewno chcesz oznaczyć \"{0}\" jako zakończone?",
|
||||||
|
"MessageConfirmMarkItemNotFinished": "Czy na pewno chcesz oznaczyć \"{0}\" jako nieukończone?",
|
||||||
"MessageConfirmMarkSeriesFinished": "Czy na pewno chcesz oznaczyć wszystkie książki w tej serii jako ukończone?",
|
"MessageConfirmMarkSeriesFinished": "Czy na pewno chcesz oznaczyć wszystkie książki w tej serii jako ukończone?",
|
||||||
"MessageConfirmMarkSeriesNotFinished": "Czy na pewno chcesz oznaczyć wszystkie książki w tej serii jako nieukończone?",
|
"MessageConfirmMarkSeriesNotFinished": "Czy na pewno chcesz oznaczyć wszystkie książki w tej serii jako nieukończone?",
|
||||||
|
"MessageConfirmNotificationTestTrigger": "Czy wywołać to powiadomienie za pomocą danych testowych?",
|
||||||
|
"MessageConfirmPurgeCache": "Wyczyszczenie pamięci podręcznej spowoduje usunięcie całego katalogu <code>/metadata/cache</code>. <br /><br />Czy na pewno chcesz usunąć katalog pamięci podręcznej?",
|
||||||
|
"MessageConfirmPurgeItemsCache": "Wyczyszczenie pamięci podręcznej elementów spowoduje usunięcie całego katalogu <code>/metadata/cache/items</code>.<br />Czy jesteś pewien?",
|
||||||
|
"MessageConfirmQuickEmbed": "Ostrzeżenie! Szybkie osadzanie nie utworzy kopii zapasowej plików audio. Upewnij się, że masz kopię zapasową plików audio. <br><br>Czy chcesz kontynuować?",
|
||||||
|
"MessageConfirmQuickMatchEpisodes": "Szybkie dopasowywanie odcinków spowoduje nadpisanie szczegółów w przypadku znalezienia dopasowania. Zaktualizowane zostaną tylko niedopasowane odcinki. Jesteś pewien?",
|
||||||
|
"MessageConfirmReScanLibraryItems": "Czy na pewno chcesz ponownie zeskanować {0} pozycji?",
|
||||||
|
"MessageConfirmRemoveAllChapters": "Czy na pewno chcesz usunąć wszystkie rozdziały?",
|
||||||
|
"MessageConfirmRemoveAuthor": "Czy na pewno chcesz usunąć autora \"{0}\"?",
|
||||||
"MessageConfirmRemoveCollection": "Czy na pewno chcesz usunąć kolekcję \"{0}\"?",
|
"MessageConfirmRemoveCollection": "Czy na pewno chcesz usunąć kolekcję \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "Czy na pewno chcesz usunąć odcinek \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "Czy na pewno chcesz usunąć odcinek \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveEpisodeNote": "Uwaga: Plik audio nie zostanie usunięty, chyba że przełączysz opcję „Twarde usunięcie pliku”",
|
||||||
"MessageConfirmRemoveEpisodes": "Czy na pewno chcesz usunąć {0} odcinki?",
|
"MessageConfirmRemoveEpisodes": "Czy na pewno chcesz usunąć {0} odcinki?",
|
||||||
"MessageConfirmRemoveListeningSessions": "Czy na pewno chcesz usunąć {0} sesji słuchania?",
|
"MessageConfirmRemoveListeningSessions": "Czy na pewno chcesz usunąć {0} sesji słuchania?",
|
||||||
|
"MessageConfirmRemoveMetadataFiles": "Czy na pewno chcesz usunąć wszystkie metadane.{0} plików w folderach elementów biblioteki?",
|
||||||
|
"MessageConfirmRemoveNarrator": "Czy na pewno chcesz usunąć lektora \"{0}\"?",
|
||||||
"MessageConfirmRemovePlaylist": "Czy jesteś pewien, że chcesz usunąć twoją playlistę \"{0}\"?",
|
"MessageConfirmRemovePlaylist": "Czy jesteś pewien, że chcesz usunąć twoją playlistę \"{0}\"?",
|
||||||
|
"MessageConfirmRenameGenre": "Czy na pewno chcesz zmienić nazwę gatunku \"{0}\" na \"{1}\" dla wszystkich elementów?",
|
||||||
|
"MessageConfirmRenameGenreMergeNote": "Uwaga: Ten gatunek już istnieje, więc zostaną połączone.",
|
||||||
|
"MessageConfirmRenameGenreWarning": "Uwaga! Podobny gatunek z inną wielkością liter już istnieje: \"{0}\".",
|
||||||
|
"MessageConfirmRenameTag": "Czy na pewno chcesz zmienić nazwę tagu \"{0}\" na \"{1}\" dla wszystkich elementów?",
|
||||||
|
"MessageConfirmRenameTagMergeNote": "Uwaga: Ten tag już istnieje, więc zostaną scalone.",
|
||||||
|
"MessageConfirmRenameTagWarning": "Uwaga! Podobny tag z inną wielkością liter już istnieje: \"{0}\".",
|
||||||
|
"MessageConfirmResetProgress": "Czy na pewno chcesz zresetować swój postęp?",
|
||||||
|
"MessageConfirmSendEbookToDevice": "Czy na pewno chcesz wysłać {0} e-booka \"{1}\" na urządzenie \"{2}\"?",
|
||||||
|
"MessageConfirmUnlinkOpenId": "Czy na pewno chcesz odłączyć tego użytkownika od OpenID?",
|
||||||
|
"MessageDaysListenedInTheLastYear": "{0} dni słuchania w ciągu ostatniego roku",
|
||||||
"MessageDownloadingEpisode": "Pobieranie odcinka",
|
"MessageDownloadingEpisode": "Pobieranie odcinka",
|
||||||
"MessageDragFilesIntoTrackOrder": "przeciągnij pliki aby ustawić właściwą kolejność utworów",
|
"MessageDragFilesIntoTrackOrder": "przeciągnij pliki aby ustawić właściwą kolejność utworów",
|
||||||
"MessageEmbedFailed": "Niepowodzenie wstawiania!",
|
"MessageEmbedFailed": "Niepowodzenie wstawiania!",
|
||||||
"MessageEmbedFinished": "Osadzanie zakończone!",
|
"MessageEmbedFinished": "Osadzanie zakończone!",
|
||||||
|
"MessageEmbedQueue": "W kolejce do osadzenia metadanych ({0} w kolejce)",
|
||||||
"MessageEpisodesQueuedForDownload": "{0} odcinki w kolejce do pobrania",
|
"MessageEpisodesQueuedForDownload": "{0} odcinki w kolejce do pobrania",
|
||||||
|
"MessageEreaderDevices": "Aby zagwarantować dostawę e-booków, konieczne może być dodanie powyższego adresu e-mail jako prawidłowego nadawcy dla każdego z urządzeń wymienionych poniżej.",
|
||||||
"MessageFeedURLWillBe": "URL kanału: {0}",
|
"MessageFeedURLWillBe": "URL kanału: {0}",
|
||||||
"MessageFetching": "Pobieranie...",
|
"MessageFetching": "Pobieranie...",
|
||||||
"MessageForceReScanDescription": "przeskanuje wszystkie pliki ponownie, jak przy świeżym skanowaniu. Tagi ID3 plików audio, pliki OPF i pliki tekstowe będą skanowane jak nowe.",
|
"MessageForceReScanDescription": "przeskanuje wszystkie pliki ponownie, jak przy świeżym skanowaniu. Tagi ID3 plików audio, pliki OPF i pliki tekstowe będą skanowane jak nowe.",
|
||||||
"MessageImportantNotice": "Ważna informacja!",
|
"MessageImportantNotice": "Ważna informacja!",
|
||||||
"MessageInsertChapterBelow": "Wstaw rozdział poniżej",
|
"MessageInsertChapterBelow": "Wstaw rozdział poniżej",
|
||||||
|
"MessageInvalidAsin": "Nieprawidłowy ASIN",
|
||||||
"MessageItemsSelected": "{0} zaznaczone elementy",
|
"MessageItemsSelected": "{0} zaznaczone elementy",
|
||||||
|
"MessageItemsUpdated": "Zaktualizowano {0} elementów",
|
||||||
"MessageJoinUsOn": "Dołącz do nas na",
|
"MessageJoinUsOn": "Dołącz do nas na",
|
||||||
"MessageLoading": "Ładowanie...",
|
"MessageLoading": "Ładowanie...",
|
||||||
"MessageLoadingFolders": "Ładowanie folderów...",
|
"MessageLoadingFolders": "Ładowanie folderów...",
|
||||||
@ -756,6 +803,9 @@
|
|||||||
"MessageNoCollections": "Brak kolekcji",
|
"MessageNoCollections": "Brak kolekcji",
|
||||||
"MessageNoCoversFound": "Okładki nieznalezione",
|
"MessageNoCoversFound": "Okładki nieznalezione",
|
||||||
"MessageNoDescription": "Brak opisu",
|
"MessageNoDescription": "Brak opisu",
|
||||||
|
"MessageNoDevices": "Brak urządzeń",
|
||||||
|
"MessageNoDownloadsInProgress": "Brak aktualnie trwających pobrań",
|
||||||
|
"MessageNoDownloadsQueued": "Brak pobrań w kolejce",
|
||||||
"MessageNoEpisodeMatchesFound": "Nie znaleziono pasujących odcinków",
|
"MessageNoEpisodeMatchesFound": "Nie znaleziono pasujących odcinków",
|
||||||
"MessageNoEpisodes": "Brak odcinków",
|
"MessageNoEpisodes": "Brak odcinków",
|
||||||
"MessageNoFoldersAvailable": "Brak dostępnych folderów",
|
"MessageNoFoldersAvailable": "Brak dostępnych folderów",
|
||||||
@ -767,25 +817,35 @@
|
|||||||
"MessageNoLogs": "Brak logów",
|
"MessageNoLogs": "Brak logów",
|
||||||
"MessageNoMediaProgress": "Brak postępu",
|
"MessageNoMediaProgress": "Brak postępu",
|
||||||
"MessageNoNotifications": "Brak powiadomień",
|
"MessageNoNotifications": "Brak powiadomień",
|
||||||
|
"MessageNoPodcastFeed": "Nieprawidłowy podcast: Brak kanału",
|
||||||
"MessageNoPodcastsFound": "Nie znaleziono podcastów",
|
"MessageNoPodcastsFound": "Nie znaleziono podcastów",
|
||||||
"MessageNoResults": "Brak wyników",
|
"MessageNoResults": "Brak wyników",
|
||||||
"MessageNoSearchResultsFor": "Brak wyników wyszukiwania dla \"{0}\"",
|
"MessageNoSearchResultsFor": "Brak wyników wyszukiwania dla \"{0}\"",
|
||||||
|
"MessageNoSeries": "Brak serii",
|
||||||
|
"MessageNoTags": "Brak tagów",
|
||||||
"MessageNoTasksRunning": "Brak uruchomionych zadań",
|
"MessageNoTasksRunning": "Brak uruchomionych zadań",
|
||||||
"MessageNoUpdatesWereNecessary": "Brak aktualizacji",
|
"MessageNoUpdatesWereNecessary": "Brak aktualizacji",
|
||||||
"MessageNoUserPlaylists": "Nie masz żadnych list odtwarzania",
|
"MessageNoUserPlaylists": "Nie masz żadnych list odtwarzania",
|
||||||
|
"MessageNoUserPlaylistsHelp": "Listy odtwarzania są prywatne. Tylko użytkownik, który je utworzył, może je zobaczyć.",
|
||||||
"MessageNotYetImplemented": "Jeszcze nie zaimplementowane",
|
"MessageNotYetImplemented": "Jeszcze nie zaimplementowane",
|
||||||
"MessageOpmlPreviewNote": "Uwaga: To jest podgląd sparsowanego pliku OPML. Tytuł podcastu wzięty został z wątku RSS.",
|
"MessageOpmlPreviewNote": "Uwaga: To jest podgląd sparsowanego pliku OPML. Tytuł podcastu wzięty został z wątku RSS.",
|
||||||
"MessageOr": "lub",
|
"MessageOr": "lub",
|
||||||
"MessagePauseChapter": "Zatrzymaj odtwarzanie rozdziały",
|
"MessagePauseChapter": "Zatrzymaj odtwarzanie rozdziały",
|
||||||
"MessagePlayChapter": "Rozpocznij odtwarzanie od początku rozdziału",
|
"MessagePlayChapter": "Rozpocznij odtwarzanie od początku rozdziału",
|
||||||
"MessagePlaylistCreateFromCollection": "Utwórz listę odtwarzania na podstawie kolekcji",
|
"MessagePlaylistCreateFromCollection": "Utwórz listę odtwarzania na podstawie kolekcji",
|
||||||
|
"MessagePleaseWait": "Proszę czekać...",
|
||||||
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nie ma adresu url kanału RSS, który mógłby zostać użyty do dopasowania",
|
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nie ma adresu url kanału RSS, który mógłby zostać użyty do dopasowania",
|
||||||
|
"MessagePodcastSearchField": "Wprowadź wyszukiwane hasło lub adres URL kanału RSS",
|
||||||
|
"MessageQuickEmbedInProgress": "Szybkie osadzanie w toku",
|
||||||
|
"MessageQuickEmbedQueue": "W kolejce do szybkiego osadzenia ({0} w kolejce)",
|
||||||
|
"MessageQuickMatchAllEpisodes": "Szybkie dopasowanie wszystkich odcinków",
|
||||||
"MessageQuickMatchDescription": "Wypełnij puste informacje i okładkę pierwszym wynikiem dopasowania z '{0}'. Nie nadpisuje szczegółów, chyba że włączone jest ustawienie serwera 'Preferuj dopasowane metadane'.",
|
"MessageQuickMatchDescription": "Wypełnij puste informacje i okładkę pierwszym wynikiem dopasowania z '{0}'. Nie nadpisuje szczegółów, chyba że włączone jest ustawienie serwera 'Preferuj dopasowane metadane'.",
|
||||||
"MessageRemoveChapter": "Usuń rozdział",
|
"MessageRemoveChapter": "Usuń rozdział",
|
||||||
"MessageRemoveEpisodes": "Usuń {0} odcinków",
|
"MessageRemoveEpisodes": "Usuń {0} odcinków",
|
||||||
"MessageRemoveFromPlayerQueue": "Usuń z kolejki odtwarzacza",
|
"MessageRemoveFromPlayerQueue": "Usuń z kolejki odtwarzacza",
|
||||||
"MessageRemoveUserWarning": "Czy na pewno chcesz trwale usunąć użytkownika \"{0}\"?",
|
"MessageRemoveUserWarning": "Czy na pewno chcesz trwale usunąć użytkownika \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "Zgłoś błędy, pomysły i pomóż rozwijać aplikację na",
|
"MessageReportBugsAndContribute": "Zgłoś błędy, pomysły i pomóż rozwijać aplikację na",
|
||||||
|
"MessageResetChaptersConfirm": "Czy na pewno chcesz zresetować rozdziały i cofnąć wprowadzone zmiany?",
|
||||||
"MessageRestoreBackupConfirm": "Czy na pewno chcesz przywrócić kopię zapasową utworzoną w dniu",
|
"MessageRestoreBackupConfirm": "Czy na pewno chcesz przywrócić kopię zapasową utworzoną w dniu",
|
||||||
"MessageRestoreBackupWarning": "Przywrócenie kopii zapasowej spowoduje nadpisanie bazy danych w folderze /config oraz okładek w folderze /metadata/items & /metadata/authors.<br /><br />Kopie zapasowe nie modyfikują żadnego pliku w folderach z plikami audio. Jeśli włączyłeś ustawienia serwera, aby przechowywać okładki i metadane w folderach biblioteki, to nie są one zapisywane w kopii zapasowej lub nadpisywane<br /><br />Wszyscy klienci korzystający z Twojego serwera będą automatycznie odświeżani.",
|
"MessageRestoreBackupWarning": "Przywrócenie kopii zapasowej spowoduje nadpisanie bazy danych w folderze /config oraz okładek w folderze /metadata/items & /metadata/authors.<br /><br />Kopie zapasowe nie modyfikują żadnego pliku w folderach z plikami audio. Jeśli włączyłeś ustawienia serwera, aby przechowywać okładki i metadane w folderach biblioteki, to nie są one zapisywane w kopii zapasowej lub nadpisywane<br /><br />Wszyscy klienci korzystający z Twojego serwera będą automatycznie odświeżani.",
|
||||||
"MessageSearchResultsFor": "Wyniki wyszukiwania dla",
|
"MessageSearchResultsFor": "Wyniki wyszukiwania dla",
|
||||||
|
@ -199,6 +199,7 @@
|
|||||||
"HeaderSettingsExperimental": "Экспериментальные функции",
|
"HeaderSettingsExperimental": "Экспериментальные функции",
|
||||||
"HeaderSettingsGeneral": "Основные",
|
"HeaderSettingsGeneral": "Основные",
|
||||||
"HeaderSettingsScanner": "Сканер",
|
"HeaderSettingsScanner": "Сканер",
|
||||||
|
"HeaderSettingsSecurity": "Безопасность",
|
||||||
"HeaderSettingsWebClient": "Веб-клиент",
|
"HeaderSettingsWebClient": "Веб-клиент",
|
||||||
"HeaderSleepTimer": "Таймер сна",
|
"HeaderSleepTimer": "Таймер сна",
|
||||||
"HeaderStatsLargestItems": "Самые большые элементы",
|
"HeaderStatsLargestItems": "Самые большые элементы",
|
||||||
@ -293,6 +294,7 @@
|
|||||||
"LabelContinueListening": "Продолжить слушать",
|
"LabelContinueListening": "Продолжить слушать",
|
||||||
"LabelContinueReading": "Продолжить чтение",
|
"LabelContinueReading": "Продолжить чтение",
|
||||||
"LabelContinueSeries": "Продолжить серию",
|
"LabelContinueSeries": "Продолжить серию",
|
||||||
|
"LabelCorsAllowed": "Разрешённые CORS источники",
|
||||||
"LabelCover": "Обложка",
|
"LabelCover": "Обложка",
|
||||||
"LabelCoverImageURL": "URL изображения обложки",
|
"LabelCoverImageURL": "URL изображения обложки",
|
||||||
"LabelCoverProvider": "Провайдер обложек",
|
"LabelCoverProvider": "Провайдер обложек",
|
||||||
@ -418,6 +420,7 @@
|
|||||||
"LabelLanguages": "Языки",
|
"LabelLanguages": "Языки",
|
||||||
"LabelLastBookAdded": "Последняя книга добавлена",
|
"LabelLastBookAdded": "Последняя книга добавлена",
|
||||||
"LabelLastBookUpdated": "Последняя книга обновлена",
|
"LabelLastBookUpdated": "Последняя книга обновлена",
|
||||||
|
"LabelLastProgressDate": "Последний прогресс: {0}",
|
||||||
"LabelLastSeen": "Последнее сканирование",
|
"LabelLastSeen": "Последнее сканирование",
|
||||||
"LabelLastTime": "Последний по времени",
|
"LabelLastTime": "Последний по времени",
|
||||||
"LabelLastUpdate": "Последний обновленный",
|
"LabelLastUpdate": "Последний обновленный",
|
||||||
@ -430,6 +433,7 @@
|
|||||||
"LabelLibraryFilterSublistEmpty": "Нет {0}",
|
"LabelLibraryFilterSublistEmpty": "Нет {0}",
|
||||||
"LabelLibraryItem": "Элемент библиотеки",
|
"LabelLibraryItem": "Элемент библиотеки",
|
||||||
"LabelLibraryName": "Имя библиотеки",
|
"LabelLibraryName": "Имя библиотеки",
|
||||||
|
"LabelLibrarySortByProgress": "Прогресс обновлён",
|
||||||
"LabelLimit": "Лимит",
|
"LabelLimit": "Лимит",
|
||||||
"LabelLineSpacing": "Межстрочный интервал",
|
"LabelLineSpacing": "Межстрочный интервал",
|
||||||
"LabelListenAgain": "Послушать снова",
|
"LabelListenAgain": "Послушать снова",
|
||||||
@ -803,6 +807,8 @@
|
|||||||
"MessageFeedURLWillBe": "URL канала будет {0}",
|
"MessageFeedURLWillBe": "URL канала будет {0}",
|
||||||
"MessageFetching": "Завершается...",
|
"MessageFetching": "Завершается...",
|
||||||
"MessageForceReScanDescription": "будет сканировать все файлы снова, как свежее сканирование. Теги ID3 аудиофайлов, OPF-файлы и текстовые файлы будут сканироваться как новые.",
|
"MessageForceReScanDescription": "будет сканировать все файлы снова, как свежее сканирование. Теги ID3 аудиофайлов, OPF-файлы и текстовые файлы будут сканироваться как новые.",
|
||||||
|
"MessageHeatmapListeningTimeTooltip": "<strong>{0} прослушивание</strong> на {1}",
|
||||||
|
"MessageHeatmapNoListeningSessions": "Нет сессий прослушивания на {0}",
|
||||||
"MessageImportantNotice": "Важное замечание!",
|
"MessageImportantNotice": "Важное замечание!",
|
||||||
"MessageInsertChapterBelow": "Вставить главу ниже",
|
"MessageInsertChapterBelow": "Вставить главу ниже",
|
||||||
"MessageInvalidAsin": "Неправильный ASIN",
|
"MessageInvalidAsin": "Неправильный ASIN",
|
||||||
@ -1030,6 +1036,7 @@
|
|||||||
"ToastInvalidImageUrl": "Неверный URL изображения",
|
"ToastInvalidImageUrl": "Неверный URL изображения",
|
||||||
"ToastInvalidMaxEpisodesToDownload": "Недопустимое максимальное количество загружаемых эпизодов",
|
"ToastInvalidMaxEpisodesToDownload": "Недопустимое максимальное количество загружаемых эпизодов",
|
||||||
"ToastInvalidUrl": "Неверный URL",
|
"ToastInvalidUrl": "Неверный URL",
|
||||||
|
"ToastInvalidUrls": "Один или несколько URL неверны",
|
||||||
"ToastItemCoverUpdateSuccess": "Обложка элемента обновлена",
|
"ToastItemCoverUpdateSuccess": "Обложка элемента обновлена",
|
||||||
"ToastItemDeletedFailed": "Не удалось удалить элемент",
|
"ToastItemDeletedFailed": "Не удалось удалить элемент",
|
||||||
"ToastItemDeletedSuccess": "Удаленный элемент",
|
"ToastItemDeletedSuccess": "Удаленный элемент",
|
||||||
|
@ -199,6 +199,7 @@
|
|||||||
"HeaderSettingsExperimental": "Експериментальні функції",
|
"HeaderSettingsExperimental": "Експериментальні функції",
|
||||||
"HeaderSettingsGeneral": "Основне",
|
"HeaderSettingsGeneral": "Основне",
|
||||||
"HeaderSettingsScanner": "Сканер",
|
"HeaderSettingsScanner": "Сканер",
|
||||||
|
"HeaderSettingsSecurity": "Безпека",
|
||||||
"HeaderSettingsWebClient": "Вебклієнт",
|
"HeaderSettingsWebClient": "Вебклієнт",
|
||||||
"HeaderSleepTimer": "Таймер вимкнення",
|
"HeaderSleepTimer": "Таймер вимкнення",
|
||||||
"HeaderStatsLargestItems": "Найбільші елементи",
|
"HeaderStatsLargestItems": "Найбільші елементи",
|
||||||
@ -293,6 +294,7 @@
|
|||||||
"LabelContinueListening": "Слухати далі",
|
"LabelContinueListening": "Слухати далі",
|
||||||
"LabelContinueReading": "Читати далі",
|
"LabelContinueReading": "Читати далі",
|
||||||
"LabelContinueSeries": "Продовжити серії",
|
"LabelContinueSeries": "Продовжити серії",
|
||||||
|
"LabelCorsAllowed": "Дозволені джерела CORS",
|
||||||
"LabelCover": "Обкладинка",
|
"LabelCover": "Обкладинка",
|
||||||
"LabelCoverImageURL": "URL-адреса обкладинки",
|
"LabelCoverImageURL": "URL-адреса обкладинки",
|
||||||
"LabelCoverProvider": "Постачальник покриття",
|
"LabelCoverProvider": "Постачальник покриття",
|
||||||
@ -1034,6 +1036,7 @@
|
|||||||
"ToastInvalidImageUrl": "Невірний URL зображення",
|
"ToastInvalidImageUrl": "Невірний URL зображення",
|
||||||
"ToastInvalidMaxEpisodesToDownload": "Невірна кількість епізодів для скачування",
|
"ToastInvalidMaxEpisodesToDownload": "Невірна кількість епізодів для скачування",
|
||||||
"ToastInvalidUrl": "Невірний URL",
|
"ToastInvalidUrl": "Невірний URL",
|
||||||
|
"ToastInvalidUrls": "Одна або декілька URL-адрес недійсні",
|
||||||
"ToastItemCoverUpdateSuccess": "Обкладинку елемента оновлено",
|
"ToastItemCoverUpdateSuccess": "Обкладинку елемента оновлено",
|
||||||
"ToastItemDeletedFailed": "Не вдалося видалити елемент",
|
"ToastItemDeletedFailed": "Не вдалося видалити елемент",
|
||||||
"ToastItemDeletedSuccess": "Видалений елемент",
|
"ToastItemDeletedSuccess": "Видалений елемент",
|
||||||
|
@ -199,6 +199,7 @@
|
|||||||
"HeaderSettingsExperimental": "实验功能",
|
"HeaderSettingsExperimental": "实验功能",
|
||||||
"HeaderSettingsGeneral": "通用",
|
"HeaderSettingsGeneral": "通用",
|
||||||
"HeaderSettingsScanner": "扫描",
|
"HeaderSettingsScanner": "扫描",
|
||||||
|
"HeaderSettingsSecurity": "安全",
|
||||||
"HeaderSettingsWebClient": "网页客户端",
|
"HeaderSettingsWebClient": "网页客户端",
|
||||||
"HeaderSleepTimer": "睡眠计时",
|
"HeaderSleepTimer": "睡眠计时",
|
||||||
"HeaderStatsLargestItems": "最大的项目",
|
"HeaderStatsLargestItems": "最大的项目",
|
||||||
@ -293,6 +294,7 @@
|
|||||||
"LabelContinueListening": "继续收听",
|
"LabelContinueListening": "继续收听",
|
||||||
"LabelContinueReading": "继续阅读",
|
"LabelContinueReading": "继续阅读",
|
||||||
"LabelContinueSeries": "继续收听系列",
|
"LabelContinueSeries": "继续收听系列",
|
||||||
|
"LabelCorsAllowed": "允许的跨域来源",
|
||||||
"LabelCover": "封面",
|
"LabelCover": "封面",
|
||||||
"LabelCoverImageURL": "封面图像 URL",
|
"LabelCoverImageURL": "封面图像 URL",
|
||||||
"LabelCoverProvider": "封面提供者",
|
"LabelCoverProvider": "封面提供者",
|
||||||
@ -1034,6 +1036,7 @@
|
|||||||
"ToastInvalidImageUrl": "图片网址无效",
|
"ToastInvalidImageUrl": "图片网址无效",
|
||||||
"ToastInvalidMaxEpisodesToDownload": "可下载的最大集数无效",
|
"ToastInvalidMaxEpisodesToDownload": "可下载的最大集数无效",
|
||||||
"ToastInvalidUrl": "网址无效",
|
"ToastInvalidUrl": "网址无效",
|
||||||
|
"ToastInvalidUrls": "一个或多个 URL 无效",
|
||||||
"ToastItemCoverUpdateSuccess": "项目封面已更新",
|
"ToastItemCoverUpdateSuccess": "项目封面已更新",
|
||||||
"ToastItemDeletedFailed": "删除项目失败",
|
"ToastItemDeletedFailed": "删除项目失败",
|
||||||
"ToastItemDeletedSuccess": "已删除项目",
|
"ToastItemDeletedSuccess": "已删除项目",
|
||||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.27.0",
|
"version": "2.28.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.27.0",
|
"version": "2.28.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.27.0",
|
"version": "2.28.0",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
@ -213,6 +213,7 @@ class Auth {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {string} authMethod - The authentication method, default is 'local'.
|
* @param {string} authMethod - The authentication method, default is 'local'.
|
||||||
|
* @returns {Object|null} - Returns error object if validation fails, null if successful
|
||||||
*/
|
*/
|
||||||
paramsToCookies(req, res, authMethod = 'local') {
|
paramsToCookies(req, res, authMethod = 'local') {
|
||||||
const TWO_MINUTES = 120000 // 2 minutes in milliseconds
|
const TWO_MINUTES = 120000 // 2 minutes in milliseconds
|
||||||
@ -227,13 +228,24 @@ class Auth {
|
|||||||
|
|
||||||
// Validate and store the callback URL
|
// Validate and store the callback URL
|
||||||
if (!callback) {
|
if (!callback) {
|
||||||
return res.status(400).send({ message: 'No callback parameter' })
|
res.status(400).send({ message: 'No callback parameter' })
|
||||||
|
return { error: 'No callback parameter' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Security: Validate callback URL is same-origin only
|
||||||
|
if (!this.oidcAuthStrategy.isValidWebCallbackUrl(callback, req)) {
|
||||||
|
Logger.warn(`[Auth] Rejected invalid callback URL: ${callback}`)
|
||||||
|
res.status(400).send({ message: 'Invalid callback URL - must be same-origin' })
|
||||||
|
return { error: 'Invalid callback URL - must be same-origin' }
|
||||||
|
}
|
||||||
|
|
||||||
res.cookie('auth_cb', callback, { maxAge: TWO_MINUTES, httpOnly: true })
|
res.cookie('auth_cb', callback, { maxAge: TWO_MINUTES, httpOnly: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the authentication method for long
|
// Store the authentication method for long
|
||||||
|
Logger.debug(`[Auth] paramsToCookies: setting auth_method cookie to ${authMethod}`)
|
||||||
res.cookie('auth_method', authMethod, { maxAge: 1000 * 60 * 60 * 24 * 365 * 10, httpOnly: true })
|
res.cookie('auth_method', authMethod, { maxAge: 1000 * 60 * 60 * 24 * 365 * 10, httpOnly: true })
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -247,6 +259,7 @@ class Auth {
|
|||||||
// Handle token generation and get userResponse object
|
// Handle token generation and get userResponse object
|
||||||
// For API based auth (e.g. mobile), we will return the refresh token in the response
|
// For API based auth (e.g. mobile), we will return the refresh token in the response
|
||||||
const isApiBased = this.isAuthMethodAPIBased(req.cookies.auth_method)
|
const isApiBased = this.isAuthMethodAPIBased(req.cookies.auth_method)
|
||||||
|
Logger.debug(`[Auth] handleLoginSuccessBasedOnCookie: isApiBased: ${isApiBased}, auth_method: ${req.cookies.auth_method}`)
|
||||||
const userResponse = await this.handleLoginSuccess(req, res, isApiBased)
|
const userResponse = await this.handleLoginSuccess(req, res, isApiBased)
|
||||||
|
|
||||||
if (isApiBased) {
|
if (isApiBased) {
|
||||||
@ -254,7 +267,6 @@ class Auth {
|
|||||||
res.json(userResponse)
|
res.json(userResponse)
|
||||||
} else {
|
} else {
|
||||||
// UI request -> check if we have a callback url
|
// UI request -> check if we have a callback url
|
||||||
// TODO: do we want to somehow limit the values for auth_cb?
|
|
||||||
if (req.cookies.auth_cb) {
|
if (req.cookies.auth_cb) {
|
||||||
let stateQuery = req.cookies.auth_state ? `&state=${req.cookies.auth_state}` : ''
|
let stateQuery = req.cookies.auth_state ? `&state=${req.cookies.auth_state}` : ''
|
||||||
// UI request -> redirect to auth_cb url and send the jwt token as parameter
|
// UI request -> redirect to auth_cb url and send the jwt token as parameter
|
||||||
@ -288,6 +300,8 @@ class Auth {
|
|||||||
userResponse.user.refreshToken = returnTokens ? refreshToken : null
|
userResponse.user.refreshToken = returnTokens ? refreshToken : null
|
||||||
userResponse.user.accessToken = accessToken
|
userResponse.user.accessToken = accessToken
|
||||||
|
|
||||||
|
Logger.debug(`[Auth] handleLoginSuccess: returnTokens: ${returnTokens}, isRefreshTokenInResponse: ${!!userResponse.user.refreshToken}`)
|
||||||
|
|
||||||
if (!returnTokens) {
|
if (!returnTokens) {
|
||||||
this.tokenManager.setRefreshTokenCookie(req, res, refreshToken)
|
this.tokenManager.setRefreshTokenCookie(req, res, refreshToken)
|
||||||
}
|
}
|
||||||
@ -350,7 +364,11 @@ class Auth {
|
|||||||
return res.status(authorizationUrlResponse.status).send(authorizationUrlResponse.error)
|
return res.status(authorizationUrlResponse.status).send(authorizationUrlResponse.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.paramsToCookies(req, res, authorizationUrlResponse.isMobileFlow ? 'openid-mobile' : 'openid')
|
// Check if paramsToCookies sent a response (e.g., due to invalid callback URL)
|
||||||
|
const cookieResult = this.paramsToCookies(req, res, authorizationUrlResponse.isMobileFlow ? 'openid-mobile' : 'openid')
|
||||||
|
if (cookieResult && cookieResult.error) {
|
||||||
|
return // Response already sent by paramsToCookies
|
||||||
|
}
|
||||||
|
|
||||||
res.redirect(authorizationUrlResponse.authorizationUrl)
|
res.redirect(authorizationUrlResponse.authorizationUrl)
|
||||||
})
|
})
|
||||||
|
@ -229,6 +229,10 @@ class Server {
|
|||||||
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'")
|
res.setHeader('Content-Security-Policy', "frame-ancestors 'self'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Security: Prevent referrer leakage to protect against token exposure
|
||||||
|
// Using 'no-referrer' to completely prevent token leakage in referer headers
|
||||||
|
res.setHeader('Referrer-Policy', 'no-referrer')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @temporary
|
* @temporary
|
||||||
* This is necessary for the ebook & cover API endpoint in the mobile apps
|
* This is necessary for the ebook & cover API endpoint in the mobile apps
|
||||||
@ -240,8 +244,8 @@ class Server {
|
|||||||
* Running in development allows cors to allow testing the mobile apps in the browser
|
* Running in development allows cors to allow testing the mobile apps in the browser
|
||||||
* or env variable ALLOW_CORS = '1'
|
* or env variable ALLOW_CORS = '1'
|
||||||
*/
|
*/
|
||||||
if (global.AllowCors || Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/(ebook|cover)(\/[0-9]+)?/)) {
|
if (global.AllowCors || Logger.isDev || req.path.match(/\/api\/items\/([a-z0-9-]{36})\/(ebook|cover)(\/[0-9]+)?/) || global.ServerSettings.allowedOrigins?.length) {
|
||||||
const allowedOrigins = ['capacitor://localhost', 'http://localhost']
|
const allowedOrigins = ['capacitor://localhost', 'http://localhost', ...(global.ServerSettings.allowedOrigins ? global.ServerSettings.allowedOrigins : [])]
|
||||||
if (global.AllowCors || Logger.isDev || allowedOrigins.some((o) => o === req.get('origin'))) {
|
if (global.AllowCors || Logger.isDev || allowedOrigins.some((o) => o === req.get('origin'))) {
|
||||||
res.header('Access-Control-Allow-Origin', req.get('origin'))
|
res.header('Access-Control-Allow-Origin', req.get('origin'))
|
||||||
res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS')
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS')
|
||||||
|
@ -110,6 +110,8 @@ class OidcAuthStrategy {
|
|||||||
* @param {Function} done - Passport callback
|
* @param {Function} done - Passport callback
|
||||||
*/
|
*/
|
||||||
async verifyCallback(tokenset, userinfo, done) {
|
async verifyCallback(tokenset, userinfo, done) {
|
||||||
|
let isNewUser = false
|
||||||
|
let user = null
|
||||||
try {
|
try {
|
||||||
Logger.debug(`[OidcAuth] openid callback userinfo=`, JSON.stringify(userinfo, null, 2))
|
Logger.debug(`[OidcAuth] openid callback userinfo=`, JSON.stringify(userinfo, null, 2))
|
||||||
|
|
||||||
@ -121,9 +123,24 @@ class OidcAuthStrategy {
|
|||||||
throw new Error(`Group claim ${Database.serverSettings.authOpenIDGroupClaim} not found or empty in userinfo`)
|
throw new Error(`Group claim ${Database.serverSettings.authOpenIDGroupClaim} not found or empty in userinfo`)
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await Database.userModel.findOrCreateUserFromOpenIdUserInfo(userinfo)
|
user = await Database.userModel.findUserFromOpenIdUserInfo(userinfo)
|
||||||
|
|
||||||
if (!user?.isActive) {
|
if (user?.error) {
|
||||||
|
throw new Error('Invalid userinfo or already linked')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
// If no existing user was matched, auto-register if configured
|
||||||
|
if (global.ServerSettings.authOpenIDAutoRegister) {
|
||||||
|
Logger.info(`[User] openid: Auto-registering user with sub "${userinfo.sub}"`, userinfo)
|
||||||
|
user = await Database.userModel.createUserFromOpenIdUserInfo(userinfo)
|
||||||
|
isNewUser = true
|
||||||
|
} else {
|
||||||
|
Logger.warn(`[User] openid: User not found and auto-register is disabled`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.isActive) {
|
||||||
throw new Error('User not active or not found')
|
throw new Error('User not active or not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +153,10 @@ class OidcAuthStrategy {
|
|||||||
return done(null, user)
|
return done(null, user)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(`[OidcAuth] openid callback error: ${error?.message}\n${error?.stack}`)
|
Logger.error(`[OidcAuth] openid callback error: ${error?.message}\n${error?.stack}`)
|
||||||
|
// Remove new user if an error occurs
|
||||||
|
if (isNewUser && user) {
|
||||||
|
await user.destroy()
|
||||||
|
}
|
||||||
return done(null, null, 'Unauthorized')
|
return done(null, null, 'Unauthorized')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -483,6 +504,49 @@ class OidcAuthStrategy {
|
|||||||
res.status(500).send('Internal Server Error')
|
res.status(500).send('Internal Server Error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if a callback URL is safe for redirect (same-origin only)
|
||||||
|
* @param {string} callbackUrl - The callback URL to validate
|
||||||
|
* @param {Request} req - Express request object to get current host
|
||||||
|
* @returns {boolean} - True if the URL is safe (same-origin), false otherwise
|
||||||
|
*/
|
||||||
|
isValidWebCallbackUrl(callbackUrl, req) {
|
||||||
|
if (!callbackUrl) return false
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Handle relative URLs - these are always safe if they start with router base path
|
||||||
|
if (callbackUrl.startsWith('/')) {
|
||||||
|
// Only allow relative paths that start with the router base path
|
||||||
|
if (callbackUrl.startsWith(global.RouterBasePath + '/')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Logger.warn(`[OidcAuth] Rejected callback URL outside router base path: ${callbackUrl}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// For absolute URLs, ensure they point to the same origin
|
||||||
|
const callbackUrlObj = new URL(callbackUrl)
|
||||||
|
const currentProtocol = req.secure || req.get('x-forwarded-proto') === 'https' ? 'https' : 'http'
|
||||||
|
const currentHost = req.get('host')
|
||||||
|
|
||||||
|
// Check if protocol and host match exactly
|
||||||
|
if (callbackUrlObj.protocol === currentProtocol + ':' && callbackUrlObj.host === currentHost) {
|
||||||
|
// Additional check: ensure path starts with router base path
|
||||||
|
if (callbackUrlObj.pathname.startsWith(global.RouterBasePath + '/')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Logger.warn(`[OidcAuth] Rejected same-origin callback URL outside router base path: ${callbackUrl}`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.warn(`[OidcAuth] Rejected callback URL to different origin: ${callbackUrl} (expected ${currentProtocol}://${currentHost})`)
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`[OidcAuth] Invalid callback URL format: ${callbackUrl}`, error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = OidcAuthStrategy
|
module.exports = OidcAuthStrategy
|
||||||
|
@ -4,6 +4,7 @@ const Logger = require('../Logger')
|
|||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
const { toNumber, isUUID } = require('../utils/index')
|
const { toNumber, isUUID } = require('../utils/index')
|
||||||
const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils')
|
const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils')
|
||||||
|
const { PlayMethod } = require('../utils/constants')
|
||||||
|
|
||||||
const ShareManager = require('../managers/ShareManager')
|
const ShareManager = require('../managers/ShareManager')
|
||||||
|
|
||||||
@ -299,6 +300,18 @@ class SessionController {
|
|||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redirect transcode requests to the HLS router
|
||||||
|
// Handles bug introduced in android v0.10.0-beta where transcode requests are made to this endpoint
|
||||||
|
if (playbackSession.playMethod === PlayMethod.TRANSCODE && audioTrack.contentUrl) {
|
||||||
|
Logger.debug(`[SessionController] Redirecting transcode request to "${audioTrack.contentUrl}"`)
|
||||||
|
return res.redirect(audioTrack.contentUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!audioTrack.metadata?.path) {
|
||||||
|
Logger.error(`[SessionController] Invalid audio track "${audioTrack.index}" for session "${req.params.id}"`)
|
||||||
|
return res.sendStatus(500)
|
||||||
|
}
|
||||||
|
|
||||||
const user = await Database.userModel.getUserById(playbackSession.userId)
|
const user = await Database.userModel.getUserById(playbackSession.userId)
|
||||||
Logger.debug(`[SessionController] Serving audio track ${audioTrack.index} for session "${req.params.id}" belonging to user "${user.username}"`)
|
Logger.debug(`[SessionController] Serving audio track ${audioTrack.index} for session "${req.params.id}" belonging to user "${user.username}"`)
|
||||||
|
|
||||||
|
@ -121,28 +121,17 @@ class PodcastManager {
|
|||||||
await fs.mkdir(this.currentDownload.libraryItem.path)
|
await fs.mkdir(this.currentDownload.libraryItem.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
let success = false
|
// Download episode and tag it
|
||||||
if (this.currentDownload.isMp3) {
|
const ffmpegDownloadResponse = await ffmpegHelpers.downloadPodcastEpisode(this.currentDownload).catch((error) => {
|
||||||
// Download episode and tag it
|
Logger.error(`[PodcastManager] Podcast Episode download failed`, error)
|
||||||
const ffmpegDownloadResponse = await ffmpegHelpers.downloadPodcastEpisode(this.currentDownload).catch((error) => {
|
})
|
||||||
Logger.error(`[PodcastManager] Podcast Episode download failed`, error)
|
let success = !!ffmpegDownloadResponse?.success
|
||||||
})
|
|
||||||
success = !!ffmpegDownloadResponse?.success
|
|
||||||
|
|
||||||
// If failed due to ffmpeg error, retry without tagging
|
// If failed due to ffmpeg error, retry without tagging
|
||||||
// e.g. RSS feed may have incorrect file extension and file type
|
// e.g. RSS feed may have incorrect file extension and file type
|
||||||
// See https://github.com/advplyr/audiobookshelf/issues/3837
|
// See https://github.com/advplyr/audiobookshelf/issues/3837
|
||||||
if (!success && ffmpegDownloadResponse?.isFfmpegError) {
|
if (!success && ffmpegDownloadResponse?.isFfmpegError) {
|
||||||
Logger.info(`[PodcastManager] Retrying episode download without tagging`)
|
Logger.info(`[PodcastManager] Retrying episode download without tagging`)
|
||||||
// Download episode only
|
|
||||||
success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath)
|
|
||||||
.then(() => true)
|
|
||||||
.catch((error) => {
|
|
||||||
Logger.error(`[PodcastManager] Podcast Episode download failed`, error)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Download episode only
|
// Download episode only
|
||||||
success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath)
|
success = await downloadFile(this.currentDownload.url, this.currentDownload.targetPath)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
|
@ -211,18 +211,18 @@ class User extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds an existing user by OpenID subject identifier, or by email/username based on server settings,
|
* Finds an existing user by OpenID subject identifier, or by email/username based on server settings
|
||||||
* or creates a new user if configured to do so.
|
* Returns null if no user is found
|
||||||
*
|
*
|
||||||
* @param {Object} userinfo
|
* @param {Object} userinfo
|
||||||
* @returns {Promise<User>}
|
* @returns {Promise<User|{error: string}>}
|
||||||
*/
|
*/
|
||||||
static async findOrCreateUserFromOpenIdUserInfo(userinfo) {
|
static async findUserFromOpenIdUserInfo(userinfo) {
|
||||||
let user = await this.getUserByOpenIDSub(userinfo.sub)
|
let user = await this.getUserByOpenIDSub(userinfo.sub)
|
||||||
|
|
||||||
// Matched by sub
|
// Matched by sub
|
||||||
if (user) {
|
if (user) {
|
||||||
Logger.debug(`[User] openid: User found by sub`)
|
Logger.debug(`[User] openid: User found by sub "${userinfo.sub}"`)
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,20 +232,27 @@ class User extends Model {
|
|||||||
// Only disallow when email_verified explicitly set to false (allow both if not set or true)
|
// Only disallow when email_verified explicitly set to false (allow both if not set or true)
|
||||||
if (userinfo.email_verified === false) {
|
if (userinfo.email_verified === false) {
|
||||||
Logger.warn(`[User] openid: User not found and email "${userinfo.email}" is not verified`)
|
Logger.warn(`[User] openid: User not found and email "${userinfo.email}" is not verified`)
|
||||||
return null
|
return {
|
||||||
|
error: 'Email not verified'
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.info(`[User] openid: User not found, checking existing with email "${userinfo.email}"`)
|
Logger.info(`[User] openid: User not found, checking existing with email "${userinfo.email}"`)
|
||||||
user = await this.getUserByEmail(userinfo.email)
|
user = await this.getUserByEmail(userinfo.email)
|
||||||
|
|
||||||
if (user?.authOpenIDSub) {
|
if (user?.authOpenIDSub) {
|
||||||
Logger.warn(`[User] openid: User found with email "${userinfo.email}" but is already matched with sub "${user.authOpenIDSub}"`)
|
Logger.warn(`[User] openid: User found with email "${userinfo.email}" but is already matched with sub "${user.authOpenIDSub}"`)
|
||||||
return null // User is linked to a different OpenID subject; do not proceed.
|
// User is linked to a different OpenID subject; do not proceed.
|
||||||
|
return {
|
||||||
|
error: 'User already linked to a different OpenID subject'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.warn(`[User] openid: User not found and no email in userinfo`)
|
Logger.warn(`[User] openid: User not found and no email in userinfo`)
|
||||||
// We deny login, because if the admin whishes to match email, it makes sense to require it
|
// We deny login, because if the admin whishes to match email, it makes sense to require it
|
||||||
return null
|
return {
|
||||||
|
error: 'No email in userinfo'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Match existing user by username
|
// Match existing user by username
|
||||||
@ -260,43 +267,40 @@ class User extends Model {
|
|||||||
username = userinfo.username
|
username = userinfo.username
|
||||||
} else {
|
} else {
|
||||||
Logger.warn(`[User] openid: User not found and neither preferred_username nor username in userinfo`)
|
Logger.warn(`[User] openid: User not found and neither preferred_username nor username in userinfo`)
|
||||||
return null
|
return {
|
||||||
|
error: 'No username in userinfo'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user = await this.getUserByUsername(username)
|
user = await this.getUserByUsername(username)
|
||||||
|
|
||||||
if (user?.authOpenIDSub) {
|
if (user?.authOpenIDSub) {
|
||||||
Logger.warn(`[User] openid: User found with username "${username}" but is already matched with sub "${user.authOpenIDSub}"`)
|
Logger.warn(`[User] openid: User found with username "${username}" but is already matched with sub "${user.authOpenIDSub}"`)
|
||||||
return null // User is linked to a different OpenID subject; do not proceed.
|
// User is linked to a different OpenID subject; do not proceed.
|
||||||
|
return {
|
||||||
|
error: 'User already linked to a different OpenID subject'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// Found existing user via email or username
|
// Found existing user via email or username
|
||||||
if (user) {
|
if (!user.isActive) {
|
||||||
if (!user.isActive) {
|
Logger.warn(`[User] openid: User found but is not active`)
|
||||||
Logger.warn(`[User] openid: User found but is not active`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update user with OpenID sub
|
|
||||||
if (!user.extraData) user.extraData = {}
|
|
||||||
user.extraData.authOpenIDSub = userinfo.sub
|
|
||||||
user.changed('extraData', true)
|
|
||||||
await user.save()
|
|
||||||
|
|
||||||
Logger.debug(`[User] openid: User found by email/username`)
|
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no existing user was matched, auto-register if configured
|
// Update user with OpenID sub
|
||||||
if (global.ServerSettings.authOpenIDAutoRegister) {
|
if (!user.extraData) user.extraData = {}
|
||||||
Logger.info(`[User] openid: Auto-registering user with sub "${userinfo.sub}"`, userinfo)
|
user.extraData.authOpenIDSub = userinfo.sub
|
||||||
user = await this.createUserFromOpenIdUserInfo(userinfo)
|
user.changed('extraData', true)
|
||||||
return user
|
await user.save()
|
||||||
}
|
|
||||||
|
|
||||||
Logger.warn(`[User] openid: User not found and auto-register is disabled`)
|
Logger.debug(`[User] openid: User found by email/username`)
|
||||||
return null
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,16 +63,6 @@ class PodcastEpisodeDownload {
|
|||||||
const enclosureType = this.rssPodcastEpisode.enclosure.type
|
const enclosureType = this.rssPodcastEpisode.enclosure.type
|
||||||
return typeof enclosureType === 'string' ? enclosureType : null
|
return typeof enclosureType === 'string' ? enclosureType : null
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* RSS feed may have an episode with file extension of mp3 but the specified enclosure type is not mpeg.
|
|
||||||
* @see https://github.com/advplyr/audiobookshelf/issues/3711
|
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
get isMp3() {
|
|
||||||
if (this.enclosureType && !this.enclosureType.includes('mpeg')) return false
|
|
||||||
return this.fileExtension === 'mp3'
|
|
||||||
}
|
|
||||||
get episodeTitle() {
|
get episodeTitle() {
|
||||||
return this.rssPodcastEpisode.title
|
return this.rssPodcastEpisode.title
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,7 @@ class ServerSettings {
|
|||||||
this.dateFormat = 'MM/dd/yyyy'
|
this.dateFormat = 'MM/dd/yyyy'
|
||||||
this.timeFormat = 'HH:mm'
|
this.timeFormat = 'HH:mm'
|
||||||
this.language = 'en-us'
|
this.language = 'en-us'
|
||||||
|
this.allowedOrigins = []
|
||||||
|
|
||||||
this.logLevel = Logger.logLevel
|
this.logLevel = Logger.logLevel
|
||||||
|
|
||||||
@ -120,6 +121,7 @@ class ServerSettings {
|
|||||||
this.dateFormat = settings.dateFormat || 'MM/dd/yyyy'
|
this.dateFormat = settings.dateFormat || 'MM/dd/yyyy'
|
||||||
this.timeFormat = settings.timeFormat || 'HH:mm'
|
this.timeFormat = settings.timeFormat || 'HH:mm'
|
||||||
this.language = settings.language || 'en-us'
|
this.language = settings.language || 'en-us'
|
||||||
|
this.allowedOrigins = settings.allowedOrigins || []
|
||||||
this.logLevel = settings.logLevel || Logger.logLevel
|
this.logLevel = settings.logLevel || Logger.logLevel
|
||||||
this.version = settings.version || null
|
this.version = settings.version || null
|
||||||
this.buildNumber = settings.buildNumber || 0 // Added v2.4.5
|
this.buildNumber = settings.buildNumber || 0 // Added v2.4.5
|
||||||
@ -231,6 +233,7 @@ class ServerSettings {
|
|||||||
dateFormat: this.dateFormat,
|
dateFormat: this.dateFormat,
|
||||||
timeFormat: this.timeFormat,
|
timeFormat: this.timeFormat,
|
||||||
language: this.language,
|
language: this.language,
|
||||||
|
allowedOrigins: this.allowedOrigins,
|
||||||
logLevel: this.logLevel,
|
logLevel: this.logLevel,
|
||||||
version: this.version,
|
version: this.version,
|
||||||
buildNumber: this.buildNumber,
|
buildNumber: this.buildNumber,
|
||||||
|
Loading…
Reference in New Issue
Block a user