Merge branch 'advplyr:master' into bookfinder-improvements

This commit is contained in:
mikiher 2023-12-08 14:07:25 +02:00 committed by GitHub
commit b6c789dee6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 558 additions and 171 deletions

View File

@ -15,24 +15,33 @@
<div class="flex my-2 -mx-2"> <div class="flex my-2 -mx-2">
<div class="w-1/2 px-2"> <div class="w-1/2 px-2">
<ui-text-input-with-label v-model="itemData.title" :disabled="processing" :label="$strings.LabelTitle" @input="titleUpdated" /> <ui-text-input-with-label v-model.trim="itemData.title" :disabled="processing" :label="$strings.LabelTitle" @input="titleUpdated" />
</div> </div>
<div class="w-1/2 px-2"> <div class="w-1/2 px-2">
<ui-text-input-with-label v-if="!isPodcast" v-model="itemData.author" :disabled="processing" :label="$strings.LabelAuthor" /> <div v-if="!isPodcast" class="flex items-end">
<ui-text-input-with-label v-model.trim="itemData.author" :disabled="processing" :label="$strings.LabelAuthor" />
<ui-tooltip :text="$strings.LabelUploaderItemFetchMetadataHelp">
<div
class="ml-2 mb-1 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-primary cursor-pointer"
@click="fetchMetadata">
<span class="text-base text-white text-opacity-80 font-mono material-icons">sync</span>
</div>
</ui-tooltip>
</div>
<div v-else class="w-full"> <div v-else class="w-full">
<p class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></p> <p class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></p>
<ui-text-input :value="directory" disabled class="w-full font-mono text-xs" style="height: 38px" /> <ui-text-input :value="directory" disabled class="w-full font-mono text-xs" />
</div> </div>
</div> </div>
</div> </div>
<div v-if="!isPodcast" class="flex my-2 -mx-2"> <div v-if="!isPodcast" class="flex my-2 -mx-2">
<div class="w-1/2 px-2"> <div class="w-1/2 px-2">
<ui-text-input-with-label v-model="itemData.series" :disabled="processing" :label="$strings.LabelSeries" note="(optional)" /> <ui-text-input-with-label v-model.trim="itemData.series" :disabled="processing" :label="$strings.LabelSeries" note="(optional)" inputClass="h-10" />
</div> </div>
<div class="w-1/2 px-2"> <div class="w-1/2 px-2">
<div class="w-full"> <div class="w-full">
<p class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></p> <label class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></label>
<ui-text-input :value="directory" disabled class="w-full font-mono text-xs" style="height: 38px" /> <ui-text-input :value="directory" disabled class="w-full font-mono text-xs h-10" />
</div> </div>
</div> </div>
</div> </div>
@ -48,8 +57,8 @@
<p class="text-base">{{ $strings.MessageUploaderItemFailed }}</p> <p class="text-base">{{ $strings.MessageUploaderItemFailed }}</p>
</widgets-alert> </widgets-alert>
<div v-if="isUploading" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 flex items-center justify-center z-20"> <div v-if="isNonInteractable" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 flex items-center justify-center z-20">
<ui-loading-indicator :text="$strings.MessageUploading" /> <ui-loading-indicator :text="nonInteractionLabel" />
</div> </div>
</div> </div>
</template> </template>
@ -61,10 +70,11 @@ export default {
props: { props: {
item: { item: {
type: Object, type: Object,
default: () => {} default: () => { }
}, },
mediaType: String, mediaType: String,
processing: Boolean processing: Boolean,
provider: String
}, },
data() { data() {
return { return {
@ -76,7 +86,8 @@ export default {
error: '', error: '',
isUploading: false, isUploading: false,
uploadFailed: false, uploadFailed: false,
uploadSuccess: false uploadSuccess: false,
isFetchingMetadata: false
} }
}, },
computed: { computed: {
@ -87,12 +98,19 @@ export default {
if (!this.itemData.title) return '' if (!this.itemData.title) return ''
if (this.isPodcast) return this.itemData.title if (this.isPodcast) return this.itemData.title
if (this.itemData.series && this.itemData.author) { const outputPathParts = [this.itemData.author, this.itemData.series, this.itemData.title]
return Path.join(this.itemData.author, this.itemData.series, this.itemData.title) const cleanedOutputPathParts = outputPathParts.filter(Boolean).map(part => this.$sanitizeFilename(part))
} else if (this.itemData.author) {
return Path.join(this.itemData.author, this.itemData.title) return Path.join(...cleanedOutputPathParts)
} else { },
return this.itemData.title isNonInteractable() {
return this.isUploading || this.isFetchingMetadata
},
nonInteractionLabel() {
if (this.isUploading) {
return this.$strings.MessageUploading
} else if (this.isFetchingMetadata) {
return this.$strings.LabelFetchingMetadata
} }
} }
}, },
@ -105,9 +123,42 @@ export default {
titleUpdated() { titleUpdated() {
this.error = '' this.error = ''
}, },
async fetchMetadata() {
if (!this.itemData.title.trim().length) {
return
}
this.isFetchingMetadata = true
this.error = ''
try {
const searchQueryString = new URLSearchParams({
title: this.itemData.title,
author: this.itemData.author,
provider: this.provider
})
const [bestCandidate, ..._rest] = await this.$axios.$get(`/api/search/books?${searchQueryString}`)
if (bestCandidate) {
this.itemData = {
...this.itemData,
title: bestCandidate.title,
author: bestCandidate.author,
series: (bestCandidate.series || [])[0]?.series
}
} else {
this.error = this.$strings.ErrorUploadFetchMetadataNoResults
}
} catch (e) {
console.error('Failed', e)
this.error = this.$strings.ErrorUploadFetchMetadataAPI
} finally {
this.isFetchingMetadata = false
}
},
getData() { getData() {
if (!this.itemData.title) { if (!this.itemData.title) {
this.error = 'Must have a title' this.error = this.$strings.ErrorUploadLacksTitle
return null return null
} }
this.error = '' this.error = ''
@ -128,4 +179,4 @@ export default {
} }
} }
} }
</script> </script>

View File

@ -50,7 +50,11 @@ export default {
label: String, label: String,
disabled: Boolean, disabled: Boolean,
readonly: Boolean, readonly: Boolean,
showEdit: Boolean showEdit: Boolean,
menuDisabled: {
type: Boolean,
default: false
},
}, },
data() { data() {
return { return {
@ -77,7 +81,7 @@ export default {
} }
}, },
showMenu() { showMenu() {
return this.isFocused return this.isFocused && !this.menuDisabled
}, },
wrapperClass() { wrapperClass() {
var classes = [] var classes = []

View File

@ -19,8 +19,8 @@
<div class="w-full h-px bg-white/10 my-4" /> <div class="w-full h-px bg-white/10 my-4" />
<p v-if="!isGuest" class="mb-4 text-lg">{{ $strings.HeaderChangePassword }}</p> <p v-if="showChangePasswordForm" class="mb-4 text-lg">{{ $strings.HeaderChangePassword }}</p>
<form v-if="!isGuest" @submit.prevent="submitChangePassword"> <form v-if="showChangePasswordForm" @submit.prevent="submitChangePassword">
<ui-text-input-with-label v-model="password" :disabled="changingPassword" type="password" :label="$strings.LabelPassword" class="my-2" /> <ui-text-input-with-label v-model="password" :disabled="changingPassword" type="password" :label="$strings.LabelPassword" class="my-2" />
<ui-text-input-with-label v-model="newPassword" :disabled="changingPassword" type="password" :label="$strings.LabelNewPassword" class="my-2" /> <ui-text-input-with-label v-model="newPassword" :disabled="changingPassword" type="password" :label="$strings.LabelNewPassword" class="my-2" />
<ui-text-input-with-label v-model="confirmPassword" :disabled="changingPassword" type="password" :label="$strings.LabelConfirmPassword" class="my-2" /> <ui-text-input-with-label v-model="confirmPassword" :disabled="changingPassword" type="password" :label="$strings.LabelConfirmPassword" class="my-2" />
@ -68,6 +68,13 @@ export default {
}, },
isGuest() { isGuest() {
return this.usertype === 'guest' return this.usertype === 'guest'
},
isPasswordAuthEnabled() {
const activeAuthMethods = this.$store.getters['getServerSetting']('authActiveAuthMethods') || []
return activeAuthMethods.includes('local')
},
showChangePasswordForm() {
return !this.isGuest && this.isPasswordAuthEnabled
} }
}, },
methods: { methods: {

View File

@ -46,6 +46,9 @@
<ui-text-input-with-label ref="openidClientSecret" v-model="newAuthSettings.authOpenIDClientSecret" :disabled="savingSettings" :label="'Client Secret'" class="mb-2" /> <ui-text-input-with-label ref="openidClientSecret" v-model="newAuthSettings.authOpenIDClientSecret" :disabled="savingSettings" :label="'Client Secret'" class="mb-2" />
<ui-multi-select ref="redirectUris" v-model="newAuthSettings.authOpenIDMobileRedirectURIs" :items="newAuthSettings.authOpenIDMobileRedirectURIs" :label="$strings.LabelMobileRedirectURIs" class="mb-2" :menuDisabled="true" :disabled="savingSettings" />
<p class="pl-4 text-sm text-gray-300 mb-2" v-html="$strings.LabelMobileRedirectURIsDescription" />
<ui-text-input-with-label ref="buttonTextInput" v-model="newAuthSettings.authOpenIDButtonText" :disabled="savingSettings" :label="$strings.LabelButtonText" class="mb-2" /> <ui-text-input-with-label ref="buttonTextInput" v-model="newAuthSettings.authOpenIDButtonText" :disabled="savingSettings" :label="$strings.LabelButtonText" class="mb-2" />
<div class="flex items-center pt-1 mb-2"> <div class="flex items-center pt-1 mb-2">
@ -187,6 +190,25 @@ export default {
this.$toast.error('Client Secret required') this.$toast.error('Client Secret required')
isValid = false isValid = false
} }
function isValidRedirectURI(uri) {
// Check for somestring://someother/string
const pattern = new RegExp('^\\w+://[\\w\\.-]+$', 'i')
return pattern.test(uri)
}
const uris = this.newAuthSettings.authOpenIDMobileRedirectURIs
if (uris.includes('*') && uris.length > 1) {
this.$toast.error('Mobile Redirect URIs: Asterisk (*) must be the only entry if used')
isValid = false
} else {
uris.forEach((uri) => {
if (uri !== '*' && !isValidRedirectURI(uri)) {
this.$toast.error(`Mobile Redirect URIs: Invalid URI ${uri}`)
isValid = false
}
})
}
return isValid return isValid
}, },
async saveSettings() { async saveSettings() {
@ -208,7 +230,11 @@ export default {
.$patch('/api/auth-settings', this.newAuthSettings) .$patch('/api/auth-settings', this.newAuthSettings)
.then((data) => { .then((data) => {
this.$store.commit('setServerSettings', data.serverSettings) this.$store.commit('setServerSettings', data.serverSettings)
this.$toast.success('Server settings updated') if (data.updated) {
this.$toast.success('Server settings updated')
} else {
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
}
}) })
.catch((error) => { .catch((error) => {
console.error('Failed to update server settings', error) console.error('Failed to update server settings', error)

View File

@ -14,6 +14,20 @@
</div> </div>
</div> </div>
<div v-if="!selectedLibraryIsPodcast" class="flex items-center mb-6">
<label class="flex cursor-pointer pt-4">
<ui-toggle-switch v-model="fetchMetadata.enabled" class="inline-flex" />
<span class="pl-2 text-base">{{ $strings.LabelAutoFetchMetadata }}</span>
</label>
<ui-tooltip :text="$strings.LabelAutoFetchMetadataHelp" class="inline-flex pt-4">
<span class="pl-1 material-icons icon-text text-sm cursor-pointer">info_outlined</span>
</ui-tooltip>
<div class="flex-grow ml-4">
<ui-dropdown v-model="fetchMetadata.provider" :items="providers" :label="$strings.LabelProvider" />
</div>
</div>
<widgets-alert v-if="error" type="error"> <widgets-alert v-if="error" type="error">
<p class="text-lg">{{ error }}</p> <p class="text-lg">{{ error }}</p>
</widgets-alert> </widgets-alert>
@ -61,9 +75,7 @@
</widgets-alert> </widgets-alert>
<!-- Item Upload cards --> <!-- Item Upload cards -->
<template v-for="item in items"> <cards-item-upload-card v-for="item in items" :key="item.index" :ref="`itemCard-${item.index}`" :media-type="selectedLibraryMediaType" :item="item" :provider="fetchMetadata.provider" :processing="processing" @remove="removeItem(item)" />
<cards-item-upload-card :ref="`itemCard-${item.index}`" :key="item.index" :media-type="selectedLibraryMediaType" :item="item" :processing="processing" @remove="removeItem(item)" />
</template>
<!-- Upload/Reset btns --> <!-- Upload/Reset btns -->
<div v-show="items.length" class="flex justify-end pb-8 pt-4"> <div v-show="items.length" class="flex justify-end pb-8 pt-4">
@ -92,13 +104,18 @@ export default {
selectedLibraryId: null, selectedLibraryId: null,
selectedFolderId: null, selectedFolderId: null,
processing: false, processing: false,
uploadFinished: false uploadFinished: false,
fetchMetadata: {
enabled: false,
provider: null
}
} }
}, },
watch: { watch: {
selectedLibrary(newVal) { selectedLibrary(newVal) {
if (newVal && !this.selectedFolderId) { if (newVal && !this.selectedFolderId) {
this.setDefaultFolder() this.setDefaultFolder()
this.setMetadataProvider()
} }
} }
}, },
@ -133,6 +150,13 @@ export default {
selectedLibraryIsPodcast() { selectedLibraryIsPodcast() {
return this.selectedLibraryMediaType === 'podcast' return this.selectedLibraryMediaType === 'podcast'
}, },
providers() {
if (this.selectedLibraryIsPodcast) return this.$store.state.scanners.podcastProviders
return this.$store.state.scanners.providers
},
canFetchMetadata() {
return !this.selectedLibraryIsPodcast && this.fetchMetadata.enabled
},
selectedFolder() { selectedFolder() {
if (!this.selectedLibrary) return null if (!this.selectedLibrary) return null
return this.selectedLibrary.folders.find((fold) => fold.id === this.selectedFolderId) return this.selectedLibrary.folders.find((fold) => fold.id === this.selectedFolderId)
@ -160,12 +184,16 @@ export default {
} }
} }
this.setDefaultFolder() this.setDefaultFolder()
this.setMetadataProvider()
}, },
setDefaultFolder() { setDefaultFolder() {
if (!this.selectedFolderId && this.selectedLibrary && this.selectedLibrary.folders.length) { if (!this.selectedFolderId && this.selectedLibrary && this.selectedLibrary.folders.length) {
this.selectedFolderId = this.selectedLibrary.folders[0].id this.selectedFolderId = this.selectedLibrary.folders[0].id
} }
}, },
setMetadataProvider() {
this.fetchMetadata.provider ||= this.$store.getters['libraries/getLibraryProvider'](this.selectedLibraryId)
},
removeItem(item) { removeItem(item) {
this.items = this.items.filter((b) => b.index !== item.index) this.items = this.items.filter((b) => b.index !== item.index)
if (!this.items.length) { if (!this.items.length) {
@ -213,27 +241,49 @@ export default {
var items = e.dataTransfer.items || [] var items = e.dataTransfer.items || []
var itemResults = await this.uploadHelpers.getItemsFromDrop(items, this.selectedLibraryMediaType) var itemResults = await this.uploadHelpers.getItemsFromDrop(items, this.selectedLibraryMediaType)
this.setResults(itemResults) this.onItemsSelected(itemResults)
}, },
inputChanged(e) { inputChanged(e) {
if (!e.target || !e.target.files) return if (!e.target || !e.target.files) return
var _files = Array.from(e.target.files) var _files = Array.from(e.target.files)
if (_files && _files.length) { if (_files && _files.length) {
var itemResults = this.uploadHelpers.getItemsFromPicker(_files, this.selectedLibraryMediaType) var itemResults = this.uploadHelpers.getItemsFromPicker(_files, this.selectedLibraryMediaType)
this.setResults(itemResults) this.onItemsSelected(itemResults)
} }
}, },
setResults(itemResults) { onItemsSelected(itemResults) {
if (this.itemSelectionSuccessful(itemResults)) {
// setTimeout ensures the new item ref is attached before this method is called
setTimeout(this.attemptMetadataFetch, 0)
}
},
itemSelectionSuccessful(itemResults) {
console.log('Upload results', itemResults)
if (itemResults.error) { if (itemResults.error) {
this.error = itemResults.error this.error = itemResults.error
this.items = [] this.items = []
this.ignoredFiles = [] this.ignoredFiles = []
} else { return false
this.error = ''
this.items = itemResults.items
this.ignoredFiles = itemResults.ignoredFiles
} }
console.log('Upload results', itemResults)
this.error = ''
this.items = itemResults.items
this.ignoredFiles = itemResults.ignoredFiles
return true
},
attemptMetadataFetch() {
if (!this.canFetchMetadata) {
return false
}
this.items.forEach((item) => {
let itemRef = this.$refs[`itemCard-${item.index}`]
if (itemRef?.length) {
itemRef[0].fetchMetadata(this.fetchMetadata.provider)
}
})
}, },
updateItemCardStatus(index, status) { updateItemCardStatus(index, status) {
var ref = this.$refs[`itemCard-${index}`] var ref = this.$refs[`itemCard-${index}`]
@ -248,8 +298,8 @@ export default {
var form = new FormData() var form = new FormData()
form.set('title', item.title) form.set('title', item.title)
if (!this.selectedLibraryIsPodcast) { if (!this.selectedLibraryIsPodcast) {
form.set('author', item.author) form.set('author', item.author || '')
form.set('series', item.series) form.set('series', item.series || '')
} }
form.set('library', this.selectedLibraryId) form.set('library', this.selectedLibraryId)
form.set('folder', this.selectedFolderId) form.set('folder', this.selectedFolderId)
@ -346,6 +396,8 @@ export default {
}, },
mounted() { mounted() {
this.selectedLibraryId = this.$store.state.libraries.currentLibraryId this.selectedLibraryId = this.$store.state.libraries.currentLibraryId
this.setMetadataProvider()
this.setDefaultFolder() this.setDefaultFolder()
window.addEventListener('dragenter', this.dragenter) window.addEventListener('dragenter', this.dragenter)
window.addEventListener('dragleave', this.dragleave) window.addEventListener('dragleave', this.dragleave)
@ -359,4 +411,4 @@ export default {
window.removeEventListener('drop', this.drop) window.removeEventListener('drop', this.drop)
} }
} }
</script> </script>

View File

@ -77,6 +77,7 @@ Vue.prototype.$sanitizeFilename = (filename, colonReplacement = ' - ') => {
.replace(lineBreaks, replacement) .replace(lineBreaks, replacement)
.replace(windowsReservedRe, replacement) .replace(windowsReservedRe, replacement)
.replace(windowsTrailingRe, replacement) .replace(windowsTrailingRe, replacement)
.replace(/\s+/g, ' ') // Replace consecutive spaces with a single space
// Check if basename is too many bytes // Check if basename is too many bytes
const ext = Path.extname(sanitized) // separate out file extension const ext = Path.extname(sanitized) // separate out file extension

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Upravit uživatelské {0}", "ButtonUserEdit": "Upravit uživatelské {0}",
"ButtonViewAll": "Zobrazit vše", "ButtonViewAll": "Zobrazit vše",
"ButtonYes": "Ano", "ButtonYes": "Ano",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Účet", "HeaderAccount": "Účet",
"HeaderAdvanced": "Pokročilé", "HeaderAdvanced": "Pokročilé",
"HeaderAppriseNotificationSettings": "Nastavení oznámení Apprise", "HeaderAppriseNotificationSettings": "Nastavení oznámení Apprise",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Autor (příjmení a jméno)", "LabelAuthorLastFirst": "Autor (příjmení a jméno)",
"LabelAuthors": "Autoři", "LabelAuthors": "Autoři",
"LabelAutoDownloadEpisodes": "Automaticky stahovat epizody", "LabelAutoDownloadEpisodes": "Automaticky stahovat epizody",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Příklad", "LabelExample": "Příklad",
"LabelExplicit": "Explicitní", "LabelExplicit": "Explicitní",
"LabelFeedURL": "URL zdroje", "LabelFeedURL": "URL zdroje",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Soubor", "LabelFile": "Soubor",
"LabelFileBirthtime": "Čas vzniku souboru", "LabelFileBirthtime": "Čas vzniku souboru",
"LabelFileModified": "Soubor změněn", "LabelFileModified": "Soubor změněn",
@ -337,6 +343,8 @@
"LabelMinute": "Minuta", "LabelMinute": "Minuta",
"LabelMissing": "Chybějící", "LabelMissing": "Chybějící",
"LabelMissingParts": "Chybějící díly", "LabelMissingParts": "Chybějící díly",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Více", "LabelMore": "Více",
"LabelMoreInfo": "Více informací", "LabelMoreInfo": "Více informací",
"LabelName": "Jméno", "LabelName": "Jméno",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Povolit přepsání existujících údajů o vybraných knihách, když je nalezena shoda", "LabelUpdateDetailsHelp": "Povolit přepsání existujících údajů o vybraných knihách, když je nalezena shoda",
"LabelUploaderDragAndDrop": "Přetáhnout soubory nebo složky", "LabelUploaderDragAndDrop": "Přetáhnout soubory nebo složky",
"LabelUploaderDropFiles": "Odstranit soubory", "LabelUploaderDropFiles": "Odstranit soubory",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Použít stopu kapitoly", "LabelUseChapterTrack": "Použít stopu kapitoly",
"LabelUseFullTrack": "Použít celou stopu", "LabelUseFullTrack": "Použít celou stopu",
"LabelUser": "Uživatel", "LabelUser": "Uživatel",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Rediger bruger {0}", "ButtonUserEdit": "Rediger bruger {0}",
"ButtonViewAll": "Vis Alle", "ButtonViewAll": "Vis Alle",
"ButtonYes": "Ja", "ButtonYes": "Ja",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Konto", "HeaderAccount": "Konto",
"HeaderAdvanced": "Avanceret", "HeaderAdvanced": "Avanceret",
"HeaderAppriseNotificationSettings": "Apprise Notifikationsindstillinger", "HeaderAppriseNotificationSettings": "Apprise Notifikationsindstillinger",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Forfatter (Efternavn, Fornavn)", "LabelAuthorLastFirst": "Forfatter (Efternavn, Fornavn)",
"LabelAuthors": "Forfattere", "LabelAuthors": "Forfattere",
"LabelAutoDownloadEpisodes": "Auto Download Episoder", "LabelAutoDownloadEpisodes": "Auto Download Episoder",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Eksempel", "LabelExample": "Eksempel",
"LabelExplicit": "Eksplisit", "LabelExplicit": "Eksplisit",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Fil", "LabelFile": "Fil",
"LabelFileBirthtime": "Fødselstidspunkt for fil", "LabelFileBirthtime": "Fødselstidspunkt for fil",
"LabelFileModified": "Fil ændret", "LabelFileModified": "Fil ændret",
@ -337,6 +343,8 @@
"LabelMinute": "Minut", "LabelMinute": "Minut",
"LabelMissing": "Mangler", "LabelMissing": "Mangler",
"LabelMissingParts": "Manglende dele", "LabelMissingParts": "Manglende dele",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Mere", "LabelMore": "Mere",
"LabelMoreInfo": "Mere info", "LabelMoreInfo": "Mere info",
"LabelName": "Navn", "LabelName": "Navn",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Tillad overskrivning af eksisterende detaljer for de valgte bøger, når der findes en match", "LabelUpdateDetailsHelp": "Tillad overskrivning af eksisterende detaljer for de valgte bøger, når der findes en match",
"LabelUploaderDragAndDrop": "Træk og slip filer eller mapper", "LabelUploaderDragAndDrop": "Træk og slip filer eller mapper",
"LabelUploaderDropFiles": "Smid filer", "LabelUploaderDropFiles": "Smid filer",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Brug kapitel-spor", "LabelUseChapterTrack": "Brug kapitel-spor",
"LabelUseFullTrack": "Brug fuldt spor", "LabelUseFullTrack": "Brug fuldt spor",
"LabelUser": "Bruger", "LabelUser": "Bruger",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Benutzer {0} bearbeiten", "ButtonUserEdit": "Benutzer {0} bearbeiten",
"ButtonViewAll": "Alles anzeigen", "ButtonViewAll": "Alles anzeigen",
"ButtonYes": "Ja", "ButtonYes": "Ja",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Konto", "HeaderAccount": "Konto",
"HeaderAdvanced": "Erweitert", "HeaderAdvanced": "Erweitert",
"HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen", "HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Autor (Nachname, Vorname)", "LabelAuthorLastFirst": "Autor (Nachname, Vorname)",
"LabelAuthors": "Autoren", "LabelAuthors": "Autoren",
"LabelAutoDownloadEpisodes": "Episoden automatisch herunterladen", "LabelAutoDownloadEpisodes": "Episoden automatisch herunterladen",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Automatischer Start", "LabelAutoLaunch": "Automatischer Start",
"LabelAutoLaunchDescription": "Automatische Weiterleitung zum Authentifizierungsanbieter beim Navigieren zur Anmeldeseite (manueller Überschreibungspfad <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Automatische Weiterleitung zum Authentifizierungsanbieter beim Navigieren zur Anmeldeseite (manueller Überschreibungspfad <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Automatische Registrierung", "LabelAutoRegister": "Automatische Registrierung",
@ -266,6 +271,7 @@
"LabelExample": "Beispiel", "LabelExample": "Beispiel",
"LabelExplicit": "Explizit (Altersbeschränkung)", "LabelExplicit": "Explizit (Altersbeschränkung)",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Datei", "LabelFile": "Datei",
"LabelFileBirthtime": "Datei erstellt", "LabelFileBirthtime": "Datei erstellt",
"LabelFileModified": "Datei geändert", "LabelFileModified": "Datei geändert",
@ -337,6 +343,8 @@
"LabelMinute": "Minute", "LabelMinute": "Minute",
"LabelMissing": "Fehlend", "LabelMissing": "Fehlend",
"LabelMissingParts": "Fehlende Teile", "LabelMissingParts": "Fehlende Teile",
"LabelMobileRedirectURIs": "Erlaubte Weiterleitungs-URIs für die mobile App",
"LabelMobileRedirectURIsDescription": "Dies ist eine Whitelist gültiger Umleitungs-URIs für mobile Apps. Der Standardwert ist <code>audiobookshelf://oauth</code>, den Sie entfernen oder durch zusätzliche URIs für die Integration von Drittanbieter-Apps ergänzen können. Die Verwendung eines Sternchens (<code>*</code>) als alleiniger Eintrag erlaubt jede URI.",
"LabelMore": "Mehr", "LabelMore": "Mehr",
"LabelMoreInfo": "Mehr Info", "LabelMoreInfo": "Mehr Info",
"LabelName": "Name", "LabelName": "Name",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Erlaube das Überschreiben bestehender Details für die ausgewählten Hörbücher wenn eine Übereinstimmung gefunden wird", "LabelUpdateDetailsHelp": "Erlaube das Überschreiben bestehender Details für die ausgewählten Hörbücher wenn eine Übereinstimmung gefunden wird",
"LabelUploaderDragAndDrop": "Ziehen und Ablegen von Dateien oder Ordnern", "LabelUploaderDragAndDrop": "Ziehen und Ablegen von Dateien oder Ordnern",
"LabelUploaderDropFiles": "Dateien löschen", "LabelUploaderDropFiles": "Dateien löschen",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Kapiteldatei verwenden", "LabelUseChapterTrack": "Kapiteldatei verwenden",
"LabelUseFullTrack": "Gesamte Datei verwenden", "LabelUseFullTrack": "Gesamte Datei verwenden",
"LabelUser": "Benutzer", "LabelUser": "Benutzer",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Edit user {0}", "ButtonUserEdit": "Edit user {0}",
"ButtonViewAll": "View All", "ButtonViewAll": "View All",
"ButtonYes": "Yes", "ButtonYes": "Yes",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Account", "HeaderAccount": "Account",
"HeaderAdvanced": "Advanced", "HeaderAdvanced": "Advanced",
"HeaderAppriseNotificationSettings": "Apprise Notification Settings", "HeaderAppriseNotificationSettings": "Apprise Notification Settings",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Author (Last, First)", "LabelAuthorLastFirst": "Author (Last, First)",
"LabelAuthors": "Authors", "LabelAuthors": "Authors",
"LabelAutoDownloadEpisodes": "Auto Download Episodes", "LabelAutoDownloadEpisodes": "Auto Download Episodes",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Example", "LabelExample": "Example",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "File", "LabelFile": "File",
"LabelFileBirthtime": "File Birthtime", "LabelFileBirthtime": "File Birthtime",
"LabelFileModified": "File Modified", "LabelFileModified": "File Modified",
@ -337,6 +343,8 @@
"LabelMinute": "Minute", "LabelMinute": "Minute",
"LabelMissing": "Missing", "LabelMissing": "Missing",
"LabelMissingParts": "Missing Parts", "LabelMissingParts": "Missing Parts",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "More", "LabelMore": "More",
"LabelMoreInfo": "More Info", "LabelMoreInfo": "More Info",
"LabelName": "Name", "LabelName": "Name",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located", "LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located",
"LabelUploaderDragAndDrop": "Drag & drop files or folders", "LabelUploaderDragAndDrop": "Drag & drop files or folders",
"LabelUploaderDropFiles": "Drop files", "LabelUploaderDropFiles": "Drop files",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Use chapter track", "LabelUseChapterTrack": "Use chapter track",
"LabelUseFullTrack": "Use full track", "LabelUseFullTrack": "Use full track",
"LabelUser": "User", "LabelUser": "User",
@ -738,4 +747,4 @@
"ToastSocketFailedToConnect": "Socket failed to connect", "ToastSocketFailedToConnect": "Socket failed to connect",
"ToastUserDeleteFailed": "Failed to delete user", "ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted" "ToastUserDeleteSuccess": "User deleted"
} }

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Editar Usuario {0}", "ButtonUserEdit": "Editar Usuario {0}",
"ButtonViewAll": "Ver Todos", "ButtonViewAll": "Ver Todos",
"ButtonYes": "Aceptar", "ButtonYes": "Aceptar",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Cuenta", "HeaderAccount": "Cuenta",
"HeaderAdvanced": "Avanzado", "HeaderAdvanced": "Avanzado",
"HeaderAppriseNotificationSettings": "Ajustes de Notificaciones de Apprise", "HeaderAppriseNotificationSettings": "Ajustes de Notificaciones de Apprise",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Autor (Apellido, Nombre)", "LabelAuthorLastFirst": "Autor (Apellido, Nombre)",
"LabelAuthors": "Autores", "LabelAuthors": "Autores",
"LabelAutoDownloadEpisodes": "Descargar Episodios Automáticamente", "LabelAutoDownloadEpisodes": "Descargar Episodios Automáticamente",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Ejemplo", "LabelExample": "Ejemplo",
"LabelExplicit": "Explicito", "LabelExplicit": "Explicito",
"LabelFeedURL": "Fuente de URL", "LabelFeedURL": "Fuente de URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Archivo", "LabelFile": "Archivo",
"LabelFileBirthtime": "Archivo Creado en", "LabelFileBirthtime": "Archivo Creado en",
"LabelFileModified": "Archivo modificado", "LabelFileModified": "Archivo modificado",
@ -337,6 +343,8 @@
"LabelMinute": "Minuto", "LabelMinute": "Minuto",
"LabelMissing": "Ausente", "LabelMissing": "Ausente",
"LabelMissingParts": "Partes Ausentes", "LabelMissingParts": "Partes Ausentes",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Más", "LabelMore": "Más",
"LabelMoreInfo": "Más Información", "LabelMoreInfo": "Más Información",
"LabelName": "Nombre", "LabelName": "Nombre",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Permitir sobrescribir detalles existentes de los libros seleccionados cuando sean encontrados", "LabelUpdateDetailsHelp": "Permitir sobrescribir detalles existentes de los libros seleccionados cuando sean encontrados",
"LabelUploaderDragAndDrop": "Arrastre y suelte archivos o carpetas", "LabelUploaderDragAndDrop": "Arrastre y suelte archivos o carpetas",
"LabelUploaderDropFiles": "Suelte los Archivos", "LabelUploaderDropFiles": "Suelte los Archivos",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Usar pista por capitulo", "LabelUseChapterTrack": "Usar pista por capitulo",
"LabelUseFullTrack": "Usar pista completa", "LabelUseFullTrack": "Usar pista completa",
"LabelUser": "Usuario", "LabelUser": "Usuario",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Modifier lutilisateur {0}", "ButtonUserEdit": "Modifier lutilisateur {0}",
"ButtonViewAll": "Afficher tout", "ButtonViewAll": "Afficher tout",
"ButtonYes": "Oui", "ButtonYes": "Oui",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Compte", "HeaderAccount": "Compte",
"HeaderAdvanced": "Avancé", "HeaderAdvanced": "Avancé",
"HeaderAppriseNotificationSettings": "Configuration des Notifications Apprise", "HeaderAppriseNotificationSettings": "Configuration des Notifications Apprise",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Auteur (Nom, Prénom)", "LabelAuthorLastFirst": "Auteur (Nom, Prénom)",
"LabelAuthors": "Auteurs", "LabelAuthors": "Auteurs",
"LabelAutoDownloadEpisodes": "Téléchargement automatique dépisode", "LabelAutoDownloadEpisodes": "Téléchargement automatique dépisode",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Exemple", "LabelExample": "Exemple",
"LabelExplicit": "Restriction", "LabelExplicit": "Restriction",
"LabelFeedURL": "URL du flux", "LabelFeedURL": "URL du flux",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Fichier", "LabelFile": "Fichier",
"LabelFileBirthtime": "Création du fichier", "LabelFileBirthtime": "Création du fichier",
"LabelFileModified": "Modification du fichier", "LabelFileModified": "Modification du fichier",
@ -337,6 +343,8 @@
"LabelMinute": "Minute", "LabelMinute": "Minute",
"LabelMissing": "Manquant", "LabelMissing": "Manquant",
"LabelMissingParts": "Parties manquantes", "LabelMissingParts": "Parties manquantes",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Plus", "LabelMore": "Plus",
"LabelMoreInfo": "Plus dinfo", "LabelMoreInfo": "Plus dinfo",
"LabelName": "Nom", "LabelName": "Nom",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Autoriser la mise à jour des détails existants lorsquune correspondance est trouvée", "LabelUpdateDetailsHelp": "Autoriser la mise à jour des détails existants lorsquune correspondance est trouvée",
"LabelUploaderDragAndDrop": "Glisser et déposer des fichiers ou dossiers", "LabelUploaderDragAndDrop": "Glisser et déposer des fichiers ou dossiers",
"LabelUploaderDropFiles": "Déposer des fichiers", "LabelUploaderDropFiles": "Déposer des fichiers",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Utiliser la piste du chapitre", "LabelUseChapterTrack": "Utiliser la piste du chapitre",
"LabelUseFullTrack": "Utiliser la piste Complète", "LabelUseFullTrack": "Utiliser la piste Complète",
"LabelUser": "Utilisateur", "LabelUser": "Utilisateur",

View File

@ -1,10 +1,10 @@
{ {
"ButtonAdd": "ઉમેરો", "ButtonAdd": "ઉમેરો",
"ButtonAddChapters": "પ્રકરણો ઉમેરો", "ButtonAddChapters": "પ્રકરણો ઉમેરો",
"ButtonAddDevice": "Add Device", "ButtonAddDevice": "ઉપકરણ ઉમેરો",
"ButtonAddLibrary": "Add Library", "ButtonAddLibrary": "પુસ્તકાલય ઉમેરો",
"ButtonAddPodcasts": "પોડકાસ્ટ ઉમેરો", "ButtonAddPodcasts": "પોડકાસ્ટ ઉમેરો",
"ButtonAddUser": "Add User", "ButtonAddUser": "વપરાશકર્તા ઉમેરો",
"ButtonAddYourFirstLibrary": "તમારી પ્રથમ પુસ્તકાલય ઉમેરો", "ButtonAddYourFirstLibrary": "તમારી પ્રથમ પુસ્તકાલય ઉમેરો",
"ButtonApply": "લાગુ કરો", "ButtonApply": "લાગુ કરો",
"ButtonApplyChapters": "પ્રકરણો લાગુ કરો", "ButtonApplyChapters": "પ્રકરણો લાગુ કરો",
@ -58,11 +58,11 @@
"ButtonRemoveAll": "બધું કાઢી નાખો", "ButtonRemoveAll": "બધું કાઢી નાખો",
"ButtonRemoveAllLibraryItems": "બધું પુસ્તકાલય વસ્તુઓ કાઢી નાખો", "ButtonRemoveAllLibraryItems": "બધું પુસ્તકાલય વસ્તુઓ કાઢી નાખો",
"ButtonRemoveFromContinueListening": "સાંભળતી પુસ્તકો માંથી કાઢી નાખો", "ButtonRemoveFromContinueListening": "સાંભળતી પુસ્તકો માંથી કાઢી નાખો",
"ButtonRemoveFromContinueReading": "Remove from Continue Reading", "ButtonRemoveFromContinueReading": "સાંભળતી પુસ્તકો માંથી કાઢી નાખો",
"ButtonRemoveSeriesFromContinueSeries": "સાંભળતી સિરીઝ માંથી કાઢી નાખો", "ButtonRemoveSeriesFromContinueSeries": "સાંભળતી સિરીઝ માંથી કાઢી નાખો",
"ButtonReScan": "ફરીથી સ્કેન કરો", "ButtonReScan": "ફરીથી સ્કેન કરો",
"ButtonReset": "રીસેટ કરો", "ButtonReset": "રીસેટ કરો",
"ButtonResetToDefault": "Reset to default", "ButtonResetToDefault": "ડિફોલ્ટ પર રીસેટ કરો",
"ButtonRestore": "પુનઃસ્થાપિત કરો", "ButtonRestore": "પુનઃસ્થાપિત કરો",
"ButtonSave": "સાચવો", "ButtonSave": "સાચવો",
"ButtonSaveAndClose": "સાચવો અને બંધ કરો", "ButtonSaveAndClose": "સાચવો અને બંધ કરો",
@ -78,7 +78,7 @@
"ButtonStartM4BEncode": "M4B એન્કોડ શરૂ કરો", "ButtonStartM4BEncode": "M4B એન્કોડ શરૂ કરો",
"ButtonStartMetadataEmbed": "મેટાડેટા એમ્બેડ શરૂ કરો", "ButtonStartMetadataEmbed": "મેટાડેટા એમ્બેડ શરૂ કરો",
"ButtonSubmit": "સબમિટ કરો", "ButtonSubmit": "સબમિટ કરો",
"ButtonTest": "Test", "ButtonTest": "પરખ કરો",
"ButtonUpload": "અપલોડ કરો", "ButtonUpload": "અપલોડ કરો",
"ButtonUploadBackup": "બેકઅપ અપલોડ કરો", "ButtonUploadBackup": "બેકઅપ અપલોડ કરો",
"ButtonUploadCover": "કવર અપલોડ કરો", "ButtonUploadCover": "કવર અપલોડ કરો",
@ -87,81 +87,84 @@
"ButtonUserEdit": "વપરાશકર્તા {0} સંપાદિત કરો", "ButtonUserEdit": "વપરાશકર્તા {0} સંપાદિત કરો",
"ButtonViewAll": "બધું જુઓ", "ButtonViewAll": "બધું જુઓ",
"ButtonYes": "હા", "ButtonYes": "હા",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "એકાઉન્ટ", "HeaderAccount": "એકાઉન્ટ",
"HeaderAdvanced": "અડ્વાન્સડ", "HeaderAdvanced": "અડ્વાન્સડ",
"HeaderAppriseNotificationSettings": "Apprise સૂચના સેટિંગ્સ", "HeaderAppriseNotificationSettings": "Apprise સૂચના સેટિંગ્સ",
"HeaderAudiobookTools": "Audiobook File Management Tools", "HeaderAudiobookTools": "ઓડિયોબુક ફાઇલ વ્યવસ્થાપન ટૂલ્સ",
"HeaderAudioTracks": "Audio Tracks", "HeaderAudioTracks": "ઓડિયો ટ્રેક્સ",
"HeaderAuthentication": "Authentication", "HeaderAuthentication": "Authentication",
"HeaderBackups": "Backups", "HeaderBackups": "બેકઅપ્સ",
"HeaderChangePassword": "Change Password", "HeaderChangePassword": "પાસવર્ડ બદલો",
"HeaderChapters": "Chapters", "HeaderChapters": "પ્રકરણો",
"HeaderChooseAFolder": "Choose a Folder", "HeaderChooseAFolder": "ફોલ્ડર પસંદ કરો",
"HeaderCollection": "Collection", "HeaderCollection": "સંગ્રહ",
"HeaderCollectionItems": "Collection Items", "HeaderCollectionItems": "સંગ્રહ વસ્તુઓ",
"HeaderCover": "Cover", "HeaderCover": "આવરણ",
"HeaderCurrentDownloads": "Current Downloads", "HeaderCurrentDownloads": "વર્તમાન ડાઉનલોડ્સ",
"HeaderDetails": "Details", "HeaderDetails": "વિગતો",
"HeaderDownloadQueue": "Download Queue", "HeaderDownloadQueue": "ડાઉનલોડ કતાર",
"HeaderEbookFiles": "Ebook Files", "HeaderEbookFiles": "ઇબુક ફાઇલો",
"HeaderEmail": "Email", "HeaderEmail": "ઈમેલ",
"HeaderEmailSettings": "Email Settings", "HeaderEmailSettings": "ઈમેલ સેટિંગ્સ",
"HeaderEpisodes": "Episodes", "HeaderEpisodes": "એપિસોડ્સ",
"HeaderEreaderDevices": "Ereader Devices", "HeaderEreaderDevices": "ઇરીડર ઉપકરણો",
"HeaderEreaderSettings": "Ereader Settings", "HeaderEreaderSettings": "ઇરીડર સેટિંગ્સ",
"HeaderFiles": "Files", "HeaderFiles": "ફાઇલો",
"HeaderFindChapters": "Find Chapters", "HeaderFindChapters": "પ્રકરણો શોધો",
"HeaderIgnoredFiles": "Ignored Files", "HeaderIgnoredFiles": "અવગણેલી ફાઇલો",
"HeaderItemFiles": "Item Files", "HeaderItemFiles": "વાસ્તુ ની ફાઈલો",
"HeaderItemMetadataUtils": "Item Metadata Utils", "HeaderItemMetadataUtils": "વસ્તુ મેટાડેટા સાધનો",
"HeaderLastListeningSession": "Last Listening Session", "HeaderLastListeningSession": "છેલ્લી સાંભળતી સેશન",
"HeaderLatestEpisodes": "Latest episodes", "HeaderLatestEpisodes": "નવીનતમ એપિસોડ્સ",
"HeaderLibraries": "Libraries", "HeaderLibraries": "પુસ્તકાલયો",
"HeaderLibraryFiles": "Library Files", "HeaderLibraryFiles": "પુસ્તકાલય ફાઇલો",
"HeaderLibraryStats": "Library Stats", "HeaderLibraryStats": "પુસ્તકાલય આંકડા",
"HeaderListeningSessions": "Listening Sessions", "HeaderListeningSessions": "સાંભળતી સેશન્સ",
"HeaderListeningStats": "Listening Stats", "HeaderListeningStats": "સાંભળતી આંકડા",
"HeaderLogin": "Login", "HeaderLogin": "લોગિન",
"HeaderLogs": "Logs", "HeaderLogs": "લોગ્સ",
"HeaderManageGenres": "Manage Genres", "HeaderManageGenres": "જાતિઓ મેનેજ કરો",
"HeaderManageTags": "Manage Tags", "HeaderManageTags": "ટેગ્સ મેનેજ કરો",
"HeaderMapDetails": "Map details", "HeaderMapDetails": "વિગતો મેપ કરો",
"HeaderMatch": "Match", "HeaderMatch": "મેળ ખાતી શોધો",
"HeaderMetadataOrderOfPrecedence": "Metadata order of precedence", "HeaderMetadataOrderOfPrecedence": "મેટાડેટા પ્રાધાન્યતાનો ક્રમ",
"HeaderMetadataToEmbed": "Metadata to embed", "HeaderMetadataToEmbed": "એમ્બેડ કરવા માટે મેટાડેટા",
"HeaderNewAccount": "New Account", "HeaderNewAccount": "નવું એકાઉન્ટ",
"HeaderNewLibrary": "New Library", "HeaderNewLibrary": "નવી પુસ્તકાલય",
"HeaderNotifications": "Notifications", "HeaderNotifications": "સૂચનાઓ",
"HeaderOpenIDConnectAuthentication": "OpenID Connect Authentication", "HeaderOpenIDConnectAuthentication": "OpenID Connect Authentication",
"HeaderOpenRSSFeed": "Open RSS Feed", "HeaderOpenRSSFeed": "RSS ફીડ ખોલો",
"HeaderOtherFiles": "Other Files", "HeaderOtherFiles": "અન્ય ફાઇલો",
"HeaderPasswordAuthentication": "Password Authentication", "HeaderPasswordAuthentication": "Password Authentication",
"HeaderPermissions": "Permissions", "HeaderPermissions": "પરવાનગીઓ",
"HeaderPlayerQueue": "Player Queue", "HeaderPlayerQueue": "પ્લેયર કતાર",
"HeaderPlaylist": "Playlist", "HeaderPlaylist": "પ્લેલિસ્ટ",
"HeaderPlaylistItems": "Playlist Items", "HeaderPlaylistItems": "પ્લેલિસ્ટ ની વસ્તુઓ",
"HeaderPodcastsToAdd": "Podcasts to Add", "HeaderPodcastsToAdd": "ઉમેરવા માટે પોડકાસ્ટ્સ",
"HeaderPreviewCover": "Preview Cover", "HeaderPreviewCover": "પૂર્વાવલોકન કવર",
"HeaderRemoveEpisode": "Remove Episode", "HeaderRemoveEpisode": "એપિસોડ કાઢી નાખો",
"HeaderRemoveEpisodes": "Remove {0} Episodes", "HeaderRemoveEpisodes": "{0} એપિસોડ્સ કાઢી નાખો",
"HeaderRSSFeedGeneral": "RSS Details", "HeaderRSSFeedGeneral": "સામાન્ય RSS ફીડ",
"HeaderRSSFeedIsOpen": "RSS Feed is Open", "HeaderRSSFeedIsOpen": "RSS ફીડ ખોલેલી છે",
"HeaderRSSFeeds": "RSS Feeds", "HeaderRSSFeeds": "RSS ફીડ્સ",
"HeaderSavedMediaProgress": "Saved Media Progress", "HeaderSavedMediaProgress": "સાચવેલ મીડિયા પ્રગતિ",
"HeaderSchedule": "Schedule", "HeaderSchedule": "સમયપત્રક",
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans", "HeaderScheduleLibraryScans": "પુસ્તકાલય સ્કેન સમયપત્રક",
"HeaderSession": "Session", "HeaderSession": "સેશન",
"HeaderSetBackupSchedule": "Set Backup Schedule", "HeaderSetBackupSchedule": "બેકઅપ સમયપત્રક સેટ કરો",
"HeaderSettings": "Settings", "HeaderSettings": "સેટિંગ્સ",
"HeaderSettingsDisplay": "Display", "HeaderSettingsDisplay": "ડિસ્પ્લે સેટિંગ્સ",
"HeaderSettingsExperimental": "Experimental Features", "HeaderSettingsExperimental": "પ્રયોગશીલ સેટિંગ્સ",
"HeaderSettingsGeneral": "General", "HeaderSettingsGeneral": "સામાન્ય સેટિંગ્સ",
"HeaderSettingsScanner": "Scanner", "HeaderSettingsScanner": "સ્કેનર સેટિંગ્સ",
"HeaderSleepTimer": "Sleep Timer", "HeaderSleepTimer": "સ્લીપ ટાઈમર",
"HeaderStatsLargestItems": "Largest Items", "HeaderStatsLargestItems": "સૌથી મોટી વસ્તુઓ",
"HeaderStatsLongestItems": "Longest Items (hrs)", "HeaderStatsLongestItems": "સૌથી લાંબી વસ્તુઓ (કલાક)",
"HeaderStatsMinutesListeningChart": "Minutes Listening (last 7 days)", "HeaderStatsMinutesListeningChart": "સાંભળવાની મિનિટ (છેલ્લા ૭ દિવસ)",
"HeaderStatsRecentSessions": "Recent Sessions", "HeaderStatsRecentSessions": "છેલ્લી સાંભળતી સેશન્સ",
"HeaderStatsTop10Authors": "Top 10 Authors", "HeaderStatsTop10Authors": "Top 10 Authors",
"HeaderStatsTop5Genres": "Top 5 Genres", "HeaderStatsTop5Genres": "Top 5 Genres",
"HeaderTableOfContents": "Table of Contents", "HeaderTableOfContents": "Table of Contents",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Author (Last, First)", "LabelAuthorLastFirst": "Author (Last, First)",
"LabelAuthors": "Authors", "LabelAuthors": "Authors",
"LabelAutoDownloadEpisodes": "Auto Download Episodes", "LabelAutoDownloadEpisodes": "Auto Download Episodes",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Example", "LabelExample": "Example",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "File", "LabelFile": "File",
"LabelFileBirthtime": "File Birthtime", "LabelFileBirthtime": "File Birthtime",
"LabelFileModified": "File Modified", "LabelFileModified": "File Modified",
@ -337,6 +343,8 @@
"LabelMinute": "Minute", "LabelMinute": "Minute",
"LabelMissing": "Missing", "LabelMissing": "Missing",
"LabelMissingParts": "Missing Parts", "LabelMissingParts": "Missing Parts",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "More", "LabelMore": "More",
"LabelMoreInfo": "More Info", "LabelMoreInfo": "More Info",
"LabelName": "Name", "LabelName": "Name",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located", "LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located",
"LabelUploaderDragAndDrop": "Drag & drop files or folders", "LabelUploaderDragAndDrop": "Drag & drop files or folders",
"LabelUploaderDropFiles": "Drop files", "LabelUploaderDropFiles": "Drop files",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Use chapter track", "LabelUseChapterTrack": "Use chapter track",
"LabelUseFullTrack": "Use full track", "LabelUseFullTrack": "Use full track",
"LabelUser": "User", "LabelUser": "User",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "उपयोगकर्ता {0} को संपादित करें", "ButtonUserEdit": "उपयोगकर्ता {0} को संपादित करें",
"ButtonViewAll": "सभी को देखें", "ButtonViewAll": "सभी को देखें",
"ButtonYes": "हाँ", "ButtonYes": "हाँ",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "खाता", "HeaderAccount": "खाता",
"HeaderAdvanced": "विकसित", "HeaderAdvanced": "विकसित",
"HeaderAppriseNotificationSettings": "Apprise अधिसूचना सेटिंग्स", "HeaderAppriseNotificationSettings": "Apprise अधिसूचना सेटिंग्स",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Author (Last, First)", "LabelAuthorLastFirst": "Author (Last, First)",
"LabelAuthors": "Authors", "LabelAuthors": "Authors",
"LabelAutoDownloadEpisodes": "Auto Download Episodes", "LabelAutoDownloadEpisodes": "Auto Download Episodes",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Example", "LabelExample": "Example",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "File", "LabelFile": "File",
"LabelFileBirthtime": "File Birthtime", "LabelFileBirthtime": "File Birthtime",
"LabelFileModified": "File Modified", "LabelFileModified": "File Modified",
@ -337,6 +343,8 @@
"LabelMinute": "Minute", "LabelMinute": "Minute",
"LabelMissing": "Missing", "LabelMissing": "Missing",
"LabelMissingParts": "Missing Parts", "LabelMissingParts": "Missing Parts",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "More", "LabelMore": "More",
"LabelMoreInfo": "More Info", "LabelMoreInfo": "More Info",
"LabelName": "Name", "LabelName": "Name",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located", "LabelUpdateDetailsHelp": "Allow overwriting of existing details for the selected books when a match is located",
"LabelUploaderDragAndDrop": "Drag & drop files or folders", "LabelUploaderDragAndDrop": "Drag & drop files or folders",
"LabelUploaderDropFiles": "Drop files", "LabelUploaderDropFiles": "Drop files",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Use chapter track", "LabelUseChapterTrack": "Use chapter track",
"LabelUseFullTrack": "Use full track", "LabelUseFullTrack": "Use full track",
"LabelUser": "User", "LabelUser": "User",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Edit user {0}", "ButtonUserEdit": "Edit user {0}",
"ButtonViewAll": "Prikaži sve", "ButtonViewAll": "Prikaži sve",
"ButtonYes": "Da", "ButtonYes": "Da",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Korisnički račun", "HeaderAccount": "Korisnički račun",
"HeaderAdvanced": "Napredno", "HeaderAdvanced": "Napredno",
"HeaderAppriseNotificationSettings": "Apprise Notification Settings", "HeaderAppriseNotificationSettings": "Apprise Notification Settings",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Author (Last, First)", "LabelAuthorLastFirst": "Author (Last, First)",
"LabelAuthors": "Autori", "LabelAuthors": "Autori",
"LabelAutoDownloadEpisodes": "Automatski preuzmi epizode", "LabelAutoDownloadEpisodes": "Automatski preuzmi epizode",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Example", "LabelExample": "Example",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Datoteka", "LabelFile": "Datoteka",
"LabelFileBirthtime": "File Birthtime", "LabelFileBirthtime": "File Birthtime",
"LabelFileModified": "File Modified", "LabelFileModified": "File Modified",
@ -337,6 +343,8 @@
"LabelMinute": "Minuta", "LabelMinute": "Minuta",
"LabelMissing": "Nedostaje", "LabelMissing": "Nedostaje",
"LabelMissingParts": "Nedostajali dijelovi", "LabelMissingParts": "Nedostajali dijelovi",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Više", "LabelMore": "Više",
"LabelMoreInfo": "More Info", "LabelMoreInfo": "More Info",
"LabelName": "Ime", "LabelName": "Ime",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Dozvoli postavljanje novih detalja za odabrane knjige nakon što je match pronađen", "LabelUpdateDetailsHelp": "Dozvoli postavljanje novih detalja za odabrane knjige nakon što je match pronađen",
"LabelUploaderDragAndDrop": "Drag & Drop datoteke ili foldere", "LabelUploaderDragAndDrop": "Drag & Drop datoteke ili foldere",
"LabelUploaderDropFiles": "Ubaci datoteke", "LabelUploaderDropFiles": "Ubaci datoteke",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Koristi poglavlja track", "LabelUseChapterTrack": "Koristi poglavlja track",
"LabelUseFullTrack": "Koristi cijeli track", "LabelUseFullTrack": "Koristi cijeli track",
"LabelUser": "Korisnik", "LabelUser": "Korisnik",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Modifica Utente {0}", "ButtonUserEdit": "Modifica Utente {0}",
"ButtonViewAll": "Mostra Tutto", "ButtonViewAll": "Mostra Tutto",
"ButtonYes": "Si", "ButtonYes": "Si",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Account", "HeaderAccount": "Account",
"HeaderAdvanced": "Avanzate", "HeaderAdvanced": "Avanzate",
"HeaderAppriseNotificationSettings": "Apprendi le impostazioni di Notifica", "HeaderAppriseNotificationSettings": "Apprendi le impostazioni di Notifica",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Autori (Per Cognome)", "LabelAuthorLastFirst": "Autori (Per Cognome)",
"LabelAuthors": "Autori", "LabelAuthors": "Autori",
"LabelAutoDownloadEpisodes": "Auto Download Episodi", "LabelAutoDownloadEpisodes": "Auto Download Episodi",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Esempio", "LabelExample": "Esempio",
"LabelExplicit": "Esplicito", "LabelExplicit": "Esplicito",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "File", "LabelFile": "File",
"LabelFileBirthtime": "Data Creazione", "LabelFileBirthtime": "Data Creazione",
"LabelFileModified": "Ultima modifica", "LabelFileModified": "Ultima modifica",
@ -337,6 +343,8 @@
"LabelMinute": "Minuto", "LabelMinute": "Minuto",
"LabelMissing": "Altro", "LabelMissing": "Altro",
"LabelMissingParts": "Parti rimantenti", "LabelMissingParts": "Parti rimantenti",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Molto", "LabelMore": "Molto",
"LabelMoreInfo": "Più Info", "LabelMoreInfo": "Più Info",
"LabelName": "Nome", "LabelName": "Nome",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Consenti la sovrascrittura dei dettagli esistenti per i libri selezionati quando viene individuata una corrispondenza", "LabelUpdateDetailsHelp": "Consenti la sovrascrittura dei dettagli esistenti per i libri selezionati quando viene individuata una corrispondenza",
"LabelUploaderDragAndDrop": "Drag & drop file o Cartelle", "LabelUploaderDragAndDrop": "Drag & drop file o Cartelle",
"LabelUploaderDropFiles": "Elimina file", "LabelUploaderDropFiles": "Elimina file",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Usa il Capitolo della Traccia", "LabelUseChapterTrack": "Usa il Capitolo della Traccia",
"LabelUseFullTrack": "Usa la traccia totale", "LabelUseFullTrack": "Usa la traccia totale",
"LabelUser": "Utente", "LabelUser": "Utente",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Redaguoti naudotoją {0}", "ButtonUserEdit": "Redaguoti naudotoją {0}",
"ButtonViewAll": "Peržiūrėti visus", "ButtonViewAll": "Peržiūrėti visus",
"ButtonYes": "Taip", "ButtonYes": "Taip",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Paskyra", "HeaderAccount": "Paskyra",
"HeaderAdvanced": "Papildomi", "HeaderAdvanced": "Papildomi",
"HeaderAppriseNotificationSettings": "Apprise pranešimo nustatymai", "HeaderAppriseNotificationSettings": "Apprise pranešimo nustatymai",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Autorius (Pavardė, Vardas)", "LabelAuthorLastFirst": "Autorius (Pavardė, Vardas)",
"LabelAuthors": "Autoriai", "LabelAuthors": "Autoriai",
"LabelAutoDownloadEpisodes": "Automatiškai atsisiųsti epizodus", "LabelAutoDownloadEpisodes": "Automatiškai atsisiųsti epizodus",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Pavyzdys", "LabelExample": "Pavyzdys",
"LabelExplicit": "Suaugusiems", "LabelExplicit": "Suaugusiems",
"LabelFeedURL": "Srauto URL", "LabelFeedURL": "Srauto URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Failas", "LabelFile": "Failas",
"LabelFileBirthtime": "Failo kūrimo laikas", "LabelFileBirthtime": "Failo kūrimo laikas",
"LabelFileModified": "Failo keitimo laikas", "LabelFileModified": "Failo keitimo laikas",
@ -337,6 +343,8 @@
"LabelMinute": "Minutė", "LabelMinute": "Minutė",
"LabelMissing": "Trūksta", "LabelMissing": "Trūksta",
"LabelMissingParts": "Trūkstamos dalys", "LabelMissingParts": "Trūkstamos dalys",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Daugiau", "LabelMore": "Daugiau",
"LabelMoreInfo": "Daugiau informacijos", "LabelMoreInfo": "Daugiau informacijos",
"LabelName": "Pavadinimas", "LabelName": "Pavadinimas",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Leisti perrašyti esamus duomenis pasirinktoms knygoms, kai yra rasta atitikmenų", "LabelUpdateDetailsHelp": "Leisti perrašyti esamus duomenis pasirinktoms knygoms, kai yra rasta atitikmenų",
"LabelUploaderDragAndDrop": "Tempkite ir paleiskite failus ar aplankus", "LabelUploaderDragAndDrop": "Tempkite ir paleiskite failus ar aplankus",
"LabelUploaderDropFiles": "Nutempti failus", "LabelUploaderDropFiles": "Nutempti failus",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Naudoti skyrių takelį", "LabelUseChapterTrack": "Naudoti skyrių takelį",
"LabelUseFullTrack": "Naudoti visą takelį", "LabelUseFullTrack": "Naudoti visą takelį",
"LabelUser": "Vartotojas", "LabelUser": "Vartotojas",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Wijzig gebruiker {0}", "ButtonUserEdit": "Wijzig gebruiker {0}",
"ButtonViewAll": "Toon alle", "ButtonViewAll": "Toon alle",
"ButtonYes": "Ja", "ButtonYes": "Ja",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Account", "HeaderAccount": "Account",
"HeaderAdvanced": "Geavanceerd", "HeaderAdvanced": "Geavanceerd",
"HeaderAppriseNotificationSettings": "Apprise-notificatie instellingen", "HeaderAppriseNotificationSettings": "Apprise-notificatie instellingen",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Auteur (Achternaam, Voornaam)", "LabelAuthorLastFirst": "Auteur (Achternaam, Voornaam)",
"LabelAuthors": "Auteurs", "LabelAuthors": "Auteurs",
"LabelAutoDownloadEpisodes": "Afleveringen automatisch downloaden", "LabelAutoDownloadEpisodes": "Afleveringen automatisch downloaden",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Voorbeeld", "LabelExample": "Voorbeeld",
"LabelExplicit": "Expliciet", "LabelExplicit": "Expliciet",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Bestand", "LabelFile": "Bestand",
"LabelFileBirthtime": "Aanmaaktijd bestand", "LabelFileBirthtime": "Aanmaaktijd bestand",
"LabelFileModified": "Bestand gewijzigd", "LabelFileModified": "Bestand gewijzigd",
@ -337,6 +343,8 @@
"LabelMinute": "Minuut", "LabelMinute": "Minuut",
"LabelMissing": "Ontbrekend", "LabelMissing": "Ontbrekend",
"LabelMissingParts": "Ontbrekende delen", "LabelMissingParts": "Ontbrekende delen",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Meer", "LabelMore": "Meer",
"LabelMoreInfo": "Meer info", "LabelMoreInfo": "Meer info",
"LabelName": "Naam", "LabelName": "Naam",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Sta overschrijven van bestaande details toe voor de geselecteerde boeken wanneer een match is gevonden", "LabelUpdateDetailsHelp": "Sta overschrijven van bestaande details toe voor de geselecteerde boeken wanneer een match is gevonden",
"LabelUploaderDragAndDrop": "Slepen & neerzeten van bestanden of mappen", "LabelUploaderDragAndDrop": "Slepen & neerzeten van bestanden of mappen",
"LabelUploaderDropFiles": "Bestanden neerzetten", "LabelUploaderDropFiles": "Bestanden neerzetten",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Gebruik hoofdstuktrack", "LabelUseChapterTrack": "Gebruik hoofdstuktrack",
"LabelUseFullTrack": "Gebruik volledige track", "LabelUseFullTrack": "Gebruik volledige track",
"LabelUser": "Gebruiker", "LabelUser": "Gebruiker",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Rediger bruker {0}", "ButtonUserEdit": "Rediger bruker {0}",
"ButtonViewAll": "Vis alt", "ButtonViewAll": "Vis alt",
"ButtonYes": "Ja", "ButtonYes": "Ja",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Konto", "HeaderAccount": "Konto",
"HeaderAdvanced": "Avansert", "HeaderAdvanced": "Avansert",
"HeaderAppriseNotificationSettings": "Apprise notifikasjonsinstillinger", "HeaderAppriseNotificationSettings": "Apprise notifikasjonsinstillinger",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Forfatter (Etternavn Fornavn)", "LabelAuthorLastFirst": "Forfatter (Etternavn Fornavn)",
"LabelAuthors": "Forfattere", "LabelAuthors": "Forfattere",
"LabelAutoDownloadEpisodes": "Last ned episoder automatisk", "LabelAutoDownloadEpisodes": "Last ned episoder automatisk",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Eksempel", "LabelExample": "Eksempel",
"LabelExplicit": "Eksplisitt", "LabelExplicit": "Eksplisitt",
"LabelFeedURL": "Feed Adresse", "LabelFeedURL": "Feed Adresse",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Fil", "LabelFile": "Fil",
"LabelFileBirthtime": "Fil Opprettelsesdato", "LabelFileBirthtime": "Fil Opprettelsesdato",
"LabelFileModified": "Fil Endret", "LabelFileModified": "Fil Endret",
@ -337,6 +343,8 @@
"LabelMinute": "Minutt", "LabelMinute": "Minutt",
"LabelMissing": "Mangler", "LabelMissing": "Mangler",
"LabelMissingParts": "Manglende deler", "LabelMissingParts": "Manglende deler",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Mer", "LabelMore": "Mer",
"LabelMoreInfo": "Mer info", "LabelMoreInfo": "Mer info",
"LabelName": "Navn", "LabelName": "Navn",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Tillat overskriving av eksisterende detaljer for de valgte bøkene når en lik bok er funnet", "LabelUpdateDetailsHelp": "Tillat overskriving av eksisterende detaljer for de valgte bøkene når en lik bok er funnet",
"LabelUploaderDragAndDrop": "Dra og slipp filer eller mapper", "LabelUploaderDragAndDrop": "Dra og slipp filer eller mapper",
"LabelUploaderDropFiles": "Slipp filer", "LabelUploaderDropFiles": "Slipp filer",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Bruk kapittelspor", "LabelUseChapterTrack": "Bruk kapittelspor",
"LabelUseFullTrack": "Bruke hele sporet", "LabelUseFullTrack": "Bruke hele sporet",
"LabelUser": "Bruker", "LabelUser": "Bruker",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Edit user {0}", "ButtonUserEdit": "Edit user {0}",
"ButtonViewAll": "Zobacz wszystko", "ButtonViewAll": "Zobacz wszystko",
"ButtonYes": "Tak", "ButtonYes": "Tak",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Konto", "HeaderAccount": "Konto",
"HeaderAdvanced": "Zaawansowane", "HeaderAdvanced": "Zaawansowane",
"HeaderAppriseNotificationSettings": "Ustawienia powiadomień Apprise", "HeaderAppriseNotificationSettings": "Ustawienia powiadomień Apprise",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Author (Malejąco)", "LabelAuthorLastFirst": "Author (Malejąco)",
"LabelAuthors": "Autorzy", "LabelAuthors": "Autorzy",
"LabelAutoDownloadEpisodes": "Automatyczne pobieranie odcinków", "LabelAutoDownloadEpisodes": "Automatyczne pobieranie odcinków",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Example", "LabelExample": "Example",
"LabelExplicit": "Nieprzyzwoite", "LabelExplicit": "Nieprzyzwoite",
"LabelFeedURL": "URL kanału", "LabelFeedURL": "URL kanału",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Plik", "LabelFile": "Plik",
"LabelFileBirthtime": "Data utworzenia pliku", "LabelFileBirthtime": "Data utworzenia pliku",
"LabelFileModified": "Data modyfikacji pliku", "LabelFileModified": "Data modyfikacji pliku",
@ -337,6 +343,8 @@
"LabelMinute": "Minuta", "LabelMinute": "Minuta",
"LabelMissing": "Brakujący", "LabelMissing": "Brakujący",
"LabelMissingParts": "Brakujące cześci", "LabelMissingParts": "Brakujące cześci",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Więcej", "LabelMore": "Więcej",
"LabelMoreInfo": "More Info", "LabelMoreInfo": "More Info",
"LabelName": "Nazwa", "LabelName": "Nazwa",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Umożliwienie nadpisania istniejących szczegółów dla wybranych książek w przypadku znalezienia dopasowania", "LabelUpdateDetailsHelp": "Umożliwienie nadpisania istniejących szczegółów dla wybranych książek w przypadku znalezienia dopasowania",
"LabelUploaderDragAndDrop": "Przeciągnij i puść foldery lub pliki", "LabelUploaderDragAndDrop": "Przeciągnij i puść foldery lub pliki",
"LabelUploaderDropFiles": "Puść pliki", "LabelUploaderDropFiles": "Puść pliki",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Użyj ścieżki rozdziału", "LabelUseChapterTrack": "Użyj ścieżki rozdziału",
"LabelUseFullTrack": "Użycie ścieżki rozdziału", "LabelUseFullTrack": "Użycie ścieżki rozdziału",
"LabelUser": "Użytkownik", "LabelUser": "Użytkownik",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Редактировать пользователя {0}", "ButtonUserEdit": "Редактировать пользователя {0}",
"ButtonViewAll": "Посмотреть все", "ButtonViewAll": "Посмотреть все",
"ButtonYes": "Да", "ButtonYes": "Да",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Учетная запись", "HeaderAccount": "Учетная запись",
"HeaderAdvanced": "Дополнительно", "HeaderAdvanced": "Дополнительно",
"HeaderAppriseNotificationSettings": "Настройки оповещений", "HeaderAppriseNotificationSettings": "Настройки оповещений",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Автор (Фамилия, Имя)", "LabelAuthorLastFirst": "Автор (Фамилия, Имя)",
"LabelAuthors": "Авторы", "LabelAuthors": "Авторы",
"LabelAutoDownloadEpisodes": "Скачивать эпизоды автоматически", "LabelAutoDownloadEpisodes": "Скачивать эпизоды автоматически",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Пример", "LabelExample": "Пример",
"LabelExplicit": "Явный", "LabelExplicit": "Явный",
"LabelFeedURL": "URL канала", "LabelFeedURL": "URL канала",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Файл", "LabelFile": "Файл",
"LabelFileBirthtime": "Дата создания", "LabelFileBirthtime": "Дата создания",
"LabelFileModified": "Дата модификации", "LabelFileModified": "Дата модификации",
@ -337,6 +343,8 @@
"LabelMinute": "Минуты", "LabelMinute": "Минуты",
"LabelMissing": "Потеряно", "LabelMissing": "Потеряно",
"LabelMissingParts": "Потерянные части", "LabelMissingParts": "Потерянные части",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Еще", "LabelMore": "Еще",
"LabelMoreInfo": "Больше информации", "LabelMoreInfo": "Больше информации",
"LabelName": "Имя", "LabelName": "Имя",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Позволяет перезаписывать текущие подробности для выбранных книг если будут найдены", "LabelUpdateDetailsHelp": "Позволяет перезаписывать текущие подробности для выбранных книг если будут найдены",
"LabelUploaderDragAndDrop": "Перетащите файлы или каталоги", "LabelUploaderDragAndDrop": "Перетащите файлы или каталоги",
"LabelUploaderDropFiles": "Перетащите файлы", "LabelUploaderDropFiles": "Перетащите файлы",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Показывать время главы", "LabelUseChapterTrack": "Показывать время главы",
"LabelUseFullTrack": "Показывать время книги", "LabelUseFullTrack": "Показывать время книги",
"LabelUser": "Пользователь", "LabelUser": "Пользователь",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "Redigera användare {0}", "ButtonUserEdit": "Redigera användare {0}",
"ButtonViewAll": "Visa alla", "ButtonViewAll": "Visa alla",
"ButtonYes": "Ja", "ButtonYes": "Ja",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Konto", "HeaderAccount": "Konto",
"HeaderAdvanced": "Avancerad", "HeaderAdvanced": "Avancerad",
"HeaderAppriseNotificationSettings": "Apprise Meddelandeinställningar", "HeaderAppriseNotificationSettings": "Apprise Meddelandeinställningar",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "Författare (Efternamn, Förnamn)", "LabelAuthorLastFirst": "Författare (Efternamn, Förnamn)",
"LabelAuthors": "Författare", "LabelAuthors": "Författare",
"LabelAutoDownloadEpisodes": "Automatisk nedladdning av avsnitt", "LabelAutoDownloadEpisodes": "Automatisk nedladdning av avsnitt",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "Exempel", "LabelExample": "Exempel",
"LabelExplicit": "Explicit", "LabelExplicit": "Explicit",
"LabelFeedURL": "Flödes-URL", "LabelFeedURL": "Flödes-URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "Fil", "LabelFile": "Fil",
"LabelFileBirthtime": "Födelse-tidpunkt för fil", "LabelFileBirthtime": "Födelse-tidpunkt för fil",
"LabelFileModified": "Fil ändrad", "LabelFileModified": "Fil ändrad",
@ -337,6 +343,8 @@
"LabelMinute": "Minut", "LabelMinute": "Minut",
"LabelMissing": "Saknad", "LabelMissing": "Saknad",
"LabelMissingParts": "Saknade delar", "LabelMissingParts": "Saknade delar",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "Mer", "LabelMore": "Mer",
"LabelMoreInfo": "Mer information", "LabelMoreInfo": "Mer information",
"LabelName": "Namn", "LabelName": "Namn",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "Tillåt överskrivning av befintliga detaljer för de valda böckerna när en matchning hittas", "LabelUpdateDetailsHelp": "Tillåt överskrivning av befintliga detaljer för de valda böckerna när en matchning hittas",
"LabelUploaderDragAndDrop": "Dra och släpp filer eller mappar", "LabelUploaderDragAndDrop": "Dra och släpp filer eller mappar",
"LabelUploaderDropFiles": "Släpp filer", "LabelUploaderDropFiles": "Släpp filer",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "Använd kapitelspår", "LabelUseChapterTrack": "Använd kapitelspår",
"LabelUseFullTrack": "Använd hela spåret", "LabelUseFullTrack": "Använd hela spåret",
"LabelUser": "Användare", "LabelUser": "Användare",

View File

@ -87,6 +87,9 @@
"ButtonUserEdit": "编辑用户 {0}", "ButtonUserEdit": "编辑用户 {0}",
"ButtonViewAll": "查看全部", "ButtonViewAll": "查看全部",
"ButtonYes": "确定", "ButtonYes": "确定",
"ErrorUploadFetchMetadataAPI": "Error fetching metadata",
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "帐户", "HeaderAccount": "帐户",
"HeaderAdvanced": "高级", "HeaderAdvanced": "高级",
"HeaderAppriseNotificationSettings": "测试通知设置", "HeaderAppriseNotificationSettings": "测试通知设置",
@ -196,6 +199,8 @@
"LabelAuthorLastFirst": "作者 (名, 姓)", "LabelAuthorLastFirst": "作者 (名, 姓)",
"LabelAuthors": "作者", "LabelAuthors": "作者",
"LabelAutoDownloadEpisodes": "自动下载剧集", "LabelAutoDownloadEpisodes": "自动下载剧集",
"LabelAutoFetchMetadata": "Auto Fetch Metadata",
"LabelAutoFetchMetadataHelp": "Fetches metadata for title, author, and series to streamline uploading. Additional metadata may have to be matched after upload.",
"LabelAutoLaunch": "Auto Launch", "LabelAutoLaunch": "Auto Launch",
"LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Auto Register", "LabelAutoRegister": "Auto Register",
@ -266,6 +271,7 @@
"LabelExample": "示例", "LabelExample": "示例",
"LabelExplicit": "信息准确", "LabelExplicit": "信息准确",
"LabelFeedURL": "源 URL", "LabelFeedURL": "源 URL",
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "文件", "LabelFile": "文件",
"LabelFileBirthtime": "文件创建时间", "LabelFileBirthtime": "文件创建时间",
"LabelFileModified": "文件修改时间", "LabelFileModified": "文件修改时间",
@ -337,6 +343,8 @@
"LabelMinute": "分钟", "LabelMinute": "分钟",
"LabelMissing": "丢失", "LabelMissing": "丢失",
"LabelMissingParts": "丢失的部分", "LabelMissingParts": "丢失的部分",
"LabelMobileRedirectURIs": "Allowed Mobile Redirect URIs",
"LabelMobileRedirectURIsDescription": "This is a whitelist of valid redirect URIs for mobile apps. The default one is <code>audiobookshelf://oauth</code>, which you can remove or supplement with additional URIs for third-party app integration. Using an asterisk (<code>*</code>) as the sole entry permits any URI.",
"LabelMore": "更多", "LabelMore": "更多",
"LabelMoreInfo": "更多..", "LabelMoreInfo": "更多..",
"LabelName": "名称", "LabelName": "名称",
@ -515,6 +523,7 @@
"LabelUpdateDetailsHelp": "找到匹配项时允许覆盖所选书籍存在的详细信息", "LabelUpdateDetailsHelp": "找到匹配项时允许覆盖所选书籍存在的详细信息",
"LabelUploaderDragAndDrop": "拖放文件或文件夹", "LabelUploaderDragAndDrop": "拖放文件或文件夹",
"LabelUploaderDropFiles": "删除文件", "LabelUploaderDropFiles": "删除文件",
"LabelUploaderItemFetchMetadataHelp": "Automatically fetch title, author, and series",
"LabelUseChapterTrack": "使用章节音轨", "LabelUseChapterTrack": "使用章节音轨",
"LabelUseFullTrack": "使用完整音轨", "LabelUseFullTrack": "使用完整音轨",
"LabelUser": "用户", "LabelUser": "用户",

View File

@ -8,6 +8,7 @@ const ExtractJwt = require('passport-jwt').ExtractJwt
const OpenIDClient = require('openid-client') const OpenIDClient = require('openid-client')
const Database = require('./Database') const Database = require('./Database')
const Logger = require('./Logger') const Logger = require('./Logger')
const e = require('express')
/** /**
* @class Class for handling all the authentication related functionality. * @class Class for handling all the authentication related functionality.
@ -15,6 +16,8 @@ const Logger = require('./Logger')
class Auth { class Auth {
constructor() { constructor() {
// Map of openId sessions indexed by oauth2 state-variable
this.openIdAuthSession = new Map()
} }
/** /**
@ -187,9 +190,10 @@ class Auth {
* @param {import('express').Response} res * @param {import('express').Response} res
*/ */
paramsToCookies(req, res) { paramsToCookies(req, res) {
if (req.query.isRest?.toLowerCase() == 'true') { // Set if isRest flag is set or if mobile oauth flow is used
if (req.query.isRest?.toLowerCase() == 'true' || req.query.redirect_uri) {
// store the isRest flag to the is_rest cookie // store the isRest flag to the is_rest cookie
res.cookie('is_rest', req.query.isRest.toLowerCase(), { res.cookie('is_rest', 'true', {
maxAge: 120000, // 2 min maxAge: 120000, // 2 min
httpOnly: true httpOnly: true
}) })
@ -283,8 +287,27 @@ class Auth {
// for API or mobile clients // for API or mobile clients
const oidcStrategy = passport._strategy('openid-client') const oidcStrategy = passport._strategy('openid-client')
const protocol = (req.secure || req.get('x-forwarded-proto') === 'https') ? 'https' : 'http' const protocol = (req.secure || req.get('x-forwarded-proto') === 'https') ? 'https' : 'http'
oidcStrategy._params.redirect_uri = new URL(`${protocol}://${req.get('host')}/auth/openid/callback`).toString()
Logger.debug(`[Auth] Set oidc redirect_uri=${oidcStrategy._params.redirect_uri}`) let mobile_redirect_uri = null
// The client wishes a different redirect_uri
// We will allow if it is in the whitelist, by saving it into this.openIdAuthSession and setting the redirect uri to /auth/openid/mobile-redirect
// where we will handle the redirect to it
if (req.query.redirect_uri) {
// Check if the redirect_uri is in the whitelist
if (Database.serverSettings.authOpenIDMobileRedirectURIs.includes(req.query.redirect_uri) ||
(Database.serverSettings.authOpenIDMobileRedirectURIs.length === 1 && Database.serverSettings.authOpenIDMobileRedirectURIs[0] === '*')) {
oidcStrategy._params.redirect_uri = new URL(`${protocol}://${req.get('host')}/auth/openid/mobile-redirect`).toString()
mobile_redirect_uri = req.query.redirect_uri
} else {
Logger.debug(`[Auth] Invalid redirect_uri=${req.query.redirect_uri} - not in whitelist`)
return res.status(400).send('Invalid redirect_uri')
}
} else {
oidcStrategy._params.redirect_uri = new URL(`${protocol}://${req.get('host')}/auth/openid/callback`).toString()
}
Logger.debug(`[Auth] Oidc redirect_uri=${oidcStrategy._params.redirect_uri}`)
const client = oidcStrategy._client const client = oidcStrategy._client
const sessionKey = oidcStrategy._key const sessionKey = oidcStrategy._key
@ -324,16 +347,21 @@ class Auth {
req.session[sessionKey] = { req.session[sessionKey] = {
...req.session[sessionKey], ...req.session[sessionKey],
...pick(params, 'nonce', 'state', 'max_age', 'response_type'), ...pick(params, 'nonce', 'state', 'max_age', 'response_type'),
mobile: req.query.isRest?.toLowerCase() === 'true' // Used in the abs callback later mobile: req.query.redirect_uri, // Used in the abs callback later, set mobile if redirect_uri is filled out
sso_redirect_uri: oidcStrategy._params.redirect_uri // Save the redirect_uri (for the SSO Provider) for the callback
} }
// We cannot save redirect_uri in the session, because it the mobile client uses browser instead of the API
// for the request to mobile-redirect and as such the session is not shared
this.openIdAuthSession.set(params.state, { mobile_redirect_uri: mobile_redirect_uri })
// Now get the URL to direct to // Now get the URL to direct to
const authorizationUrl = client.authorizationUrl({ const authorizationUrl = client.authorizationUrl({
...params, ...params,
scope: 'openid profile email', scope: 'openid profile email',
response_type: 'code', response_type: 'code',
code_challenge, code_challenge,
code_challenge_method, code_challenge_method
}) })
// params (isRest, callback) to a cookie that will be send to the client // params (isRest, callback) to a cookie that will be send to the client
@ -347,6 +375,37 @@ class Auth {
} }
}) })
// This will be the oauth2 callback route for mobile clients
// It will redirect to an app-link like audiobookshelf://oauth
router.get('/auth/openid/mobile-redirect', (req, res) => {
try {
// Extract the state parameter from the request
const { state, code } = req.query
// Check if the state provided is in our list
if (!state || !this.openIdAuthSession.has(state)) {
Logger.error('[Auth] /auth/openid/mobile-redirect route: State parameter mismatch')
return res.status(400).send('State parameter mismatch')
}
let mobile_redirect_uri = this.openIdAuthSession.get(state).mobile_redirect_uri
if (!mobile_redirect_uri) {
Logger.error('[Auth] No redirect URI')
return res.status(400).send('No redirect URI')
}
this.openIdAuthSession.delete(state)
const redirectUri = `${mobile_redirect_uri}?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`
// Redirect to the overwrite URI saved in the map
res.redirect(redirectUri)
} catch (error) {
Logger.error(`[Auth] Error in /auth/openid/mobile-redirect route: ${error}`)
res.status(500).send('Internal Server Error')
}
})
// openid strategy callback route (this receives the token from the configured openid login provider) // openid strategy callback route (this receives the token from the configured openid login provider)
router.get('/auth/openid/callback', (req, res, next) => { router.get('/auth/openid/callback', (req, res, next) => {
const oidcStrategy = passport._strategy('openid-client') const oidcStrategy = passport._strategy('openid-client')
@ -403,11 +462,8 @@ class Auth {
// While not required by the standard, the passport plugin re-sends the original redirect_uri in the token request // While not required by the standard, the passport plugin re-sends the original redirect_uri in the token request
// We need to set it correctly, as some SSO providers (e.g. keycloak) check that parameter when it is provided // We need to set it correctly, as some SSO providers (e.g. keycloak) check that parameter when it is provided
if (req.session[sessionKey].mobile) { // We set it here again because the passport param can change between requests
return passport.authenticate('openid-client', { redirect_uri: 'audiobookshelf://oauth' }, passportCallback(req, res, next))(req, res, next) return passport.authenticate('openid-client', { redirect_uri: req.session[sessionKey].sso_redirect_uri }, passportCallback(req, res, next))(req, res, next)
} else {
return passport.authenticate('openid-client', passportCallback(req, res, next))(req, res, next)
}
}, },
// on a successfull login: read the cookies and react like the client requested (callback or json) // on a successfull login: read the cookies and react like the client requested (callback or json)
this.handleLoginSuccessBasedOnCookie.bind(this)) this.handleLoginSuccessBasedOnCookie.bind(this))
@ -542,13 +598,13 @@ class Auth {
// Load the user given it's username // Load the user given it's username
const user = await Database.userModel.getUserByUsername(username.toLowerCase()) const user = await Database.userModel.getUserByUsername(username.toLowerCase())
if (!user || !user.isActive) { if (!user?.isActive) {
done(null, null) done(null, null)
return return
} }
// Check passwordless root user // Check passwordless root user
if (user.type === 'root' && (!user.pash || user.pash === '')) { if (user.type === 'root' && !user.pash) {
if (password) { if (password) {
// deny login // deny login
done(null, null) done(null, null)
@ -557,6 +613,10 @@ class Auth {
// approve login // approve login
done(null, user) done(null, user)
return return
} else if (!user.pash) {
Logger.error(`[Auth] User "${user.username}"/"${user.type}" attempted to login without a password set`)
done(null, null)
return
} }
// Check password match // Check password match

View File

@ -8,6 +8,7 @@ const Database = require('../Database')
const libraryItemFilters = require('../utils/queries/libraryItemFilters') const libraryItemFilters = require('../utils/queries/libraryItemFilters')
const patternValidation = require('../libs/nodeCron/pattern-validation') const patternValidation = require('../libs/nodeCron/pattern-validation')
const { isObject, getTitleIgnorePrefix } = require('../utils/index') const { isObject, getTitleIgnorePrefix } = require('../utils/index')
const { sanitizeFilename } = require('../utils/fileUtils')
const TaskManager = require('../managers/TaskManager') const TaskManager = require('../managers/TaskManager')
@ -32,12 +33,9 @@ class MiscController {
Logger.error('Invalid request, no files') Logger.error('Invalid request, no files')
return res.sendStatus(400) return res.sendStatus(400)
} }
const files = Object.values(req.files) const files = Object.values(req.files)
const title = req.body.title const { title, author, series, folder: folderId, library: libraryId } = req.body
const author = req.body.author
const series = req.body.series
const libraryId = req.body.library
const folderId = req.body.folder
const library = await Database.libraryModel.getOldById(libraryId) const library = await Database.libraryModel.getOldById(libraryId)
if (!library) { if (!library) {
@ -52,43 +50,29 @@ class MiscController {
return res.status(500).send(`Invalid post data`) return res.status(500).send(`Invalid post data`)
} }
// For setting permissions recursively // Podcasts should only be one folder deep
let outputDirectory = '' const outputDirectoryParts = library.isPodcast ? [title] : [author, series, title]
let firstDirPath = '' // `.filter(Boolean)` to strip out all the potentially missing details (eg: `author`)
// before sanitizing all the directory parts to remove illegal chars and finally prepending
if (library.isPodcast) { // Podcasts only in 1 folder // the base folder path
outputDirectory = Path.join(folder.fullPath, title) const cleanedOutputDirectoryParts = outputDirectoryParts.filter(Boolean).map(part => sanitizeFilename(part))
firstDirPath = outputDirectory const outputDirectory = Path.join(...[folder.fullPath, ...cleanedOutputDirectoryParts])
} else {
firstDirPath = Path.join(folder.fullPath, author)
if (series && author) {
outputDirectory = Path.join(folder.fullPath, author, series, title)
} else if (author) {
outputDirectory = Path.join(folder.fullPath, author, title)
} else {
outputDirectory = Path.join(folder.fullPath, title)
}
}
if (await fs.pathExists(outputDirectory)) {
Logger.error(`[Server] Upload directory "${outputDirectory}" already exists`)
return res.status(500).send(`Directory "${outputDirectory}" already exists`)
}
await fs.ensureDir(outputDirectory) await fs.ensureDir(outputDirectory)
Logger.info(`Uploading ${files.length} files to`, outputDirectory) Logger.info(`Uploading ${files.length} files to`, outputDirectory)
for (let i = 0; i < files.length; i++) { for (const file of files) {
var file = files[i] const path = Path.join(outputDirectory, sanitizeFilename(file.name))
var path = Path.join(outputDirectory, file.name) await file.mv(path)
await file.mv(path).then(() => { .then(() => {
return true return true
}).catch((error) => { })
Logger.error('Failed to move file', path, error) .catch((error) => {
return false Logger.error('Failed to move file', path, error)
}) return false
})
} }
res.sendStatus(200) res.sendStatus(200)
@ -645,6 +629,27 @@ class MiscController {
} else { } else {
Logger.warn(`[MiscController] Invalid value for authActiveAuthMethods`) Logger.warn(`[MiscController] Invalid value for authActiveAuthMethods`)
} }
} else if (key === 'authOpenIDMobileRedirectURIs') {
function isValidRedirectURI(uri) {
if (typeof uri !== 'string') return false
const pattern = new RegExp('^\\w+://[\\w.-]+$', 'i')
return pattern.test(uri)
}
const uris = settingsUpdate[key]
if (!Array.isArray(uris) ||
(uris.includes('*') && uris.length > 1) ||
uris.some(uri => uri !== '*' && !isValidRedirectURI(uri))) {
Logger.warn(`[MiscController] Invalid value for authOpenIDMobileRedirectURIs`)
continue
}
// Update the URIs
if (Database.serverSettings[key].some(uri => !uris.includes(uri)) || uris.some(uri => !Database.serverSettings[key].includes(uri))) {
Logger.debug(`[MiscController] Updating auth settings key "${key}" from "${Database.serverSettings[key]}" to "${uris}"`)
Database.serverSettings[key] = uris
hasUpdates = true
}
} else { } else {
const updatedValueType = typeof settingsUpdate[key] const updatedValueType = typeof settingsUpdate[key]
if (['authOpenIDAutoLaunch', 'authOpenIDAutoRegister'].includes(key)) { if (['authOpenIDAutoLaunch', 'authOpenIDAutoRegister'].includes(key)) {
@ -687,8 +692,9 @@ class MiscController {
} }
res.json({ res.json({
updated: hasUpdates,
serverSettings: Database.serverSettings.toJSONForBrowser() serverSettings: Database.serverSettings.toJSONForBrowser()
}) })
} }
} }
module.exports = new MiscController() module.exports = new MiscController()

View File

@ -13,7 +13,7 @@ class ApiCacheManager {
} }
init(database = Database) { init(database = Database) {
let hooks = ['afterCreate', 'afterUpdate', 'afterDestroy', 'afterBulkCreate', 'afterBulkUpdate', 'afterBulkDestroy'] let hooks = ['afterCreate', 'afterUpdate', 'afterDestroy', 'afterBulkCreate', 'afterBulkUpdate', 'afterBulkDestroy', 'afterUpsert']
hooks.forEach(hook => database.sequelize.addHook(hook, (model) => this.clear(model, hook))) hooks.forEach(hook => database.sequelize.addHook(hook, (model) => this.clear(model, hook)))
} }

View File

@ -71,6 +71,7 @@ class ServerSettings {
this.authOpenIDAutoLaunch = false this.authOpenIDAutoLaunch = false
this.authOpenIDAutoRegister = false this.authOpenIDAutoRegister = false
this.authOpenIDMatchExistingBy = null this.authOpenIDMatchExistingBy = null
this.authOpenIDMobileRedirectURIs = ['audiobookshelf://oauth']
if (settings) { if (settings) {
this.construct(settings) this.construct(settings)
@ -126,6 +127,7 @@ class ServerSettings {
this.authOpenIDAutoLaunch = !!settings.authOpenIDAutoLaunch this.authOpenIDAutoLaunch = !!settings.authOpenIDAutoLaunch
this.authOpenIDAutoRegister = !!settings.authOpenIDAutoRegister this.authOpenIDAutoRegister = !!settings.authOpenIDAutoRegister
this.authOpenIDMatchExistingBy = settings.authOpenIDMatchExistingBy || null this.authOpenIDMatchExistingBy = settings.authOpenIDMatchExistingBy || null
this.authOpenIDMobileRedirectURIs = settings.authOpenIDMobileRedirectURIs || ['audiobookshelf://oauth']
if (!Array.isArray(this.authActiveAuthMethods)) { if (!Array.isArray(this.authActiveAuthMethods)) {
this.authActiveAuthMethods = ['local'] this.authActiveAuthMethods = ['local']
@ -211,7 +213,8 @@ class ServerSettings {
authOpenIDButtonText: this.authOpenIDButtonText, authOpenIDButtonText: this.authOpenIDButtonText,
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch, authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
authOpenIDAutoRegister: this.authOpenIDAutoRegister, authOpenIDAutoRegister: this.authOpenIDAutoRegister,
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs // Do not return to client
} }
} }
@ -220,6 +223,7 @@ class ServerSettings {
delete json.tokenSecret delete json.tokenSecret
delete json.authOpenIDClientID delete json.authOpenIDClientID
delete json.authOpenIDClientSecret delete json.authOpenIDClientSecret
delete json.authOpenIDMobileRedirectURIs
return json return json
} }
@ -254,7 +258,8 @@ class ServerSettings {
authOpenIDButtonText: this.authOpenIDButtonText, authOpenIDButtonText: this.authOpenIDButtonText,
authOpenIDAutoLaunch: this.authOpenIDAutoLaunch, authOpenIDAutoLaunch: this.authOpenIDAutoLaunch,
authOpenIDAutoRegister: this.authOpenIDAutoRegister, authOpenIDAutoRegister: this.authOpenIDAutoRegister,
authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy authOpenIDMatchExistingBy: this.authOpenIDMatchExistingBy,
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs // Do not return to client
} }
} }

View File

@ -18,6 +18,27 @@ class Audible {
} }
} }
/**
* Audible will sometimes send sequences with "Book 1" or "2, Dramatized Adaptation"
* @see https://github.com/advplyr/audiobookshelf/issues/2380
* @see https://github.com/advplyr/audiobookshelf/issues/1339
*
* @param {string} seriesName
* @param {string} sequence
* @returns {string}
*/
cleanSeriesSequence(seriesName, sequence) {
if (!sequence) return ''
let updatedSequence = sequence.replace(/Book /, '').trim()
if (updatedSequence.includes(' ')) {
updatedSequence = updatedSequence.split(' ').shift().replace(/,$/, '')
}
if (sequence !== updatedSequence) {
Logger.debug(`[Audible] Series "${seriesName}" sequence was cleaned from "${sequence}" to "${updatedSequence}"`)
}
return updatedSequence
}
cleanResult(item) { cleanResult(item) {
const { title, subtitle, asin, authors, narrators, publisherName, summary, releaseDate, image, genres, seriesPrimary, seriesSecondary, language, runtimeLengthMin, formatType } = item const { title, subtitle, asin, authors, narrators, publisherName, summary, releaseDate, image, genres, seriesPrimary, seriesSecondary, language, runtimeLengthMin, formatType } = item
@ -25,13 +46,13 @@ class Audible {
if (seriesPrimary) { if (seriesPrimary) {
series.push({ series.push({
series: seriesPrimary.name, series: seriesPrimary.name,
sequence: (seriesPrimary.position || '').replace(/Book /, '') // Can be badly formatted see #1339 sequence: this.cleanSeriesSequence(seriesPrimary.name, seriesPrimary.position || '')
}) })
} }
if (seriesSecondary) { if (seriesSecondary) {
series.push({ series.push({
series: seriesSecondary.name, series: seriesSecondary.name,
sequence: (seriesSecondary.position || '').replace(/Book /, '') sequence: this.cleanSeriesSequence(seriesSecondary.name, seriesSecondary.position || '')
}) })
} }
@ -64,7 +85,7 @@ class Audible {
} }
asinSearch(asin, region) { asinSearch(asin, region) {
asin = encodeURIComponent(asin); asin = encodeURIComponent(asin)
var regionQuery = region ? `?region=${region}` : '' var regionQuery = region ? `?region=${region}` : ''
var url = `https://api.audnex.us/books/${asin}${regionQuery}` var url = `https://api.audnex.us/books/${asin}${regionQuery}`
Logger.debug(`[Audible] ASIN url: ${url}`) Logger.debug(`[Audible] ASIN url: ${url}`)

View File

@ -308,6 +308,7 @@ module.exports.sanitizeFilename = (filename, colonReplacement = ' - ') => {
.replace(lineBreaks, replacement) .replace(lineBreaks, replacement)
.replace(windowsReservedRe, replacement) .replace(windowsReservedRe, replacement)
.replace(windowsTrailingRe, replacement) .replace(windowsTrailingRe, replacement)
.replace(/\s+/g, ' ') // Replace consecutive spaces with a single space
// Check if basename is too many bytes // Check if basename is too many bytes
const ext = Path.extname(sanitized) // separate out file extension const ext = Path.extname(sanitized) // separate out file extension