mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-06-03 17:30:39 +02:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a7a3a56509 | |||
| 4082fadf90 | |||
| 93160b83bf | |||
| 472240f994 | |||
| c3f0fb8e5e | |||
| b156ebeb9f | |||
| e4c775c847 | |||
| 45e8e72759 | |||
| 0ae7340889 | |||
| 8c38987d92 | |||
| 878f0787ba | |||
| 880d85eaef | |||
| f7aaebc1de | |||
| d96ebbe82d | |||
| 70d67156f0 | |||
| f293b317be | |||
| 1f23794f88 | |||
| e6bfd118f6 | |||
| 1166400ab1 | |||
| 55f0ac871b | |||
| 3584f6e24f | |||
| 23bf2594c8 | |||
| 8fb460ce05 | |||
| 8c4bbfd6a2 | |||
| 742961e0b8 | |||
| 5b6807892f | |||
| b911a25c57 | |||
| 53110674e4 | |||
| f963cd4753 | |||
| 0dccc3bcae | |||
| 5b4fd5b254 | |||
| bdb9d3caeb | |||
| 9aca824b59 | |||
| 8e891805eb | |||
| 2760517445 | |||
| 889ee33320 | |||
| 4f65801713 | |||
| 3e75acd4ef | |||
| 3e8fe2ef60 | |||
| 0bc441de20 | |||
| a8c2f0d4c8 | |||
| b59da8bd0c | |||
| 77cb4f75c6 | |||
| 9cf1711fae | |||
| f472116dc3 | |||
| c7eb9d7799 | |||
| c66380eaeb | |||
| 1bebb22705 | |||
| 4e96649fe3 | |||
| a21cec806e | |||
| 8a3b8d2249 | |||
| 581277914c | |||
| e678fe6e2f | |||
| 3845940245 | |||
| 6c63e2131c | |||
| e25e2b238f | |||
| 99110f587a | |||
| b553e959e2 | |||
| f7b94a4b6d | |||
| e9a705587a | |||
| bf6d81b333 | |||
| 9c44fc0d01 | |||
| 5017e7ce9e | |||
| de25763a74 | |||
| a894ceb9cf | |||
| 387e58a714 |
@@ -13,9 +13,17 @@
|
|||||||
<div class="grow" />
|
<div class="grow" />
|
||||||
<p class="text-sm md:text-base">{{ book.publishedYear }}</p>
|
<p class="text-sm md:text-base">{{ book.publishedYear }}</p>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">{{ $getString('LabelByAuthor', [book.author]) }}</p>
|
|
||||||
<p v-if="book.narrator" class="text-gray-400 text-xs">{{ $strings.LabelNarrators }}: {{ book.narrator }}</p>
|
<div class="flex items-center">
|
||||||
<p v-if="book.duration" class="text-gray-400 text-xs">{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
|
<div>
|
||||||
|
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">{{ $getString('LabelByAuthor', [book.author]) }}</p>
|
||||||
|
<p v-if="book.narrator" class="text-gray-400 text-xs">{{ $strings.LabelNarrators }}: {{ book.narrator }}</p>
|
||||||
|
<p v-if="book.duration" class="text-gray-400 text-xs">{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="grow" />
|
||||||
|
<div v-if="book.matchConfidence" class="rounded-full px-2 py-1 text-xs whitespace-nowrap text-white" :class="book.matchConfidence > 0.95 ? 'bg-success/80' : 'bg-info/80'">{{ $strings.LabelMatchConfidence }}: {{ (book.matchConfidence * 100).toFixed(0) }}%</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="book.series?.length" class="flex py-1 -mx-1">
|
<div v-if="book.series?.length" class="flex py-1 -mx-1">
|
||||||
<div v-for="(series, index) in book.series" :key="index" class="bg-white/10 rounded-full px-1 py-0.5 mx-1">
|
<div v-for="(series, index) in book.series" :key="index" class="bg-white/10 rounded-full px-1 py-0.5 mx-1">
|
||||||
<p class="leading-3 text-xs text-gray-400">
|
<p class="leading-3 text-xs text-gray-400">
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/3">
|
<div class="w-full md:w-1/3">
|
||||||
<p v-if="!isMediaItemShareSession" class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
|
<p v-if="!isMediaItemShareSession" class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
|
||||||
<p v-if="!isMediaItemShareSession" class="mb-1 text-xs">{{ _session.userId }}</p>
|
<p v-if="!isMediaItemShareSession" class="mb-1 text-xs">{{ username }}</p>
|
||||||
|
|
||||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelMediaPlayer }}</p>
|
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelMediaPlayer }}</p>
|
||||||
<p class="mb-1">{{ playMethodName }}</p>
|
<p class="mb-1">{{ playMethodName }}</p>
|
||||||
@@ -132,6 +132,9 @@ export default {
|
|||||||
_session() {
|
_session() {
|
||||||
return this.session || {}
|
return this.session || {}
|
||||||
},
|
},
|
||||||
|
username() {
|
||||||
|
return this._session.user?.username || this._session.userId || ''
|
||||||
|
},
|
||||||
deviceInfo() {
|
deviceInfo() {
|
||||||
return this._session.deviceInfo || {}
|
return this._session.deviceInfo || {}
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.26.0",
|
"version": "2.26.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.26.0",
|
"version": "2.26.3",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf-client",
|
"name": "audiobookshelf-client",
|
||||||
"version": "2.26.0",
|
"version": "2.26.3",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
|
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center w-24 min-w-24 sm:w-32 sm:min-w-32">
|
<td class="text-center w-24 min-w-24 sm:w-32 sm:min-w-32">
|
||||||
<p class="text-xs font-mono">{{ $elapsedPretty(session.timeListening) }}</p>
|
<p class="text-xs font-mono">{{ $elapsedPrettyLocalized(session.timeListening) }}</p>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center hover:underline w-24 min-w-24" @click.stop="clickCurrentTime(session)">
|
<td class="text-center hover:underline w-24 min-w-24" @click.stop="clickCurrentTime(session)">
|
||||||
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
||||||
|
|||||||
@@ -13,8 +13,10 @@
|
|||||||
<widgets-online-indicator :value="!!userOnline" />
|
<widgets-online-indicator :value="!!userOnline" />
|
||||||
<h1 class="text-xl pl-2">{{ username }}</h1>
|
<h1 class="text-xl pl-2">{{ username }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="legacyToken" class="flex text-xs mt-4">
|
<div v-if="legacyToken" class="text-xs space-y-2 mt-4">
|
||||||
<ui-text-input-with-label label="Legacy API Token" :value="legacyToken" readonly show-copy />
|
<ui-text-input-with-label label="Legacy API Token" :value="legacyToken" readonly show-copy />
|
||||||
|
|
||||||
|
<p class="text-warning" v-html="$strings.MessageAuthenticationLegacyTokenWarning" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full h-px bg-white/10 my-2" />
|
<div class="w-full h-px bg-white/10 my-2" />
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
|
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<p class="text-xs font-mono">{{ $elapsedPretty(session.timeListening) }}</p>
|
<p class="text-xs font-mono">{{ $elapsedPrettyLocalized(session.timeListening) }}</p>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
|
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
|
||||||
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
||||||
|
|||||||
@@ -191,7 +191,10 @@ export default {
|
|||||||
|
|
||||||
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
|
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
|
||||||
this.$store.commit('user/setUser', user)
|
this.$store.commit('user/setUser', user)
|
||||||
this.$store.commit('user/setAccessToken', user.accessToken)
|
// Access token only returned from login, not authorize
|
||||||
|
if (user.accessToken) {
|
||||||
|
this.$store.commit('user/setAccessToken', user.accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
this.$store.dispatch('user/loadUserSettings')
|
this.$store.dispatch('user/loadUserSettings')
|
||||||
},
|
},
|
||||||
@@ -225,6 +228,8 @@ export default {
|
|||||||
|
|
||||||
this.processing = true
|
this.processing = true
|
||||||
|
|
||||||
|
this.$store.commit('user/setAccessToken', token)
|
||||||
|
|
||||||
return this.$axios
|
return this.$axios
|
||||||
.$post('/api/authorize', null, {
|
.$post('/api/authorize', null, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -240,6 +245,7 @@ export default {
|
|||||||
this.showNewAuthSystemAdminMessage = res.user.type === 'admin' || res.user.type === 'root'
|
this.showNewAuthSystemAdminMessage = res.user.type === 'admin' || res.user.type === 'root'
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setUser(res)
|
this.setUser(res)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -37,6 +37,48 @@ Vue.prototype.$elapsedPretty = (seconds, useFullNames = false, useMilliseconds =
|
|||||||
return `${hours} ${useFullNames ? `hour${hours === 1 ? '' : 's'}` : 'hr'} ${minutes} ${useFullNames ? `minute${minutes === 1 ? '' : 's'}` : 'min'}`
|
return `${hours} ${useFullNames ? `hour${hours === 1 ? '' : 's'}` : 'hr'} ${minutes} ${useFullNames ? `minute${minutes === 1 ? '' : 's'}` : 'min'}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vue.prototype.$elapsedPrettyLocalized = (seconds, useFullNames = false, useMilliseconds = false) => {
|
||||||
|
if (isNaN(seconds) || seconds === null) return ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const df = new Intl.DurationFormat(Vue.prototype.$languageCodes.current, {
|
||||||
|
style: useFullNames ? 'long' : 'short'
|
||||||
|
})
|
||||||
|
|
||||||
|
const duration = {}
|
||||||
|
|
||||||
|
if (seconds < 60) {
|
||||||
|
if (useMilliseconds && seconds < 1) {
|
||||||
|
duration.milliseconds = Math.floor(seconds * 1000)
|
||||||
|
} else {
|
||||||
|
duration.seconds = Math.floor(seconds)
|
||||||
|
}
|
||||||
|
} else if (seconds < 3600) {
|
||||||
|
// 1 hour
|
||||||
|
duration.minutes = Math.floor(seconds / 60)
|
||||||
|
} else if (seconds < 86400) {
|
||||||
|
// 1 day
|
||||||
|
duration.hours = Math.floor(seconds / 3600)
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
if (minutes > 0) {
|
||||||
|
duration.minutes = minutes
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
duration.days = Math.floor(seconds / 86400)
|
||||||
|
const hours = Math.floor((seconds % 86400) / 3600)
|
||||||
|
if (hours > 0) {
|
||||||
|
duration.hours = hours
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return df.format(duration)
|
||||||
|
} catch (error) {
|
||||||
|
// Handle not supported
|
||||||
|
console.warn('Intl.DurationFormat not supported, not localizing duration')
|
||||||
|
return Vue.prototype.$elapsedPretty(seconds, useFullNames, useMilliseconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Vue.prototype.$secondsToTimestamp = (seconds, includeMs = false, alwaysIncludeHours = false) => {
|
Vue.prototype.$secondsToTimestamp = (seconds, includeMs = false, alwaysIncludeHours = false) => {
|
||||||
if (!seconds) {
|
if (!seconds) {
|
||||||
return alwaysIncludeHours ? '00:00:00' : '0:00'
|
return alwaysIncludeHours ? '00:00:00' : '0:00'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Přidat",
|
"ButtonAdd": "Přidat",
|
||||||
|
"ButtonAddApiKey": "Přidat API klíč",
|
||||||
"ButtonAddChapters": "Přidat kapitoly",
|
"ButtonAddChapters": "Přidat kapitoly",
|
||||||
"ButtonAddDevice": "Přidat zařízení",
|
"ButtonAddDevice": "Přidat zařízení",
|
||||||
"ButtonAddLibrary": "Přidat knihovnu",
|
"ButtonAddLibrary": "Přidat knihovnu",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Vybrat složku",
|
"ButtonChooseAFolder": "Vybrat složku",
|
||||||
"ButtonChooseFiles": "Vybrat soubory",
|
"ButtonChooseFiles": "Vybrat soubory",
|
||||||
"ButtonClearFilter": "Vymazat filtr",
|
"ButtonClearFilter": "Vymazat filtr",
|
||||||
|
"ButtonClose": "Zavřít",
|
||||||
"ButtonCloseFeed": "Zavřít kanál",
|
"ButtonCloseFeed": "Zavřít kanál",
|
||||||
"ButtonCloseSession": "Zavřít otevřenou relaci",
|
"ButtonCloseSession": "Zavřít otevřenou relaci",
|
||||||
"ButtonCollections": "Kolekce",
|
"ButtonCollections": "Kolekce",
|
||||||
@@ -119,6 +121,7 @@
|
|||||||
"HeaderAccount": "Účet",
|
"HeaderAccount": "Účet",
|
||||||
"HeaderAddCustomMetadataProvider": "Přidat vlastního poskytovatele metadat",
|
"HeaderAddCustomMetadataProvider": "Přidat vlastního poskytovatele metadat",
|
||||||
"HeaderAdvanced": "Pokročilé",
|
"HeaderAdvanced": "Pokročilé",
|
||||||
|
"HeaderApiKeys": "API klíče",
|
||||||
"HeaderAppriseNotificationSettings": "Nastavení oznámení Apprise",
|
"HeaderAppriseNotificationSettings": "Nastavení oznámení Apprise",
|
||||||
"HeaderAudioTracks": "Zvukové stopy",
|
"HeaderAudioTracks": "Zvukové stopy",
|
||||||
"HeaderAudiobookTools": "Nástroje pro správu souborů audioknih",
|
"HeaderAudiobookTools": "Nástroje pro správu souborů audioknih",
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "Pořadí priorit metadat",
|
"HeaderMetadataOrderOfPrecedence": "Pořadí priorit metadat",
|
||||||
"HeaderMetadataToEmbed": "Metadata k vložení",
|
"HeaderMetadataToEmbed": "Metadata k vložení",
|
||||||
"HeaderNewAccount": "Nový účet",
|
"HeaderNewAccount": "Nový účet",
|
||||||
|
"HeaderNewApiKey": "Nový API klíč",
|
||||||
"HeaderNewLibrary": "Nová knihovna",
|
"HeaderNewLibrary": "Nová knihovna",
|
||||||
"HeaderNotificationCreate": "Vytvořit notifikaci",
|
"HeaderNotificationCreate": "Vytvořit notifikaci",
|
||||||
"HeaderNotificationUpdate": "Aktualizovat notifikaci",
|
"HeaderNotificationUpdate": "Aktualizovat notifikaci",
|
||||||
@@ -206,6 +210,7 @@
|
|||||||
"HeaderTableOfContents": "Obsah",
|
"HeaderTableOfContents": "Obsah",
|
||||||
"HeaderTools": "Nástroje",
|
"HeaderTools": "Nástroje",
|
||||||
"HeaderUpdateAccount": "Aktualizovat účet",
|
"HeaderUpdateAccount": "Aktualizovat účet",
|
||||||
|
"HeaderUpdateApiKey": "Aktualizovat API klíč",
|
||||||
"HeaderUpdateAuthor": "Aktualizovat autora",
|
"HeaderUpdateAuthor": "Aktualizovat autora",
|
||||||
"HeaderUpdateDetails": "Aktualizovat podrobnosti",
|
"HeaderUpdateDetails": "Aktualizovat podrobnosti",
|
||||||
"HeaderUpdateLibrary": "Aktualizovat knihovnu",
|
"HeaderUpdateLibrary": "Aktualizovat knihovnu",
|
||||||
@@ -235,6 +240,10 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Všichni uživatelé kromě hostů",
|
"LabelAllUsersExcludingGuests": "Všichni uživatelé kromě hostů",
|
||||||
"LabelAllUsersIncludingGuests": "Všichni uživatelé včetně hostů",
|
"LabelAllUsersIncludingGuests": "Všichni uživatelé včetně hostů",
|
||||||
"LabelAlreadyInYourLibrary": "Již ve vaší knihovně",
|
"LabelAlreadyInYourLibrary": "Již ve vaší knihovně",
|
||||||
|
"LabelApiKeyCreated": "API klíč \"{0}\" byl úspěšně vytvořen.",
|
||||||
|
"LabelApiKeyCreatedDescription": "Zkopírujte si API klíč nyní, později již nebude možné jej zobrazit.",
|
||||||
|
"LabelApiKeyUser": "Vydávat se za uživatele",
|
||||||
|
"LabelApiKeyUserDescription": "Tento API klíč bude mít stejná oprávnění jako uživatel za něhož vystupuje. V protokolech to bude vypadat jako kdyby požadavky vytvářel přímo daný uživatel.",
|
||||||
"LabelApiToken": "API Token",
|
"LabelApiToken": "API Token",
|
||||||
"LabelAppend": "Připojit",
|
"LabelAppend": "Připojit",
|
||||||
"LabelAudioBitrate": "Bitový tok zvuku (např. 128k)",
|
"LabelAudioBitrate": "Bitový tok zvuku (např. 128k)",
|
||||||
@@ -346,6 +355,10 @@
|
|||||||
"LabelExample": "Příklad",
|
"LabelExample": "Příklad",
|
||||||
"LabelExpandSeries": "Rozbalit série",
|
"LabelExpandSeries": "Rozbalit série",
|
||||||
"LabelExpandSubSeries": "Rozbalit podsérie",
|
"LabelExpandSubSeries": "Rozbalit podsérie",
|
||||||
|
"LabelExpired": "Expirovaný",
|
||||||
|
"LabelExpiresAt": "Expiruje v",
|
||||||
|
"LabelExpiresInSeconds": "Expiruje za (sekundy)",
|
||||||
|
"LabelExpiresNever": "Nikdy",
|
||||||
"LabelExplicit": "Explicitně",
|
"LabelExplicit": "Explicitně",
|
||||||
"LabelExplicitChecked": "Explicitní (zaškrtnuto)",
|
"LabelExplicitChecked": "Explicitní (zaškrtnuto)",
|
||||||
"LabelExplicitUnchecked": "Není explicitní (nezaškrtnuto)",
|
"LabelExplicitUnchecked": "Není explicitní (nezaškrtnuto)",
|
||||||
@@ -455,6 +468,7 @@
|
|||||||
"LabelNewestEpisodes": "Nejnovější epizody",
|
"LabelNewestEpisodes": "Nejnovější epizody",
|
||||||
"LabelNextBackupDate": "Datum příští zálohy",
|
"LabelNextBackupDate": "Datum příští zálohy",
|
||||||
"LabelNextScheduledRun": "Další naplánované spuštění",
|
"LabelNextScheduledRun": "Další naplánované spuštění",
|
||||||
|
"LabelNoApiKeys": "Žádné API klíče",
|
||||||
"LabelNoCustomMetadataProviders": "Žádní vlastní poskytovatelé metadat",
|
"LabelNoCustomMetadataProviders": "Žádní vlastní poskytovatelé metadat",
|
||||||
"LabelNoEpisodesSelected": "Nebyly vybrány žádné epizody",
|
"LabelNoEpisodesSelected": "Nebyly vybrány žádné epizody",
|
||||||
"LabelNotFinished": "Nedokončeno",
|
"LabelNotFinished": "Nedokončeno",
|
||||||
@@ -544,6 +558,7 @@
|
|||||||
"LabelSelectAll": "Vybrat vše",
|
"LabelSelectAll": "Vybrat vše",
|
||||||
"LabelSelectAllEpisodes": "Vybrat všechny epizody",
|
"LabelSelectAllEpisodes": "Vybrat všechny epizody",
|
||||||
"LabelSelectEpisodesShowing": "Vyberte {0} epizody, které se zobrazují",
|
"LabelSelectEpisodesShowing": "Vyberte {0} epizody, které se zobrazují",
|
||||||
|
"LabelSelectUser": "Vybrat uživatele",
|
||||||
"LabelSelectUsers": "Vybrat uživatele",
|
"LabelSelectUsers": "Vybrat uživatele",
|
||||||
"LabelSendEbookToDevice": "Odeslat e-knihu do...",
|
"LabelSendEbookToDevice": "Odeslat e-knihu do...",
|
||||||
"LabelSequence": "Sekvence",
|
"LabelSequence": "Sekvence",
|
||||||
@@ -708,7 +723,9 @@
|
|||||||
"MessageAddToPlayerQueue": "Přidat do fronty přehrávače",
|
"MessageAddToPlayerQueue": "Přidat do fronty přehrávače",
|
||||||
"MessageAppriseDescription": "Abyste mohli používat tuto funkci, musíte mít spuštěnou instanci <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> nebo API, které bude zpracovávat stejné požadavky. <br />Adresa URL API Apprise by měla být úplná URL cesta pro odeslání oznámení, např. pokud je vaše instance API obsluhována na adrese <code>http://192.168.1.1:8337</code> pak byste měli zadat <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Abyste mohli používat tuto funkci, musíte mít spuštěnou instanci <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> nebo API, které bude zpracovávat stejné požadavky. <br />Adresa URL API Apprise by měla být úplná URL cesta pro odeslání oznámení, např. pokud je vaše instance API obsluhována na adrese <code>http://192.168.1.1:8337</code> pak byste měli zadat <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageAsinCheck": "Ujistěte se, že používáte ASIN ze správného regionu Audible a ne z Amazonu.",
|
"MessageAsinCheck": "Ujistěte se, že používáte ASIN ze správného regionu Audible a ne z Amazonu.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Zastaralé API tokeny budou v budoucnu odstraněny. Použijte místo nich <a href=\"/config/api-keys\">API klíče</a>.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Po uložení restartujte server, aby se změny OIDC použily.",
|
"MessageAuthenticationOIDCChangesRestart": "Po uložení restartujte server, aby se změny OIDC použily.",
|
||||||
|
"MessageAuthenticationSecurityMessage": "Bezpečnost autentizace byla vylepšena. Všichni uživatelé se musí znovu přihlásit.",
|
||||||
"MessageBackupsDescription": "Zálohy zahrnují uživatele, průběh uživatele, podrobnosti o položkách knihovny, nastavení serveru a obrázky uložené v <code>/metadata/items</code> a <code>/metadata/authors</code>. Zálohy <strong>ne</strong> zahrnují všechny soubory uložené ve složkách knihovny.",
|
"MessageBackupsDescription": "Zálohy zahrnují uživatele, průběh uživatele, podrobnosti o položkách knihovny, nastavení serveru a obrázky uložené v <code>/metadata/items</code> a <code>/metadata/authors</code>. Zálohy <strong>ne</strong> zahrnují všechny soubory uložené ve složkách knihovny.",
|
||||||
"MessageBackupsLocationEditNote": "Poznámka: Změna umístění záloh nepřesune ani nezmění existující zálohy",
|
"MessageBackupsLocationEditNote": "Poznámka: Změna umístění záloh nepřesune ani nezmění existující zálohy",
|
||||||
"MessageBackupsLocationNoEditNote": "Poznámka: Umístění záloh je nastavené z proměnných prostředí a nelze zde změnit.",
|
"MessageBackupsLocationNoEditNote": "Poznámka: Umístění záloh je nastavené z proměnných prostředí a nelze zde změnit.",
|
||||||
@@ -730,6 +747,7 @@
|
|||||||
"MessageChaptersNotFound": "Kapitoly nenalezeny",
|
"MessageChaptersNotFound": "Kapitoly nenalezeny",
|
||||||
"MessageCheckingCron": "Kontrola cronu...",
|
"MessageCheckingCron": "Kontrola cronu...",
|
||||||
"MessageConfirmCloseFeed": "Opravdu chcete zavřít tento kanál?",
|
"MessageConfirmCloseFeed": "Opravdu chcete zavřít tento kanál?",
|
||||||
|
"MessageConfirmDeleteApiKey": "Opravdu chcete vymazat API klíč \"{0}\"?",
|
||||||
"MessageConfirmDeleteBackup": "Opravdu chcete smazat zálohu pro {0}?",
|
"MessageConfirmDeleteBackup": "Opravdu chcete smazat zálohu pro {0}?",
|
||||||
"MessageConfirmDeleteDevice": "Opravdu chcete vymazat zařízení e-reader \"{0}\"?",
|
"MessageConfirmDeleteDevice": "Opravdu chcete vymazat zařízení e-reader \"{0}\"?",
|
||||||
"MessageConfirmDeleteFile": "Tento krok smaže soubor ze souborového systému. Jsi si jisti?",
|
"MessageConfirmDeleteFile": "Tento krok smaže soubor ze souborového systému. Jsi si jisti?",
|
||||||
@@ -1001,6 +1019,8 @@
|
|||||||
"ToastEpisodeDownloadQueueClearSuccess": "Fronta stahování epizod je prázdná",
|
"ToastEpisodeDownloadQueueClearSuccess": "Fronta stahování epizod je prázdná",
|
||||||
"ToastEpisodeUpdateSuccess": "{0} epizod aktualizováno",
|
"ToastEpisodeUpdateSuccess": "{0} epizod aktualizováno",
|
||||||
"ToastErrorCannotShare": "Na tomto zařízení nelze nativně sdílet",
|
"ToastErrorCannotShare": "Na tomto zařízení nelze nativně sdílet",
|
||||||
|
"ToastFailedToCreate": "Nepodařilo se vytvořit",
|
||||||
|
"ToastFailedToDelete": "Nepodařilo se odstranit",
|
||||||
"ToastFailedToLoadData": "Nepodařilo se načíst data",
|
"ToastFailedToLoadData": "Nepodařilo se načíst data",
|
||||||
"ToastFailedToMatch": "Nepodařilo se spárovat",
|
"ToastFailedToMatch": "Nepodařilo se spárovat",
|
||||||
"ToastFailedToShare": "Sdílení selhalo",
|
"ToastFailedToShare": "Sdílení selhalo",
|
||||||
@@ -1032,6 +1052,7 @@
|
|||||||
"ToastMustHaveAtLeastOnePath": "Musí mít minimálně jednu cestu",
|
"ToastMustHaveAtLeastOnePath": "Musí mít minimálně jednu cestu",
|
||||||
"ToastNameEmailRequired": "Jméno a email jsou vyžadovány",
|
"ToastNameEmailRequired": "Jméno a email jsou vyžadovány",
|
||||||
"ToastNameRequired": "Jméno je vyžadováno",
|
"ToastNameRequired": "Jméno je vyžadováno",
|
||||||
|
"ToastNewApiKeyUserError": "Je nutné vybrat uživatele",
|
||||||
"ToastNewEpisodesFound": "{0} nových epizod bylo nalezeno",
|
"ToastNewEpisodesFound": "{0} nových epizod bylo nalezeno",
|
||||||
"ToastNewUserCreatedFailed": "Chyba při vytváření účtu: \"{0}\"",
|
"ToastNewUserCreatedFailed": "Chyba při vytváření účtu: \"{0}\"",
|
||||||
"ToastNewUserCreatedSuccess": "Vytvořen nový účet",
|
"ToastNewUserCreatedSuccess": "Vytvořen nový účet",
|
||||||
|
|||||||
+24
-2
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Hinzufügen",
|
"ButtonAdd": "Hinzufügen",
|
||||||
|
"ButtonAddApiKey": "API-Schlüssel hinzufügen",
|
||||||
"ButtonAddChapters": "Kapitel hinzufügen",
|
"ButtonAddChapters": "Kapitel hinzufügen",
|
||||||
"ButtonAddDevice": "Gerät hinzufügen",
|
"ButtonAddDevice": "Gerät hinzufügen",
|
||||||
"ButtonAddLibrary": "Bibliothek hinzufügen",
|
"ButtonAddLibrary": "Bibliothek hinzufügen",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Wähle einen Ordner",
|
"ButtonChooseAFolder": "Wähle einen Ordner",
|
||||||
"ButtonChooseFiles": "Wähle eine Datei",
|
"ButtonChooseFiles": "Wähle eine Datei",
|
||||||
"ButtonClearFilter": "Filter löschen",
|
"ButtonClearFilter": "Filter löschen",
|
||||||
|
"ButtonClose": "Schließen",
|
||||||
"ButtonCloseFeed": "Feed schließen",
|
"ButtonCloseFeed": "Feed schließen",
|
||||||
"ButtonCloseSession": "Offene Sitzung schließen",
|
"ButtonCloseSession": "Offene Sitzung schließen",
|
||||||
"ButtonCollections": "Sammlungen",
|
"ButtonCollections": "Sammlungen",
|
||||||
@@ -119,6 +121,7 @@
|
|||||||
"HeaderAccount": "Konto",
|
"HeaderAccount": "Konto",
|
||||||
"HeaderAddCustomMetadataProvider": "Benutzerdefinierten Metadatenanbieter hinzufügen",
|
"HeaderAddCustomMetadataProvider": "Benutzerdefinierten Metadatenanbieter hinzufügen",
|
||||||
"HeaderAdvanced": "Erweitert",
|
"HeaderAdvanced": "Erweitert",
|
||||||
|
"HeaderApiKeys": "API-Schlüssel",
|
||||||
"HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen",
|
"HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen",
|
||||||
"HeaderAudioTracks": "Audiodateien",
|
"HeaderAudioTracks": "Audiodateien",
|
||||||
"HeaderAudiobookTools": "Hörbuch-Dateiverwaltungswerkzeuge",
|
"HeaderAudiobookTools": "Hörbuch-Dateiverwaltungswerkzeuge",
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "Metadaten Rangfolge",
|
"HeaderMetadataOrderOfPrecedence": "Metadaten Rangfolge",
|
||||||
"HeaderMetadataToEmbed": "Einzubettende Metadaten",
|
"HeaderMetadataToEmbed": "Einzubettende Metadaten",
|
||||||
"HeaderNewAccount": "Neues Konto",
|
"HeaderNewAccount": "Neues Konto",
|
||||||
|
"HeaderNewApiKey": "Neuen API-Schlüssel erstellen",
|
||||||
"HeaderNewLibrary": "Neue Bibliothek",
|
"HeaderNewLibrary": "Neue Bibliothek",
|
||||||
"HeaderNotificationCreate": "Benachrichtigung erstellen",
|
"HeaderNotificationCreate": "Benachrichtigung erstellen",
|
||||||
"HeaderNotificationUpdate": "Benachrichtigung bearbeiten",
|
"HeaderNotificationUpdate": "Benachrichtigung bearbeiten",
|
||||||
@@ -206,6 +210,7 @@
|
|||||||
"HeaderTableOfContents": "Inhaltsverzeichnis",
|
"HeaderTableOfContents": "Inhaltsverzeichnis",
|
||||||
"HeaderTools": "Werkzeuge",
|
"HeaderTools": "Werkzeuge",
|
||||||
"HeaderUpdateAccount": "Konto aktualisieren",
|
"HeaderUpdateAccount": "Konto aktualisieren",
|
||||||
|
"HeaderUpdateApiKey": "API-Schlüssel aktualisieren",
|
||||||
"HeaderUpdateAuthor": "Autor aktualisieren",
|
"HeaderUpdateAuthor": "Autor aktualisieren",
|
||||||
"HeaderUpdateDetails": "Details aktualisieren",
|
"HeaderUpdateDetails": "Details aktualisieren",
|
||||||
"HeaderUpdateLibrary": "Bibliothek aktualisieren",
|
"HeaderUpdateLibrary": "Bibliothek aktualisieren",
|
||||||
@@ -235,6 +240,10 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Alle Benutzer außer Gästen",
|
"LabelAllUsersExcludingGuests": "Alle Benutzer außer Gästen",
|
||||||
"LabelAllUsersIncludingGuests": "Alle Benutzer und Gäste",
|
"LabelAllUsersIncludingGuests": "Alle Benutzer und Gäste",
|
||||||
"LabelAlreadyInYourLibrary": "Bereits in der Bibliothek",
|
"LabelAlreadyInYourLibrary": "Bereits in der Bibliothek",
|
||||||
|
"LabelApiKeyCreated": "API-Schlüssel \"{0}\" erfolgreich erstellt.",
|
||||||
|
"LabelApiKeyCreatedDescription": "Speichere den API-Schlüssel an einem sicheren Ort, du wirst ihn später nicht mehr abrufen können.",
|
||||||
|
"LabelApiKeyUser": "Im Kontext eines Nutzers agieren",
|
||||||
|
"LabelApiKeyUserDescription": "Dieser API-Schlüssel hat die gleichen Berechtigungen wie der Benutzer, in dessen Namen er erstellt wurde .In den Protokollen wird es aussehen, als ob der Benutzer die Anfrage durchführte.",
|
||||||
"LabelApiToken": "API Schlüssel",
|
"LabelApiToken": "API Schlüssel",
|
||||||
"LabelAppend": "Anhängen",
|
"LabelAppend": "Anhängen",
|
||||||
"LabelAudioBitrate": "Audiobitrate (z. B. 128 kbit/s)",
|
"LabelAudioBitrate": "Audiobitrate (z. B. 128 kbit/s)",
|
||||||
@@ -346,7 +355,11 @@
|
|||||||
"LabelExample": "Beispiel",
|
"LabelExample": "Beispiel",
|
||||||
"LabelExpandSeries": "Serie ausklappen",
|
"LabelExpandSeries": "Serie ausklappen",
|
||||||
"LabelExpandSubSeries": "Unterserie ausklappen",
|
"LabelExpandSubSeries": "Unterserie ausklappen",
|
||||||
"LabelExplicit": "Explizit (Altersbeschränkung)",
|
"LabelExpired": "Abgelaufen",
|
||||||
|
"LabelExpiresAt": "Läuft ab am",
|
||||||
|
"LabelExpiresInSeconds": "Ablauf in (seconds) Sekunden",
|
||||||
|
"LabelExpiresNever": "Niemals",
|
||||||
|
"LabelExplicit": "Explizit",
|
||||||
"LabelExplicitChecked": "Explicit (Altersbeschränkung) (angehakt)",
|
"LabelExplicitChecked": "Explicit (Altersbeschränkung) (angehakt)",
|
||||||
"LabelExplicitUnchecked": "Not Explicit (Altersbeschränkung) (nicht angehakt)",
|
"LabelExplicitUnchecked": "Not Explicit (Altersbeschränkung) (nicht angehakt)",
|
||||||
"LabelExportOPML": "OPML exportieren",
|
"LabelExportOPML": "OPML exportieren",
|
||||||
@@ -425,6 +438,7 @@
|
|||||||
"LabelLogLevelWarn": "Warnungen",
|
"LabelLogLevelWarn": "Warnungen",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Suche nach neuen Episoden nach diesem Datum",
|
"LabelLookForNewEpisodesAfterDate": "Suche nach neuen Episoden nach diesem Datum",
|
||||||
"LabelLowestPriority": "Niedrigste Priorität",
|
"LabelLowestPriority": "Niedrigste Priorität",
|
||||||
|
"LabelMatchConfidence": "Zuversicht",
|
||||||
"LabelMatchExistingUsersBy": "Zuordnen existierender Benutzer mit",
|
"LabelMatchExistingUsersBy": "Zuordnen existierender Benutzer mit",
|
||||||
"LabelMatchExistingUsersByDescription": "Wird zum Verbinden vorhandener Benutzer verwendet. Sobald die Verbindung hergestellt ist, wird den Benutzern eine eindeutige ID vom SSO-Anbieter zugeordnet",
|
"LabelMatchExistingUsersByDescription": "Wird zum Verbinden vorhandener Benutzer verwendet. Sobald die Verbindung hergestellt ist, wird den Benutzern eine eindeutige ID vom SSO-Anbieter zugeordnet",
|
||||||
"LabelMaxEpisodesToDownload": "Max. Anzahl an Episoden zum Herunterladen, 0 für unbegrenzte Episoden.",
|
"LabelMaxEpisodesToDownload": "Max. Anzahl an Episoden zum Herunterladen, 0 für unbegrenzte Episoden.",
|
||||||
@@ -455,6 +469,7 @@
|
|||||||
"LabelNewestEpisodes": "Neueste Episoden",
|
"LabelNewestEpisodes": "Neueste Episoden",
|
||||||
"LabelNextBackupDate": "Nächstes Sicherungsdatum",
|
"LabelNextBackupDate": "Nächstes Sicherungsdatum",
|
||||||
"LabelNextScheduledRun": "Nächster planmäßiger Durchlauf",
|
"LabelNextScheduledRun": "Nächster planmäßiger Durchlauf",
|
||||||
|
"LabelNoApiKeys": "Keine API-Schlüssel vorhanden",
|
||||||
"LabelNoCustomMetadataProviders": "Keine benutzerdefinierten Metadata Anbieter",
|
"LabelNoCustomMetadataProviders": "Keine benutzerdefinierten Metadata Anbieter",
|
||||||
"LabelNoEpisodesSelected": "Keine Episoden ausgewählt",
|
"LabelNoEpisodesSelected": "Keine Episoden ausgewählt",
|
||||||
"LabelNotFinished": "Nicht beendet",
|
"LabelNotFinished": "Nicht beendet",
|
||||||
@@ -544,6 +559,7 @@
|
|||||||
"LabelSelectAll": "Alles auswählen",
|
"LabelSelectAll": "Alles auswählen",
|
||||||
"LabelSelectAllEpisodes": "Alle Episoden auswählen",
|
"LabelSelectAllEpisodes": "Alle Episoden auswählen",
|
||||||
"LabelSelectEpisodesShowing": "{0} ausgewählte Episoden werden angezeigt",
|
"LabelSelectEpisodesShowing": "{0} ausgewählte Episoden werden angezeigt",
|
||||||
|
"LabelSelectUser": "Ausgewählter Benutzer",
|
||||||
"LabelSelectUsers": "Benutzer auswählen",
|
"LabelSelectUsers": "Benutzer auswählen",
|
||||||
"LabelSendEbookToDevice": "E-Buch senden an …",
|
"LabelSendEbookToDevice": "E-Buch senden an …",
|
||||||
"LabelSequence": "Reihenfolge",
|
"LabelSequence": "Reihenfolge",
|
||||||
@@ -708,7 +724,9 @@
|
|||||||
"MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen",
|
"MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen",
|
||||||
"MessageAppriseDescription": "Um diese Funktion nutzen zu können, musst du eine Instanz von <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann. <br />Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter <code>http://192.168.1.1:8337</code> läuft, würdest du <code>http://192.168.1.1:8337/notify</code> eingeben.",
|
"MessageAppriseDescription": "Um diese Funktion nutzen zu können, musst du eine Instanz von <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann. <br />Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter <code>http://192.168.1.1:8337</code> läuft, würdest du <code>http://192.168.1.1:8337/notify</code> eingeben.",
|
||||||
"MessageAsinCheck": "Stellen Sie sicher, dass Sie die ASIN aus der richtigen Audible Region verwenden, nicht Amazon.",
|
"MessageAsinCheck": "Stellen Sie sicher, dass Sie die ASIN aus der richtigen Audible Region verwenden, nicht Amazon.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Alte API tokens werden in Zukunft entfernt. Benutze stattdessen <a href=\"/config/api-keys\">API Keys</a>.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Nach dem Speichern muss der Server neugestartet werden um die OIDC Änderungen zu übernehmen.",
|
"MessageAuthenticationOIDCChangesRestart": "Nach dem Speichern muss der Server neugestartet werden um die OIDC Änderungen zu übernehmen.",
|
||||||
|
"MessageAuthenticationSecurityMessage": "Die Anmeldung wurde abgesichert. Benutzersitzungen werden getrennt, alle Benutzer müssen sich erneut anmelden.",
|
||||||
"MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in <code>/metadata/items</code> & <code>/metadata/authors</code> gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Medien-Ordnern) gespeichert sind.",
|
"MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in <code>/metadata/items</code> & <code>/metadata/authors</code> gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Medien-Ordnern) gespeichert sind.",
|
||||||
"MessageBackupsLocationEditNote": "Hinweis: Durch das Aktualisieren des Backup-Speicherorts werden vorhandene Sicherungen nicht verschoben oder geändert",
|
"MessageBackupsLocationEditNote": "Hinweis: Durch das Aktualisieren des Backup-Speicherorts werden vorhandene Sicherungen nicht verschoben oder geändert",
|
||||||
"MessageBackupsLocationNoEditNote": "Hinweis: Der Sicherungsspeicherort wird über eine Umgebungsvariable festgelegt und kann hier nicht geändert werden.",
|
"MessageBackupsLocationNoEditNote": "Hinweis: Der Sicherungsspeicherort wird über eine Umgebungsvariable festgelegt und kann hier nicht geändert werden.",
|
||||||
@@ -730,6 +748,7 @@
|
|||||||
"MessageChaptersNotFound": "Kapitel gefunden nicht",
|
"MessageChaptersNotFound": "Kapitel gefunden nicht",
|
||||||
"MessageCheckingCron": "Überprüfe Cron...",
|
"MessageCheckingCron": "Überprüfe Cron...",
|
||||||
"MessageConfirmCloseFeed": "Feed wird geschlossen! Bist du dir sicher?",
|
"MessageConfirmCloseFeed": "Feed wird geschlossen! Bist du dir sicher?",
|
||||||
|
"MessageConfirmDeleteApiKey": "Möchtest du den API-Schlüssel \"{0}\" wirklich entfernen ?",
|
||||||
"MessageConfirmDeleteBackup": "Sicherung für {0} wird gelöscht! Bist du dir sicher?",
|
"MessageConfirmDeleteBackup": "Sicherung für {0} wird gelöscht! Bist du dir sicher?",
|
||||||
"MessageConfirmDeleteDevice": "Möchtest du das Lesegerät „{0}“ wirklich löschen?",
|
"MessageConfirmDeleteDevice": "Möchtest du das Lesegerät „{0}“ wirklich löschen?",
|
||||||
"MessageConfirmDeleteFile": "Datei wird vom System gelöscht! Bist du dir sicher?",
|
"MessageConfirmDeleteFile": "Datei wird vom System gelöscht! Bist du dir sicher?",
|
||||||
@@ -1001,7 +1020,9 @@
|
|||||||
"ToastEpisodeDownloadQueueClearSuccess": "Warteschlange für Episoden-Downloads gelöscht",
|
"ToastEpisodeDownloadQueueClearSuccess": "Warteschlange für Episoden-Downloads gelöscht",
|
||||||
"ToastEpisodeUpdateSuccess": "{0} Episoden aktualisiert",
|
"ToastEpisodeUpdateSuccess": "{0} Episoden aktualisiert",
|
||||||
"ToastErrorCannotShare": "Das kann nicht nativ auf diesem Gerät freigegeben werden",
|
"ToastErrorCannotShare": "Das kann nicht nativ auf diesem Gerät freigegeben werden",
|
||||||
"ToastFailedToLoadData": "Daten laden fehlgeschlagen",
|
"ToastFailedToCreate": "Fehler beim Erzeugen",
|
||||||
|
"ToastFailedToDelete": "Fehler beim Löschen",
|
||||||
|
"ToastFailedToLoadData": "Fehler beim laden der Daten",
|
||||||
"ToastFailedToMatch": "Fehler beim Abgleich",
|
"ToastFailedToMatch": "Fehler beim Abgleich",
|
||||||
"ToastFailedToShare": "Fehler beim Teilen",
|
"ToastFailedToShare": "Fehler beim Teilen",
|
||||||
"ToastFailedToUpdate": "Aktualisierung ist fehlgeschlagen",
|
"ToastFailedToUpdate": "Aktualisierung ist fehlgeschlagen",
|
||||||
@@ -1032,6 +1053,7 @@
|
|||||||
"ToastMustHaveAtLeastOnePath": "Es muss mindestens ein Pfad angegeben werden",
|
"ToastMustHaveAtLeastOnePath": "Es muss mindestens ein Pfad angegeben werden",
|
||||||
"ToastNameEmailRequired": "Name und E-Mail sind erforderlich",
|
"ToastNameEmailRequired": "Name und E-Mail sind erforderlich",
|
||||||
"ToastNameRequired": "Name ist erforderlich",
|
"ToastNameRequired": "Name ist erforderlich",
|
||||||
|
"ToastNewApiKeyUserError": "Bitte wähle einen Benutzer aus (Pflichtfeld)",
|
||||||
"ToastNewEpisodesFound": "{0} neue Episoden gefunden",
|
"ToastNewEpisodesFound": "{0} neue Episoden gefunden",
|
||||||
"ToastNewUserCreatedFailed": "Fehler beim erstellen des Accounts: \"{ 0}\"",
|
"ToastNewUserCreatedFailed": "Fehler beim erstellen des Accounts: \"{ 0}\"",
|
||||||
"ToastNewUserCreatedSuccess": "Neuer Account erstellt",
|
"ToastNewUserCreatedSuccess": "Neuer Account erstellt",
|
||||||
|
|||||||
@@ -438,6 +438,7 @@
|
|||||||
"LabelLogLevelWarn": "Warn",
|
"LabelLogLevelWarn": "Warn",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
|
||||||
"LabelLowestPriority": "Lowest Priority",
|
"LabelLowestPriority": "Lowest Priority",
|
||||||
|
"LabelMatchConfidence": "Confidence",
|
||||||
"LabelMatchExistingUsersBy": "Match existing users by",
|
"LabelMatchExistingUsersBy": "Match existing users by",
|
||||||
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
|
||||||
"LabelMaxEpisodesToDownload": "Max # of episodes to download. Use 0 for unlimited.",
|
"LabelMaxEpisodesToDownload": "Max # of episodes to download. Use 0 for unlimited.",
|
||||||
@@ -723,6 +724,7 @@
|
|||||||
"MessageAddToPlayerQueue": "Add to player queue",
|
"MessageAddToPlayerQueue": "Add to player queue",
|
||||||
"MessageAppriseDescription": "To use this feature you will need to have an instance of <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> running or an api that will handle those same requests. <br />The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at <code>http://192.168.1.1:8337</code> then you would put <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "To use this feature you will need to have an instance of <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> running or an api that will handle those same requests. <br />The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at <code>http://192.168.1.1:8337</code> then you would put <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageAsinCheck": "Ensure you are using the ASIN from the correct Audible region, not Amazon.",
|
"MessageAsinCheck": "Ensure you are using the ASIN from the correct Audible region, not Amazon.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Legacy API tokens will be removed in the future. Use <a href=\"/config/api-keys\">API Keys</a> instead.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Restart your server after saving to apply OIDC changes.",
|
"MessageAuthenticationOIDCChangesRestart": "Restart your server after saving to apply OIDC changes.",
|
||||||
"MessageAuthenticationSecurityMessage": "Authentication has been improved for security. All users are required to re-login.",
|
"MessageAuthenticationSecurityMessage": "Authentication has been improved for security. All users are required to re-login.",
|
||||||
"MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in <code>/metadata/items</code> & <code>/metadata/authors</code>. Backups <strong>do not</strong> include any files stored in your library folders.",
|
"MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in <code>/metadata/items</code> & <code>/metadata/authors</code>. Backups <strong>do not</strong> include any files stored in your library folders.",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Ajouter",
|
"ButtonAdd": "Ajouter",
|
||||||
|
"ButtonAddApiKey": "Ajouter une clé API",
|
||||||
"ButtonAddChapters": "Ajouter des chapitres",
|
"ButtonAddChapters": "Ajouter des chapitres",
|
||||||
"ButtonAddDevice": "Ajouter un appareil",
|
"ButtonAddDevice": "Ajouter un appareil",
|
||||||
"ButtonAddLibrary": "Ajouter une bibliothèque",
|
"ButtonAddLibrary": "Ajouter une bibliothèque",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Sélectionner un dossier",
|
"ButtonChooseAFolder": "Sélectionner un dossier",
|
||||||
"ButtonChooseFiles": "Sélectionner des fichiers",
|
"ButtonChooseFiles": "Sélectionner des fichiers",
|
||||||
"ButtonClearFilter": "Effacer le filtre",
|
"ButtonClearFilter": "Effacer le filtre",
|
||||||
|
"ButtonClose": "Fermer",
|
||||||
"ButtonCloseFeed": "Fermer le flux",
|
"ButtonCloseFeed": "Fermer le flux",
|
||||||
"ButtonCloseSession": "Fermer la session",
|
"ButtonCloseSession": "Fermer la session",
|
||||||
"ButtonCollections": "Collections",
|
"ButtonCollections": "Collections",
|
||||||
@@ -119,6 +121,7 @@
|
|||||||
"HeaderAccount": "Compte",
|
"HeaderAccount": "Compte",
|
||||||
"HeaderAddCustomMetadataProvider": "Ajouter un fournisseur de métadonnées personnalisé",
|
"HeaderAddCustomMetadataProvider": "Ajouter un fournisseur de métadonnées personnalisé",
|
||||||
"HeaderAdvanced": "Avancé",
|
"HeaderAdvanced": "Avancé",
|
||||||
|
"HeaderApiKeys": "Clés API",
|
||||||
"HeaderAppriseNotificationSettings": "Configuration des notifications Apprise",
|
"HeaderAppriseNotificationSettings": "Configuration des notifications Apprise",
|
||||||
"HeaderAudioTracks": "Pistes audio",
|
"HeaderAudioTracks": "Pistes audio",
|
||||||
"HeaderAudiobookTools": "Outils de gestion de fichiers de livres audio",
|
"HeaderAudiobookTools": "Outils de gestion de fichiers de livres audio",
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "Ordre de priorité des métadonnées",
|
"HeaderMetadataOrderOfPrecedence": "Ordre de priorité des métadonnées",
|
||||||
"HeaderMetadataToEmbed": "Métadonnées à intégrer",
|
"HeaderMetadataToEmbed": "Métadonnées à intégrer",
|
||||||
"HeaderNewAccount": "Nouveau compte",
|
"HeaderNewAccount": "Nouveau compte",
|
||||||
|
"HeaderNewApiKey": "Nouvelle clé API",
|
||||||
"HeaderNewLibrary": "Nouvelle bibliothèque",
|
"HeaderNewLibrary": "Nouvelle bibliothèque",
|
||||||
"HeaderNotificationCreate": "Créer une notification",
|
"HeaderNotificationCreate": "Créer une notification",
|
||||||
"HeaderNotificationUpdate": "Mise à jour de la notification",
|
"HeaderNotificationUpdate": "Mise à jour de la notification",
|
||||||
@@ -177,6 +181,7 @@
|
|||||||
"HeaderPlaylist": "Liste de lecture",
|
"HeaderPlaylist": "Liste de lecture",
|
||||||
"HeaderPlaylistItems": "Éléments de la liste de lecture",
|
"HeaderPlaylistItems": "Éléments de la liste de lecture",
|
||||||
"HeaderPodcastsToAdd": "Podcasts à ajouter",
|
"HeaderPodcastsToAdd": "Podcasts à ajouter",
|
||||||
|
"HeaderPresets": "Préréglages",
|
||||||
"HeaderPreviewCover": "Prévisualiser la couverture",
|
"HeaderPreviewCover": "Prévisualiser la couverture",
|
||||||
"HeaderRSSFeedGeneral": "Détails du flux RSS",
|
"HeaderRSSFeedGeneral": "Détails du flux RSS",
|
||||||
"HeaderRSSFeedIsOpen": "Le flux RSS est actif",
|
"HeaderRSSFeedIsOpen": "Le flux RSS est actif",
|
||||||
@@ -205,6 +210,7 @@
|
|||||||
"HeaderTableOfContents": "Table des matières",
|
"HeaderTableOfContents": "Table des matières",
|
||||||
"HeaderTools": "Outils",
|
"HeaderTools": "Outils",
|
||||||
"HeaderUpdateAccount": "Mettre à jour le compte",
|
"HeaderUpdateAccount": "Mettre à jour le compte",
|
||||||
|
"HeaderUpdateApiKey": "Mettre à jour la clé API",
|
||||||
"HeaderUpdateAuthor": "Mettre à jour l’auteur",
|
"HeaderUpdateAuthor": "Mettre à jour l’auteur",
|
||||||
"HeaderUpdateDetails": "Mettre à jour les détails",
|
"HeaderUpdateDetails": "Mettre à jour les détails",
|
||||||
"HeaderUpdateLibrary": "Mettre à jour la bibliothèque",
|
"HeaderUpdateLibrary": "Mettre à jour la bibliothèque",
|
||||||
@@ -234,6 +240,10 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Tous les utilisateurs à l’exception des invités",
|
"LabelAllUsersExcludingGuests": "Tous les utilisateurs à l’exception des invités",
|
||||||
"LabelAllUsersIncludingGuests": "Tous les utilisateurs, y compris les invités",
|
"LabelAllUsersIncludingGuests": "Tous les utilisateurs, y compris les invités",
|
||||||
"LabelAlreadyInYourLibrary": "Déjà dans la bibliothèque",
|
"LabelAlreadyInYourLibrary": "Déjà dans la bibliothèque",
|
||||||
|
"LabelApiKeyCreated": "La clé API « {0} » a été créée avec succès.",
|
||||||
|
"LabelApiKeyCreatedDescription": "Assurez-vous de copier la clé API maintenant car vous ne pourrez plus la voir.",
|
||||||
|
"LabelApiKeyUser": "Agir au nom de l’utilisateur",
|
||||||
|
"LabelApiKeyUserDescription": "Cette clé API disposera des mêmes autorisations que l’utilisateur pour lequel elle agit. Elle apparaîtra dans les journaux comme si c’était l’utilisateur qui effectuait la requête.",
|
||||||
"LabelApiToken": "Token API",
|
"LabelApiToken": "Token API",
|
||||||
"LabelAppend": "Ajouter",
|
"LabelAppend": "Ajouter",
|
||||||
"LabelAudioBitrate": "Débit audio (par exemple 128k)",
|
"LabelAudioBitrate": "Débit audio (par exemple 128k)",
|
||||||
@@ -345,6 +355,10 @@
|
|||||||
"LabelExample": "Exemple",
|
"LabelExample": "Exemple",
|
||||||
"LabelExpandSeries": "Développer la série",
|
"LabelExpandSeries": "Développer la série",
|
||||||
"LabelExpandSubSeries": "Développer les sous-séries",
|
"LabelExpandSubSeries": "Développer les sous-séries",
|
||||||
|
"LabelExpired": "Expiré",
|
||||||
|
"LabelExpiresAt": "Expire à",
|
||||||
|
"LabelExpiresInSeconds": "Expire dans (secondes)",
|
||||||
|
"LabelExpiresNever": "Jamais",
|
||||||
"LabelExplicit": "Restriction",
|
"LabelExplicit": "Restriction",
|
||||||
"LabelExplicitChecked": "Explicite (vérifié)",
|
"LabelExplicitChecked": "Explicite (vérifié)",
|
||||||
"LabelExplicitUnchecked": "Non explicite (non vérifié)",
|
"LabelExplicitUnchecked": "Non explicite (non vérifié)",
|
||||||
@@ -454,6 +468,7 @@
|
|||||||
"LabelNewestEpisodes": "Épisodes récents",
|
"LabelNewestEpisodes": "Épisodes récents",
|
||||||
"LabelNextBackupDate": "Date de la prochaine sauvegarde",
|
"LabelNextBackupDate": "Date de la prochaine sauvegarde",
|
||||||
"LabelNextScheduledRun": "Prochain lancement prévu",
|
"LabelNextScheduledRun": "Prochain lancement prévu",
|
||||||
|
"LabelNoApiKeys": "Aucune clé API",
|
||||||
"LabelNoCustomMetadataProviders": "Aucun fournisseurs de métadonnées personnalisés",
|
"LabelNoCustomMetadataProviders": "Aucun fournisseurs de métadonnées personnalisés",
|
||||||
"LabelNoEpisodesSelected": "Aucun épisode sélectionné",
|
"LabelNoEpisodesSelected": "Aucun épisode sélectionné",
|
||||||
"LabelNotFinished": "Non terminé",
|
"LabelNotFinished": "Non terminé",
|
||||||
@@ -543,6 +558,7 @@
|
|||||||
"LabelSelectAll": "Tout sélectionner",
|
"LabelSelectAll": "Tout sélectionner",
|
||||||
"LabelSelectAllEpisodes": "Sélectionner tous les épisodes",
|
"LabelSelectAllEpisodes": "Sélectionner tous les épisodes",
|
||||||
"LabelSelectEpisodesShowing": "Sélectionner {0} épisode(s) en cours",
|
"LabelSelectEpisodesShowing": "Sélectionner {0} épisode(s) en cours",
|
||||||
|
"LabelSelectUser": "Sélectionner l’utilisateur",
|
||||||
"LabelSelectUsers": "Sélectionner les utilisateurs",
|
"LabelSelectUsers": "Sélectionner les utilisateurs",
|
||||||
"LabelSendEbookToDevice": "Envoyer le livre numérique à…",
|
"LabelSendEbookToDevice": "Envoyer le livre numérique à…",
|
||||||
"LabelSequence": "Séquence",
|
"LabelSequence": "Séquence",
|
||||||
@@ -707,7 +723,9 @@
|
|||||||
"MessageAddToPlayerQueue": "Ajouter en file d’attente",
|
"MessageAddToPlayerQueue": "Ajouter en file d’attente",
|
||||||
"MessageAppriseDescription": "Nécessite une instance d’<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes.<br />L’URL de l’API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Nécessite une instance d’<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API Apprise</a> pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes.<br />L’URL de l’API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur <code>http://192.168.1.1:8337</code> alors vous devez mettre <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageAsinCheck": "Assurez-vous d’utiliser l’ASIN de la bonne région Audible, et non d’Amazon.",
|
"MessageAsinCheck": "Assurez-vous d’utiliser l’ASIN de la bonne région Audible, et non d’Amazon.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Les jetons d’API hérités seront supprimés à l’avenir. Utilisez plutôt les <a href=\"/config/api-keys\">clés API</a>.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Redémarrez votre serveur après avoir enregistré pour appliquer les modifications OIDC.",
|
"MessageAuthenticationOIDCChangesRestart": "Redémarrez votre serveur après avoir enregistré pour appliquer les modifications OIDC.",
|
||||||
|
"MessageAuthenticationSecurityMessage": "L’authentification a été améliorée pour plus de sécurité. Tous les utilisateurs doivent se reconnecter.",
|
||||||
"MessageBackupsDescription": "Les sauvegardes incluent les utilisateurs, la progression des utilisateurs, les détails des éléments de la bibliothèque, les paramètres du serveur et les images stockées dans <code>/metadata/items</code> & <code>/metadata/authors</code>. Les sauvegardes <strong>n’incluent pas</strong> les fichiers stockés dans les dossiers de votre bibliothèque.",
|
"MessageBackupsDescription": "Les sauvegardes incluent les utilisateurs, la progression des utilisateurs, les détails des éléments de la bibliothèque, les paramètres du serveur et les images stockées dans <code>/metadata/items</code> & <code>/metadata/authors</code>. Les sauvegardes <strong>n’incluent pas</strong> les fichiers stockés dans les dossiers de votre bibliothèque.",
|
||||||
"MessageBackupsLocationEditNote": "Remarque : Mettre à jour l'emplacement de sauvegarde ne déplacera pas ou ne modifiera pas les sauvegardes existantes",
|
"MessageBackupsLocationEditNote": "Remarque : Mettre à jour l'emplacement de sauvegarde ne déplacera pas ou ne modifiera pas les sauvegardes existantes",
|
||||||
"MessageBackupsLocationNoEditNote": "Remarque : l’emplacement de sauvegarde est défini via une variable d’environnement et ne peut pas être modifié ici.",
|
"MessageBackupsLocationNoEditNote": "Remarque : l’emplacement de sauvegarde est défini via une variable d’environnement et ne peut pas être modifié ici.",
|
||||||
@@ -729,6 +747,7 @@
|
|||||||
"MessageChaptersNotFound": "Chapitres non trouvés",
|
"MessageChaptersNotFound": "Chapitres non trouvés",
|
||||||
"MessageCheckingCron": "Vérification du cron…",
|
"MessageCheckingCron": "Vérification du cron…",
|
||||||
"MessageConfirmCloseFeed": "Êtes-vous sûr·e de vouloir fermer ce flux ?",
|
"MessageConfirmCloseFeed": "Êtes-vous sûr·e de vouloir fermer ce flux ?",
|
||||||
|
"MessageConfirmDeleteApiKey": "Êtes-vous sûr de vouloir supprimer la clé API « {0} » ?",
|
||||||
"MessageConfirmDeleteBackup": "Êtes-vous sûr·e de vouloir supprimer la sauvegarde de « {0} » ?",
|
"MessageConfirmDeleteBackup": "Êtes-vous sûr·e de vouloir supprimer la sauvegarde de « {0} » ?",
|
||||||
"MessageConfirmDeleteDevice": "Êtes-vous sûr·e de vouloir supprimer la liseuse « {0} » ?",
|
"MessageConfirmDeleteDevice": "Êtes-vous sûr·e de vouloir supprimer la liseuse « {0} » ?",
|
||||||
"MessageConfirmDeleteFile": "Cela supprimera le fichier de votre système de fichiers. Êtes-vous sûr ?",
|
"MessageConfirmDeleteFile": "Cela supprimera le fichier de votre système de fichiers. Êtes-vous sûr ?",
|
||||||
@@ -756,6 +775,7 @@
|
|||||||
"MessageConfirmRemoveAuthor": "Êtes-vous sûr·e de vouloir supprimer l’auteur « {0} » ?",
|
"MessageConfirmRemoveAuthor": "Êtes-vous sûr·e de vouloir supprimer l’auteur « {0} » ?",
|
||||||
"MessageConfirmRemoveCollection": "Êtes-vous sûr·e de vouloir supprimer la collection « {0} » ?",
|
"MessageConfirmRemoveCollection": "Êtes-vous sûr·e de vouloir supprimer la collection « {0} » ?",
|
||||||
"MessageConfirmRemoveEpisode": "Êtes-vous sûr·e de vouloir supprimer l’épisode « {0} » ?",
|
"MessageConfirmRemoveEpisode": "Êtes-vous sûr·e de vouloir supprimer l’épisode « {0} » ?",
|
||||||
|
"MessageConfirmRemoveEpisodeNote": "Remarque : cela ne supprime pas le fichier audio, sauf si vous activez « Supprimer définitivement le fichier »",
|
||||||
"MessageConfirmRemoveEpisodes": "Êtes-vous sûr·e de vouloir supprimer {0} épisodes ?",
|
"MessageConfirmRemoveEpisodes": "Êtes-vous sûr·e de vouloir supprimer {0} épisodes ?",
|
||||||
"MessageConfirmRemoveListeningSessions": "Êtes-vous sûr·e de vouloir supprimer {0} sessions d’écoute ?",
|
"MessageConfirmRemoveListeningSessions": "Êtes-vous sûr·e de vouloir supprimer {0} sessions d’écoute ?",
|
||||||
"MessageConfirmRemoveMetadataFiles": "Êtes-vous sûr·e de vouloir supprimer tous les fichiers « metatadata.{0} » des dossiers d’éléments de votre bibliothèque ?",
|
"MessageConfirmRemoveMetadataFiles": "Êtes-vous sûr·e de vouloir supprimer tous les fichiers « metatadata.{0} » des dossiers d’éléments de votre bibliothèque ?",
|
||||||
@@ -917,6 +937,8 @@
|
|||||||
"NotificationOnBackupCompletedDescription": "Déclenché lorsqu’une sauvegarde est terminée",
|
"NotificationOnBackupCompletedDescription": "Déclenché lorsqu’une sauvegarde est terminée",
|
||||||
"NotificationOnBackupFailedDescription": "Déclenché lorsqu'une sauvegarde échoue",
|
"NotificationOnBackupFailedDescription": "Déclenché lorsqu'une sauvegarde échoue",
|
||||||
"NotificationOnEpisodeDownloadedDescription": "Déclenché lorsqu’un épisode de podcast est téléchargé automatiquement",
|
"NotificationOnEpisodeDownloadedDescription": "Déclenché lorsqu’un épisode de podcast est téléchargé automatiquement",
|
||||||
|
"NotificationOnRSSFeedDisabledDescription": "Déclenché lorsque les téléchargements automatiques d’épisodes sont désactivés en raison d’un trop grand nombre de tentatives infructueuses",
|
||||||
|
"NotificationOnRSSFeedFailedDescription": "Déclenché lorsque la demande de flux RSS échoue pour un téléchargement automatique d’épisode",
|
||||||
"NotificationOnTestDescription": "Événement pour tester le système de notification",
|
"NotificationOnTestDescription": "Événement pour tester le système de notification",
|
||||||
"PlaceholderNewCollection": "Nom de la nouvelle collection",
|
"PlaceholderNewCollection": "Nom de la nouvelle collection",
|
||||||
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
|
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
|
||||||
@@ -997,6 +1019,8 @@
|
|||||||
"ToastEpisodeDownloadQueueClearSuccess": "File d’attente de téléchargement des épisodes effacée",
|
"ToastEpisodeDownloadQueueClearSuccess": "File d’attente de téléchargement des épisodes effacée",
|
||||||
"ToastEpisodeUpdateSuccess": "{0} épisodes mis à jour",
|
"ToastEpisodeUpdateSuccess": "{0} épisodes mis à jour",
|
||||||
"ToastErrorCannotShare": "Impossible de partager nativement sur cet appareil",
|
"ToastErrorCannotShare": "Impossible de partager nativement sur cet appareil",
|
||||||
|
"ToastFailedToCreate": "Échec de la création",
|
||||||
|
"ToastFailedToDelete": "Échec de la suppression",
|
||||||
"ToastFailedToLoadData": "Échec du chargement des données",
|
"ToastFailedToLoadData": "Échec du chargement des données",
|
||||||
"ToastFailedToMatch": "Échec de la correspondance",
|
"ToastFailedToMatch": "Échec de la correspondance",
|
||||||
"ToastFailedToShare": "Échec du partage",
|
"ToastFailedToShare": "Échec du partage",
|
||||||
@@ -1028,6 +1052,7 @@
|
|||||||
"ToastMustHaveAtLeastOnePath": "Doit avoir au moins un chemin",
|
"ToastMustHaveAtLeastOnePath": "Doit avoir au moins un chemin",
|
||||||
"ToastNameEmailRequired": "Le nom et le courriel sont requis",
|
"ToastNameEmailRequired": "Le nom et le courriel sont requis",
|
||||||
"ToastNameRequired": "Le nom est requis",
|
"ToastNameRequired": "Le nom est requis",
|
||||||
|
"ToastNewApiKeyUserError": "Vous devez sélectionner un utilisateur",
|
||||||
"ToastNewEpisodesFound": "{0} nouveaux épisodes trouvés",
|
"ToastNewEpisodesFound": "{0} nouveaux épisodes trouvés",
|
||||||
"ToastNewUserCreatedFailed": "La création du compte à échouée : « {0} »",
|
"ToastNewUserCreatedFailed": "La création du compte à échouée : « {0} »",
|
||||||
"ToastNewUserCreatedSuccess": "Nouveau compte créé",
|
"ToastNewUserCreatedSuccess": "Nouveau compte créé",
|
||||||
|
|||||||
+23
-1
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Dodaj",
|
"ButtonAdd": "Dodaj",
|
||||||
|
"ButtonAddApiKey": "Dodaj API ključ",
|
||||||
"ButtonAddChapters": "Dodaj poglavlja",
|
"ButtonAddChapters": "Dodaj poglavlja",
|
||||||
"ButtonAddDevice": "Dodaj uređaj",
|
"ButtonAddDevice": "Dodaj uređaj",
|
||||||
"ButtonAddLibrary": "Dodaj knjižnicu",
|
"ButtonAddLibrary": "Dodaj knjižnicu",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Odaberi mapu",
|
"ButtonChooseAFolder": "Odaberi mapu",
|
||||||
"ButtonChooseFiles": "Odaberi datoteke",
|
"ButtonChooseFiles": "Odaberi datoteke",
|
||||||
"ButtonClearFilter": "Poništi filter",
|
"ButtonClearFilter": "Poništi filter",
|
||||||
|
"ButtonClose": "Zatvori",
|
||||||
"ButtonCloseFeed": "Zatvori izvor",
|
"ButtonCloseFeed": "Zatvori izvor",
|
||||||
"ButtonCloseSession": "Zatvori otvorenu sesiju",
|
"ButtonCloseSession": "Zatvori otvorenu sesiju",
|
||||||
"ButtonCollections": "Zbirke",
|
"ButtonCollections": "Zbirke",
|
||||||
@@ -119,6 +121,7 @@
|
|||||||
"HeaderAccount": "Korisnički račun",
|
"HeaderAccount": "Korisnički račun",
|
||||||
"HeaderAddCustomMetadataProvider": "Dodaj prilagođenog pružatelja meta-podataka",
|
"HeaderAddCustomMetadataProvider": "Dodaj prilagođenog pružatelja meta-podataka",
|
||||||
"HeaderAdvanced": "Napredno",
|
"HeaderAdvanced": "Napredno",
|
||||||
|
"HeaderApiKeys": "API ključevi",
|
||||||
"HeaderAppriseNotificationSettings": "Postavke obavijesti Apprise",
|
"HeaderAppriseNotificationSettings": "Postavke obavijesti Apprise",
|
||||||
"HeaderAudioTracks": "Zvučni zapisi",
|
"HeaderAudioTracks": "Zvučni zapisi",
|
||||||
"HeaderAudiobookTools": "Alati za upravljanje datotekama zvučnih knjiga",
|
"HeaderAudiobookTools": "Alati za upravljanje datotekama zvučnih knjiga",
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "Redoslijed prihvaćanja meta-podataka",
|
"HeaderMetadataOrderOfPrecedence": "Redoslijed prihvaćanja meta-podataka",
|
||||||
"HeaderMetadataToEmbed": "Meta-podatci za ugradnju",
|
"HeaderMetadataToEmbed": "Meta-podatci za ugradnju",
|
||||||
"HeaderNewAccount": "Novi korisnički račun",
|
"HeaderNewAccount": "Novi korisnički račun",
|
||||||
|
"HeaderNewApiKey": "Novi API ključ",
|
||||||
"HeaderNewLibrary": "Nova knjižnica",
|
"HeaderNewLibrary": "Nova knjižnica",
|
||||||
"HeaderNotificationCreate": "Izradi obavijest",
|
"HeaderNotificationCreate": "Izradi obavijest",
|
||||||
"HeaderNotificationUpdate": "Ažuriraj obavijest",
|
"HeaderNotificationUpdate": "Ažuriraj obavijest",
|
||||||
@@ -206,6 +210,7 @@
|
|||||||
"HeaderTableOfContents": "Sadržaj",
|
"HeaderTableOfContents": "Sadržaj",
|
||||||
"HeaderTools": "Alati",
|
"HeaderTools": "Alati",
|
||||||
"HeaderUpdateAccount": "Ažuriraj korisnički račun",
|
"HeaderUpdateAccount": "Ažuriraj korisnički račun",
|
||||||
|
"HeaderUpdateApiKey": "Ažuriraj API ključ",
|
||||||
"HeaderUpdateAuthor": "Ažuriraj autora",
|
"HeaderUpdateAuthor": "Ažuriraj autora",
|
||||||
"HeaderUpdateDetails": "Ažuriraj pojedinosti",
|
"HeaderUpdateDetails": "Ažuriraj pojedinosti",
|
||||||
"HeaderUpdateLibrary": "Ažuriraj knjižnicu",
|
"HeaderUpdateLibrary": "Ažuriraj knjižnicu",
|
||||||
@@ -235,6 +240,10 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Svi korisnici osim gostiju",
|
"LabelAllUsersExcludingGuests": "Svi korisnici osim gostiju",
|
||||||
"LabelAllUsersIncludingGuests": "Svi korisnici uključujući i goste",
|
"LabelAllUsersIncludingGuests": "Svi korisnici uključujući i goste",
|
||||||
"LabelAlreadyInYourLibrary": "Već u vašoj knjižnici",
|
"LabelAlreadyInYourLibrary": "Već u vašoj knjižnici",
|
||||||
|
"LabelApiKeyCreated": "API ključ \"{0}\" uspješno izrađen.",
|
||||||
|
"LabelApiKeyCreatedDescription": "Ne zaboravite odmah kopirati API ključ jer ga više nećete moći vidjeti.",
|
||||||
|
"LabelApiKeyUser": "Izvršavaj u ime korisnika",
|
||||||
|
"LabelApiKeyUserDescription": "Ovaj API ključ imat će iste dozvole kao i korisnik u čije ime djeluje. U zapisnicima će biti zabilježeno da je korisnik slao zahtjeve.",
|
||||||
"LabelApiToken": "API Token",
|
"LabelApiToken": "API Token",
|
||||||
"LabelAppend": "Pridodaj",
|
"LabelAppend": "Pridodaj",
|
||||||
"LabelAudioBitrate": "Kvaliteta zvučnog zapisa (npr. 128k)",
|
"LabelAudioBitrate": "Kvaliteta zvučnog zapisa (npr. 128k)",
|
||||||
@@ -346,7 +355,11 @@
|
|||||||
"LabelExample": "Primjer",
|
"LabelExample": "Primjer",
|
||||||
"LabelExpandSeries": "Serijal prikaži prošireno",
|
"LabelExpandSeries": "Serijal prikaži prošireno",
|
||||||
"LabelExpandSubSeries": "Podserijal prikaži prošireno",
|
"LabelExpandSubSeries": "Podserijal prikaži prošireno",
|
||||||
"LabelExplicit": "Eksplicitni sadržaj",
|
"LabelExpired": "Istekao",
|
||||||
|
"LabelExpiresAt": "Istječe",
|
||||||
|
"LabelExpiresInSeconds": "Istječe za (sekundi)",
|
||||||
|
"LabelExpiresNever": "Nikada",
|
||||||
|
"LabelExplicit": "Eksplicitno",
|
||||||
"LabelExplicitChecked": "Eksplicitni sadržaj (označeno)",
|
"LabelExplicitChecked": "Eksplicitni sadržaj (označeno)",
|
||||||
"LabelExplicitUnchecked": "Nije eksplicitni sadržaj (odznačeno)",
|
"LabelExplicitUnchecked": "Nije eksplicitni sadržaj (odznačeno)",
|
||||||
"LabelExportOPML": "Izvoz OPML-a",
|
"LabelExportOPML": "Izvoz OPML-a",
|
||||||
@@ -455,6 +468,7 @@
|
|||||||
"LabelNewestEpisodes": "Najnoviji nastavci",
|
"LabelNewestEpisodes": "Najnoviji nastavci",
|
||||||
"LabelNextBackupDate": "Sljedeća izrada sigurnosne kopije",
|
"LabelNextBackupDate": "Sljedeća izrada sigurnosne kopije",
|
||||||
"LabelNextScheduledRun": "Sljedeće zakazano izvođenje",
|
"LabelNextScheduledRun": "Sljedeće zakazano izvođenje",
|
||||||
|
"LabelNoApiKeys": "Nema API ključeva",
|
||||||
"LabelNoCustomMetadataProviders": "Nema prilagođenih pružatelja meta-podataka",
|
"LabelNoCustomMetadataProviders": "Nema prilagođenih pružatelja meta-podataka",
|
||||||
"LabelNoEpisodesSelected": "Nema odabranih nastavaka",
|
"LabelNoEpisodesSelected": "Nema odabranih nastavaka",
|
||||||
"LabelNotFinished": "Nije dovršeno",
|
"LabelNotFinished": "Nije dovršeno",
|
||||||
@@ -544,6 +558,7 @@
|
|||||||
"LabelSelectAll": "Označi sve",
|
"LabelSelectAll": "Označi sve",
|
||||||
"LabelSelectAllEpisodes": "Označi sve nastavke",
|
"LabelSelectAllEpisodes": "Označi sve nastavke",
|
||||||
"LabelSelectEpisodesShowing": "Prikazujem {0} odabranih nastavaka",
|
"LabelSelectEpisodesShowing": "Prikazujem {0} odabranih nastavaka",
|
||||||
|
"LabelSelectUser": "Odaberite korisnika",
|
||||||
"LabelSelectUsers": "Označi korisnike",
|
"LabelSelectUsers": "Označi korisnike",
|
||||||
"LabelSendEbookToDevice": "Pošalji e-knjigu …",
|
"LabelSendEbookToDevice": "Pošalji e-knjigu …",
|
||||||
"LabelSequence": "Slijed",
|
"LabelSequence": "Slijed",
|
||||||
@@ -708,7 +723,9 @@
|
|||||||
"MessageAddToPlayerQueue": "Dodaj u redoslijed izvođenja",
|
"MessageAddToPlayerQueue": "Dodaj u redoslijed izvođenja",
|
||||||
"MessageAppriseDescription": "Da biste se koristili ovom značajkom, treba vam instanca <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API-ja</a> ili API koji može rukovati istom vrstom zahtjeva.<br />The Adresa Apprise API-ja treba biti puna URL putanja za slanje obavijesti, npr. ako vam se API instanca poslužuje na adresi <code>http://192.168.1.1:8337</code> trebate upisati <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Da biste se koristili ovom značajkom, treba vam instanca <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API-ja</a> ili API koji može rukovati istom vrstom zahtjeva.<br />The Adresa Apprise API-ja treba biti puna URL putanja za slanje obavijesti, npr. ako vam se API instanca poslužuje na adresi <code>http://192.168.1.1:8337</code> trebate upisati <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageAsinCheck": "Upišite ASIN iz odgovarajuće Audibleove regije, ne s Amazonov.",
|
"MessageAsinCheck": "Upišite ASIN iz odgovarajuće Audibleove regije, ne s Amazonov.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Starije API tokene ćemo ukloniti. Umjesto njih, koristite se <a href=\"/config/api-keys\">API ključevima</a> .",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Ponovno pokrenite poslužitelj da biste primijenili OIDC promjene.",
|
"MessageAuthenticationOIDCChangesRestart": "Ponovno pokrenite poslužitelj da biste primijenili OIDC promjene.",
|
||||||
|
"MessageAuthenticationSecurityMessage": "Provjera autentičnosti poboljšana je radi sigurnosti. Svi se korisnici moraju ponovno prijaviti.",
|
||||||
"MessageBackupsDescription": "Sigurnosne kopije sadrže korisnike, korisnikov napredak medija, pojedinosti knjižničke građe, postavke poslužitelja i slike koje se spremaju u <code>/metadata/items</code> & <code>/metadata/authors</code>. Sigurnosne kopije ne sadrže niti jednu datoteku iz mapa knjižnice.",
|
"MessageBackupsDescription": "Sigurnosne kopije sadrže korisnike, korisnikov napredak medija, pojedinosti knjižničke građe, postavke poslužitelja i slike koje se spremaju u <code>/metadata/items</code> & <code>/metadata/authors</code>. Sigurnosne kopije ne sadrže niti jednu datoteku iz mapa knjižnice.",
|
||||||
"MessageBackupsLocationEditNote": "Napomena: Uređivanje lokacije za sigurnosne kopije ne premješta ili mijenja postojeće sigurnosne kopije",
|
"MessageBackupsLocationEditNote": "Napomena: Uređivanje lokacije za sigurnosne kopije ne premješta ili mijenja postojeće sigurnosne kopije",
|
||||||
"MessageBackupsLocationNoEditNote": "Napomena: Lokacija za sigurnosne kopije zadana je kroz varijablu okoline i ovdje se ne može izmijeniti.",
|
"MessageBackupsLocationNoEditNote": "Napomena: Lokacija za sigurnosne kopije zadana je kroz varijablu okoline i ovdje se ne može izmijeniti.",
|
||||||
@@ -730,6 +747,7 @@
|
|||||||
"MessageChaptersNotFound": "Poglavlja nisu pronađena",
|
"MessageChaptersNotFound": "Poglavlja nisu pronađena",
|
||||||
"MessageCheckingCron": "Provjeravam cron...",
|
"MessageCheckingCron": "Provjeravam cron...",
|
||||||
"MessageConfirmCloseFeed": "Sigurno želite zatvoriti ovaj izvor?",
|
"MessageConfirmCloseFeed": "Sigurno želite zatvoriti ovaj izvor?",
|
||||||
|
"MessageConfirmDeleteApiKey": "Sigurno želite izbrisati API ključ \"{0}\"?",
|
||||||
"MessageConfirmDeleteBackup": "Sigurno želite izbrisati sigurnosnu kopiju za {0}?",
|
"MessageConfirmDeleteBackup": "Sigurno želite izbrisati sigurnosnu kopiju za {0}?",
|
||||||
"MessageConfirmDeleteDevice": "Sigurno želite izbrisati e-čitač \"{0}\"?",
|
"MessageConfirmDeleteDevice": "Sigurno želite izbrisati e-čitač \"{0}\"?",
|
||||||
"MessageConfirmDeleteFile": "Ovo će izbrisati datoteke s datotečnog sustava. Jeste li sigurni?",
|
"MessageConfirmDeleteFile": "Ovo će izbrisati datoteke s datotečnog sustava. Jeste li sigurni?",
|
||||||
@@ -757,6 +775,7 @@
|
|||||||
"MessageConfirmRemoveAuthor": "Sigurno želite ukloniti autora \"{0}\"?",
|
"MessageConfirmRemoveAuthor": "Sigurno želite ukloniti autora \"{0}\"?",
|
||||||
"MessageConfirmRemoveCollection": "Sigurno želite obrisati kolekciju \"{0}\"?",
|
"MessageConfirmRemoveCollection": "Sigurno želite obrisati kolekciju \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "Sigurno želite ukloniti nastavak \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "Sigurno želite ukloniti nastavak \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveEpisodeNote": "Napomena: Ova funkcija neće izbrisati zvučnu datoteku ukoliko ne uključite opciju \"Izbriši datoteku zauvijek\"",
|
||||||
"MessageConfirmRemoveEpisodes": "Sigurno želite ukloniti {0} nastavaka?",
|
"MessageConfirmRemoveEpisodes": "Sigurno želite ukloniti {0} nastavaka?",
|
||||||
"MessageConfirmRemoveListeningSessions": "Sigurno želite ukloniti {0} sesija slušanja?",
|
"MessageConfirmRemoveListeningSessions": "Sigurno želite ukloniti {0} sesija slušanja?",
|
||||||
"MessageConfirmRemoveMetadataFiles": "Sigurno želite ukloniti sve datoteke metadata.{0} u mapama vaših knjižničkih stavki?",
|
"MessageConfirmRemoveMetadataFiles": "Sigurno želite ukloniti sve datoteke metadata.{0} u mapama vaših knjižničkih stavki?",
|
||||||
@@ -1000,6 +1019,8 @@
|
|||||||
"ToastEpisodeDownloadQueueClearSuccess": "Redoslijed preuzimanja nastavaka očišćen",
|
"ToastEpisodeDownloadQueueClearSuccess": "Redoslijed preuzimanja nastavaka očišćen",
|
||||||
"ToastEpisodeUpdateSuccess": "{0} nastavak/a ažurirano",
|
"ToastEpisodeUpdateSuccess": "{0} nastavak/a ažurirano",
|
||||||
"ToastErrorCannotShare": "Dijeljenje na ovaj uređaj nije moguće",
|
"ToastErrorCannotShare": "Dijeljenje na ovaj uređaj nije moguće",
|
||||||
|
"ToastFailedToCreate": "Izrada nije uspjela",
|
||||||
|
"ToastFailedToDelete": "Brisanje nije uspjelo",
|
||||||
"ToastFailedToLoadData": "Učitavanje podataka nije uspjelo",
|
"ToastFailedToLoadData": "Učitavanje podataka nije uspjelo",
|
||||||
"ToastFailedToMatch": "Nije prepoznato",
|
"ToastFailedToMatch": "Nije prepoznato",
|
||||||
"ToastFailedToShare": "Dijeljenje nije uspjelo",
|
"ToastFailedToShare": "Dijeljenje nije uspjelo",
|
||||||
@@ -1031,6 +1052,7 @@
|
|||||||
"ToastMustHaveAtLeastOnePath": "Mora postojati barem jedna putanja",
|
"ToastMustHaveAtLeastOnePath": "Mora postojati barem jedna putanja",
|
||||||
"ToastNameEmailRequired": "Ime i adresa e-pošte su obavezni",
|
"ToastNameEmailRequired": "Ime i adresa e-pošte su obavezni",
|
||||||
"ToastNameRequired": "Ime je obavezno",
|
"ToastNameRequired": "Ime je obavezno",
|
||||||
|
"ToastNewApiKeyUserError": "Morate odabrati korisnika",
|
||||||
"ToastNewEpisodesFound": "pronađeno {0} novih nastavaka",
|
"ToastNewEpisodesFound": "pronađeno {0} novih nastavaka",
|
||||||
"ToastNewUserCreatedFailed": "Račun \"{0}\" nije uspješno izrađen",
|
"ToastNewUserCreatedFailed": "Račun \"{0}\" nije uspješno izrađen",
|
||||||
"ToastNewUserCreatedSuccess": "Novi račun izrađen",
|
"ToastNewUserCreatedSuccess": "Novi račun izrađen",
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Aggiungi",
|
"ButtonAdd": "Aggiungi",
|
||||||
|
"ButtonAddApiKey": "Aggiungi chiave API",
|
||||||
"ButtonAddChapters": "Aggiungi Capitoli",
|
"ButtonAddChapters": "Aggiungi Capitoli",
|
||||||
"ButtonAddDevice": "Aggiungi Dispositivo",
|
"ButtonAddDevice": "Aggiungi Dispositivo",
|
||||||
"ButtonAddLibrary": "Aggiungi Libreria",
|
"ButtonAddLibrary": "Aggiungi Libreria",
|
||||||
"ButtonAddPodcasts": "Aggiungi Podcast",
|
"ButtonAddPodcasts": "Aggiungi Podcast",
|
||||||
"ButtonAddUser": "Aggiungi User",
|
"ButtonAddUser": "Aggiungi Utente",
|
||||||
"ButtonAddYourFirstLibrary": "Aggiungi la tua prima libreria",
|
"ButtonAddYourFirstLibrary": "Aggiungi la tua prima libreria",
|
||||||
"ButtonApply": "Applica",
|
"ButtonApply": "Applica",
|
||||||
"ButtonApplyChapters": "Applica",
|
"ButtonApplyChapters": "Applica Capitoli",
|
||||||
"ButtonAuthors": "Autori",
|
"ButtonAuthors": "Autori",
|
||||||
"ButtonBack": "Indietro",
|
"ButtonBack": "Indietro",
|
||||||
"ButtonBatchEditPopulateFromExisting": "Popola da esistente",
|
"ButtonBatchEditPopulateFromExisting": "Popola da esistente",
|
||||||
"ButtonBatchEditPopulateMapDetails": "Inserisci i dettagli della mappa",
|
"ButtonBatchEditPopulateMapDetails": "Inserisci i dettagli della mappa",
|
||||||
"ButtonBrowseForFolder": "Per Cartella",
|
"ButtonBrowseForFolder": "Sfoglia per Cartella",
|
||||||
"ButtonCancel": "Annulla",
|
"ButtonCancel": "Annulla",
|
||||||
"ButtonCancelEncode": "Ferma la codifica",
|
"ButtonCancelEncode": "Ferma la codifica",
|
||||||
"ButtonChangeRootPassword": "Cambia la Password di root",
|
"ButtonChangeRootPassword": "Cambia la Password di root",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Seleziona la Cartella",
|
"ButtonChooseAFolder": "Seleziona la Cartella",
|
||||||
"ButtonChooseFiles": "Seleziona i File",
|
"ButtonChooseFiles": "Seleziona i File",
|
||||||
"ButtonClearFilter": "Elimina filtri",
|
"ButtonClearFilter": "Elimina filtri",
|
||||||
|
"ButtonClose": "Chiudi",
|
||||||
"ButtonCloseFeed": "Chiudi flusso",
|
"ButtonCloseFeed": "Chiudi flusso",
|
||||||
"ButtonCloseSession": "Chiudi la sessione aperta",
|
"ButtonCloseSession": "Chiudi la sessione aperta",
|
||||||
"ButtonCollections": "Raccolte",
|
"ButtonCollections": "Raccolte",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Dodaj",
|
"ButtonAdd": "Dodaj",
|
||||||
|
"ButtonAddApiKey": "Dodaj klucz API",
|
||||||
"ButtonAddChapters": "Dodaj rozdziały",
|
"ButtonAddChapters": "Dodaj rozdziały",
|
||||||
"ButtonAddDevice": "Dodaj urządzenie",
|
"ButtonAddDevice": "Dodaj urządzenie",
|
||||||
"ButtonAddLibrary": "Dodaj bibliotekę",
|
"ButtonAddLibrary": "Dodaj bibliotekę",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Wybierz folder",
|
"ButtonChooseAFolder": "Wybierz folder",
|
||||||
"ButtonChooseFiles": "Wybierz pliki",
|
"ButtonChooseFiles": "Wybierz pliki",
|
||||||
"ButtonClearFilter": "Wyczyść filtr",
|
"ButtonClearFilter": "Wyczyść filtr",
|
||||||
|
"ButtonClose": "Zamknij",
|
||||||
"ButtonCloseFeed": "Zamknij kanał",
|
"ButtonCloseFeed": "Zamknij kanał",
|
||||||
"ButtonCloseSession": "Zamknij otwartą sesję",
|
"ButtonCloseSession": "Zamknij otwartą sesję",
|
||||||
"ButtonCollections": "Kolekcje",
|
"ButtonCollections": "Kolekcje",
|
||||||
@@ -119,6 +121,7 @@
|
|||||||
"HeaderAccount": "Konto",
|
"HeaderAccount": "Konto",
|
||||||
"HeaderAddCustomMetadataProvider": "Dodaj niestandardowego dostawcę metadanych",
|
"HeaderAddCustomMetadataProvider": "Dodaj niestandardowego dostawcę metadanych",
|
||||||
"HeaderAdvanced": "Zaawansowane",
|
"HeaderAdvanced": "Zaawansowane",
|
||||||
|
"HeaderApiKeys": "Klucze API",
|
||||||
"HeaderAppriseNotificationSettings": "Ustawienia powiadomień Apprise",
|
"HeaderAppriseNotificationSettings": "Ustawienia powiadomień Apprise",
|
||||||
"HeaderAudioTracks": "Ścieżki audio",
|
"HeaderAudioTracks": "Ścieżki audio",
|
||||||
"HeaderAudiobookTools": "Narzędzia do zarządzania audiobookami",
|
"HeaderAudiobookTools": "Narzędzia do zarządzania audiobookami",
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "Kolejność metadanych",
|
"HeaderMetadataOrderOfPrecedence": "Kolejność metadanych",
|
||||||
"HeaderMetadataToEmbed": "Metadane do osadzenia",
|
"HeaderMetadataToEmbed": "Metadane do osadzenia",
|
||||||
"HeaderNewAccount": "Nowe konto",
|
"HeaderNewAccount": "Nowe konto",
|
||||||
|
"HeaderNewApiKey": "Nowy klucz API",
|
||||||
"HeaderNewLibrary": "Nowa biblioteka",
|
"HeaderNewLibrary": "Nowa biblioteka",
|
||||||
"HeaderNotificationCreate": "Utwórz powiadomienie",
|
"HeaderNotificationCreate": "Utwórz powiadomienie",
|
||||||
"HeaderNotificationUpdate": "Zaktualizuj powiadomienie",
|
"HeaderNotificationUpdate": "Zaktualizuj powiadomienie",
|
||||||
@@ -206,6 +210,7 @@
|
|||||||
"HeaderTableOfContents": "Spis treści",
|
"HeaderTableOfContents": "Spis treści",
|
||||||
"HeaderTools": "Narzędzia",
|
"HeaderTools": "Narzędzia",
|
||||||
"HeaderUpdateAccount": "Zaktualizuj konto",
|
"HeaderUpdateAccount": "Zaktualizuj konto",
|
||||||
|
"HeaderUpdateApiKey": "Aktualizuj klucz API",
|
||||||
"HeaderUpdateAuthor": "Zaktualizuj autorów",
|
"HeaderUpdateAuthor": "Zaktualizuj autorów",
|
||||||
"HeaderUpdateDetails": "Zaktualizuj szczegóły",
|
"HeaderUpdateDetails": "Zaktualizuj szczegóły",
|
||||||
"HeaderUpdateLibrary": "Zaktualizuj bibliotekę",
|
"HeaderUpdateLibrary": "Zaktualizuj bibliotekę",
|
||||||
@@ -235,6 +240,7 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Wszyscy użytkownicy z wyłączeniem gości",
|
"LabelAllUsersExcludingGuests": "Wszyscy użytkownicy z wyłączeniem gości",
|
||||||
"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.",
|
||||||
"LabelApiToken": "API Token",
|
"LabelApiToken": "API Token",
|
||||||
"LabelAppend": "Dołącz",
|
"LabelAppend": "Dołącz",
|
||||||
"LabelAudioBitrate": "Audio Bitrate (np. 128k)",
|
"LabelAudioBitrate": "Audio Bitrate (np. 128k)",
|
||||||
@@ -324,6 +330,10 @@
|
|||||||
"LabelEmbeddedCover": "Wbudowana okładka",
|
"LabelEmbeddedCover": "Wbudowana okładka",
|
||||||
"LabelEnable": "Włącz",
|
"LabelEnable": "Włącz",
|
||||||
"LabelEncodingBackupLocation": "Kopia zapasowa twoich oryginalnych plików audio będzie się znajdować w:",
|
"LabelEncodingBackupLocation": "Kopia zapasowa twoich oryginalnych plików audio będzie się znajdować w:",
|
||||||
|
"LabelEncodingChaptersNotEmbedded": "W audiobookach wielościeżkowych rozdziały nie są osadzone.",
|
||||||
|
"LabelEncodingClearItemCache": "Pamiętaj o okresowym czyszczeniu pamięci podręcznej elementów.",
|
||||||
|
"LabelEncodingFinishedM4B": "Ukończony plik M4B zostanie umieszczony w folderze audiobooka pod adresem:",
|
||||||
|
"LabelEncodingInfoEmbedded": "Metadane zostaną osadzone w ścieżkach audio w folderze z audiobookiem.",
|
||||||
"LabelEnd": "Zakończ",
|
"LabelEnd": "Zakończ",
|
||||||
"LabelEndOfChapter": "Koniec rozdziału",
|
"LabelEndOfChapter": "Koniec rozdziału",
|
||||||
"LabelEpisode": "Odcinek",
|
"LabelEpisode": "Odcinek",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Добавить",
|
"ButtonAdd": "Добавить",
|
||||||
|
"ButtonAddApiKey": "Добавить API ключ",
|
||||||
"ButtonAddChapters": "Добавить главы",
|
"ButtonAddChapters": "Добавить главы",
|
||||||
"ButtonAddDevice": "Добавить устройство",
|
"ButtonAddDevice": "Добавить устройство",
|
||||||
"ButtonAddLibrary": "Добавить библиотеку",
|
"ButtonAddLibrary": "Добавить библиотеку",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Выбор папки",
|
"ButtonChooseAFolder": "Выбор папки",
|
||||||
"ButtonChooseFiles": "Выбор файлов",
|
"ButtonChooseFiles": "Выбор файлов",
|
||||||
"ButtonClearFilter": "Очистить фильтр",
|
"ButtonClearFilter": "Очистить фильтр",
|
||||||
|
"ButtonClose": "Закрыть",
|
||||||
"ButtonCloseFeed": "Закрыть канал",
|
"ButtonCloseFeed": "Закрыть канал",
|
||||||
"ButtonCloseSession": "Закрыть открытый сеанс",
|
"ButtonCloseSession": "Закрыть открытый сеанс",
|
||||||
"ButtonCollections": "Коллекции",
|
"ButtonCollections": "Коллекции",
|
||||||
@@ -119,6 +121,7 @@
|
|||||||
"HeaderAccount": "Учетная запись",
|
"HeaderAccount": "Учетная запись",
|
||||||
"HeaderAddCustomMetadataProvider": "Добавление пользовательского поставщика метаданных",
|
"HeaderAddCustomMetadataProvider": "Добавление пользовательского поставщика метаданных",
|
||||||
"HeaderAdvanced": "Дополнительно",
|
"HeaderAdvanced": "Дополнительно",
|
||||||
|
"HeaderApiKeys": "API ключи",
|
||||||
"HeaderAppriseNotificationSettings": "Настройки оповещений",
|
"HeaderAppriseNotificationSettings": "Настройки оповещений",
|
||||||
"HeaderAudioTracks": "Аудио треки",
|
"HeaderAudioTracks": "Аудио треки",
|
||||||
"HeaderAudiobookTools": "Инструменты файлов аудиокниг",
|
"HeaderAudiobookTools": "Инструменты файлов аудиокниг",
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "Порядок приоритета метаданных",
|
"HeaderMetadataOrderOfPrecedence": "Порядок приоритета метаданных",
|
||||||
"HeaderMetadataToEmbed": "Метаинформация для встраивания",
|
"HeaderMetadataToEmbed": "Метаинформация для встраивания",
|
||||||
"HeaderNewAccount": "Новая учетная запись",
|
"HeaderNewAccount": "Новая учетная запись",
|
||||||
|
"HeaderNewApiKey": "Новый API ключ",
|
||||||
"HeaderNewLibrary": "Новая библиотека",
|
"HeaderNewLibrary": "Новая библиотека",
|
||||||
"HeaderNotificationCreate": "Создать уведомление",
|
"HeaderNotificationCreate": "Создать уведомление",
|
||||||
"HeaderNotificationUpdate": "Уведомление об обновлении",
|
"HeaderNotificationUpdate": "Уведомление об обновлении",
|
||||||
@@ -206,6 +210,7 @@
|
|||||||
"HeaderTableOfContents": "Содержание",
|
"HeaderTableOfContents": "Содержание",
|
||||||
"HeaderTools": "Инструменты",
|
"HeaderTools": "Инструменты",
|
||||||
"HeaderUpdateAccount": "Обновить учетную запись",
|
"HeaderUpdateAccount": "Обновить учетную запись",
|
||||||
|
"HeaderUpdateApiKey": "Обновить API ключ",
|
||||||
"HeaderUpdateAuthor": "Обновить автора",
|
"HeaderUpdateAuthor": "Обновить автора",
|
||||||
"HeaderUpdateDetails": "Обновить детали",
|
"HeaderUpdateDetails": "Обновить детали",
|
||||||
"HeaderUpdateLibrary": "Обновить библиотеку",
|
"HeaderUpdateLibrary": "Обновить библиотеку",
|
||||||
@@ -235,6 +240,10 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Все пользователи, кроме гостей",
|
"LabelAllUsersExcludingGuests": "Все пользователи, кроме гостей",
|
||||||
"LabelAllUsersIncludingGuests": "Все пользователи, включая гостей",
|
"LabelAllUsersIncludingGuests": "Все пользователи, включая гостей",
|
||||||
"LabelAlreadyInYourLibrary": "Уже в Вашей библиотеке",
|
"LabelAlreadyInYourLibrary": "Уже в Вашей библиотеке",
|
||||||
|
"LabelApiKeyCreated": "API ключ \"{0}\" успешно создан.",
|
||||||
|
"LabelApiKeyCreatedDescription": "Обязательно скопируйте API-ключ сейчас, так как вы больше не сможете его увидеть.",
|
||||||
|
"LabelApiKeyUser": "Управление от пользователя",
|
||||||
|
"LabelApiKeyUserDescription": "Этот API-ключ будет иметь те же права доступа, что и пользователь, от имени которого он действует. В логах это будет отображаться так же, как если бы пользователь отправлял запрос.",
|
||||||
"LabelApiToken": "Токен API",
|
"LabelApiToken": "Токен API",
|
||||||
"LabelAppend": "Добавить",
|
"LabelAppend": "Добавить",
|
||||||
"LabelAudioBitrate": "Битрейт (напр. 128k)",
|
"LabelAudioBitrate": "Битрейт (напр. 128k)",
|
||||||
@@ -346,6 +355,10 @@
|
|||||||
"LabelExample": "Пример",
|
"LabelExample": "Пример",
|
||||||
"LabelExpandSeries": "Развернуть серию",
|
"LabelExpandSeries": "Развернуть серию",
|
||||||
"LabelExpandSubSeries": "Развернуть подсерию",
|
"LabelExpandSubSeries": "Развернуть подсерию",
|
||||||
|
"LabelExpired": "Истекший",
|
||||||
|
"LabelExpiresAt": "Истекает в",
|
||||||
|
"LabelExpiresInSeconds": "Истекает через (секунд)",
|
||||||
|
"LabelExpiresNever": "Никогда",
|
||||||
"LabelExplicit": "18+",
|
"LabelExplicit": "18+",
|
||||||
"LabelExplicitChecked": "18+ (отмечено)",
|
"LabelExplicitChecked": "18+ (отмечено)",
|
||||||
"LabelExplicitUnchecked": "+18 (не отмечено)",
|
"LabelExplicitUnchecked": "+18 (не отмечено)",
|
||||||
@@ -425,6 +438,7 @@
|
|||||||
"LabelLogLevelWarn": "Предупреждение",
|
"LabelLogLevelWarn": "Предупреждение",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
"LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты",
|
||||||
"LabelLowestPriority": "Самый низкий приоритет",
|
"LabelLowestPriority": "Самый низкий приоритет",
|
||||||
|
"LabelMatchConfidence": "Уверенность",
|
||||||
"LabelMatchExistingUsersBy": "Сопоставление существующих пользователей по",
|
"LabelMatchExistingUsersBy": "Сопоставление существующих пользователей по",
|
||||||
"LabelMatchExistingUsersByDescription": "Используется для подключения существующих пользователей. После подключения пользователям будет присвоен уникальный идентификатор от поставщика единого входа",
|
"LabelMatchExistingUsersByDescription": "Используется для подключения существующих пользователей. После подключения пользователям будет присвоен уникальный идентификатор от поставщика единого входа",
|
||||||
"LabelMaxEpisodesToDownload": "Максимальное количество эпизодов для загрузки. Используйте 0 для неограниченного количества.",
|
"LabelMaxEpisodesToDownload": "Максимальное количество эпизодов для загрузки. Используйте 0 для неограниченного количества.",
|
||||||
@@ -455,6 +469,7 @@
|
|||||||
"LabelNewestEpisodes": "Новые эпизоды",
|
"LabelNewestEpisodes": "Новые эпизоды",
|
||||||
"LabelNextBackupDate": "Следующая дата бэкапирования",
|
"LabelNextBackupDate": "Следующая дата бэкапирования",
|
||||||
"LabelNextScheduledRun": "Следущий запланированный запуск",
|
"LabelNextScheduledRun": "Следущий запланированный запуск",
|
||||||
|
"LabelNoApiKeys": "API ключи отсутствуют",
|
||||||
"LabelNoCustomMetadataProviders": "Нет пользовательских поставщиков метаданных",
|
"LabelNoCustomMetadataProviders": "Нет пользовательских поставщиков метаданных",
|
||||||
"LabelNoEpisodesSelected": "Эпизоды не выбраны",
|
"LabelNoEpisodesSelected": "Эпизоды не выбраны",
|
||||||
"LabelNotFinished": "Не завершено",
|
"LabelNotFinished": "Не завершено",
|
||||||
@@ -544,6 +559,7 @@
|
|||||||
"LabelSelectAll": "Выбрать все",
|
"LabelSelectAll": "Выбрать все",
|
||||||
"LabelSelectAllEpisodes": "Выбрать все эпизоды",
|
"LabelSelectAllEpisodes": "Выбрать все эпизоды",
|
||||||
"LabelSelectEpisodesShowing": "Выберите {0} эпизодов для показа",
|
"LabelSelectEpisodesShowing": "Выберите {0} эпизодов для показа",
|
||||||
|
"LabelSelectUser": "Выбрать пользователя",
|
||||||
"LabelSelectUsers": "Выбор пользователей",
|
"LabelSelectUsers": "Выбор пользователей",
|
||||||
"LabelSendEbookToDevice": "Отправить e-книгу в...",
|
"LabelSendEbookToDevice": "Отправить e-книгу в...",
|
||||||
"LabelSequence": "Последовательность",
|
"LabelSequence": "Последовательность",
|
||||||
@@ -708,7 +724,9 @@
|
|||||||
"MessageAddToPlayerQueue": "Добавить в очередь проигрывателя",
|
"MessageAddToPlayerQueue": "Добавить в очередь проигрывателя",
|
||||||
"MessageAppriseDescription": "Для использования этой функции необходимо иметь запущенный экземпляр <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или api которое обрабатывает те же самые запросы. <br />URL-адрес API Apprise должен быть полным URL-адресом для отправки уведомления, т.е., если API запущено по адресу <code>http://192.168.1.1:8337</code> тогда нужно указать <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Для использования этой функции необходимо иметь запущенный экземпляр <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или api которое обрабатывает те же самые запросы. <br />URL-адрес API Apprise должен быть полным URL-адресом для отправки уведомления, т.е., если API запущено по адресу <code>http://192.168.1.1:8337</code> тогда нужно указать <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageAsinCheck": "Убедитесь, что вы используете ASIN из правильной региональной зоны Audible, а не из Amazon.",
|
"MessageAsinCheck": "Убедитесь, что вы используете ASIN из правильной региональной зоны Audible, а не из Amazon.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Устаревшие токены API в будущем будут удалены. Вместо них используйте <a href=\"/config/api-keys\">API-ключи</a>.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Перезапустите ваш сервер после сохранения для применения изменений в OIDC.",
|
"MessageAuthenticationOIDCChangesRestart": "Перезапустите ваш сервер после сохранения для применения изменений в OIDC.",
|
||||||
|
"MessageAuthenticationSecurityMessage": "В целях безопасности была улучшена аутентификация. Всем пользователям необходимо повторно войти в систему.",
|
||||||
"MessageBackupsDescription": "Бэкап включает пользователей, прогресс пользователей, данные элементов библиотеки, настройки сервера и изображения хранящиеся в <code>/metadata/items</code> и <code>/metadata/authors</code>. Бэкапы <strong>НЕ</strong> сохраняют файлы из папок библиотек.",
|
"MessageBackupsDescription": "Бэкап включает пользователей, прогресс пользователей, данные элементов библиотеки, настройки сервера и изображения хранящиеся в <code>/metadata/items</code> и <code>/metadata/authors</code>. Бэкапы <strong>НЕ</strong> сохраняют файлы из папок библиотек.",
|
||||||
"MessageBackupsLocationEditNote": "Примечание: Обновление местоположения резервной копии не приведет к перемещению или изменению существующих резервных копий",
|
"MessageBackupsLocationEditNote": "Примечание: Обновление местоположения резервной копии не приведет к перемещению или изменению существующих резервных копий",
|
||||||
"MessageBackupsLocationNoEditNote": "Примечание: Местоположение резервного копирования задается с помощью переменной среды и не может быть изменено здесь.",
|
"MessageBackupsLocationNoEditNote": "Примечание: Местоположение резервного копирования задается с помощью переменной среды и не может быть изменено здесь.",
|
||||||
@@ -730,6 +748,7 @@
|
|||||||
"MessageChaptersNotFound": "Главы не найденны",
|
"MessageChaptersNotFound": "Главы не найденны",
|
||||||
"MessageCheckingCron": "Проверка cron...",
|
"MessageCheckingCron": "Проверка cron...",
|
||||||
"MessageConfirmCloseFeed": "Вы уверены, что хотите закрыть этот канал?",
|
"MessageConfirmCloseFeed": "Вы уверены, что хотите закрыть этот канал?",
|
||||||
|
"MessageConfirmDeleteApiKey": "Вы уверены, что хотите удалить API ключ \"{0}\"?",
|
||||||
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
"MessageConfirmDeleteBackup": "Вы уверены, что хотите удалить бэкап для {0}?",
|
||||||
"MessageConfirmDeleteDevice": "Вы уверены, что хотите удалить устройство для чтения электронных книг \"{0}\"?",
|
"MessageConfirmDeleteDevice": "Вы уверены, что хотите удалить устройство для чтения электронных книг \"{0}\"?",
|
||||||
"MessageConfirmDeleteFile": "Это удалит файл из Вашей файловой системы. Вы уверены?",
|
"MessageConfirmDeleteFile": "Это удалит файл из Вашей файловой системы. Вы уверены?",
|
||||||
@@ -757,6 +776,7 @@
|
|||||||
"MessageConfirmRemoveAuthor": "Вы уверены, что хотите удалить автора \"{0}\"?",
|
"MessageConfirmRemoveAuthor": "Вы уверены, что хотите удалить автора \"{0}\"?",
|
||||||
"MessageConfirmRemoveCollection": "Вы уверены, что хотите удалить коллекцию \"{0}\"?",
|
"MessageConfirmRemoveCollection": "Вы уверены, что хотите удалить коллекцию \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "Вы уверены, что хотите удалить эпизод \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "Вы уверены, что хотите удалить эпизод \"{0}\"?",
|
||||||
|
"MessageConfirmRemoveEpisodeNote": "Примечание: Это не приведет к удалению аудиофайла, если не включить опцию \"Жесткое удаление файла\"",
|
||||||
"MessageConfirmRemoveEpisodes": "Вы уверены, что хотите удалить {0} эпизодов?",
|
"MessageConfirmRemoveEpisodes": "Вы уверены, что хотите удалить {0} эпизодов?",
|
||||||
"MessageConfirmRemoveListeningSessions": "Вы уверены, что хотите удалить {0} сеансов прослушивания?",
|
"MessageConfirmRemoveListeningSessions": "Вы уверены, что хотите удалить {0} сеансов прослушивания?",
|
||||||
"MessageConfirmRemoveMetadataFiles": "Вы уверены, что хотите удалить все файлы metadata. {0} файлов из папок элементов вашей библиотеки?",
|
"MessageConfirmRemoveMetadataFiles": "Вы уверены, что хотите удалить все файлы metadata. {0} файлов из папок элементов вашей библиотеки?",
|
||||||
@@ -1000,6 +1020,8 @@
|
|||||||
"ToastEpisodeDownloadQueueClearSuccess": "Очередь загрузки эпизода очищена",
|
"ToastEpisodeDownloadQueueClearSuccess": "Очередь загрузки эпизода очищена",
|
||||||
"ToastEpisodeUpdateSuccess": "{0 эпизодов обновлено",
|
"ToastEpisodeUpdateSuccess": "{0 эпизодов обновлено",
|
||||||
"ToastErrorCannotShare": "Невозможно предоставить общий доступ на этом устройстве",
|
"ToastErrorCannotShare": "Невозможно предоставить общий доступ на этом устройстве",
|
||||||
|
"ToastFailedToCreate": "Не удалось создать",
|
||||||
|
"ToastFailedToDelete": "Не удалось удалить",
|
||||||
"ToastFailedToLoadData": "Не удалось загрузить данные",
|
"ToastFailedToLoadData": "Не удалось загрузить данные",
|
||||||
"ToastFailedToMatch": "Не удалось найти совпадения",
|
"ToastFailedToMatch": "Не удалось найти совпадения",
|
||||||
"ToastFailedToShare": "Не удалось поделиться",
|
"ToastFailedToShare": "Не удалось поделиться",
|
||||||
@@ -1031,6 +1053,7 @@
|
|||||||
"ToastMustHaveAtLeastOnePath": "Должен быть хотя бы один путь",
|
"ToastMustHaveAtLeastOnePath": "Должен быть хотя бы один путь",
|
||||||
"ToastNameEmailRequired": "Имя и адрес электронной почты обязательны",
|
"ToastNameEmailRequired": "Имя и адрес электронной почты обязательны",
|
||||||
"ToastNameRequired": "Имя обязательно для заполнения",
|
"ToastNameRequired": "Имя обязательно для заполнения",
|
||||||
|
"ToastNewApiKeyUserError": "Необходимо выбрать пользователя",
|
||||||
"ToastNewEpisodesFound": "{0} новых эпизодов найдено",
|
"ToastNewEpisodesFound": "{0} новых эпизодов найдено",
|
||||||
"ToastNewUserCreatedFailed": "Не удалось создать учетную запись: \"{0}\"",
|
"ToastNewUserCreatedFailed": "Не удалось создать учетную запись: \"{0}\"",
|
||||||
"ToastNewUserCreatedSuccess": "Новая учетная запись создана",
|
"ToastNewUserCreatedSuccess": "Новая учетная запись создана",
|
||||||
|
|||||||
+24
-1
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Lägg till",
|
"ButtonAdd": "Lägg till",
|
||||||
|
"ButtonAddApiKey": "Addera API-nyckel",
|
||||||
"ButtonAddChapters": "Lägg till kapitel",
|
"ButtonAddChapters": "Lägg till kapitel",
|
||||||
"ButtonAddDevice": "Lägg till enhet",
|
"ButtonAddDevice": "Lägg till enhet",
|
||||||
"ButtonAddLibrary": "Lägg till bibliotek",
|
"ButtonAddLibrary": "Lägg till bibliotek",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Välj en mapp",
|
"ButtonChooseAFolder": "Välj en mapp",
|
||||||
"ButtonChooseFiles": "Välj filer",
|
"ButtonChooseFiles": "Välj filer",
|
||||||
"ButtonClearFilter": "Rensa filter",
|
"ButtonClearFilter": "Rensa filter",
|
||||||
|
"ButtonClose": "Stäng",
|
||||||
"ButtonCloseFeed": "Stäng flöde",
|
"ButtonCloseFeed": "Stäng flöde",
|
||||||
"ButtonCloseSession": "Stäng öppen session",
|
"ButtonCloseSession": "Stäng öppen session",
|
||||||
"ButtonCollections": "Samlingar",
|
"ButtonCollections": "Samlingar",
|
||||||
@@ -119,6 +121,7 @@
|
|||||||
"HeaderAccount": "Konto",
|
"HeaderAccount": "Konto",
|
||||||
"HeaderAddCustomMetadataProvider": "Addera egen källa för metadata",
|
"HeaderAddCustomMetadataProvider": "Addera egen källa för metadata",
|
||||||
"HeaderAdvanced": "Avancerad",
|
"HeaderAdvanced": "Avancerad",
|
||||||
|
"HeaderApiKeys": "API-nyckel",
|
||||||
"HeaderAppriseNotificationSettings": "Inställningar av meddelanden med Apprise",
|
"HeaderAppriseNotificationSettings": "Inställningar av meddelanden med Apprise",
|
||||||
"HeaderAudioTracks": "Ljudfiler",
|
"HeaderAudioTracks": "Ljudfiler",
|
||||||
"HeaderAudiobookTools": "Hantering av ljudboksfiler",
|
"HeaderAudiobookTools": "Hantering av ljudboksfiler",
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "Prioriteringsordning vid inläsning av metadata",
|
"HeaderMetadataOrderOfPrecedence": "Prioriteringsordning vid inläsning av metadata",
|
||||||
"HeaderMetadataToEmbed": "Metadata som kommer att adderas",
|
"HeaderMetadataToEmbed": "Metadata som kommer att adderas",
|
||||||
"HeaderNewAccount": "Nytt konto",
|
"HeaderNewAccount": "Nytt konto",
|
||||||
|
"HeaderNewApiKey": "Ny API-nyckel",
|
||||||
"HeaderNewLibrary": "Nytt bibliotek",
|
"HeaderNewLibrary": "Nytt bibliotek",
|
||||||
"HeaderNotificationCreate": "Addera ett meddelande",
|
"HeaderNotificationCreate": "Addera ett meddelande",
|
||||||
"HeaderNotificationUpdate": "Uppdateringsnotis",
|
"HeaderNotificationUpdate": "Uppdateringsnotis",
|
||||||
@@ -205,6 +209,7 @@
|
|||||||
"HeaderTableOfContents": "Innehållsförteckning",
|
"HeaderTableOfContents": "Innehållsförteckning",
|
||||||
"HeaderTools": "Verktyg",
|
"HeaderTools": "Verktyg",
|
||||||
"HeaderUpdateAccount": "Uppdatera konto",
|
"HeaderUpdateAccount": "Uppdatera konto",
|
||||||
|
"HeaderUpdateApiKey": "Uppdatera API-nyckel",
|
||||||
"HeaderUpdateAuthor": "Uppdatera författare",
|
"HeaderUpdateAuthor": "Uppdatera författare",
|
||||||
"HeaderUpdateDetails": "Uppdatera detaljer om boken",
|
"HeaderUpdateDetails": "Uppdatera detaljer om boken",
|
||||||
"HeaderUpdateLibrary": "Uppdatera bibliotek",
|
"HeaderUpdateLibrary": "Uppdatera bibliotek",
|
||||||
@@ -234,6 +239,9 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Alla användare utom gäster",
|
"LabelAllUsersExcludingGuests": "Alla användare utom gäster",
|
||||||
"LabelAllUsersIncludingGuests": "Alla användare inklusive gäster",
|
"LabelAllUsersIncludingGuests": "Alla användare inklusive gäster",
|
||||||
"LabelAlreadyInYourLibrary": "Finns redan i samlingen",
|
"LabelAlreadyInYourLibrary": "Finns redan i samlingen",
|
||||||
|
"LabelApiKeyCreated": "API-nyckel \"{0}\" har adderats.",
|
||||||
|
"LabelApiKeyCreatedDescription": "Se till att kopiera API-nyckeln omedelbart eftersom du inte kommer att kunna se den igen.",
|
||||||
|
"LabelApiKeyUserDescription": "Denna API-nyckel kommer att ha samma behörigheter som användaren den agerar på uppdrag av. Detta kommer att visas på samma sätt i loggarna som om användaren gjorde begäran.",
|
||||||
"LabelApiToken": "API-token",
|
"LabelApiToken": "API-token",
|
||||||
"LabelAppend": "Lägg till",
|
"LabelAppend": "Lägg till",
|
||||||
"LabelAudioBitrate": "Bitrate (t.ex. 128k)",
|
"LabelAudioBitrate": "Bitrate (t.ex. 128k)",
|
||||||
@@ -345,7 +353,11 @@
|
|||||||
"LabelExample": "Exempel",
|
"LabelExample": "Exempel",
|
||||||
"LabelExpandSeries": "Expandera serier",
|
"LabelExpandSeries": "Expandera serier",
|
||||||
"LabelExpandSubSeries": "Expandera Underserier",
|
"LabelExpandSubSeries": "Expandera Underserier",
|
||||||
"LabelExplicit": "Explicit version",
|
"LabelExpired": "Upphört",
|
||||||
|
"LabelExpiresAt": "Gäller till och med",
|
||||||
|
"LabelExpiresInSeconds": "Upphör om (sekunder)",
|
||||||
|
"LabelExpiresNever": "Aldrig",
|
||||||
|
"LabelExplicit": "Bestämd",
|
||||||
"LabelExplicitChecked": "Explicit version (markerad)",
|
"LabelExplicitChecked": "Explicit version (markerad)",
|
||||||
"LabelExplicitUnchecked": "Ej Explicit version (ej markerad)",
|
"LabelExplicitUnchecked": "Ej Explicit version (ej markerad)",
|
||||||
"LabelExportOPML": "Exportera OPML-information",
|
"LabelExportOPML": "Exportera OPML-information",
|
||||||
@@ -454,6 +466,7 @@
|
|||||||
"LabelNewestEpisodes": "Senaste avsnitten",
|
"LabelNewestEpisodes": "Senaste avsnitten",
|
||||||
"LabelNextBackupDate": "Nästa tillfälle för säkerhetskopiering",
|
"LabelNextBackupDate": "Nästa tillfälle för säkerhetskopiering",
|
||||||
"LabelNextScheduledRun": "Nästa schemalagda körning",
|
"LabelNextScheduledRun": "Nästa schemalagda körning",
|
||||||
|
"LabelNoApiKeys": "Ingen API-nyckel",
|
||||||
"LabelNoCustomMetadataProviders": "Ingen egen källa för metadata",
|
"LabelNoCustomMetadataProviders": "Ingen egen källa för metadata",
|
||||||
"LabelNoEpisodesSelected": "Inga avsnitt har valts",
|
"LabelNoEpisodesSelected": "Inga avsnitt har valts",
|
||||||
"LabelNotFinished": "Ej avslutad",
|
"LabelNotFinished": "Ej avslutad",
|
||||||
@@ -470,11 +483,15 @@
|
|||||||
"LabelNotificationsMaxQueueSizeHelp": "Evenemang är begränsade till att utlösa ett per sekund. Evenemang kommer att ignoreras om kön är full. Detta förhindrar aviseringsspam.",
|
"LabelNotificationsMaxQueueSizeHelp": "Evenemang är begränsade till att utlösa ett per sekund. Evenemang kommer att ignoreras om kön är full. Detta förhindrar aviseringsspam.",
|
||||||
"LabelNumberOfBooks": "Antal böcker",
|
"LabelNumberOfBooks": "Antal böcker",
|
||||||
"LabelNumberOfEpisodes": "# av Avsnitt",
|
"LabelNumberOfEpisodes": "# av Avsnitt",
|
||||||
|
"LabelOpenIDAdvancedPermsClaimDescription": "Namn på OpenID-anspråket som innehåller avancerade behörigheter för användaråtgärder i applikationen, vilka gäller för icke-administratörsroller (<b>om konfigurerat</b>). Om anspråket saknas i svaret kommer åtkomst till ABS att nekas. Om ett enskilt alternativ saknas kommer det att behandlas som <code>falskt</code>. Se till att identitetsleverantörens anspråk matchar den förväntade strukturen:",
|
||||||
|
"LabelOpenIDClaims": "Lämna följande alternativ tomma för att inaktivera avancerad grupp- och behörighetstilldelning, och tilldela då automatiskt gruppen 'Användare'.",
|
||||||
|
"LabelOpenIDGroupClaimDescription": "Namn på OpenID-anspråket som innehåller en lista över användarens grupper. Vanligtvis kallat <code>groups</code>. <b>Om det är konfigurerat</b> kommer programmet automatiskt att tilldela roller baserat på användarens gruppmedlemskap, förutsatt att dessa grupper namnges utan att skiftlägeskänsligt tolkas som 'admin', 'user' eller 'guest' i anspråket. Anspråket ska innehålla en lista, och om en användare tillhör flera grupper kommer programmet att tilldela den roll som motsvarar den högsta åtkomstnivån. Om ingen grupp matchar kommer åtkomst att nekas.",
|
||||||
"LabelOpenRSSFeed": "Öppna RSS-flöde",
|
"LabelOpenRSSFeed": "Öppna RSS-flöde",
|
||||||
"LabelOverwrite": "Skriv över",
|
"LabelOverwrite": "Skriv över",
|
||||||
"LabelPaginationPageXOfY": "Sida {0} av {1}",
|
"LabelPaginationPageXOfY": "Sida {0} av {1}",
|
||||||
"LabelPassword": "Lösenord",
|
"LabelPassword": "Lösenord",
|
||||||
"LabelPath": "Sökväg",
|
"LabelPath": "Sökväg",
|
||||||
|
"LabelPermanent": "Permanent",
|
||||||
"LabelPermissionsAccessAllLibraries": "Kan komma åt alla bibliotek",
|
"LabelPermissionsAccessAllLibraries": "Kan komma åt alla bibliotek",
|
||||||
"LabelPermissionsAccessAllTags": "Kan komma åt alla taggar",
|
"LabelPermissionsAccessAllTags": "Kan komma åt alla taggar",
|
||||||
"LabelPermissionsAccessExplicitContent": "Kan komma åt explicit version",
|
"LabelPermissionsAccessExplicitContent": "Kan komma åt explicit version",
|
||||||
@@ -486,6 +503,7 @@
|
|||||||
"LabelPersonalYearReview": "En sammanställning av ditt år, sidan {0}",
|
"LabelPersonalYearReview": "En sammanställning av ditt år, sidan {0}",
|
||||||
"LabelPhotoPathURL": "Bildsökväg/URL",
|
"LabelPhotoPathURL": "Bildsökväg/URL",
|
||||||
"LabelPlayMethod": "Spelläge",
|
"LabelPlayMethod": "Spelläge",
|
||||||
|
"LabelPlaybackRateIncrementDecrement": "Uppspelningshastighetsökning/minskning",
|
||||||
"LabelPlayerChapterNumberMarker": "{0} av {1}",
|
"LabelPlayerChapterNumberMarker": "{0} av {1}",
|
||||||
"LabelPlaylists": "Spellistor",
|
"LabelPlaylists": "Spellistor",
|
||||||
"LabelPodcast": "Podcast",
|
"LabelPodcast": "Podcast",
|
||||||
@@ -524,6 +542,7 @@
|
|||||||
"LabelReleaseDate": "Utgivningsdatum",
|
"LabelReleaseDate": "Utgivningsdatum",
|
||||||
"LabelRemoveAllMetadataAbs": "Radera alla 'metadata.abs' filer",
|
"LabelRemoveAllMetadataAbs": "Radera alla 'metadata.abs' filer",
|
||||||
"LabelRemoveAllMetadataJson": "Radera alla 'metadata.json' filer",
|
"LabelRemoveAllMetadataJson": "Radera alla 'metadata.json' filer",
|
||||||
|
"LabelRemoveAudibleBranding": "Ta bort Audible intro och outro från kapitel",
|
||||||
"LabelRemoveCover": "Ta bort omslag",
|
"LabelRemoveCover": "Ta bort omslag",
|
||||||
"LabelRemoveMetadataFile": "Radera metadata-filer i alla mappar i biblioteket",
|
"LabelRemoveMetadataFile": "Radera metadata-filer i alla mappar i biblioteket",
|
||||||
"LabelRemoveMetadataFileHelp": "Radera alla 'metadata.json' och 'metadata.abs' filer i dina {0} mappar.",
|
"LabelRemoveMetadataFileHelp": "Radera alla 'metadata.json' och 'metadata.abs' filer i dina {0} mappar.",
|
||||||
@@ -536,6 +555,7 @@
|
|||||||
"LabelSelectAll": "Välj alla",
|
"LabelSelectAll": "Välj alla",
|
||||||
"LabelSelectAllEpisodes": "Välj alla avsnitt",
|
"LabelSelectAllEpisodes": "Välj alla avsnitt",
|
||||||
"LabelSelectEpisodesShowing": "Välj {0} avsnitt som visas",
|
"LabelSelectEpisodesShowing": "Välj {0} avsnitt som visas",
|
||||||
|
"LabelSelectUser": "Välj användare",
|
||||||
"LabelSelectUsers": "Välj användare",
|
"LabelSelectUsers": "Välj användare",
|
||||||
"LabelSendEbookToDevice": "Skicka e-bok till...",
|
"LabelSendEbookToDevice": "Skicka e-bok till...",
|
||||||
"LabelSequence": "Ordningsnummer",
|
"LabelSequence": "Ordningsnummer",
|
||||||
@@ -694,6 +714,7 @@
|
|||||||
"LabelYourProgress": "Framsteg",
|
"LabelYourProgress": "Framsteg",
|
||||||
"MessageAddToPlayerQueue": "Lägg till i spellistan",
|
"MessageAddToPlayerQueue": "Lägg till i spellistan",
|
||||||
"MessageAppriseDescription": "För att använda den här funktionen behöver du ha en instans av <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> igång eller en API som hanterar dessa begäranden. <br />Apprise API-urlen bör vara hela URL-sökvägen för att skicka meddelandet, t.ex., om din API-instans är tillgänglig på <code>http://192.168.1.1:8337</code>, bör du ange <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "För att använda den här funktionen behöver du ha en instans av <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> igång eller en API som hanterar dessa begäranden. <br />Apprise API-urlen bör vara hela URL-sökvägen för att skicka meddelandet, t.ex., om din API-instans är tillgänglig på <code>http://192.168.1.1:8337</code>, bör du ange <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
|
"MessageAuthenticationSecurityMessage": "Identifieringen av användare har förbättrats av säkerhetsskäl. Alla användare måste därför logga in på nytt.",
|
||||||
"MessageBackupsDescription": "Säkerhetskopior inkluderar användare, användarnas framsteg, biblioteksobjekt,<br>serverinställningar och bilder lagrade i <code>/metadata/items</code> & <code>/metadata/authors</code>.<br>De inkluderar <strong>INTE</strong> några filer lagrade i dina biblioteksmappar.",
|
"MessageBackupsDescription": "Säkerhetskopior inkluderar användare, användarnas framsteg, biblioteksobjekt,<br>serverinställningar och bilder lagrade i <code>/metadata/items</code> & <code>/metadata/authors</code>.<br>De inkluderar <strong>INTE</strong> några filer lagrade i dina biblioteksmappar.",
|
||||||
"MessageBackupsLocationEditNote": "OBS: När du ändrar plats för säkerhetskopiorna så flyttas INTE gamla säkerhetskopior dit",
|
"MessageBackupsLocationEditNote": "OBS: När du ändrar plats för säkerhetskopiorna så flyttas INTE gamla säkerhetskopior dit",
|
||||||
"MessageBackupsLocationNoEditNote": "OBS: Platsen där säkerhetskopiorna lagras bestäms av en central inställning och kan inte ändras här.",
|
"MessageBackupsLocationNoEditNote": "OBS: Platsen där säkerhetskopiorna lagras bestäms av en central inställning och kan inte ändras här.",
|
||||||
@@ -714,6 +735,7 @@
|
|||||||
"MessageChapterStartIsAfter": "Kapitlets start är efter din ljudboks slut",
|
"MessageChapterStartIsAfter": "Kapitlets start är efter din ljudboks slut",
|
||||||
"MessageCheckingCron": "Kontrollerar cron...",
|
"MessageCheckingCron": "Kontrollerar cron...",
|
||||||
"MessageConfirmCloseFeed": "Är du säker på att du vill stänga detta flöde?",
|
"MessageConfirmCloseFeed": "Är du säker på att du vill stänga detta flöde?",
|
||||||
|
"MessageConfirmDeleteApiKey": "Är du säker på att du vill radera API-nyckel \"{0}\"?",
|
||||||
"MessageConfirmDeleteBackup": "Är du säker på att du vill radera säkerhetskopian för {0}?",
|
"MessageConfirmDeleteBackup": "Är du säker på att du vill radera säkerhetskopian för {0}?",
|
||||||
"MessageConfirmDeleteDevice": "Är du säkert på att du vill radera enheten för e-böcker \"{0}\"?",
|
"MessageConfirmDeleteDevice": "Är du säkert på att du vill radera enheten för e-böcker \"{0}\"?",
|
||||||
"MessageConfirmDeleteFile": "Detta kommer att radera filen från ditt filsystem. Är du säker?",
|
"MessageConfirmDeleteFile": "Detta kommer att radera filen från ditt filsystem. Är du säker?",
|
||||||
@@ -764,6 +786,7 @@
|
|||||||
"MessageForceReScanDescription": "kommer att göra en omgångssökning av alla filer som en färsk sökning. ID3-taggar för ljudfiler, OPF-filer och textfiler kommer att sökas som nya.",
|
"MessageForceReScanDescription": "kommer att göra en omgångssökning av alla filer som en färsk sökning. ID3-taggar för ljudfiler, OPF-filer och textfiler kommer att sökas som nya.",
|
||||||
"MessageImportantNotice": "Viktig meddelande!",
|
"MessageImportantNotice": "Viktig meddelande!",
|
||||||
"MessageInsertChapterBelow": "Infoga kapitel nedanför",
|
"MessageInsertChapterBelow": "Infoga kapitel nedanför",
|
||||||
|
"MessageInvalidAsin": "Felaktig ASIN-kod",
|
||||||
"MessageItemsSelected": "{0} objekt markerade",
|
"MessageItemsSelected": "{0} objekt markerade",
|
||||||
"MessageItemsUpdated": "{0} Objekt uppdaterade",
|
"MessageItemsUpdated": "{0} Objekt uppdaterade",
|
||||||
"MessageJoinUsOn": "Anslut dig till oss på",
|
"MessageJoinUsOn": "Anslut dig till oss på",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Ekle",
|
"ButtonAdd": "Ekle",
|
||||||
|
"ButtonAddApiKey": "API Anahtarı Ekle",
|
||||||
"ButtonAddChapters": "Bölüm Ekle",
|
"ButtonAddChapters": "Bölüm Ekle",
|
||||||
"ButtonAddDevice": "Cihaz Ekle",
|
"ButtonAddDevice": "Cihaz Ekle",
|
||||||
"ButtonAddLibrary": "Kütüphane Ekle",
|
"ButtonAddLibrary": "Kütüphane Ekle",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Klasör seç",
|
"ButtonChooseAFolder": "Klasör seç",
|
||||||
"ButtonChooseFiles": "Dosya seç",
|
"ButtonChooseFiles": "Dosya seç",
|
||||||
"ButtonClearFilter": "Filtreyi Temizle",
|
"ButtonClearFilter": "Filtreyi Temizle",
|
||||||
|
"ButtonClose": "Kapat",
|
||||||
"ButtonCloseFeed": "Akışı Kapat",
|
"ButtonCloseFeed": "Akışı Kapat",
|
||||||
"ButtonCloseSession": "Acık Oturumu Kapat",
|
"ButtonCloseSession": "Acık Oturumu Kapat",
|
||||||
"ButtonCollections": "Koleksiyonlar",
|
"ButtonCollections": "Koleksiyonlar",
|
||||||
@@ -95,7 +97,17 @@
|
|||||||
"ButtonSearch": "Ara",
|
"ButtonSearch": "Ara",
|
||||||
"ButtonSelectFolderPath": "Klasör Yolunu Seç",
|
"ButtonSelectFolderPath": "Klasör Yolunu Seç",
|
||||||
"ButtonSeries": "Seriler",
|
"ButtonSeries": "Seriler",
|
||||||
|
"ButtonShare": "Paylaş",
|
||||||
|
"ButtonStats": "İstatistikler",
|
||||||
"ButtonSubmit": "Gönder",
|
"ButtonSubmit": "Gönder",
|
||||||
|
"ButtonTest": "Dene",
|
||||||
|
"ButtonUnlinkOpenId": "OpenID ilişiğini kaldır",
|
||||||
|
"ButtonUpload": "Yükle",
|
||||||
|
"ButtonUploadBackup": "Yedeği Yükle",
|
||||||
|
"ButtonUploadCover": "Kapağı Yükle",
|
||||||
|
"ButtonUploadOPMLFile": "OPML Dosyası Yükle",
|
||||||
|
"ButtonUserDelete": "{0} kullanıcısını sil.",
|
||||||
|
"ButtonUserEdit": "{0} kullanıcısını düzenle",
|
||||||
"ButtonViewAll": "Tümünü Görüntüle",
|
"ButtonViewAll": "Tümünü Görüntüle",
|
||||||
"ButtonYes": "Evet",
|
"ButtonYes": "Evet",
|
||||||
"ErrorUploadFetchMetadataAPI": "Üst veriyi almakta hata",
|
"ErrorUploadFetchMetadataAPI": "Üst veriyi almakta hata",
|
||||||
@@ -104,6 +116,7 @@
|
|||||||
"HeaderAccount": "Hesap",
|
"HeaderAccount": "Hesap",
|
||||||
"HeaderAddCustomMetadataProvider": "Özel Üstveri Sağlayıcısı Ekle",
|
"HeaderAddCustomMetadataProvider": "Özel Üstveri Sağlayıcısı Ekle",
|
||||||
"HeaderAdvanced": "Gelişmiş",
|
"HeaderAdvanced": "Gelişmiş",
|
||||||
|
"HeaderApiKeys": "API Anahtarları",
|
||||||
"HeaderAppriseNotificationSettings": "Bildirim Ayarlarının Haberini Ver",
|
"HeaderAppriseNotificationSettings": "Bildirim Ayarlarının Haberini Ver",
|
||||||
"HeaderAudioTracks": "Ses Kanalları",
|
"HeaderAudioTracks": "Ses Kanalları",
|
||||||
"HeaderAudiobookTools": "Sesli Kitap Dosya Yönetim Araçları",
|
"HeaderAudiobookTools": "Sesli Kitap Dosya Yönetim Araçları",
|
||||||
@@ -111,13 +124,23 @@
|
|||||||
"HeaderBackups": "Yedeklemeler",
|
"HeaderBackups": "Yedeklemeler",
|
||||||
"HeaderChangePassword": "Parolayı Değiştir",
|
"HeaderChangePassword": "Parolayı Değiştir",
|
||||||
"HeaderChapters": "Bölümler",
|
"HeaderChapters": "Bölümler",
|
||||||
|
"HeaderChooseAFolder": "Klasör Seç",
|
||||||
"HeaderCollection": "Koleksiyon",
|
"HeaderCollection": "Koleksiyon",
|
||||||
"HeaderCollectionItems": "Koleksiyon Öğeleri",
|
"HeaderCollectionItems": "Koleksiyon Öğeleri",
|
||||||
|
"HeaderCover": "Kapak",
|
||||||
|
"HeaderCurrentDownloads": "Geçerli İndirmeler",
|
||||||
|
"HeaderCustomMessageOnLogin": "Girişteki Kişiselleştirilmiş Mesaj",
|
||||||
|
"HeaderCustomMetadataProviders": "Kişiselleştirilmiş Metadata Sağlayıcıları",
|
||||||
"HeaderDetails": "Detaylar",
|
"HeaderDetails": "Detaylar",
|
||||||
|
"HeaderDownloadQueue": "Kuyruktakileri İndir",
|
||||||
"HeaderEbookFiles": "Ebook Dosyaları",
|
"HeaderEbookFiles": "Ebook Dosyaları",
|
||||||
|
"HeaderEmail": "Email",
|
||||||
|
"HeaderEmailSettings": "Email Ayarları",
|
||||||
"HeaderEpisodes": "Bölümler",
|
"HeaderEpisodes": "Bölümler",
|
||||||
|
"HeaderEreaderDevices": "Ekitap Cihazları",
|
||||||
"HeaderEreaderSettings": "Ereader Ayarları",
|
"HeaderEreaderSettings": "Ereader Ayarları",
|
||||||
"HeaderFiles": "Dosyalar",
|
"HeaderFiles": "Dosyalar",
|
||||||
|
"HeaderFindChapters": "Bölümleri Bul",
|
||||||
"HeaderIgnoredFiles": "Görmezden Gelinen Dosyalar",
|
"HeaderIgnoredFiles": "Görmezden Gelinen Dosyalar",
|
||||||
"HeaderItemFiles": "Öğe Dosyaları",
|
"HeaderItemFiles": "Öğe Dosyaları",
|
||||||
"HeaderItemMetadataUtils": "Öğe Üstveri Araçları",
|
"HeaderItemMetadataUtils": "Öğe Üstveri Araçları",
|
||||||
|
|||||||
+24
-2
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "Додати",
|
"ButtonAdd": "Додати",
|
||||||
|
"ButtonAddApiKey": "Додати ключ API",
|
||||||
"ButtonAddChapters": "Додати глави",
|
"ButtonAddChapters": "Додати глави",
|
||||||
"ButtonAddDevice": "Додати пристрій",
|
"ButtonAddDevice": "Додати пристрій",
|
||||||
"ButtonAddLibrary": "Додати бібліотеку",
|
"ButtonAddLibrary": "Додати бібліотеку",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "Обрати теку",
|
"ButtonChooseAFolder": "Обрати теку",
|
||||||
"ButtonChooseFiles": "Обрати файли",
|
"ButtonChooseFiles": "Обрати файли",
|
||||||
"ButtonClearFilter": "Очистити фільтр",
|
"ButtonClearFilter": "Очистити фільтр",
|
||||||
|
"ButtonClose": "Закрити",
|
||||||
"ButtonCloseFeed": "Закрити стрічку",
|
"ButtonCloseFeed": "Закрити стрічку",
|
||||||
"ButtonCloseSession": "Закрити відкритий сеанс",
|
"ButtonCloseSession": "Закрити відкритий сеанс",
|
||||||
"ButtonCollections": "Добірки",
|
"ButtonCollections": "Добірки",
|
||||||
@@ -119,6 +121,7 @@
|
|||||||
"HeaderAccount": "Профіль",
|
"HeaderAccount": "Профіль",
|
||||||
"HeaderAddCustomMetadataProvider": "Додати користувацький постачальник метаданих",
|
"HeaderAddCustomMetadataProvider": "Додати користувацький постачальник метаданих",
|
||||||
"HeaderAdvanced": "Розширені",
|
"HeaderAdvanced": "Розширені",
|
||||||
|
"HeaderApiKeys": "Ключі API",
|
||||||
"HeaderAppriseNotificationSettings": "Налаштування сповіщень Apprise",
|
"HeaderAppriseNotificationSettings": "Налаштування сповіщень Apprise",
|
||||||
"HeaderAudioTracks": "Аудіодоріжки",
|
"HeaderAudioTracks": "Аудіодоріжки",
|
||||||
"HeaderAudiobookTools": "Інструменти керування файлами книг",
|
"HeaderAudiobookTools": "Інструменти керування файлами книг",
|
||||||
@@ -162,6 +165,7 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "Порядок метаданих",
|
"HeaderMetadataOrderOfPrecedence": "Порядок метаданих",
|
||||||
"HeaderMetadataToEmbed": "Вбудувати метадані",
|
"HeaderMetadataToEmbed": "Вбудувати метадані",
|
||||||
"HeaderNewAccount": "Новий профіль",
|
"HeaderNewAccount": "Новий профіль",
|
||||||
|
"HeaderNewApiKey": "Новий ключ API",
|
||||||
"HeaderNewLibrary": "Нова бібліотека",
|
"HeaderNewLibrary": "Нова бібліотека",
|
||||||
"HeaderNotificationCreate": "Створити сповіщення",
|
"HeaderNotificationCreate": "Створити сповіщення",
|
||||||
"HeaderNotificationUpdate": "Оновити сповіщення",
|
"HeaderNotificationUpdate": "Оновити сповіщення",
|
||||||
@@ -206,6 +210,7 @@
|
|||||||
"HeaderTableOfContents": "Зміст",
|
"HeaderTableOfContents": "Зміст",
|
||||||
"HeaderTools": "Інструменти",
|
"HeaderTools": "Інструменти",
|
||||||
"HeaderUpdateAccount": "Оновити профіль",
|
"HeaderUpdateAccount": "Оновити профіль",
|
||||||
|
"HeaderUpdateApiKey": "Оновити ключ API",
|
||||||
"HeaderUpdateAuthor": "Оновити автора",
|
"HeaderUpdateAuthor": "Оновити автора",
|
||||||
"HeaderUpdateDetails": "Оновити подробиці",
|
"HeaderUpdateDetails": "Оновити подробиці",
|
||||||
"HeaderUpdateLibrary": "Оновити бібліотеку",
|
"HeaderUpdateLibrary": "Оновити бібліотеку",
|
||||||
@@ -235,6 +240,10 @@
|
|||||||
"LabelAllUsersExcludingGuests": "Усі, крім гостей",
|
"LabelAllUsersExcludingGuests": "Усі, крім гостей",
|
||||||
"LabelAllUsersIncludingGuests": "Усі, включно з гостями",
|
"LabelAllUsersIncludingGuests": "Усі, включно з гостями",
|
||||||
"LabelAlreadyInYourLibrary": "Вже у вашій бібліотеці",
|
"LabelAlreadyInYourLibrary": "Вже у вашій бібліотеці",
|
||||||
|
"LabelApiKeyCreated": "Ключ API \"{0}\" успішно створено.",
|
||||||
|
"LabelApiKeyCreatedDescription": "Обов’язково скопіюйте ключ API зараз, оскільки ви більше не зможете його побачити.",
|
||||||
|
"LabelApiKeyUser": "Діяти від імені користувача",
|
||||||
|
"LabelApiKeyUserDescription": "Цей ключ API матиме ті самі дозволи, що й користувач, від імені якого він діє. Це відображатиметься в журналах так само, як і в разі надсилання запиту користувачем.",
|
||||||
"LabelApiToken": "Токен API",
|
"LabelApiToken": "Токен API",
|
||||||
"LabelAppend": "Додати",
|
"LabelAppend": "Додати",
|
||||||
"LabelAudioBitrate": "Бітрейт аудіо (наприклад, 128k)",
|
"LabelAudioBitrate": "Бітрейт аудіо (наприклад, 128k)",
|
||||||
@@ -346,6 +355,10 @@
|
|||||||
"LabelExample": "Приклад",
|
"LabelExample": "Приклад",
|
||||||
"LabelExpandSeries": "Розгорнути серії",
|
"LabelExpandSeries": "Розгорнути серії",
|
||||||
"LabelExpandSubSeries": "Розгорнути підсерії",
|
"LabelExpandSubSeries": "Розгорнути підсерії",
|
||||||
|
"LabelExpired": "Термін дії минув",
|
||||||
|
"LabelExpiresAt": "Термін дії закінчується о",
|
||||||
|
"LabelExpiresInSeconds": "Термін дії закінчується через (секунди)",
|
||||||
|
"LabelExpiresNever": "Ніколи",
|
||||||
"LabelExplicit": "Відвертий",
|
"LabelExplicit": "Відвертий",
|
||||||
"LabelExplicitChecked": "Відверта (з прапорцем)",
|
"LabelExplicitChecked": "Відверта (з прапорцем)",
|
||||||
"LabelExplicitUnchecked": "Не відверта (без прапорця)",
|
"LabelExplicitUnchecked": "Не відверта (без прапорця)",
|
||||||
@@ -425,6 +438,7 @@
|
|||||||
"LabelLogLevelWarn": "Увага",
|
"LabelLogLevelWarn": "Увага",
|
||||||
"LabelLookForNewEpisodesAfterDate": "Шукати нові епізоди після вказаної дати",
|
"LabelLookForNewEpisodesAfterDate": "Шукати нові епізоди після вказаної дати",
|
||||||
"LabelLowestPriority": "Найнижчий пріоритет",
|
"LabelLowestPriority": "Найнижчий пріоритет",
|
||||||
|
"LabelMatchConfidence": "Впевненість",
|
||||||
"LabelMatchExistingUsersBy": "Шукати наявних користувачів за",
|
"LabelMatchExistingUsersBy": "Шукати наявних користувачів за",
|
||||||
"LabelMatchExistingUsersByDescription": "Використовується для підключення наявних користувачів. Після підключення користувач отримає унікальний id від вашого сервісу SSO",
|
"LabelMatchExistingUsersByDescription": "Використовується для підключення наявних користувачів. Після підключення користувач отримає унікальний id від вашого сервісу SSO",
|
||||||
"LabelMaxEpisodesToDownload": "Максимальна кількість епізодів для скачування. Використовуйте 0 для необмеженої кількості.",
|
"LabelMaxEpisodesToDownload": "Максимальна кількість епізодів для скачування. Використовуйте 0 для необмеженої кількості.",
|
||||||
@@ -455,6 +469,7 @@
|
|||||||
"LabelNewestEpisodes": "Нові епізоди",
|
"LabelNewestEpisodes": "Нові епізоди",
|
||||||
"LabelNextBackupDate": "Дата наступного резервного копіювання",
|
"LabelNextBackupDate": "Дата наступного резервного копіювання",
|
||||||
"LabelNextScheduledRun": "Наступний запланований запуск",
|
"LabelNextScheduledRun": "Наступний запланований запуск",
|
||||||
|
"LabelNoApiKeys": "Без ключів API",
|
||||||
"LabelNoCustomMetadataProviders": "Без постачальників метаданих",
|
"LabelNoCustomMetadataProviders": "Без постачальників метаданих",
|
||||||
"LabelNoEpisodesSelected": "Не вибрано жодного епізоду",
|
"LabelNoEpisodesSelected": "Не вибрано жодного епізоду",
|
||||||
"LabelNotFinished": "Незавершені",
|
"LabelNotFinished": "Незавершені",
|
||||||
@@ -544,6 +559,7 @@
|
|||||||
"LabelSelectAll": "Вибрати все",
|
"LabelSelectAll": "Вибрати все",
|
||||||
"LabelSelectAllEpisodes": "Вибрати всі епізоди",
|
"LabelSelectAllEpisodes": "Вибрати всі епізоди",
|
||||||
"LabelSelectEpisodesShowing": "Вибрати {0} показаних епізодів",
|
"LabelSelectEpisodesShowing": "Вибрати {0} показаних епізодів",
|
||||||
|
"LabelSelectUser": "Виберіть користувача",
|
||||||
"LabelSelectUsers": "Вибрати користувачів",
|
"LabelSelectUsers": "Вибрати користувачів",
|
||||||
"LabelSendEbookToDevice": "Надіслати електронну книгу на...",
|
"LabelSendEbookToDevice": "Надіслати електронну книгу на...",
|
||||||
"LabelSequence": "Послідовність",
|
"LabelSequence": "Послідовність",
|
||||||
@@ -708,7 +724,9 @@
|
|||||||
"MessageAddToPlayerQueue": "Додати до черги відтворення",
|
"MessageAddToPlayerQueue": "Додати до черги відтворення",
|
||||||
"MessageAppriseDescription": "Щоб скористатися цією функцією, вам потрібно мати запущену <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> або API, що оброблятиме ті ж запити. <br />Аби надсилати сповіщення, URL-адреса API Apprise мусить бути повною, наприклад, якщо ваш API розміщено за адресою <code>http://192.168.1.1:8337</code>, то необхідно вказати адресу <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "Щоб скористатися цією функцією, вам потрібно мати запущену <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> або API, що оброблятиме ті ж запити. <br />Аби надсилати сповіщення, URL-адреса API Apprise мусить бути повною, наприклад, якщо ваш API розміщено за адресою <code>http://192.168.1.1:8337</code>, то необхідно вказати адресу <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageAsinCheck": "Переконайтесь, що ви використовуєте ASIN з правильної регіональної Audible зони, а не з Amazon.",
|
"MessageAsinCheck": "Переконайтесь, що ви використовуєте ASIN з правильної регіональної Audible зони, а не з Amazon.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "Застарілі токени API будуть видалені в майбутньому. Натомість використовуйте <a href=\"/config/api-keys\">Ключі API</a>.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "Перезавантажте сервер після збереження, щоб застосувати зміни OIDC.",
|
"MessageAuthenticationOIDCChangesRestart": "Перезавантажте сервер після збереження, щоб застосувати зміни OIDC.",
|
||||||
|
"MessageAuthenticationSecurityMessage": "Автентифікацію покращено для безпеки. Усім користувачам потрібно повторно увійти в систему.",
|
||||||
"MessageBackupsDescription": "Резервні копії містять користувачів, прогрес, подробиці елементів бібліотеки, налаштування сервера та зображення з <code>/metadata/items</code> та <code>/metadata/authors</code>. Резервні копії <strong>не</strong> містять жодних файлів з тек бібліотеки.",
|
"MessageBackupsDescription": "Резервні копії містять користувачів, прогрес, подробиці елементів бібліотеки, налаштування сервера та зображення з <code>/metadata/items</code> та <code>/metadata/authors</code>. Резервні копії <strong>не</strong> містять жодних файлів з тек бібліотеки.",
|
||||||
"MessageBackupsLocationEditNote": "Примітка: оновлення розташування резервної копії не переносить та не змінює існуючих копій",
|
"MessageBackupsLocationEditNote": "Примітка: оновлення розташування резервної копії не переносить та не змінює існуючих копій",
|
||||||
"MessageBackupsLocationNoEditNote": "Примітка: розташування резервної копії встановлюється за допомогою змінної середовища та не може бути змінене тут.",
|
"MessageBackupsLocationNoEditNote": "Примітка: розташування резервної копії встановлюється за допомогою змінної середовища та не може бути змінене тут.",
|
||||||
@@ -730,6 +748,7 @@
|
|||||||
"MessageChaptersNotFound": "Розділи не знайдені",
|
"MessageChaptersNotFound": "Розділи не знайдені",
|
||||||
"MessageCheckingCron": "Перевірка планувальника...",
|
"MessageCheckingCron": "Перевірка планувальника...",
|
||||||
"MessageConfirmCloseFeed": "Ви дійсно бажаєте закрити цей канал?",
|
"MessageConfirmCloseFeed": "Ви дійсно бажаєте закрити цей канал?",
|
||||||
|
"MessageConfirmDeleteApiKey": "Ви впевнені, що хочете видалити ключ API? \"{0}\"?",
|
||||||
"MessageConfirmDeleteBackup": "Ви дійсно бажаєте видалити резервну копію за {0}?",
|
"MessageConfirmDeleteBackup": "Ви дійсно бажаєте видалити резервну копію за {0}?",
|
||||||
"MessageConfirmDeleteDevice": "Ви впевнені, що хочете видалити пристрій для читання \"{0}\"?",
|
"MessageConfirmDeleteDevice": "Ви впевнені, що хочете видалити пристрій для читання \"{0}\"?",
|
||||||
"MessageConfirmDeleteFile": "Файл буде видалено з вашої файлової системи. Ви впевнені?",
|
"MessageConfirmDeleteFile": "Файл буде видалено з вашої файлової системи. Ви впевнені?",
|
||||||
@@ -819,7 +838,7 @@
|
|||||||
"MessageNoItems": "Елементи відсутні",
|
"MessageNoItems": "Елементи відсутні",
|
||||||
"MessageNoItemsFound": "Елементів не знайдено",
|
"MessageNoItemsFound": "Елементів не знайдено",
|
||||||
"MessageNoListeningSessions": "Сеанси прослуховування відсутні",
|
"MessageNoListeningSessions": "Сеанси прослуховування відсутні",
|
||||||
"MessageNoLogs": "Немає журнали",
|
"MessageNoLogs": "Немає журналів'",
|
||||||
"MessageNoMediaProgress": "Прогрес відсутній",
|
"MessageNoMediaProgress": "Прогрес відсутній",
|
||||||
"MessageNoNotifications": "Сповіщення відсутні",
|
"MessageNoNotifications": "Сповіщення відсутні",
|
||||||
"MessageNoPodcastFeed": "Некоректний подкаст: немає каналу",
|
"MessageNoPodcastFeed": "Некоректний подкаст: немає каналу",
|
||||||
@@ -1001,6 +1020,8 @@
|
|||||||
"ToastEpisodeDownloadQueueClearSuccess": "Чергу на скачування епізодів очищено",
|
"ToastEpisodeDownloadQueueClearSuccess": "Чергу на скачування епізодів очищено",
|
||||||
"ToastEpisodeUpdateSuccess": "{0} епізодів оновлено",
|
"ToastEpisodeUpdateSuccess": "{0} епізодів оновлено",
|
||||||
"ToastErrorCannotShare": "Не можна типово поширити на цей пристрій",
|
"ToastErrorCannotShare": "Не можна типово поширити на цей пристрій",
|
||||||
|
"ToastFailedToCreate": "Не вдалося створити",
|
||||||
|
"ToastFailedToDelete": "Не вдалося видалити",
|
||||||
"ToastFailedToLoadData": "Не вдалося завантажити дані",
|
"ToastFailedToLoadData": "Не вдалося завантажити дані",
|
||||||
"ToastFailedToMatch": "Не вдалося знайти відповідність",
|
"ToastFailedToMatch": "Не вдалося знайти відповідність",
|
||||||
"ToastFailedToShare": "Не вдалося поділитися",
|
"ToastFailedToShare": "Не вдалося поділитися",
|
||||||
@@ -1032,6 +1053,7 @@
|
|||||||
"ToastMustHaveAtLeastOnePath": "Повинен бути хоча б один шлях",
|
"ToastMustHaveAtLeastOnePath": "Повинен бути хоча б один шлях",
|
||||||
"ToastNameEmailRequired": "Ім'я та електронна пошта обов'язкові",
|
"ToastNameEmailRequired": "Ім'я та електронна пошта обов'язкові",
|
||||||
"ToastNameRequired": "Ім'я обов'язкове",
|
"ToastNameRequired": "Ім'я обов'язкове",
|
||||||
|
"ToastNewApiKeyUserError": "Потрібно вибрати користувача",
|
||||||
"ToastNewEpisodesFound": "{0} нових епізодів знайдено",
|
"ToastNewEpisodesFound": "{0} нових епізодів знайдено",
|
||||||
"ToastNewUserCreatedFailed": "Не вдалося створити акаунт: \"{0}\"",
|
"ToastNewUserCreatedFailed": "Не вдалося створити акаунт: \"{0}\"",
|
||||||
"ToastNewUserCreatedSuccess": "Новий акаунт створено",
|
"ToastNewUserCreatedSuccess": "Новий акаунт створено",
|
||||||
@@ -1066,7 +1088,7 @@
|
|||||||
"ToastProviderRemoveSuccess": "Постачальник видалений",
|
"ToastProviderRemoveSuccess": "Постачальник видалений",
|
||||||
"ToastRSSFeedCloseFailed": "Не вдалося закрити RSS-канал",
|
"ToastRSSFeedCloseFailed": "Не вдалося закрити RSS-канал",
|
||||||
"ToastRSSFeedCloseSuccess": "RSS-канал закрито",
|
"ToastRSSFeedCloseSuccess": "RSS-канал закрито",
|
||||||
"ToastRemoveFailed": "Не вдалося видалити",
|
"ToastRemoveFailed": "Не вдалося вилучити",
|
||||||
"ToastRemoveItemFromCollectionFailed": "Не вдалося видалити елемент із добірки",
|
"ToastRemoveItemFromCollectionFailed": "Не вдалося видалити елемент із добірки",
|
||||||
"ToastRemoveItemFromCollectionSuccess": "Елемент видалено з добірки",
|
"ToastRemoveItemFromCollectionSuccess": "Елемент видалено з добірки",
|
||||||
"ToastRemoveItemsWithIssuesFailed": "Не вдалося видалити елементи бібліотеки з проблемами",
|
"ToastRemoveItemsWithIssuesFailed": "Не вдалося видалити елементи бібліотеки з проблемами",
|
||||||
|
|||||||
+32
-10
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"ButtonAdd": "添加",
|
"ButtonAdd": "添加",
|
||||||
|
"ButtonAddApiKey": "添加 API 密钥",
|
||||||
"ButtonAddChapters": "添加章节",
|
"ButtonAddChapters": "添加章节",
|
||||||
"ButtonAddDevice": "添加设备",
|
"ButtonAddDevice": "添加设备",
|
||||||
"ButtonAddLibrary": "添加库",
|
"ButtonAddLibrary": "添加库",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"ButtonChooseAFolder": "选择文件夹",
|
"ButtonChooseAFolder": "选择文件夹",
|
||||||
"ButtonChooseFiles": "选择文件",
|
"ButtonChooseFiles": "选择文件",
|
||||||
"ButtonClearFilter": "清除过滤器",
|
"ButtonClearFilter": "清除过滤器",
|
||||||
|
"ButtonClose": "关闭",
|
||||||
"ButtonCloseFeed": "关闭源",
|
"ButtonCloseFeed": "关闭源",
|
||||||
"ButtonCloseSession": "关闭活动会话",
|
"ButtonCloseSession": "关闭活动会话",
|
||||||
"ButtonCollections": "收藏",
|
"ButtonCollections": "收藏",
|
||||||
@@ -119,6 +121,7 @@
|
|||||||
"HeaderAccount": "帐户",
|
"HeaderAccount": "帐户",
|
||||||
"HeaderAddCustomMetadataProvider": "添加自定义元数据提供商",
|
"HeaderAddCustomMetadataProvider": "添加自定义元数据提供商",
|
||||||
"HeaderAdvanced": "高级",
|
"HeaderAdvanced": "高级",
|
||||||
|
"HeaderApiKeys": "API 密钥",
|
||||||
"HeaderAppriseNotificationSettings": "测试通知设置",
|
"HeaderAppriseNotificationSettings": "测试通知设置",
|
||||||
"HeaderAudioTracks": "音轨",
|
"HeaderAudioTracks": "音轨",
|
||||||
"HeaderAudiobookTools": "有声读物文件管理工具",
|
"HeaderAudiobookTools": "有声读物文件管理工具",
|
||||||
@@ -162,12 +165,13 @@
|
|||||||
"HeaderMetadataOrderOfPrecedence": "元数据优先级",
|
"HeaderMetadataOrderOfPrecedence": "元数据优先级",
|
||||||
"HeaderMetadataToEmbed": "嵌入元数据",
|
"HeaderMetadataToEmbed": "嵌入元数据",
|
||||||
"HeaderNewAccount": "新建帐户",
|
"HeaderNewAccount": "新建帐户",
|
||||||
|
"HeaderNewApiKey": "新建 API 密钥",
|
||||||
"HeaderNewLibrary": "新建媒体库",
|
"HeaderNewLibrary": "新建媒体库",
|
||||||
"HeaderNotificationCreate": "创建通知",
|
"HeaderNotificationCreate": "创建通知",
|
||||||
"HeaderNotificationUpdate": "更新通知",
|
"HeaderNotificationUpdate": "更新通知",
|
||||||
"HeaderNotifications": "通知",
|
"HeaderNotifications": "通知",
|
||||||
"HeaderOpenIDConnectAuthentication": "OpenID 连接身份验证",
|
"HeaderOpenIDConnectAuthentication": "OpenID 连接身份验证",
|
||||||
"HeaderOpenListeningSessions": "打开收听会话",
|
"HeaderOpenListeningSessions": "活动中会话",
|
||||||
"HeaderOpenRSSFeed": "打开 RSS 源",
|
"HeaderOpenRSSFeed": "打开 RSS 源",
|
||||||
"HeaderOtherFiles": "其他文件",
|
"HeaderOtherFiles": "其他文件",
|
||||||
"HeaderPasswordAuthentication": "密码认证",
|
"HeaderPasswordAuthentication": "密码认证",
|
||||||
@@ -206,6 +210,7 @@
|
|||||||
"HeaderTableOfContents": "目录",
|
"HeaderTableOfContents": "目录",
|
||||||
"HeaderTools": "工具",
|
"HeaderTools": "工具",
|
||||||
"HeaderUpdateAccount": "更新帐户",
|
"HeaderUpdateAccount": "更新帐户",
|
||||||
|
"HeaderUpdateApiKey": "更新 API 密钥",
|
||||||
"HeaderUpdateAuthor": "更新作者",
|
"HeaderUpdateAuthor": "更新作者",
|
||||||
"HeaderUpdateDetails": "更新详情",
|
"HeaderUpdateDetails": "更新详情",
|
||||||
"HeaderUpdateLibrary": "更新媒体库",
|
"HeaderUpdateLibrary": "更新媒体库",
|
||||||
@@ -235,6 +240,10 @@
|
|||||||
"LabelAllUsersExcludingGuests": "除访客外的所有用户",
|
"LabelAllUsersExcludingGuests": "除访客外的所有用户",
|
||||||
"LabelAllUsersIncludingGuests": "包括访客的所有用户",
|
"LabelAllUsersIncludingGuests": "包括访客的所有用户",
|
||||||
"LabelAlreadyInYourLibrary": "已存在你的库中",
|
"LabelAlreadyInYourLibrary": "已存在你的库中",
|
||||||
|
"LabelApiKeyCreated": "API 密钥 \"{0}\" 创建成功.",
|
||||||
|
"LabelApiKeyCreatedDescription": "请确保现在就复制 API 密钥, 之后将无法再次查看.",
|
||||||
|
"LabelApiKeyUser": "代用户操作",
|
||||||
|
"LabelApiKeyUserDescription": "此 API 密钥将具有与其代理的用户相同的权限. 在日志中, 其请求将被视为由该用户直接发出.",
|
||||||
"LabelApiToken": "API 令牌",
|
"LabelApiToken": "API 令牌",
|
||||||
"LabelAppend": "附加",
|
"LabelAppend": "附加",
|
||||||
"LabelAudioBitrate": "音频比特率 (例如: 128k)",
|
"LabelAudioBitrate": "音频比特率 (例如: 128k)",
|
||||||
@@ -320,7 +329,7 @@
|
|||||||
"LabelEmailSettingsRejectUnauthorized": "拒绝未经授权的证书",
|
"LabelEmailSettingsRejectUnauthorized": "拒绝未经授权的证书",
|
||||||
"LabelEmailSettingsRejectUnauthorizedHelp": "禁用SSL证书验证可能会使你的连接面临安全风险, 例如中间人攻击. 只有当你了解其中的含义并信任所连接的邮件服务器时, 才能禁用此选项.",
|
"LabelEmailSettingsRejectUnauthorizedHelp": "禁用SSL证书验证可能会使你的连接面临安全风险, 例如中间人攻击. 只有当你了解其中的含义并信任所连接的邮件服务器时, 才能禁用此选项.",
|
||||||
"LabelEmailSettingsSecure": "安全",
|
"LabelEmailSettingsSecure": "安全",
|
||||||
"LabelEmailSettingsSecureHelp": "开启此选项时,将始终通过TLS连接服务器。关闭此选项时,仅在服务器支持STARTTLS扩展时使用TLS。在大多数情况下,如果连接到端口465,请将此项设为开启。如果连接到端口587或25,请将此设置保持为关闭。(来自nodemailer.com/smtp/#authentication)",
|
"LabelEmailSettingsSecureHelp": "开启此选项时, 将始终通过TLS连接服务器. 关闭此选项时, 仅在服务器支持STARTTLS扩展时使用TLS. 在大多数情况下, 如果连接到端口465, 请将此项设为开启. 如果连接到端口587或25, 请将此设置保持为关闭. (来自nodemailer.com/smtp/#authentication)",
|
||||||
"LabelEmailSettingsTestAddress": "测试地址",
|
"LabelEmailSettingsTestAddress": "测试地址",
|
||||||
"LabelEmbeddedCover": "嵌入封面",
|
"LabelEmbeddedCover": "嵌入封面",
|
||||||
"LabelEnable": "启用",
|
"LabelEnable": "启用",
|
||||||
@@ -346,8 +355,12 @@
|
|||||||
"LabelExample": "示例",
|
"LabelExample": "示例",
|
||||||
"LabelExpandSeries": "展开系列",
|
"LabelExpandSeries": "展开系列",
|
||||||
"LabelExpandSubSeries": "展开子系列",
|
"LabelExpandSubSeries": "展开子系列",
|
||||||
|
"LabelExpired": "已过期",
|
||||||
|
"LabelExpiresAt": "过期时间",
|
||||||
|
"LabelExpiresInSeconds": "有效期 (秒)",
|
||||||
|
"LabelExpiresNever": "从不",
|
||||||
"LabelExplicit": "含成人内容",
|
"LabelExplicit": "含成人内容",
|
||||||
"LabelExplicitChecked": "成人内容(已核实)",
|
"LabelExplicitChecked": "成人内容 (已核实)",
|
||||||
"LabelExplicitUnchecked": "无成人内容 (未核实)",
|
"LabelExplicitUnchecked": "无成人内容 (未核实)",
|
||||||
"LabelExportOPML": "导出 OPML",
|
"LabelExportOPML": "导出 OPML",
|
||||||
"LabelFeedURL": "源 URL",
|
"LabelFeedURL": "源 URL",
|
||||||
@@ -425,6 +438,7 @@
|
|||||||
"LabelLogLevelWarn": "警告",
|
"LabelLogLevelWarn": "警告",
|
||||||
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
"LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集",
|
||||||
"LabelLowestPriority": "最低优先级",
|
"LabelLowestPriority": "最低优先级",
|
||||||
|
"LabelMatchConfidence": "置信度",
|
||||||
"LabelMatchExistingUsersBy": "匹配现有用户",
|
"LabelMatchExistingUsersBy": "匹配现有用户",
|
||||||
"LabelMatchExistingUsersByDescription": "用于连接现有用户. 连接后, 用户将通过 SSO 提供商提供的唯一 id 进行匹配",
|
"LabelMatchExistingUsersByDescription": "用于连接现有用户. 连接后, 用户将通过 SSO 提供商提供的唯一 id 进行匹配",
|
||||||
"LabelMaxEpisodesToDownload": "可下载的最大集数. 输入 0 表示无限制.",
|
"LabelMaxEpisodesToDownload": "可下载的最大集数. 输入 0 表示无限制.",
|
||||||
@@ -455,6 +469,7 @@
|
|||||||
"LabelNewestEpisodes": "最新剧集",
|
"LabelNewestEpisodes": "最新剧集",
|
||||||
"LabelNextBackupDate": "下次备份日期",
|
"LabelNextBackupDate": "下次备份日期",
|
||||||
"LabelNextScheduledRun": "下次任务运行",
|
"LabelNextScheduledRun": "下次任务运行",
|
||||||
|
"LabelNoApiKeys": "无 API 密钥",
|
||||||
"LabelNoCustomMetadataProviders": "没有自定义元数据提供商",
|
"LabelNoCustomMetadataProviders": "没有自定义元数据提供商",
|
||||||
"LabelNoEpisodesSelected": "未选择任何剧集",
|
"LabelNoEpisodesSelected": "未选择任何剧集",
|
||||||
"LabelNotFinished": "未听完",
|
"LabelNotFinished": "未听完",
|
||||||
@@ -544,6 +559,7 @@
|
|||||||
"LabelSelectAll": "全选",
|
"LabelSelectAll": "全选",
|
||||||
"LabelSelectAllEpisodes": "选择所有剧集",
|
"LabelSelectAllEpisodes": "选择所有剧集",
|
||||||
"LabelSelectEpisodesShowing": "选择正在播放的 {0} 剧集",
|
"LabelSelectEpisodesShowing": "选择正在播放的 {0} 剧集",
|
||||||
|
"LabelSelectUser": "选择用户",
|
||||||
"LabelSelectUsers": "选择用户",
|
"LabelSelectUsers": "选择用户",
|
||||||
"LabelSendEbookToDevice": "发送电子书到...",
|
"LabelSendEbookToDevice": "发送电子书到...",
|
||||||
"LabelSequence": "序列",
|
"LabelSequence": "序列",
|
||||||
@@ -610,12 +626,12 @@
|
|||||||
"LabelStart": "开始",
|
"LabelStart": "开始",
|
||||||
"LabelStartTime": "开始时间",
|
"LabelStartTime": "开始时间",
|
||||||
"LabelStarted": "开始于",
|
"LabelStarted": "开始于",
|
||||||
"LabelStartedAt": "从这开始",
|
"LabelStartedAt": "收听始于",
|
||||||
"LabelStatsAudioTracks": "音轨",
|
"LabelStatsAudioTracks": "音轨",
|
||||||
"LabelStatsAuthors": "作者",
|
"LabelStatsAuthors": "作者",
|
||||||
"LabelStatsBestDay": "单日最高",
|
"LabelStatsBestDay": "单日最高",
|
||||||
"LabelStatsDailyAverage": "每日平均值",
|
"LabelStatsDailyAverage": "每日平均值",
|
||||||
"LabelStatsDays": "连续收听",
|
"LabelStatsDays": "连续",
|
||||||
"LabelStatsDaysListened": "收听天数",
|
"LabelStatsDaysListened": "收听天数",
|
||||||
"LabelStatsHours": "小时",
|
"LabelStatsHours": "小时",
|
||||||
"LabelStatsInARow": "天",
|
"LabelStatsInARow": "天",
|
||||||
@@ -708,13 +724,15 @@
|
|||||||
"MessageAddToPlayerQueue": "添加到播放队列",
|
"MessageAddToPlayerQueue": "添加到播放队列",
|
||||||
"MessageAppriseDescription": "要使用此功能,你需要运行一个 <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> 实例或一个可以处理这些相同请求的 API. <br />Apprise API Url 应该是发送通知的完整 URL 路径, 例如: 如果你的 API 实例运行在 <code>http://192.168.1.1:8337</code>, 那么你可以输入 <code>http://192.168.1.1:8337/notify</code>.",
|
"MessageAppriseDescription": "要使用此功能,你需要运行一个 <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> 实例或一个可以处理这些相同请求的 API. <br />Apprise API Url 应该是发送通知的完整 URL 路径, 例如: 如果你的 API 实例运行在 <code>http://192.168.1.1:8337</code>, 那么你可以输入 <code>http://192.168.1.1:8337/notify</code>.",
|
||||||
"MessageAsinCheck": "确保你使用的 ASIN 来自正确的 Audible 地区, 而不是亚马逊.",
|
"MessageAsinCheck": "确保你使用的 ASIN 来自正确的 Audible 地区, 而不是亚马逊.",
|
||||||
|
"MessageAuthenticationLegacyTokenWarning": "旧版 API 令牌将来会被移除. 请改用 <a href=\"/config/api-keys\">API 密钥</a>.",
|
||||||
"MessageAuthenticationOIDCChangesRestart": "保存后重新启动服务器以应用 OIDC 更改.",
|
"MessageAuthenticationOIDCChangesRestart": "保存后重新启动服务器以应用 OIDC 更改.",
|
||||||
|
"MessageAuthenticationSecurityMessage": "身份验证安全性已增强, 所有用户都需要重新登录.",
|
||||||
"MessageBackupsDescription": "备份包括用户, 用户进度, 媒体库项目详细信息, 服务器设置和图像, 存储在 <code>/metadata/items</code> & <code>/metadata/authors</code>. 备份不包括存储在你的媒体库文件夹中的任何文件.",
|
"MessageBackupsDescription": "备份包括用户, 用户进度, 媒体库项目详细信息, 服务器设置和图像, 存储在 <code>/metadata/items</code> & <code>/metadata/authors</code>. 备份不包括存储在你的媒体库文件夹中的任何文件.",
|
||||||
"MessageBackupsLocationEditNote": "注意: 更新备份位置不会移动或修改现有备份",
|
"MessageBackupsLocationEditNote": "注意: 更新备份位置不会移动或修改现有备份",
|
||||||
"MessageBackupsLocationNoEditNote": "注意: 备份位置是通过环境变量设置的, 不能在此处更改.",
|
"MessageBackupsLocationNoEditNote": "注意: 备份位置是通过环境变量设置的, 不能在此处更改.",
|
||||||
"MessageBackupsLocationPathEmpty": "备份位置路径不能为空",
|
"MessageBackupsLocationPathEmpty": "备份位置路径不能为空",
|
||||||
"MessageBatchEditPopulateMapDetailsAllHelp": "使用所有项目的数据填充已启用的字段. 具有多个值的字段将被合并",
|
"MessageBatchEditPopulateMapDetailsAllHelp": "使用所有项目的数据填充已启用的字段. 具有多个值的字段将被合并",
|
||||||
"MessageBatchEditPopulateMapDetailsItemHelp": "提取此项目的信息,填入上方所有勾选的编辑框中",
|
"MessageBatchEditPopulateMapDetailsItemHelp": "提取此项目的信息, 填入上方所有勾选的编辑框中",
|
||||||
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
|
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
|
||||||
"MessageBookshelfNoCollections": "你尚未进行任何收藏",
|
"MessageBookshelfNoCollections": "你尚未进行任何收藏",
|
||||||
"MessageBookshelfNoCollectionsHelp": "收藏是公开的. 所有有权访问图书馆的用户都可以看到它们.",
|
"MessageBookshelfNoCollectionsHelp": "收藏是公开的. 所有有权访问图书馆的用户都可以看到它们.",
|
||||||
@@ -730,6 +748,7 @@
|
|||||||
"MessageChaptersNotFound": "未找到章节",
|
"MessageChaptersNotFound": "未找到章节",
|
||||||
"MessageCheckingCron": "检查计划任务...",
|
"MessageCheckingCron": "检查计划任务...",
|
||||||
"MessageConfirmCloseFeed": "你确定要关闭此订阅源吗?",
|
"MessageConfirmCloseFeed": "你确定要关闭此订阅源吗?",
|
||||||
|
"MessageConfirmDeleteApiKey": "你确定要删除 API 密钥 \"{0}\" 吗?",
|
||||||
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
"MessageConfirmDeleteBackup": "你确定要删除备份 {0}?",
|
||||||
"MessageConfirmDeleteDevice": "你确定要删除电子阅读器设备 \"{0}\" 吗?",
|
"MessageConfirmDeleteDevice": "你确定要删除电子阅读器设备 \"{0}\" 吗?",
|
||||||
"MessageConfirmDeleteFile": "这将从文件系统中删除该文件. 你确定吗?",
|
"MessageConfirmDeleteFile": "这将从文件系统中删除该文件. 你确定吗?",
|
||||||
@@ -757,7 +776,7 @@
|
|||||||
"MessageConfirmRemoveAuthor": "你确定要删除作者 \"{0}\"?",
|
"MessageConfirmRemoveAuthor": "你确定要删除作者 \"{0}\"?",
|
||||||
"MessageConfirmRemoveCollection": "你确定要移除收藏 \"{0}\"?",
|
"MessageConfirmRemoveCollection": "你确定要移除收藏 \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisode": "你确定要移除剧集 \"{0}\"?",
|
"MessageConfirmRemoveEpisode": "你确定要移除剧集 \"{0}\"?",
|
||||||
"MessageConfirmRemoveEpisodeNote": "注意:此操作不会删除音频文件,除非勾选“完全删除文件”选项",
|
"MessageConfirmRemoveEpisodeNote": "注意: 此操作不会删除音频文件, 除非勾选 \"完全删除文件\" 选项",
|
||||||
"MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
|
"MessageConfirmRemoveEpisodes": "你确定要移除 {0} 剧集?",
|
||||||
"MessageConfirmRemoveListeningSessions": "你确定要移除 {0} 收听会话吗?",
|
"MessageConfirmRemoveListeningSessions": "你确定要移除 {0} 收听会话吗?",
|
||||||
"MessageConfirmRemoveMetadataFiles": "你确实要删除库项目文件夹中的所有 metadata.{0} 文件吗?",
|
"MessageConfirmRemoveMetadataFiles": "你确实要删除库项目文件夹中的所有 metadata.{0} 文件吗?",
|
||||||
@@ -788,7 +807,7 @@
|
|||||||
"MessageInvalidAsin": "无效的 ASIN",
|
"MessageInvalidAsin": "无效的 ASIN",
|
||||||
"MessageItemsSelected": "已选定 {0} 个项目",
|
"MessageItemsSelected": "已选定 {0} 个项目",
|
||||||
"MessageItemsUpdated": "已更新 {0} 个项目",
|
"MessageItemsUpdated": "已更新 {0} 个项目",
|
||||||
"MessageJoinUsOn": "加入我们",
|
"MessageJoinUsOn": "加入我们的",
|
||||||
"MessageLoading": "正在加载...",
|
"MessageLoading": "正在加载...",
|
||||||
"MessageLoadingFolders": "加载文件夹...",
|
"MessageLoadingFolders": "加载文件夹...",
|
||||||
"MessageLogsDescription": "日志以 JSON 文件形式存储在 <code>/metadata/logs</code> 目录中. 崩溃日志存储在 <code>/metadata/logs/crash_logs.txt</code> 目录中.",
|
"MessageLogsDescription": "日志以 JSON 文件形式存储在 <code>/metadata/logs</code> 目录中. 崩溃日志存储在 <code>/metadata/logs/crash_logs.txt</code> 目录中.",
|
||||||
@@ -849,12 +868,12 @@
|
|||||||
"MessageRemoveEpisodes": "移除 {0} 剧集",
|
"MessageRemoveEpisodes": "移除 {0} 剧集",
|
||||||
"MessageRemoveFromPlayerQueue": "从播放队列中移除",
|
"MessageRemoveFromPlayerQueue": "从播放队列中移除",
|
||||||
"MessageRemoveUserWarning": "是否确实要永久删除用户 \"{0}\"?",
|
"MessageRemoveUserWarning": "是否确实要永久删除用户 \"{0}\"?",
|
||||||
"MessageReportBugsAndContribute": "报告错误、请求功能和贡献在",
|
"MessageReportBugsAndContribute": "反馈问题, 建议功能或参与贡献, 请访问",
|
||||||
"MessageResetChaptersConfirm": "你确定要重置章节并撤消你所做的更改吗?",
|
"MessageResetChaptersConfirm": "你确定要重置章节并撤消你所做的更改吗?",
|
||||||
"MessageRestoreBackupConfirm": "你确定要恢复创建的这个备份",
|
"MessageRestoreBackupConfirm": "你确定要恢复创建的这个备份",
|
||||||
"MessageRestoreBackupWarning": "恢复备份将覆盖位于 /config 的整个数据库并覆盖 /metadata/items & /metadata/authors 中的图像.<br /><br />备份不会修改媒体库文件夹中的任何文件. 如果你已启用服务器设置将封面和元数据存储在库文件夹中,则不会备份或覆盖这些内容.<br /><br />将自动刷新使用服务器的所有客户端.",
|
"MessageRestoreBackupWarning": "恢复备份将覆盖位于 /config 的整个数据库并覆盖 /metadata/items & /metadata/authors 中的图像.<br /><br />备份不会修改媒体库文件夹中的任何文件. 如果你已启用服务器设置将封面和元数据存储在库文件夹中,则不会备份或覆盖这些内容.<br /><br />将自动刷新使用服务器的所有客户端.",
|
||||||
"MessageScheduleLibraryScanNote": "对于大多数用户, 建议禁用此功能并保持文件夹监视程序设置启用. 文件夹监视程序将自动检测库文件夹中的更改. 文件夹监视程序不适用于每个文件系统 (如 NFS), 因此可以使用计划库扫描.",
|
"MessageScheduleLibraryScanNote": "对于大多数用户, 建议禁用此功能并保持文件夹监视程序设置启用. 文件夹监视程序将自动检测库文件夹中的更改. 文件夹监视程序不适用于每个文件系统 (如 NFS), 因此可以使用计划库扫描.",
|
||||||
"MessageScheduleRunEveryWeekdayAtTime": "每隔 {0} 在 {1} 运行一次",
|
"MessageScheduleRunEveryWeekdayAtTime": "每 {0} 的 {1} 执行",
|
||||||
"MessageSearchResultsFor": "搜索结果",
|
"MessageSearchResultsFor": "搜索结果",
|
||||||
"MessageSelected": "{0} 已选择",
|
"MessageSelected": "{0} 已选择",
|
||||||
"MessageSeriesSequenceCannotContainSpaces": "系列序列不能包含空格",
|
"MessageSeriesSequenceCannotContainSpaces": "系列序列不能包含空格",
|
||||||
@@ -1001,6 +1020,8 @@
|
|||||||
"ToastEpisodeDownloadQueueClearSuccess": "剧集下载队列已清空",
|
"ToastEpisodeDownloadQueueClearSuccess": "剧集下载队列已清空",
|
||||||
"ToastEpisodeUpdateSuccess": "已更新 {0} 剧集",
|
"ToastEpisodeUpdateSuccess": "已更新 {0} 剧集",
|
||||||
"ToastErrorCannotShare": "无法在此设备上本地共享",
|
"ToastErrorCannotShare": "无法在此设备上本地共享",
|
||||||
|
"ToastFailedToCreate": "创建失败",
|
||||||
|
"ToastFailedToDelete": "删除失败",
|
||||||
"ToastFailedToLoadData": "加载数据失败",
|
"ToastFailedToLoadData": "加载数据失败",
|
||||||
"ToastFailedToMatch": "匹配失败",
|
"ToastFailedToMatch": "匹配失败",
|
||||||
"ToastFailedToShare": "分享失败",
|
"ToastFailedToShare": "分享失败",
|
||||||
@@ -1032,6 +1053,7 @@
|
|||||||
"ToastMustHaveAtLeastOnePath": "必须至少有一个路径",
|
"ToastMustHaveAtLeastOnePath": "必须至少有一个路径",
|
||||||
"ToastNameEmailRequired": "姓名和电子邮件为必填项",
|
"ToastNameEmailRequired": "姓名和电子邮件为必填项",
|
||||||
"ToastNameRequired": "姓名为必填项",
|
"ToastNameRequired": "姓名为必填项",
|
||||||
|
"ToastNewApiKeyUserError": "必须选择一个用户",
|
||||||
"ToastNewEpisodesFound": "找到 {0} 个新剧集",
|
"ToastNewEpisodesFound": "找到 {0} 个新剧集",
|
||||||
"ToastNewUserCreatedFailed": "无法创建帐户: \"{0}\"",
|
"ToastNewUserCreatedFailed": "无法创建帐户: \"{0}\"",
|
||||||
"ToastNewUserCreatedSuccess": "已创建新帐户",
|
"ToastNewUserCreatedSuccess": "已创建新帐户",
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.26.0",
|
"version": "2.26.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.26.0",
|
"version": "2.26.3",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "audiobookshelf",
|
"name": "audiobookshelf",
|
||||||
"version": "2.26.0",
|
"version": "2.26.3",
|
||||||
"buildNumber": 1,
|
"buildNumber": 1,
|
||||||
"description": "Self-hosted audiobook and podcast server",
|
"description": "Self-hosted audiobook and podcast server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|||||||
+1
-1
@@ -240,7 +240,7 @@ 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 (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]+)?/)) {
|
||||||
const allowedOrigins = ['capacitor://localhost', 'http://localhost']
|
const allowedOrigins = ['capacitor://localhost', 'http://localhost']
|
||||||
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'))
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ 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, this)
|
let user = await Database.userModel.findOrCreateUserFromOpenIdUserInfo(userinfo)
|
||||||
|
|
||||||
if (!user?.isActive) {
|
if (!user?.isActive) {
|
||||||
throw new Error('User not active or not found')
|
throw new Error('User not active or not found')
|
||||||
|
|||||||
@@ -81,6 +81,18 @@ class TokenManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a JWT token for a given user
|
||||||
|
* TODO: Old method with no expiration
|
||||||
|
* @deprecated
|
||||||
|
*
|
||||||
|
* @param {{ id:string, username:string }} user
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static generateAccessToken(user) {
|
||||||
|
return jwt.sign({ userId: user.id, username: user.username }, TokenManager.TokenSecret)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to generate a jwt token for a given user
|
* Function to generate a jwt token for a given user
|
||||||
* TODO: Old method with no expiration
|
* TODO: Old method with no expiration
|
||||||
@@ -90,7 +102,7 @@ class TokenManager {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
generateAccessToken(user) {
|
generateAccessToken(user) {
|
||||||
return jwt.sign({ userId: user.id, username: user.username }, TokenManager.TokenSecret)
|
return TokenManager.generateAccessToken(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ class MeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { password, newPassword } = req.body
|
const { password, newPassword } = req.body
|
||||||
if (!password || !newPassword || typeof password !== 'string' || typeof newPassword !== 'string') {
|
if ((typeof password !== 'string' && password !== null) || (typeof newPassword !== 'string' && newPassword !== null)) {
|
||||||
return res.status(400).send('Missing or invalid password or new password')
|
return res.status(400).send('Missing or invalid password or new password')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,26 +57,24 @@ class SessionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let where = null
|
let where = null
|
||||||
const include = [
|
|
||||||
{
|
|
||||||
model: Database.models.device
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
where = {
|
where = {
|
||||||
userId
|
userId
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
include.push({
|
|
||||||
model: Database.userModel,
|
|
||||||
attributes: ['id', 'username']
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rows, count } = await Database.playbackSessionModel.findAndCountAll({
|
const { rows, count } = await Database.playbackSessionModel.findAndCountAll({
|
||||||
where,
|
where,
|
||||||
include,
|
include: [
|
||||||
|
{
|
||||||
|
model: Database.deviceModel
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: Database.userModel,
|
||||||
|
attributes: ['id', 'username']
|
||||||
|
}
|
||||||
|
],
|
||||||
order: [[orderKey, orderDesc]],
|
order: [[orderKey, orderDesc]],
|
||||||
limit: itemsPerPage,
|
limit: itemsPerPage,
|
||||||
offset: itemsPerPage * page
|
offset: itemsPerPage * page
|
||||||
@@ -290,7 +288,12 @@ class SessionController {
|
|||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
const audioTrack = playbackSession.audioTracks.find((t) => t.index === audioTrackIndex)
|
let audioTrack = playbackSession.audioTracks.find((t) => toNumber(t.index, 1) === audioTrackIndex)
|
||||||
|
|
||||||
|
// Support clients passing 0 or 1 for podcast episode audio track index (handles old episodes pre-v2.21.0 having null index)
|
||||||
|
if (!audioTrack && playbackSession.mediaType === 'podcast' && audioTrackIndex === 0) {
|
||||||
|
audioTrack = playbackSession.audioTracks[0]
|
||||||
|
}
|
||||||
if (!audioTrack) {
|
if (!audioTrack) {
|
||||||
Logger.error(`[SessionController] Unable to find audio track with index=${audioTrackIndex}`)
|
Logger.error(`[SessionController] Unable to find audio track with index=${audioTrackIndex}`)
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
|
|||||||
@@ -439,7 +439,16 @@ class UserController {
|
|||||||
const page = toNumber(req.query.page, 0)
|
const page = toNumber(req.query.page, 0)
|
||||||
|
|
||||||
const start = page * itemsPerPage
|
const start = page * itemsPerPage
|
||||||
const sessions = listeningSessions.slice(start, start + itemsPerPage)
|
// Map user to sessions to match the format of the sessions endpoint
|
||||||
|
const sessions = listeningSessions.slice(start, start + itemsPerPage).map((session) => {
|
||||||
|
return {
|
||||||
|
...session,
|
||||||
|
user: {
|
||||||
|
id: req.reqUser.id,
|
||||||
|
username: req.reqUser.username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
total: listeningSessions.length,
|
total: listeningSessions.length,
|
||||||
|
|||||||
+139
-10
@@ -7,7 +7,7 @@ const FantLab = require('../providers/FantLab')
|
|||||||
const AudiobookCovers = require('../providers/AudiobookCovers')
|
const AudiobookCovers = require('../providers/AudiobookCovers')
|
||||||
const CustomProviderAdapter = require('../providers/CustomProviderAdapter')
|
const CustomProviderAdapter = require('../providers/CustomProviderAdapter')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const { levenshteinDistance, escapeRegExp } = require('../utils/index')
|
const { levenshteinDistance, levenshteinSimilarity, escapeRegExp, isValidASIN } = require('../utils/index')
|
||||||
const htmlSanitizer = require('../utils/htmlSanitizer')
|
const htmlSanitizer = require('../utils/htmlSanitizer')
|
||||||
|
|
||||||
class BookFinder {
|
class BookFinder {
|
||||||
@@ -385,7 +385,11 @@ class BookFinder {
|
|||||||
|
|
||||||
if (!title) return books
|
if (!title) return books
|
||||||
|
|
||||||
books = await this.runSearch(title, author, provider, asin, maxTitleDistance, maxAuthorDistance)
|
const isTitleAsin = isValidASIN(title.toUpperCase())
|
||||||
|
|
||||||
|
let actualTitleQuery = title
|
||||||
|
let actualAuthorQuery = author
|
||||||
|
books = await this.runSearch(actualTitleQuery, actualAuthorQuery, provider, asin, maxTitleDistance, maxAuthorDistance)
|
||||||
|
|
||||||
if (!books.length && maxFuzzySearches > 0) {
|
if (!books.length && maxFuzzySearches > 0) {
|
||||||
// Normalize title and author
|
// Normalize title and author
|
||||||
@@ -408,19 +412,26 @@ class BookFinder {
|
|||||||
for (const titlePart of titleParts) titleCandidates.add(titlePart)
|
for (const titlePart of titleParts) titleCandidates.add(titlePart)
|
||||||
titleCandidates = titleCandidates.getCandidates()
|
titleCandidates = titleCandidates.getCandidates()
|
||||||
for (const titleCandidate of titleCandidates) {
|
for (const titleCandidate of titleCandidates) {
|
||||||
if (titleCandidate == title && authorCandidate == author) continue // We already tried this
|
if (titleCandidate == actualTitleQuery && authorCandidate == actualAuthorQuery) continue // We already tried this
|
||||||
if (++numFuzzySearches > maxFuzzySearches) break loop_author
|
if (++numFuzzySearches > maxFuzzySearches) break loop_author
|
||||||
books = await this.runSearch(titleCandidate, authorCandidate, provider, asin, maxTitleDistance, maxAuthorDistance)
|
actualTitleQuery = titleCandidate
|
||||||
|
actualAuthorQuery = authorCandidate
|
||||||
|
books = await this.runSearch(actualTitleQuery, actualAuthorQuery, provider, asin, maxTitleDistance, maxAuthorDistance)
|
||||||
if (books.length) break loop_author
|
if (books.length) break loop_author
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (books.length) {
|
if (books.length) {
|
||||||
const resultsHaveDuration = provider.startsWith('audible')
|
const isAudibleProvider = provider.startsWith('audible')
|
||||||
if (resultsHaveDuration && libraryItem?.media?.duration) {
|
const libraryItemDurationMinutes = libraryItem?.media?.duration ? libraryItem.media.duration / 60 : null
|
||||||
const libraryItemDurationMinutes = libraryItem.media.duration / 60
|
|
||||||
// If provider results have duration, sort by ascendinge duration difference from libraryItem
|
books.forEach((book) => {
|
||||||
|
if (typeof book !== 'object' || !isAudibleProvider) return
|
||||||
|
book.matchConfidence = this.calculateMatchConfidence(book, libraryItemDurationMinutes, actualTitleQuery, actualAuthorQuery, isTitleAsin)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isAudibleProvider && libraryItemDurationMinutes) {
|
||||||
books.sort((a, b) => {
|
books.sort((a, b) => {
|
||||||
const aDuration = a.duration || Number.POSITIVE_INFINITY
|
const aDuration = a.duration || Number.POSITIVE_INFINITY
|
||||||
const bDuration = b.duration || Number.POSITIVE_INFINITY
|
const bDuration = b.duration || Number.POSITIVE_INFINITY
|
||||||
@@ -433,6 +444,120 @@ class BookFinder {
|
|||||||
return books
|
return books
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate match confidence score for a book
|
||||||
|
* @param {Object} book - The book object to calculate confidence for
|
||||||
|
* @param {number|null} libraryItemDurationMinutes - Duration of library item in minutes
|
||||||
|
* @param {string} actualTitleQuery - Actual title query
|
||||||
|
* @param {string} actualAuthorQuery - Actual author query
|
||||||
|
* @param {boolean} isTitleAsin - Whether the title is an ASIN
|
||||||
|
* @returns {number|null} - Match confidence score or null if not applicable
|
||||||
|
*/
|
||||||
|
calculateMatchConfidence(book, libraryItemDurationMinutes, actualTitleQuery, actualAuthorQuery, isTitleAsin) {
|
||||||
|
// ASIN results are always a match
|
||||||
|
if (isTitleAsin) return 1.0
|
||||||
|
|
||||||
|
let durationScore
|
||||||
|
if (libraryItemDurationMinutes && typeof book.duration === 'number') {
|
||||||
|
const durationDiff = Math.abs(book.duration - libraryItemDurationMinutes)
|
||||||
|
// Duration scores:
|
||||||
|
// diff | score
|
||||||
|
// 0 | 1.0
|
||||||
|
// 1 | 1.0
|
||||||
|
// 2 | 0.9
|
||||||
|
// 3 | 0.8
|
||||||
|
// 4 | 0.7
|
||||||
|
// 5 | 0.6
|
||||||
|
// 6 | 0.48
|
||||||
|
// 7 | 0.36
|
||||||
|
// 8 | 0.24
|
||||||
|
// 9 | 0.12
|
||||||
|
// 10 | 0.0
|
||||||
|
if (durationDiff <= 1) {
|
||||||
|
// Covers durationDiff = 0 for score 1.0
|
||||||
|
durationScore = 1.0
|
||||||
|
} else if (durationDiff <= 5) {
|
||||||
|
// (1, 5] - Score from 1.0 down to 0.6
|
||||||
|
// Linearly interpolates between (1, 1.0) and (5, 0.6)
|
||||||
|
// Equation: y = 1.0 - 0.08 * x
|
||||||
|
durationScore = 1.1 - 0.1 * durationDiff
|
||||||
|
} else if (durationDiff <= 10) {
|
||||||
|
// (5, 10] - Score from 0.6 down to 0.0
|
||||||
|
// Linearly interpolates between (5, 0.6) and (10, 0.0)
|
||||||
|
// Equation: y = 1.2 - 0.12 * x
|
||||||
|
durationScore = 1.2 - 0.12 * durationDiff
|
||||||
|
} else {
|
||||||
|
// durationDiff > 10 - Score is 0.0
|
||||||
|
durationScore = 0.0
|
||||||
|
}
|
||||||
|
Logger.debug(`[BookFinder] Duration diff: ${durationDiff}, durationScore: ${durationScore}`)
|
||||||
|
} else {
|
||||||
|
// Default score if library item duration or book duration is not available
|
||||||
|
durationScore = 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateTitleScore = (titleQuery, book, keepSubtitle = false) => {
|
||||||
|
const cleanTitle = cleanTitleForCompares(book.title || '', keepSubtitle)
|
||||||
|
const cleanSubtitle = keepSubtitle && book.subtitle ? `: ${book.subtitle}` : ''
|
||||||
|
const normBookTitle = `${cleanTitle}${cleanSubtitle}`
|
||||||
|
const normTitleQuery = cleanTitleForCompares(titleQuery, keepSubtitle)
|
||||||
|
const titleSimilarity = levenshteinSimilarity(normTitleQuery, normBookTitle)
|
||||||
|
Logger.debug(`[BookFinder] keepSubtitle: ${keepSubtitle}, normBookTitle: ${normBookTitle}, normTitleQuery: ${normTitleQuery}, titleSimilarity: ${titleSimilarity}`)
|
||||||
|
return titleSimilarity
|
||||||
|
}
|
||||||
|
const titleQueryHasSubtitle = hasSubtitle(actualTitleQuery)
|
||||||
|
const titleScore = calculateTitleScore(actualTitleQuery, book, titleQueryHasSubtitle)
|
||||||
|
|
||||||
|
let authorScore
|
||||||
|
const normAuthorQuery = cleanAuthorForCompares(actualAuthorQuery)
|
||||||
|
const normBookAuthor = cleanAuthorForCompares(book.author || '')
|
||||||
|
if (!normAuthorQuery) {
|
||||||
|
// Original query had no author
|
||||||
|
authorScore = 1.0 // Neutral score
|
||||||
|
} else {
|
||||||
|
// Original query HAS an author (cleanedQueryAuthorForScore is not empty)
|
||||||
|
if (normBookAuthor) {
|
||||||
|
const bookAuthorParts = normBookAuthor.split(',').map((name) => name.trim().toLowerCase())
|
||||||
|
// Filter out empty parts that might result from ", ," or trailing/leading commas
|
||||||
|
const validBookAuthorParts = bookAuthorParts.filter((p) => p.length > 0)
|
||||||
|
|
||||||
|
if (validBookAuthorParts.length === 0) {
|
||||||
|
// Book author string was present but effectively empty (e.g. ",,")
|
||||||
|
// Since cleanedQueryAuthorForScore is non-empty here, this is a mismatch.
|
||||||
|
authorScore = 0.0
|
||||||
|
} else {
|
||||||
|
let maxPartScore = levenshteinSimilarity(normAuthorQuery, normBookAuthor)
|
||||||
|
Logger.debug(`[BookFinder] normAuthorQuery: ${normAuthorQuery}, normBookAuthor: ${normBookAuthor}, similarity: ${maxPartScore}`)
|
||||||
|
if (validBookAuthorParts.length > 1 || normBookAuthor.includes(',')) {
|
||||||
|
validBookAuthorParts.forEach((part) => {
|
||||||
|
// part is guaranteed to be non-empty here
|
||||||
|
// cleanedQueryAuthorForScore is also guaranteed non-empty here.
|
||||||
|
// levenshteinDistance lowercases by default, but part is already lowercased.
|
||||||
|
const similarity = levenshteinSimilarity(normAuthorQuery, part)
|
||||||
|
Logger.debug(`[BookFinder] normAuthorQuery: ${normAuthorQuery}, bookAuthorPart: ${part}, similarity: ${similarity}`)
|
||||||
|
const currentPartScore = similarity
|
||||||
|
maxPartScore = Math.max(maxPartScore, currentPartScore)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
authorScore = maxPartScore
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Book has NO author (or not a string, or empty string)
|
||||||
|
// Query has an author (cleanedQueryAuthorForScore is non-empty), book does not.
|
||||||
|
authorScore = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const W_DURATION = 0.7
|
||||||
|
const W_TITLE = 0.2
|
||||||
|
const W_AUTHOR = 0.1
|
||||||
|
|
||||||
|
Logger.debug(`[BookFinder] Duration score: ${durationScore}, Title score: ${titleScore}, Author score: ${authorScore}`)
|
||||||
|
const confidence = W_DURATION * durationScore + W_TITLE * titleScore + W_AUTHOR * authorScore
|
||||||
|
Logger.debug(`[BookFinder] Confidence: ${confidence}`)
|
||||||
|
return Math.max(0, Math.min(1, confidence))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for books
|
* Search for books
|
||||||
*
|
*
|
||||||
@@ -464,6 +589,7 @@ class BookFinder {
|
|||||||
} else {
|
} else {
|
||||||
books = await this.getGoogleBooksResults(title, author)
|
books = await this.getGoogleBooksResults(title, author)
|
||||||
}
|
}
|
||||||
|
|
||||||
books.forEach((book) => {
|
books.forEach((book) => {
|
||||||
if (book.description) {
|
if (book.description) {
|
||||||
book.description = htmlSanitizer.sanitize(book.description)
|
book.description = htmlSanitizer.sanitize(book.description)
|
||||||
@@ -505,6 +631,9 @@ class BookFinder {
|
|||||||
}
|
}
|
||||||
module.exports = new BookFinder()
|
module.exports = new BookFinder()
|
||||||
|
|
||||||
|
function hasSubtitle(title) {
|
||||||
|
return title.includes(':') || title.includes(' - ')
|
||||||
|
}
|
||||||
function stripSubtitle(title) {
|
function stripSubtitle(title) {
|
||||||
if (title.includes(':')) {
|
if (title.includes(':')) {
|
||||||
return title.split(':')[0].trim()
|
return title.split(':')[0].trim()
|
||||||
@@ -523,12 +652,12 @@ function replaceAccentedChars(str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanTitleForCompares(title) {
|
function cleanTitleForCompares(title, keepSubtitle = false) {
|
||||||
if (!title) return ''
|
if (!title) return ''
|
||||||
title = stripRedundantSpaces(title)
|
title = stripRedundantSpaces(title)
|
||||||
|
|
||||||
// Remove subtitle if there (i.e. "Cool Book: Coolest Ever" becomes "Cool Book")
|
// Remove subtitle if there (i.e. "Cool Book: Coolest Ever" becomes "Cool Book")
|
||||||
let stripped = stripSubtitle(title)
|
let stripped = keepSubtitle ? title : stripSubtitle(title)
|
||||||
|
|
||||||
// Remove text in paranthesis (i.e. "Ender's Game (Ender's Saga)" becomes "Ender's Game")
|
// Remove text in paranthesis (i.e. "Ender's Game (Ender's Saga)" becomes "Ender's Game")
|
||||||
let cleaned = stripped.replace(/ *\([^)]*\) */g, '')
|
let cleaned = stripped.replace(/ *\([^)]*\) */g, '')
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ class PodcastEpisode extends Model {
|
|||||||
const track = structuredClone(this.audioFile)
|
const track = structuredClone(this.audioFile)
|
||||||
track.startOffset = 0
|
track.startOffset = 0
|
||||||
track.title = this.audioFile.metadata.filename
|
track.title = this.audioFile.metadata.filename
|
||||||
|
track.index = 1 // Podcast episodes only have one track
|
||||||
track.contentUrl = `/api/items/${libraryItemId}/file/${track.ino}`
|
track.contentUrl = `/api/items/${libraryItemId}/file/${track.ino}`
|
||||||
return track
|
return track
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
const uuidv4 = require('uuid').v4
|
const uuidv4 = require('uuid').v4
|
||||||
const sequelize = require('sequelize')
|
const sequelize = require('sequelize')
|
||||||
|
const { LRUCache } = require('lru-cache')
|
||||||
|
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const { isNullOrNaN } = require('../utils')
|
const { isNullOrNaN } = require('../utils')
|
||||||
const { LRUCache } = require('lru-cache')
|
const TokenManager = require('../auth/TokenManager')
|
||||||
|
|
||||||
class UserCache {
|
class UserCache {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -213,10 +215,9 @@ class User extends Model {
|
|||||||
* or creates a new user if configured to do so.
|
* or creates a new user if configured to do so.
|
||||||
*
|
*
|
||||||
* @param {Object} userinfo
|
* @param {Object} userinfo
|
||||||
* @param {import('../Auth')} auth
|
|
||||||
* @returns {Promise<User>}
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
static async findOrCreateUserFromOpenIdUserInfo(userinfo, auth) {
|
static async findOrCreateUserFromOpenIdUserInfo(userinfo) {
|
||||||
let user = await this.getUserByOpenIDSub(userinfo.sub)
|
let user = await this.getUserByOpenIDSub(userinfo.sub)
|
||||||
|
|
||||||
// Matched by sub
|
// Matched by sub
|
||||||
@@ -290,7 +291,7 @@ class User extends Model {
|
|||||||
// If no existing user was matched, auto-register if configured
|
// If no existing user was matched, auto-register if configured
|
||||||
if (global.ServerSettings.authOpenIDAutoRegister) {
|
if (global.ServerSettings.authOpenIDAutoRegister) {
|
||||||
Logger.info(`[User] openid: Auto-registering user with sub "${userinfo.sub}"`, userinfo)
|
Logger.info(`[User] openid: Auto-registering user with sub "${userinfo.sub}"`, userinfo)
|
||||||
user = await this.createUserFromOpenIdUserInfo(userinfo, auth)
|
user = await this.createUserFromOpenIdUserInfo(userinfo)
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,16 +302,15 @@ class User extends Model {
|
|||||||
/**
|
/**
|
||||||
* Create user from openid userinfo
|
* Create user from openid userinfo
|
||||||
* @param {Object} userinfo
|
* @param {Object} userinfo
|
||||||
* @param {import('../Auth')} auth
|
|
||||||
* @returns {Promise<User>}
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
static async createUserFromOpenIdUserInfo(userinfo, auth) {
|
static async createUserFromOpenIdUserInfo(userinfo) {
|
||||||
const userId = uuidv4()
|
const userId = uuidv4()
|
||||||
// TODO: Ensure username is unique?
|
// TODO: Ensure username is unique?
|
||||||
const username = userinfo.preferred_username || userinfo.name || userinfo.sub
|
const username = userinfo.preferred_username || userinfo.name || userinfo.sub
|
||||||
const email = userinfo.email && userinfo.email_verified ? userinfo.email : null
|
const email = userinfo.email && userinfo.email_verified ? userinfo.email : null
|
||||||
|
|
||||||
const token = auth.generateAccessToken({ id: userId, username })
|
const token = TokenManager.generateAccessToken({ id: userId, username })
|
||||||
|
|
||||||
const newUser = {
|
const newUser = {
|
||||||
id: userId,
|
id: userId,
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ const levenshteinDistance = (str1, str2, caseSensitive = false) => {
|
|||||||
}
|
}
|
||||||
module.exports.levenshteinDistance = levenshteinDistance
|
module.exports.levenshteinDistance = levenshteinDistance
|
||||||
|
|
||||||
|
const levenshteinSimilarity = (str1, str2, caseSensitive = false) => {
|
||||||
|
const distance = levenshteinDistance(str1, str2, caseSensitive)
|
||||||
|
const maxLength = Math.max(str1.length, str2.length)
|
||||||
|
if (maxLength === 0) return 1
|
||||||
|
return 1 - distance / maxLength
|
||||||
|
}
|
||||||
|
module.exports.levenshteinSimilarity = levenshteinSimilarity
|
||||||
|
|
||||||
module.exports.isObject = (val) => {
|
module.exports.isObject = (val) => {
|
||||||
return val !== null && typeof val === 'object'
|
return val !== null && typeof val === 'object'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ const bookFinder = require('../../../server/finders/BookFinder')
|
|||||||
const { LogLevel } = require('../../../server/utils/constants')
|
const { LogLevel } = require('../../../server/utils/constants')
|
||||||
const Logger = require('../../../server/Logger')
|
const Logger = require('../../../server/Logger')
|
||||||
Logger.setLogLevel(LogLevel.INFO)
|
Logger.setLogLevel(LogLevel.INFO)
|
||||||
|
const { levenshteinDistance } = require('../../../server/utils/index')
|
||||||
|
|
||||||
|
// levenshteinDistance is needed for manual calculation of expected scores in tests.
|
||||||
|
// Assuming it's accessible for testing purposes or we mock/replicate its basic behavior if needed.
|
||||||
|
// For now, we'll assume bookFinder.search uses it internally correctly.
|
||||||
|
// const { levenshteinDistance } = require('../../../server/utils/index') // Not used directly in test logic, but for reasoning.
|
||||||
|
|
||||||
describe('TitleCandidates', () => {
|
describe('TitleCandidates', () => {
|
||||||
describe('cleanAuthor non-empty', () => {
|
describe('cleanAuthor non-empty', () => {
|
||||||
@@ -326,31 +332,262 @@ describe('search', () => {
|
|||||||
const sorted = [{ duration: 1000 }, { duration: 500 }, { duration: 2000 }, { duration: 3000 }]
|
const sorted = [{ duration: 1000 }, { duration: 500 }, { duration: 2000 }, { duration: 3000 }]
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
runSearchStub.withArgs(t, a, provider).resolves(unsorted)
|
runSearchStub.withArgs(t, a, provider).resolves(structuredClone(unsorted))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sinon.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns results sorted by library item duration diff', async () => {
|
it('returns results sorted by library item duration diff', async () => {
|
||||||
expect(await bookFinder.search(libraryItem, provider, t, a)).to.deep.equal(sorted)
|
const result = (await bookFinder.search(libraryItem, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||||
|
expect(result).to.deep.equal(sorted)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns unsorted results if library item is null', async () => {
|
it('returns unsorted results if library item is null', async () => {
|
||||||
expect(await bookFinder.search(null, provider, t, a)).to.deep.equal(unsorted)
|
const result = (await bookFinder.search(null, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||||
|
expect(result).to.deep.equal(unsorted)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns unsorted results if library item duration is undefined', async () => {
|
it('returns unsorted results if library item duration is undefined', async () => {
|
||||||
expect(await bookFinder.search({ media: {} }, provider, t, a)).to.deep.equal(unsorted)
|
const result = (await bookFinder.search({ media: {} }, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||||
|
expect(result).to.deep.equal(unsorted)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns unsorted results if library item media is undefined', async () => {
|
it('returns unsorted results if library item media is undefined', async () => {
|
||||||
expect(await bookFinder.search({}, provider, t, a)).to.deep.equal(unsorted)
|
const result = (await bookFinder.search({}, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||||
|
expect(result).to.deep.equal(unsorted)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return a result last if it has no duration', async () => {
|
it('should return a result last if it has no duration', async () => {
|
||||||
const unsorted = [{}, { duration: 3000 }, { duration: 2000 }, { duration: 1000 }, { duration: 500 }]
|
const unsorted = [{}, { duration: 3000 }, { duration: 2000 }, { duration: 1000 }, { duration: 500 }]
|
||||||
const sorted = [{ duration: 1000 }, { duration: 500 }, { duration: 2000 }, { duration: 3000 }, {}]
|
const sorted = [{ duration: 1000 }, { duration: 500 }, { duration: 2000 }, { duration: 3000 }, {}]
|
||||||
runSearchStub.withArgs(t, a, provider).resolves(unsorted)
|
runSearchStub.withArgs(t, a, provider).resolves(structuredClone(unsorted))
|
||||||
|
const result = (await bookFinder.search(libraryItem, provider, t, a)).map((r) => (r.duration ? { duration: r.duration } : {}))
|
||||||
|
expect(result).to.deep.equal(sorted)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
expect(await bookFinder.search(libraryItem, provider, t, a)).to.deep.equal(sorted)
|
describe('matchConfidence score', () => {
|
||||||
|
const W_DURATION = 0.7
|
||||||
|
const W_TITLE = 0.2
|
||||||
|
const W_AUTHOR = 0.1
|
||||||
|
const DEFAULT_DURATION_SCORE_MISSING_INFO = 0.1
|
||||||
|
|
||||||
|
const libraryItemPerfectDuration = { media: { duration: 600 } } // 10 minutes
|
||||||
|
|
||||||
|
// Helper to calculate expected title/author score based on Levenshtein
|
||||||
|
// Assumes queryPart and bookPart are already "cleaned" for length calculation consistency with BookFinder.js
|
||||||
|
const calculateStringMatchScore = (cleanedQueryPart, cleanedBookPart) => {
|
||||||
|
if (!cleanedQueryPart) return cleanedBookPart ? 0 : 1 // query empty: 1 if book empty, else 0
|
||||||
|
if (!cleanedBookPart) return 0 // query non-empty, book empty: 0
|
||||||
|
|
||||||
|
// Use the imported levenshteinDistance. It defaults to case-insensitive, which is what we want.
|
||||||
|
const distance = levenshteinDistance(cleanedQueryPart, cleanedBookPart)
|
||||||
|
return Math.max(0, 1 - distance / Math.max(cleanedQueryPart.length, cleanedBookPart.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
runSearchStub.resolves([])
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('for audible provider', () => {
|
||||||
|
const provider = 'audible'
|
||||||
|
|
||||||
|
it('should be 1.0 for perfect duration, title, and author match', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
// durationScore = 1.0 (diff 0 <= 1 min)
|
||||||
|
// titleScore = 1.0 (exact match)
|
||||||
|
// authorScore = 1.0 (exact match)
|
||||||
|
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly score a large duration mismatch', async () => {
|
||||||
|
const bookResults = [{ duration: 21, title: 'The Great Novel', author: 'John Doe' }] // 21 min, diff = 11 min
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
// durationScore = 0.0
|
||||||
|
// titleScore = 1.0
|
||||||
|
// authorScore = 1.0
|
||||||
|
const expectedConfidence = W_DURATION * 0.0 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly score a medium duration mismatch', async () => {
|
||||||
|
const bookResults = [{ duration: 16, title: 'The Great Novel', author: 'John Doe' }] // 16 min, diff = 6 min
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
// durationScore = 1.2 - 6 * 0.12 = 0.48
|
||||||
|
// titleScore = 1.0
|
||||||
|
// authorScore = 1.0
|
||||||
|
const expectedConfidence = W_DURATION * 0.48 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly score a minor duration mismatch', async () => {
|
||||||
|
const bookResults = [{ duration: 14, title: 'The Great Novel', author: 'John Doe' }] // 14 min, diff = 4 min
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
// durationScore = 1.1 - 4 * 0.1 = 0.7
|
||||||
|
// titleScore = 1.0
|
||||||
|
// authorScore = 1.0
|
||||||
|
const expectedConfidence = W_DURATION * 0.7 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly score a tiny duration mismatch', async () => {
|
||||||
|
const bookResults = [{ duration: 11, title: 'The Great Novel', author: 'John Doe' }] // 11 min, diff = 1 min
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
// durationScore = 1.0
|
||||||
|
// titleScore = 1.0
|
||||||
|
// authorScore = 1.0
|
||||||
|
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should use default duration score if libraryItem duration is missing', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search({ media: {} }, provider, 'The Great Novel', 'John Doe')
|
||||||
|
// durationScore = DEFAULT_DURATION_SCORE_MISSING_INFO (0.2)
|
||||||
|
const expectedConfidence = W_DURATION * DEFAULT_DURATION_SCORE_MISSING_INFO + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should use default duration score if book duration is missing', async () => {
|
||||||
|
const bookResults = [{ title: 'The Great Novel', author: 'John Doe' }] // No duration in book
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
// durationScore = DEFAULT_DURATION_SCORE_MISSING_INFO (0.2)
|
||||||
|
const expectedConfidence = W_DURATION * DEFAULT_DURATION_SCORE_MISSING_INFO + W_TITLE * 1.0 + W_AUTHOR * 1.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly score a partial title match', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
// Query: 'Novel Ex', Book: 'Novel'
|
||||||
|
// cleanTitleForCompares('Novel Ex') -> 'novel ex' (length 8)
|
||||||
|
// cleanTitleForCompares('Novel') -> 'novel' (length 5)
|
||||||
|
// levenshteinDistance('novel ex', 'novel') = 3
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'Novel Ex', 'John Doe')
|
||||||
|
const expectedTitleScore = calculateStringMatchScore('novel ex', 'novel') // 1 - (3/8) = 0.625
|
||||||
|
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * expectedTitleScore + W_AUTHOR * 1.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly score a partial author match (comma-separated)', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'Jane Smith, Jon Doee' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
// Query: 'Jon Doe', Book part: 'Jon Doee'
|
||||||
|
// cleanAuthorForCompares('Jon Doe') -> 'jon doe' (length 7)
|
||||||
|
// book author part (already lowercased) -> 'jon doee' (length 8)
|
||||||
|
// levenshteinDistance('jon doe', 'jon doee') = 1
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'Jon Doe')
|
||||||
|
// For the author part 'jon doee':
|
||||||
|
const expectedAuthorPartScore = calculateStringMatchScore('jon doe', 'jon doee') // 1 - (1/7)
|
||||||
|
// Assuming 'jane smith' gives a lower or 0 score, max score will be from 'jon doee'
|
||||||
|
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * expectedAuthorPartScore
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should give authorScore 0 if query has author but book does not', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: null }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
// authorScore = 0.0
|
||||||
|
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * 0.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should give authorScore 1.0 if query has no author', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', '') // Empty author
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles book author string that is only commas correctly (score 0)', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: ',, ,, ,' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
// cleanedQueryAuthorForScore = "john doe"
|
||||||
|
// book.author leads to validBookAuthorParts being empty.
|
||||||
|
// authorScore = 0.0
|
||||||
|
const expectedConfidence = W_DURATION * 1.0 + W_TITLE * 1.0 + W_AUTHOR * 0.0
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(expectedConfidence, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 1.0 for ASIN results', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'B000F28ZJ4', null)
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 1.0 when author matches one of the book authors', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe, Jane Smith' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 1.0 when author query and multiple book authors are the same', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe, Jane Smith' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe, Jane Smith')
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly score against a book with a subtitle when the query has a subtitle', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', subtitle: 'A Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel: A Novel', 'John Doe')
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should correctly score against a book with a subtitle when the query does not have a subtitle', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', subtitle: 'A Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('after fuzzy searches', () => {
|
||||||
|
it('should return 1.0 for a title candidate match', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves([])
|
||||||
|
runSearchStub.withArgs('the great novel', 'john doe').resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel - A Novel', 'John Doe')
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return 1.0 for an author candidate match', async () => {
|
||||||
|
const bookResults = [{ duration: 10, title: 'The Great Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves([])
|
||||||
|
runSearchStub.withArgs('the great novel', 'john doe').resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe, Jane Smith')
|
||||||
|
expect(results[0].matchConfidence).to.be.closeTo(1.0, 0.001)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('for non-audible provider (e.g., google)', () => {
|
||||||
|
const provider = 'google'
|
||||||
|
it('should have not have matchConfidence', async () => {
|
||||||
|
const bookResults = [{ title: 'The Great Novel', author: 'John Doe' }]
|
||||||
|
runSearchStub.resolves(bookResults)
|
||||||
|
const results = await bookFinder.search(libraryItemPerfectDuration, provider, 'The Great Novel', 'John Doe')
|
||||||
|
expect(results[0]).to.not.have.property('matchConfidence')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user