Translation strings added (#3304)

* Update: `pages/items/_id` toast messages

* Update: account modal strings

* Update: audio file data modal strings

* Update: sleep timer set string

* Update: loading indicator string

* Update: lazy book card strings

* Reorder keys

* Fix: syntax error in LazyBookCard

* Fix: json ordering

* Fix: fix double message definition

* Update: login form toast strings

* Update: batch delete toast

* Update: collection add toast messages

* Replace: toasts in BookShelfToolbar

* Update: playlist edit toasts

* Update: Details tab

* Add: title required string

* Update: ereader toasts

* Update: author toasts, title and name required toasts

* Clean up "no updates" strings

* Change: slug strings

* Update: cover modal toasts

* Change: cancel encode toasts

* Change: failed to share toasts

* Simplify: "renameFail" and "removeFail" toasts

* Fix: ordering

* Change: chapters remove toast

* Update: notification strings

* Revert: loading indicator (error in browser)

* Update: collectionBooksTable toast

* Update: "failed to get" strings

* Update: backup strings

* Update: custom provider strings

* Update: sessions strings

* Update: email strings

* Update sort display translation strings, update podcast episode queue strings to use translation

* Fix loading indicator please wait translation

* Consolidate translations and reduce number of toasts

---------

Co-authored-by: advplyr <advplyr@protonmail.com>
This commit is contained in:
Nicholas W 2024-08-30 15:47:49 -07:00 committed by GitHub
parent acc4bdbc5e
commit f0e70ed27b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 330 additions and 237 deletions

View File

@ -332,13 +332,13 @@ export default {
libraryItemIds: this.selectedMediaItems.map((i) => i.id)
})
.then(() => {
this.$toast.success('Batch delete success')
this.$toast.success(this.$strings.ToastBatchDeleteSuccess)
this.$store.commit('globals/resetSelectedMediaItems', [])
this.$eventBus.$emit('bookshelf_clear_selection')
})
.catch((error) => {
console.error('Batch delete failed', error)
this.$toast.error('Batch delete failed')
this.$toast.error(this.$strings.ToastBatchDeleteFailed)
})
.finally(() => {
this.$store.commit('setProcessingBatch', false)

View File

@ -473,11 +473,11 @@ export default {
this.$axios
.$get(`/api/me/series/${this.seriesId}/readd-to-continue-listening`)
.then(() => {
this.$toast.success('Series re-added to continue listening')
this.$toast.success(this.$strings.ToastItemUpdateSuccess)
})
.catch((error) => {
console.error('Failed to re-add series to continue listening', error)
this.$toast.error('Failed to re-add series to continue listening')
this.$toast.error(this.$strings.ToastItemUpdateFailed)
})
.finally(() => {
this.processingSeries = false
@ -504,7 +504,7 @@ export default {
})
if (!response) {
console.error(`Author ${author.name} not found`)
this.$toast.error(`Author ${author.name} not found`)
this.$toast.error(this.$getString('ToastAuthorNotFound', [author.name]))
} else if (response.updated) {
if (response.author.imagePath) console.log(`Author ${response.author.name} was updated`)
else console.log(`Author ${response.author.name} was updated (no image found)`)
@ -522,13 +522,13 @@ export default {
this.$axios
.$delete(`/api/libraries/${this.currentLibraryId}/issues`)
.then(() => {
this.$toast.success('Removed library items with issues')
this.$toast.success(this.$strings.ToastRemoveItemsWithIssuesSuccess)
this.$router.push(`/library/${this.currentLibraryId}/bookshelf`)
this.$store.dispatch('libraries/fetch', this.currentLibraryId)
})
.catch((error) => {
console.error('Failed to remove library items with issues', error)
this.$toast.error('Failed to remove library items with issues')
this.$toast.error(this.$strings.ToastRemoveItemsWithIssuesFailed)
})
.finally(() => {
this.processingIssues = false

View File

@ -346,14 +346,14 @@ export default {
},
displaySortLine() {
if (this.collapsedSeries) return null
if (this.orderBy === 'mtimeMs') return 'Modified ' + this.$formatDate(this._libraryItem.mtimeMs, this.dateFormat)
if (this.orderBy === 'birthtimeMs') return 'Born ' + this.$formatDate(this._libraryItem.birthtimeMs, this.dateFormat)
if (this.orderBy === 'addedAt') return 'Added ' + this.$formatDate(this._libraryItem.addedAt, this.dateFormat)
if (this.orderBy === 'media.duration') return 'Duration: ' + this.$elapsedPrettyExtended(this.media.duration, false)
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} Episodes`
if (this.orderBy === 'mtimeMs') return this.$getString('LabelFileModifiedDate', [this.$formatDate(this._libraryItem.mtimeMs, this.dateFormat)])
if (this.orderBy === 'birthtimeMs') return this.$getString('LabelFileBornDate', [this.$formatDate(this._libraryItem.birthtimeMs, this.dateFormat)])
if (this.orderBy === 'addedAt') return this.$getString('LabelAddedDate', [this.$formatDate(this._libraryItem.addedAt, this.dateFormat)])
if (this.orderBy === 'media.duration') return this.$strings.LabelDuration + ': ' + this.$elapsedPrettyExtended(this.media.duration, false)
if (this.orderBy === 'size') return this.$strings.LabelSize + ': ' + this.$bytesPretty(this._libraryItem.size)
if (this.orderBy === 'media.numTracks') return `${this.numEpisodes} ` + this.$strings.LabelEpisodes
if (this.orderBy === 'media.metadata.publishedYear') {
if (this.mediaMetadata.publishedYear) return 'Published ' + this.mediaMetadata.publishedYear
if (this.mediaMetadata.publishedYear) return this.$getString('LabelPublishedDate', [this.mediaMetadata.publishedYear])
return '\u00A0'
}
return null
@ -710,7 +710,7 @@ export default {
toggleFinished(confirmed = false) {
if (!this.itemIsFinished && this.userProgressPercent > 0 && !confirmed) {
const payload = {
message: `Are you sure you want to mark "${this.displayTitle}" as finished?`,
message: this.$getString('MessageConfirmMarkItemFinished', [this.displayTitle]),
callback: (confirmed) => {
if (confirmed) {
this.toggleFinished(true)
@ -755,18 +755,18 @@ export default {
.then((data) => {
var result = data.result
if (!result) {
this.$toast.error(`Re-Scan Failed for "${this.title}"`)
this.$toast.error(this.$getString('ToastRescanFailed', [this.displayTitle]))
} else if (result === 'UPDATED') {
this.$toast.success(`Re-Scan complete item was updated`)
this.$toast.success(this.$strings.ToastRescanUpdated)
} else if (result === 'UPTODATE') {
this.$toast.success(`Re-Scan complete item was up to date`)
this.$toast.success(this.$strings.ToastRescanUpToDate)
} else if (result === 'REMOVED') {
this.$toast.error(`Re-Scan complete item was removed`)
this.$toast.error(this.$strings.ToastRescanRemoved)
}
})
.catch((error) => {
console.error('Failed to scan library item', error)
this.$toast.error('Failed to scan library item')
this.$toast.error(this.$strings.ToastScanFailed)
})
.finally(() => {
this.processing = false
@ -823,7 +823,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove series from home', error)
this.$toast.error('Failed to update user')
this.$toast.error(this.$strings.ToastFailedToUpdateUser)
})
.finally(() => {
this.processing = false
@ -841,7 +841,7 @@ export default {
})
.catch((error) => {
console.error('Failed to hide item from home', error)
this.$toast.error('Failed to update user')
this.$toast.error(this.$strings.ToastFailedToUpdateUser)
})
.finally(() => {
this.processing = false
@ -856,7 +856,7 @@ export default {
episodeId: this.recentEpisode.id,
title: this.recentEpisode.title,
subtitle: this.mediaMetadata.title,
caption: this.recentEpisode.publishedAt ? `Published ${this.$formatDate(this.recentEpisode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: this.recentEpisode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(this.recentEpisode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: this.recentEpisode.audioFile.duration || null,
coverPath: this.media.coverPath || null
}
@ -906,11 +906,11 @@ export default {
axios
.$delete(`/api/items/${this.libraryItemId}?hard=${hardDelete ? 1 : 0}`)
.then(() => {
this.$toast.success('Item deleted')
this.$toast.success(this.$strings.ToastItemDeletedSuccess)
})
.catch((error) => {
console.error('Failed to delete item', error)
this.$toast.error('Failed to delete item')
this.$toast.error(this.$strings.ToastItemDeletedFailed)
})
.finally(() => {
this.processing = false
@ -1016,7 +1016,7 @@ export default {
episodeId: episode.id,
title: episode.title,
subtitle: this.mediaMetadata.title,
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: episode.audioFile.duration || null,
coverPath: this.media.coverPath || null
})

View File

@ -96,7 +96,7 @@ export default {
displaySortLine() {
switch (this.orderBy) {
case 'addedAt':
return `${this.$strings.LabelAdded} ${this.$formatDate(this.addedAt, this.dateFormat)}`
return this.$getString('LabelAddedDate', [this.$formatDate(this.addedAt, this.dateFormat)])
case 'totalDuration':
return `${this.$strings.LabelDuration} ${this.$elapsedPrettyExtended(this.totalDuration, false)}`
case 'lastBookUpdated':

View File

@ -4,11 +4,11 @@
<p class="text-base md:text-lg font-semibold pr-4">{{ eventName }}</p>
<div class="flex-grow" />
<ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" @click.stop="fireTestEventAndSucceed">Fire onTest Event</ui-btn>
<ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" color="red-600" @click.stop="fireTestEventAndFail">Fire & Fail</ui-btn>
<ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" @click.stop="fireTestEventAndSucceed">{{ this.$strings.ButtonFireOnTest }}</ui-btn>
<ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" color="red-600" @click.stop="fireTestEventAndFail">{{ this.$strings.ButtonFireAndFail }}</ui-btn>
<!-- <ui-btn v-if="eventName === 'onTest' && notification.enabled" :loading="testing" small class="mr-2" @click.stop="rapidFireTestEvents">Rapid Fire</ui-btn> -->
<ui-btn v-else-if="notification.enabled" :loading="sendingTest" small class="mr-2" @click.stop="sendTestClick">Test</ui-btn>
<ui-btn v-else :loading="enabling" small color="success" class="mr-2" @click="enableNotification">Enable</ui-btn>
<ui-btn v-else-if="notification.enabled" :loading="sendingTest" small class="mr-2" @click.stop="sendTestClick">{{ this.$strings.ButtonTest }}</ui-btn>
<ui-btn v-else :loading="enabling" small color="success" class="mr-2" @click="enableNotification">{{ this.$strings.ButtonEnable }}</ui-btn>
<ui-icon-btn :size="7" icon-font-size="1.1rem" icon="edit" class="mr-2" @click="editNotification" />
<ui-icon-btn bg-color="error" :size="7" icon-font-size="1.2rem" icon="delete" @click="deleteNotificationClick" />
@ -65,12 +65,12 @@ export default {
this.$axios
.$get(`/api/notifications/test?fail=${intentionallyFail ? 1 : 0}`)
.then(() => {
this.$toast.success('Triggered onTest Event')
this.$toast.success(this.$strings.ToastNotificationTestTriggerSuccess)
})
.catch((error) => {
console.error('Failed', error)
const errorMsg = error.response ? error.response.data : null
this.$toast.error(`Failed: ${errorMsg}` || 'Failed to trigger onTest event')
this.$toast.error(`Failed: ${errorMsg}` || this.$strings.ToastNotificationTestTriggerFailed)
})
.finally(() => {
this.testing = false
@ -91,7 +91,7 @@ export default {
// End testing functions
sendTestClick() {
const payload = {
message: `Trigger this notification with test data?`,
message: this.$strings.MessageConfirmNotificationTestTrigger,
callback: (confirmed) => {
if (confirmed) {
this.sendTest()
@ -106,12 +106,12 @@ export default {
this.$axios
.$get(`/api/notifications/${this.notification.id}/test`)
.then(() => {
this.$toast.success('Triggered test notification')
this.$toast.success(this.$strings.ToastNotificationTestTriggerSuccess)
})
.catch((error) => {
console.error('Failed', error)
const errorMsg = error.response ? error.response.data : null
this.$toast.error(`Failed: ${errorMsg}` || 'Failed to trigger test notification')
this.$toast.error(`Failed: ${errorMsg}` || this.$strings.ToastNotificationTestTriggerFailed)
})
.finally(() => {
this.sendingTest = false
@ -127,11 +127,10 @@ export default {
.$patch(`/api/notifications/${this.notification.id}`, payload)
.then((updatedSettings) => {
this.$emit('update', updatedSettings)
this.$toast.success('Notification enabled')
})
.catch((error) => {
console.error('Failed to update notification', error)
this.$toast.error('Failed to update notification')
this.$toast.error(this.$strings.ToastNotificationUpdateFailed)
})
.finally(() => {
this.enabling = false
@ -139,7 +138,7 @@ export default {
},
deleteNotificationClick() {
const payload = {
message: `Are you sure you want to delete this notification?`,
message: this.$strings.MessageConfirmDeleteNotification,
callback: (confirmed) => {
if (confirmed) {
this.deleteNotification()
@ -155,11 +154,10 @@ export default {
.$delete(`/api/notifications/${this.notification.id}`)
.then((updatedSettings) => {
this.$emit('update', updatedSettings)
this.$toast.success('Deleted notification')
})
.catch((error) => {
console.error('Failed', error)
this.$toast.error('Failed to delete notification')
this.$toast.error(this.$strings.ToastNotificationDeleteFailed)
})
.finally(() => {
this.deleting = false
@ -171,4 +169,4 @@ export default {
},
mounted() {}
}
</script>
</script>

View File

@ -111,7 +111,7 @@
</div>
<div class="flex pt-4 px-2">
<ui-btn v-if="hasOpenIDLink" small :loading="unlinkingFromOpenID" color="primary" type="button" class="mr-2" @click.stop="unlinkOpenID">Unlink OpenID</ui-btn>
<ui-btn v-if="hasOpenIDLink" small :loading="unlinkingFromOpenID" color="primary" type="button" class="mr-2" @click.stop="unlinkOpenID">{{ $strings.ButtonUnlinkOpenId }}</ui-btn>
<ui-btn v-if="isEditingRoot" small class="flex items-center" to="/account">{{ $strings.ButtonChangeRootPassword }}</ui-btn>
<div class="flex-grow" />
<ui-btn color="success" type="submit">{{ $strings.ButtonSubmit }}</ui-btn>
@ -212,19 +212,19 @@ export default {
},
unlinkOpenID() {
const payload = {
message: 'Are you sure you want to unlink this user from OpenID?',
message: this.$strings.MessageConfirmUnlinkOpenId,
callback: (confirmed) => {
if (confirmed) {
this.unlinkingFromOpenID = true
this.$axios
.$patch(`/api/users/${this.account.id}/openid-unlink`)
.then(() => {
this.$toast.success('User unlinked from OpenID')
this.$toast.success(this.$strings.ToastUnlinkOpenIdSuccess)
this.show = false
})
.catch((error) => {
console.error('Failed to unlink user from OpenID', error)
this.$toast.error('Failed to unlink user from OpenID')
this.$toast.error(this.$strings.ToastUnlinkOpenIdFailed)
})
.finally(() => {
this.unlinkingFromOpenID = false
@ -265,15 +265,15 @@ export default {
},
submitForm() {
if (!this.newUser.username) {
this.$toast.error('Enter a username')
this.$toast.error(this.$strings.ToastNewUserUsernameError)
return
}
if (!this.newUser.permissions.accessAllLibraries && !this.newUser.librariesAccessible.length) {
this.$toast.error('Must select at least one library')
this.$toast.error(this.$strings.ToastNewUserLibraryError)
return
}
if (!this.newUser.permissions.accessAllTags && !this.newUser.itemTagsSelected.length) {
this.$toast.error('Must select at least one tag')
this.$toast.error(this.$strings.ToastNewUserTagError)
return
}
@ -313,12 +313,12 @@ export default {
this.processing = false
console.error('Failed to update account', error)
var errMsg = error.response ? error.response.data || '' : ''
this.$toast.error(errMsg || 'Failed to update account')
this.$toast.error(errMsg || this.$strings.ToastFailedToUpdateAccount)
})
},
submitCreateAccount() {
if (!this.newUser.password) {
this.$toast.error('Must have a password, only root user can have an empty password')
this.$toast.error(this.$strings.ToastNewUserPasswordError)
return
}
@ -329,9 +329,9 @@ export default {
.then((data) => {
this.processing = false
if (data.error) {
this.$toast.error(`Failed to create account: ${data.error}`)
this.$toast.error(this.$strings.ToastNewUserCreatedFailed + ': ' + data.error)
} else {
this.$toast.success('New account created')
this.$toast.success(this.$strings.ToastNewUserCreatedSuccess)
this.show = false
}
})

View File

@ -2,7 +2,7 @@
<modals-modal ref="modal" v-model="show" name="custom-metadata-provider" :width="600" :height="'unset'" :processing="processing">
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
<p class="text-3xl text-white truncate">Add custom metadata provider</p>
<p class="text-3xl text-white truncate">{{ $strings.HeaderAddCustomMetadataProvider }}</p>
</div>
</template>
<form @submit.prevent="submitForm">
@ -20,7 +20,7 @@
<ui-text-input-with-label v-model="newUrl" label="URL" />
</div>
<div class="w-full mb-2 p-1">
<ui-text-input-with-label v-model="newAuthHeaderValue" :label="'Authorization Header Value'" type="password" />
<ui-text-input-with-label v-model="newAuthHeaderValue" :label="$strings.LabelProviderAuthorizationValue" type="password" />
</div>
<div class="flex px-1 pt-4">
<div class="flex-grow" />
@ -67,7 +67,7 @@ export default {
methods: {
submitForm() {
if (!this.newName || !this.newUrl) {
this.$toast.error('Must add name and url')
this.$toast.error(this.$strings.ToastProviderNameAndUrlRequired)
return
}
@ -81,13 +81,13 @@ export default {
})
.then((data) => {
this.$emit('added', data.provider)
this.$toast.success('New provider added')
this.$toast.success(this.$strings.ToastProviderCreatedSuccess)
this.show = false
})
.catch((error) => {
const errorMsg = error.response?.data || 'Unknown error'
console.error('Failed to add provider', error)
this.$toast.error('Failed to add provider: ' + errorMsg)
this.$toast.error(this.$strings.ToastProviderCreatedFailed + ': ' + errorMsg)
})
.finally(() => {
this.processing = false

View File

@ -4,7 +4,7 @@
<div class="flex items-center justify-between">
<p class="text-base text-gray-200 truncate">{{ metadata.filename }}</p>
<ui-btn v-if="ffprobeData" small class="ml-2" @click="ffprobeData = null">{{ $strings.ButtonReset }}</ui-btn>
<ui-btn v-else-if="userIsAdminOrUp" small :loading="probingFile" class="ml-2" @click="getFFProbeData">Probe Audio File</ui-btn>
<ui-btn v-else-if="userIsAdminOrUp" small :loading="probingFile" class="ml-2" @click="getFFProbeData">{{ $strings.ButtonProbeAudioFile }}</ui-btn>
</div>
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
@ -159,7 +159,7 @@ export default {
})
.catch((error) => {
console.error('Failed to get ffprobe data', error)
this.$toast.error('FFProbe failed')
this.$toast.error(this.$strings.ToastFailedToLoadData)
})
.finally(() => {
this.probingFile = false

View File

@ -9,7 +9,7 @@
<widgets-cron-expression-builder ref="expressionBuilder" v-model="newCronExpression" @input="expressionUpdated" />
<div class="flex items-center justify-end">
<ui-btn :disabled="!isUpdated" @click="submit">{{ isUpdated ? $strings.ButtonSave : $strings.MessageNoUpdateNecessary }}</ui-btn>
<ui-btn :disabled="!isUpdated" @click="submit">{{ isUpdated ? $strings.ButtonSave : $strings.MessageNoUpdatesWereNecessary }}</ui-btn>
</div>
</div>
</modals-modal>

View File

@ -94,7 +94,7 @@ export default {
this.$toast.success(this.$strings.ToastBookmarkRemoveSuccess)
})
.catch((error) => {
this.$toast.error(this.$strings.ToastBookmarkRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
console.error(error)
})
this.show = false

View File

@ -100,7 +100,7 @@
<div class="flex items-center">
<ui-btn v-if="!isOpenSession && !isMediaItemShareSession" small color="error" @click.stop="deleteSessionClick">{{ $strings.ButtonDelete }}</ui-btn>
<ui-btn v-else-if="!isMediaItemShareSession" small color="error" @click.stop="closeSessionClick">Close Open Session</ui-btn>
<ui-btn v-else-if="!isMediaItemShareSession" small color="error" @click.stop="closeSessionClick">{{ $strings.ButtonCloseSession }}</ui-btn>
</div>
</div>
</modals-modal>
@ -206,14 +206,13 @@ export default {
this.$axios
.$post(`/api/session/${this._session.id}/close`)
.then(() => {
this.$toast.success('Session closed')
this.show = false
this.$emit('closedSession')
})
.catch((error) => {
console.error('Failed to close session', error)
const errMsg = error.response?.data || ''
this.$toast.error(errMsg || 'Failed to close open session')
this.$toast.error(errMsg || this.$strings.ToastSessionCloseFailed)
})
.finally(() => {
this.processing = false

View File

@ -165,7 +165,7 @@ export default {
},
openShare() {
if (!this.newShareSlug) {
this.$toast.error('Slug is required')
this.$toast.error(this.$strings.ToastSlugRequired)
return
}
const payload = {

View File

@ -15,7 +15,7 @@
</template>
<form class="flex items-center justify-center px-6 py-3" @submit.prevent="submitCustomTime">
<ui-text-input v-model="customTime" type="number" step="any" min="0.1" :placeholder="$strings.LabelTimeInMinutes" class="w-48" />
<ui-btn color="success" type="submit" :padding-x="0" class="h-9 w-12 flex items-center justify-center ml-1">Set</ui-btn>
<ui-btn color="success" type="submit" :padding-x="0" class="h-9 w-18 flex items-center justify-center ml-1">{{ $strings.ButtonSubmit }}</ui-btn>
</form>
</div>
<div v-if="timerSet" class="w-full p-4">

View File

@ -78,14 +78,13 @@ export default {
if (data.error) {
this.$toast.error(data.error)
} else {
this.$toast.success('Cover Uploaded')
this.resetCoverPreview()
}
this.processingUpload = false
})
.catch((error) => {
console.error('Failed', error)
var errorMsg = error.response && error.response.data ? error.response.data : 'Unknown Error'
var errorMsg = error.response && error.response.data ? error.response.data : this.$strings.ToastUnknownError
this.$toast.error(errorMsg)
this.processingUpload = false
})
@ -95,7 +94,7 @@ export default {
var success = await this.$axios.$post(`/api/${this.entity}/${this.entityId}/cover`, { url: this.imageUrl }).catch((error) => {
console.error('Failed to download cover from url', error)
var errorMsg = error.response && error.response.data ? error.response.data : 'Unknown Error'
var errorMsg = error.response && error.response.data ? error.response.data : this.$strings.ToastUnknownError
this.$toast.error(errorMsg)
return false
})
@ -104,4 +103,4 @@ export default {
}
}
}
</script>
</script>

View File

@ -116,12 +116,12 @@ export default {
this.$axios
.$delete(`/api/authors/${this.authorId}`)
.then(() => {
this.$toast.success('Author removed')
this.$toast.success(this.$strings.ToastAuthorRemoveSuccess)
this.show = false
})
.catch((error) => {
console.error('Failed to remove author', error)
this.$toast.error('Failed to remove author')
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.processing = false
@ -141,7 +141,7 @@ export default {
}
})
if (!Object.keys(updatePayload).length) {
this.$toast.info(this.$strings.MessageNoUpdateNecessary)
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
return
}
this.processing = true
@ -158,7 +158,7 @@ export default {
} else if (result.merged) {
this.$toast.success(this.$strings.ToastAuthorUpdateMerged)
this.show = false
} else this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
} else this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
}
this.processing = false
},
@ -174,7 +174,7 @@ export default {
})
.catch((error) => {
console.error('Failed', error)
this.$toast.error(this.$strings.ToastAuthorImageRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.processing = false
@ -182,7 +182,7 @@ export default {
},
submitUploadCover() {
if (!this.imageUrl?.startsWith('http:') && !this.imageUrl?.startsWith('https:')) {
this.$toast.error('Invalid image url')
this.$toast.error(this.$strings.ToastInvalidImageUrl)
return
}
@ -194,14 +194,14 @@ export default {
.$post(`/api/authors/${this.authorId}/image`, updatePayload)
.then((data) => {
this.imageUrl = ''
this.$toast.success('Author image updated')
this.$toast.success(this.$strings.ToastAuthorUpdateSuccess)
this.authorCopy.updatedAt = data.author.updatedAt
this.authorCopy.imagePath = data.author.imagePath
})
.catch((error) => {
console.error('Failed', error)
this.$toast.error(error.response.data || 'Failed to remove author image')
this.$toast.error(error.response.data || this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.processing = false
@ -209,7 +209,7 @@ export default {
},
async searchAuthor() {
if (!this.authorCopy.name && !this.authorCopy.asin) {
this.$toast.error('Must enter an author name')
this.$toast.error(this.$strings.ToastNameRequired)
return
}
this.processing = true
@ -228,17 +228,19 @@ export default {
return null
})
if (!response) {
this.$toast.error('Author not found')
this.$toast.error(this.$strings.ToastAuthorSearchNotFound)
} else if (response.updated) {
if (response.author.imagePath) {
this.$toast.success(this.$strings.ToastAuthorUpdateSuccess)
} else this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound)
} else {
this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound)
}
this.authorCopy = {
...response.author
}
} else {
this.$toast.info('No updates were made for Author')
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
}
this.processing = false
}

View File

@ -143,7 +143,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove books from collection', error)
this.$toast.error(this.$strings.ToastCollectionItemsRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
this.processing = false
})
} else {
@ -157,7 +157,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove book from collection', error)
this.$toast.error(this.$strings.ToastCollectionItemsRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
this.processing = false
})
}
@ -172,12 +172,12 @@ export default {
.$post(`/api/collections/${collection.id}/batch/add`, { books: this.selectedBookIds })
.then((updatedCollection) => {
console.log(`Books added to collection`, updatedCollection)
this.$toast.success('Books added to collection')
this.$toast.success(this.$strings.ToastCollectionItemsAddSuccess)
this.processing = false
})
.catch((error) => {
console.error('Failed to add books to collection', error)
this.$toast.error('Failed to add books to collection')
this.$toast.error(this.$strings.ToastCollectionItemsAddFailed)
this.processing = false
})
} else {
@ -187,12 +187,12 @@ export default {
.$post(`/api/collections/${collection.id}/book`, { id: this.selectedLibraryItemId })
.then((updatedCollection) => {
console.log(`Book added to collection`, updatedCollection)
this.$toast.success('Book added to collection')
this.$toast.success(this.$strings.ToastCollectionItemsAddSuccess)
this.processing = false
})
.catch((error) => {
console.error('Failed to add book to collection', error)
this.$toast.error('Failed to add book to collection')
this.$toast.error(this.$strings.ToastCollectionItemsAddFailed)
this.processing = false
})
}
@ -221,7 +221,7 @@ export default {
.catch((error) => {
console.error('Failed to create collection', error)
var errMsg = error.response ? error.response.data || '' : ''
this.$toast.error(`Failed to create collection: ${errMsg}`)
this.$toast.error(this.$strings.ToastCollectionCreateFailed + ': ' + errMsg)
this.processing = false
})
}

View File

@ -106,7 +106,7 @@ export default {
.catch((error) => {
console.error('Failed to remove collection', error)
this.processing = false
this.$toast.error(this.$strings.ToastCollectionRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
})
}
},
@ -115,7 +115,7 @@ export default {
return
}
if (!this.newCollectionName) {
return this.$toast.error('Collection must have a name')
return this.$toast.error(this.$strings.ToastNameRequired)
}
this.processing = true

View File

@ -125,12 +125,12 @@ export default {
this.$refs.ereaderEmailInput.blur()
if (!this.newDevice.name?.trim() || !this.newDevice.email?.trim()) {
this.$toast.error('Name and email required')
this.$toast.error(this.$strings.ToastNameEmailRequired)
return
}
if (this.newDevice.availabilityOption === 'specificUsers' && !this.newDevice.users.length) {
this.$toast.error('Must select at least one user')
this.$toast.error(this.$strings.ToastSelectAtLeastOneUser)
return
}
if (this.newDevice.availabilityOption !== 'specificUsers') {
@ -142,14 +142,14 @@ export default {
if (!this.ereaderDevice) {
if (this.existingDevices.some((d) => d.name === this.newDevice.name)) {
this.$toast.error('Ereader device with that name already exists')
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
return
}
this.submitCreate()
} else {
if (this.ereaderDevice.name !== this.newDevice.name && this.existingDevices.some((d) => d.name === this.newDevice.name)) {
this.$toast.error('Ereader device with that name already exists')
this.$toast.error(this.$strings.ToastDeviceNameAlreadyExists)
return
}
@ -174,12 +174,11 @@ export default {
.$post(`/api/emails/ereader-devices`, payload)
.then((data) => {
this.$emit('update', data.ereaderDevices)
this.$toast.success('Device updated')
this.show = false
})
.catch((error) => {
console.error('Failed to update device', error)
this.$toast.error('Failed to update device')
this.$toast.error(this.$strings.ToastDeviceUpdateFailed)
})
.finally(() => {
this.processing = false
@ -201,12 +200,11 @@ export default {
.$post('/api/emails/ereader-devices', payload)
.then((data) => {
this.$emit('update', data.ereaderDevices || [])
this.$toast.success('Device added')
this.show = false
})
.catch((error) => {
console.error('Failed to add device', error)
this.$toast.error('Failed to add device')
this.$toast.error(this.$strings.ToastDeviceAddFailed)
})
.finally(() => {
this.processing = false

View File

@ -194,7 +194,6 @@ export default {
if (data.error) {
this.$toast.error(data.error)
} else {
this.$toast.success('Cover Uploaded')
this.resetCoverPreview()
}
this.processingUpload = false
@ -204,7 +203,7 @@ export default {
if (error.response && error.response.data) {
this.$toast.error(error.response.data)
} else {
this.$toast.error('Oops, something went wrong...')
this.$toast.error(this.$strings.ToastUnknownError)
}
this.processingUpload = false
})
@ -255,7 +254,7 @@ export default {
},
async updateCover(cover) {
if (!cover.startsWith('http:') && !cover.startsWith('https:')) {
this.$toast.error('Invalid URL')
this.$toast.error(this.$strings.ToastInvalidUrl)
return
}
@ -264,11 +263,10 @@ export default {
.$post(`/api/items/${this.libraryItemId}/cover`, { url: cover })
.then(() => {
this.imageUrl = ''
this.$toast.success('Update Successful')
})
.catch((error) => {
console.error('Failed to update cover', error)
this.$toast.error(error.response?.data || 'Failed to update cover')
this.$toast.error(error.response?.data || this.$strings.ToastCoverUpdateFailed)
})
.finally(() => {
this.isProcessing = false
@ -308,12 +306,9 @@ export default {
this.isProcessing = true
this.$axios
.$patch(`/api/items/${this.libraryItemId}/cover`, { cover: coverFile.metadata.path })
.then(() => {
this.$toast.success('Update Successful')
})
.catch((error) => {
console.error('Failed to set local cover', error)
this.$toast.error(error.response?.data || 'Failed to set cover')
this.$toast.error(error.response?.data || this.$strings.ToastCoverUpdateFailed)
})
.finally(() => {
this.isProcessing = false
@ -321,4 +316,4 @@ export default {
}
}
}
</script>
</script>

View File

@ -92,7 +92,7 @@ export default {
var { title, author } = this.$refs.itemDetailsEdit.getTitleAndAuthorName()
if (!title) {
this.$toast.error('Must have a title for quick match')
this.$toast.error(this.$strings.ToastTitleRequired)
return
}
this.quickMatching = true
@ -108,9 +108,9 @@ export default {
if (res.warning) {
this.$toast.warning(res.warning)
} else if (res.updated) {
this.$toast.success('Item details updated')
this.$toast.success(this.$strings.ToastNoUpdatesNecessary)
} else {
this.$toast.info('No updates were made')
this.$toast.info(this.$strings.ToastItemDetailsUpdateUnneeded)
}
})
.catch((error) => {
@ -128,18 +128,18 @@ export default {
this.rescanning = false
var result = data.result
if (!result) {
this.$toast.error(`Re-Scan Failed for "${this.title}"`)
this.$toast.error(this.$getString('ToastRescanFailed', [this.title]))
} else if (result === 'UPDATED') {
this.$toast.success(`Re-Scan complete item was updated`)
this.$toast.success(this.$strings.ToastRescanUpdated)
} else if (result === 'UPTODATE') {
this.$toast.success(`Re-Scan complete item was up to date`)
this.$toast.success(this.$strings.ToastRescanUpToDate)
} else if (result === 'REMOVED') {
this.$toast.error(`Re-Scan complete item was removed`)
this.$toast.error(this.$strings.ToastRescanRemoved)
}
})
.catch((error) => {
console.error('Failed to scan library item', error)
this.$toast.error('Failed to scan library item')
this.$toast.error(this.$strings.ToastScanFailed)
this.rescanning = false
})
},
@ -156,7 +156,7 @@ export default {
}
var updatedDetails = this.$refs.itemDetailsEdit.getDetails()
if (!updatedDetails.hasChanges) {
this.$toast.info('No changes were made')
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
return false
}
return this.updateDetails(updatedDetails)
@ -170,7 +170,7 @@ export default {
this.isProcessing = false
if (updateResult) {
if (updateResult.updated) {
this.$toast.success('Item details updated')
this.$toast.success(this.$strings.MessageItemDetailsUpdated)
return true
} else {
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
@ -217,4 +217,4 @@ export default {
height: calc(100% - 80px);
max-height: calc(100% - 80px);
}
</style>
</style>

View File

@ -397,7 +397,7 @@ export default {
},
submitSearch() {
if (!this.searchTitle) {
this.$toast.warning('Search title is required')
this.$toast.warning(this.$strings.ToastTitleRequired)
return
}
this.persistProvider()
@ -618,7 +618,7 @@ export default {
if (updateResult.updated) {
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
} else {
this.$toast.info(this.$strings.ToastItemDetailsUpdateUnneeded)
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
}
this.clearSelectedMatch()
this.$emit('selectTab', 'details')

View File

@ -163,7 +163,7 @@ export default {
this.isProcessing = false
if (updateResult) {
if (updateResult.updated) {
this.$toast.success('Item details updated')
this.$toast.success(this.$strings.ToastItemDetailsUpdateSuccess)
return true
} else {
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)

View File

@ -156,7 +156,7 @@ export default {
},
validate() {
if (!this.libraryCopy.name) {
this.$toast.error('Library must have a name')
this.$toast.error(this.$strings.ToastNameRequired)
return false
}
if (!this.libraryCopy.folders.length) {
@ -205,7 +205,7 @@ export default {
submitUpdateLibrary() {
var newLibraryPayload = this.getLibraryUpdatePayload()
if (!Object.keys(newLibraryPayload).length) {
this.$toast.info('No updates are necessary')
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
return
}
@ -264,4 +264,4 @@ export default {
.tab.tab-selected {
height: 41px;
}
</style>
</style>

View File

@ -162,7 +162,7 @@ export default {
})
.catch((error) => {
console.error('Failed to get filesystem paths', error)
this.$toast.error('Failed to get filesystem paths')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return []
})
.finally(() => {

View File

@ -78,4 +78,4 @@ export default {
},
mounted() {}
}
</script>
</script>

View File

@ -86,7 +86,7 @@ export default {
return this.selectedEventData && this.selectedEventData.requiresLibrary
},
title() {
return this.isNew ? 'Create Notification' : 'Update Notification'
return this.isNew ? this.$strings.HeaderNotificationCreate : this.$strings.HeaderNotificationUpdate
},
availableVariables() {
return this.selectedEventData ? this.selectedEventData.variables || null : null
@ -104,9 +104,9 @@ export default {
},
submitForm() {
this.$refs.urlsInput?.forceBlur()
if (!this.newNotification.urls.length) {
this.$toast.error('Must enter an Apprise URL')
this.$toast.error(this.$strings.ToastAppriseUrlRequired)
return
}
@ -127,12 +127,12 @@ export default {
.$patch(`/api/notifications/${payload.id}`, payload)
.then((updatedSettings) => {
this.$emit('update', updatedSettings)
this.$toast.success('Notification updated')
this.$toast.success(this.$strings.ToastNotificationUpdateSuccess)
this.show = false
})
.catch((error) => {
console.error('Failed to update notification', error)
this.$toast.error('Failed to update notification')
this.$toast.error(this.$strings.ToastNotificationUpdateFailed)
})
.finally(() => {
this.processing = false
@ -149,12 +149,11 @@ export default {
.$post('/api/notifications', payload)
.then((updatedSettings) => {
this.$emit('update', updatedSettings)
this.$toast.success('Notification created')
this.show = false
})
.catch((error) => {
console.error('Failed to create notification', error)
this.$toast.error('Failed to create notification')
this.$toast.error(this.$strings.ToastNotificationCreateFailed)
})
.finally(() => {
this.processing = false

View File

@ -130,12 +130,12 @@ export default {
.$post(`/api/playlists/${playlist.id}/batch/remove`, { items: itemObjects })
.then((updatedPlaylist) => {
console.log(`Items removed from playlist`, updatedPlaylist)
this.$toast.success('Playlist item(s) removed')
this.$toast.success(this.$strings.ToastPlaylistUpdateSuccess)
this.processing = false
})
.catch((error) => {
console.error('Failed to remove items from playlist', error)
this.$toast.error('Failed to remove playlist item(s)')
this.$toast.error(this.$strings.ToastPlaylistUpdateFailed)
this.processing = false
})
},
@ -148,12 +148,12 @@ export default {
.$post(`/api/playlists/${playlist.id}/batch/add`, { items: itemObjects })
.then((updatedPlaylist) => {
console.log(`Items added to playlist`, updatedPlaylist)
this.$toast.success('Items added to playlist')
this.$toast.success(this.$strings.ToastPlaylistUpdateSuccess)
this.processing = false
})
.catch((error) => {
console.error('Failed to add items to playlist', error)
this.$toast.error('Failed to add items to playlist')
this.$toast.error(this.$strings.ToastPlaylistUpdateFailed)
this.processing = false
})
},
@ -174,14 +174,14 @@ export default {
.$post('/api/playlists', newPlaylist)
.then((data) => {
console.log('New playlist created', data)
this.$toast.success(`Playlist "${data.name}" created`)
this.$toast.success(this.$strings.ToastPlaylistCreateSuccess + ': ' + data.name)
this.processing = false
this.newPlaylistName = ''
})
.catch((error) => {
console.error('Failed to create playlist', error)
var errMsg = error.response ? error.response.data || '' : ''
this.$toast.error(`Failed to create playlist: ${errMsg}`)
this.$toast.error(this.$strings.ToastPlaylistCreateFailed + ': ' + errMsg)
this.processing = false
})
}

View File

@ -86,7 +86,7 @@ export default {
.catch((error) => {
console.error('Failed to remove playlist', error)
this.processing = false
this.$toast.error(this.$strings.ToastPlaylistRemoveFailed)
this.$toast.error(this.$strings.ToastRemoveFailed)
})
}
},
@ -95,7 +95,7 @@ export default {
return
}
if (!this.newPlaylistName) {
return this.$toast.error('Playlist must have a name')
return this.$toast.error(this.$strings.ToastNameRequired)
}
this.processing = true

View File

@ -142,7 +142,7 @@ export default {
const updatedDetails = this.getUpdatePayload()
if (!Object.keys(updatedDetails).length) {
this.$toast.info('No changes were made')
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
return false
}
return this.updateDetails(updatedDetails)

View File

@ -105,7 +105,7 @@ export default {
}
const updatePayload = this.getUpdatePayload(episodeData)
if (!Object.keys(updatePayload).length) {
return this.$toast.info('No updates are necessary')
return this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
}
console.log('Episode update payload', updatePayload)
@ -126,7 +126,7 @@ export default {
},
submitForm() {
if (!this.episodeTitle || !this.episodeTitle.length) {
this.$toast.error('Must enter an episode title')
this.$toast.error(this.$strings.ToastTitleRequired)
return
}
this.searchedTitle = this.episodeTitle

View File

@ -121,14 +121,14 @@ export default {
methods: {
openFeed() {
if (!this.newFeedSlug) {
this.$toast.error('Must set a feed slug')
this.$toast.error(this.$strings.ToastSlugRequired)
return
}
const sanitized = this.$sanitizeSlug(this.newFeedSlug)
if (this.newFeedSlug !== sanitized) {
this.newFeedSlug = sanitized
this.$toast.warning('Slug had to be modified - Run again')
this.$toast.warning(this.$strings.ToastSlugMustChange)
return
}

View File

@ -261,7 +261,7 @@ export default {
.catch((error) => {
console.error('Failed to share', error)
if (error.name !== 'AbortError') {
this.$toast.error('Failed to share: ' + error.message)
this.$toast.error(this.$strings.ToastFailedToShare + ': ' + error.message)
}
})
} else {

View File

@ -237,7 +237,7 @@ export default {
.catch((error) => {
console.error('Failed to share', error)
if (error.name !== 'AbortError') {
this.$toast.error('Failed to share: ' + error.message)
this.$toast.error(this.$strings.ToastFailedToShare + ': ' + error.message)
}
})
} else {

View File

@ -167,7 +167,7 @@ export default {
.catch((error) => {
console.error('Failed to share', error)
if (error.name !== 'AbortError') {
this.$toast.error('Failed to share: ' + error.message)
this.$toast.error(this.$strings.ToastFailedToShare + ': ' + error.message)
}
})
} else {

View File

@ -186,7 +186,7 @@ export default {
mounted() {
this.loadBackups()
if (this.$route.query.backup) {
this.$toast.success('Backup applied successfully')
this.$toast.success(this.$strings.ToastBackupAppliedSuccess)
}
}
}

View File

@ -78,7 +78,7 @@ export default {
})
.catch((error) => {
console.error('Failed to update collection', error)
this.$toast.error('Failed to save collection books order')
this.$toast.error(this.$strings.ToastCollectionUpdateFailed)
})
},
editBook(book) {
@ -110,4 +110,4 @@ export default {
.collection-book-leave-active {
position: absolute;
}
</style>
</style>

View File

@ -45,7 +45,7 @@ export default {
methods: {
removeProvider(provider) {
const payload = {
message: `Are you sure you want remove custom metadata provider "${provider.name}"?`,
message: this.$getString('MessageConfirmDeleteMetadataProvider', [provider.name]),
callback: (confirmed) => {
if (confirmed) {
this.$emit('update:processing', true)
@ -53,12 +53,12 @@ export default {
this.$axios
.$delete(`/api/custom-metadata-providers/${provider.id}`)
.then(() => {
this.$toast.success('Provider removed')
this.$toast.success(this.$strings.ToastProviderRemoveSuccess)
this.$emit('removed', provider.id)
})
.catch((error) => {
console.error('Failed to remove provider', error)
this.$toast.error('Failed to remove provider')
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.$emit('update:processing', false)

View File

@ -92,7 +92,7 @@ export default {
})
.catch((error) => {
console.error('Failed to update playlist', error)
this.$toast.error('Failed to save playlist items order')
this.$toast.error(this.$strings.ToastPlaylistUpdateFailed)
})
},
init() {
@ -119,4 +119,4 @@ export default {
.playlist-item-leave-active {
position: absolute;
}
</style>
</style>

View File

@ -218,12 +218,12 @@ export default {
this.$toast.success(this.$strings.ToastPlaylistRemoveSuccess)
} else {
console.log(`Item removed from playlist`, updatedPlaylist)
this.$toast.success('Item removed from playlist')
this.$toast.success(this.$strings.ToastPlaylistUpdateSuccess)
}
})
.catch((error) => {
console.error('Failed to remove item from playlist', error)
this.$toast.error('Failed to remove item from playlist')
this.$toast.error(this.$strings.ToastPlaylistUpdateFailed)
})
.finally(() => {
this.processingRemove = false

View File

@ -270,7 +270,7 @@ export default {
if (data.numEpisodesUpdated) {
this.$toast.success(`${data.numEpisodesUpdated} episodes updated`)
} else {
this.$toast.info('No changes were made')
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
}
})
.catch((error) => {
@ -295,7 +295,7 @@ export default {
episodeId: episode.id,
title: episode.title,
subtitle: this.mediaMetadata.title,
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: episode.audioFile.duration || null,
coverPath: this.media.coverPath || null
}
@ -372,7 +372,7 @@ export default {
episodeId: episode.id,
title: episode.title,
subtitle: this.mediaMetadata.title,
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: episode.audioFile.duration || null,
coverPath: this.media.coverPath || null
})

View File

@ -7,7 +7,7 @@
<div class="absolute top-0 mt-1 w-3 h-3 rounded-full bg-green-500"></div>
<div class="absolute top-0 mt-1 w-3 h-3 rounded-full bg-green-500"></div>
</div>
<div class="text-gray-200 text-xs font-light mt-2 text-center">{{ text }}</div>
<div class="text-gray-200 text-xs font-light mt-2 text-center">{{ message }}</div>
</div>
</div>
</template>
@ -17,7 +17,12 @@ export default {
props: {
text: {
type: String,
default: 'Please Wait...'
default: null
}
},
computed: {
message() {
return this.text || this.$strings.MessagePleaseWait
}
}
}
@ -67,4 +72,4 @@ export default {
transform: translate(24px, 0);
}
}
</style>
</style>

View File

@ -117,10 +117,10 @@ export default {
},
submitChangePassword() {
if (this.newPassword !== this.confirmPassword) {
return this.$toast.error('New password and confirm password do not match')
return this.$toast.error(this.$strings.ToastUserPasswordMismatch)
}
if (this.password === this.newPassword) {
return this.$toast.error('Password and New Password cannot be the same')
return this.$toast.error(this.$strings.ToastUserPasswordMustChange)
}
this.changingPassword = true
this.$axios
@ -130,16 +130,16 @@ export default {
})
.then((res) => {
if (res.success) {
this.$toast.success('Password Changed Successfully')
this.$toast.success(this.$strings.ToastUserPasswordChangeSuccess)
this.resetForm()
} else {
this.$toast.error(res.error || 'Unknown Error')
this.$toast.error(res.error || this.$strings.ToastUnknownError)
}
this.changingPassword = false
})
.catch((error) => {
console.error(error)
this.$toast.error('Api call failed')
this.$toast.error(this.$strings.ToastUnknownError)
this.changingPassword = false
})
}
@ -148,4 +148,4 @@ export default {
this.selectedLanguage = this.$languageCodes.current
}
}
</script>
</script>

View File

@ -560,7 +560,7 @@ export default {
.catch((error) => {
this.findingChapters = false
console.error('Failed to get chapter data', error)
this.$toast.error('Failed to find chapters')
this.$toast.error(this.$strings.ToastFailedToLoadData)
this.showFindChaptersModal = false
})
},
@ -611,7 +611,7 @@ export default {
.$post(`/api/items/${this.libraryItem.id}/chapters`, payload)
.then((data) => {
if (data.updated) {
this.$toast.success('Chapters removed')
this.$toast.success(this.$strings.ToastChaptersRemoved)
if (this.previousRoute) {
this.$router.push(this.previousRoute)
} else {
@ -623,7 +623,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove chapters', error)
this.$toast.error('Failed to remove chapters')
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.saving = false

View File

@ -331,11 +331,11 @@ export default {
this.$axios
.$delete(`/api/tools/item/${this.libraryItemId}/encode-m4b`)
.then(() => {
this.$toast.success('Encode canceled')
this.$toast.success(this.$strings.ToastEncodeCancelSucces)
})
.catch((error) => {
console.error('Failed to cancel encode', error)
this.$toast.error('Failed to cancel encode')
this.$toast.error(this.$strings.ToastEncodeCancelFailed)
})
.finally(() => {
this.isCancelingEncode = false

View File

@ -366,7 +366,7 @@ export default {
}
}
if (!updates.length) {
return this.$toast.warning('No updates were made')
return this.$toast.warning(this.$strings.ToastNoUpdatesNecessary)
}
console.log('Pushing updates', updates)
@ -406,4 +406,4 @@ export default {
transform: translateY(-100%);
transition: all 150ms ease-in 0s;
}
</style>
</style>

View File

@ -162,7 +162,7 @@ export default {
})
.catch((error) => {
console.error('Failed to save backup path', error)
const errorMsg = error.response?.data || 'Failed to save backup path'
const errorMsg = error.response?.data || this.$strings.ToastBackupPathUpdateFailed
this.$toast.error(errorMsg)
})
.finally(() => {
@ -171,11 +171,11 @@ export default {
},
updateBackupsSettings() {
if (isNaN(this.maxBackupSize) || this.maxBackupSize < 0) {
this.$toast.error('Invalid maximum backup size')
this.$toast.error(this.$strings.ToastBackupInvalidMaxSize)
return
}
if (isNaN(this.backupsToKeep) || this.backupsToKeep <= 0 || this.backupsToKeep > 99) {
this.$toast.error('Invalid number of backups to keep')
this.$toast.error(this.$strings.ToastBackupInvalidMaxKeep)
return
}
const updatePayload = {

View File

@ -109,7 +109,7 @@
</tr>
</table>
<div v-else-if="!loading" class="text-center py-4">
<p class="text-lg text-gray-100">No Devices</p>
<p class="text-lg text-gray-100">{{ $strings.MessageNoDevices }}</p>
</div>
</app-settings-content>
@ -199,7 +199,7 @@ export default {
},
deleteDeviceClick(device) {
const payload = {
message: `Are you sure you want to delete e-reader device "${device.name}"?`,
message: this.$getString('MessageConfirmDeleteDevice', [device.name]),
callback: (confirmed) => {
if (confirmed) {
this.deleteDevice(device)
@ -218,11 +218,10 @@ export default {
.$post(`/api/emails/ereader-devices`, payload)
.then((data) => {
this.ereaderDevicesUpdated(data.ereaderDevices)
this.$toast.success('Device deleted')
})
.catch((error) => {
console.error('Failed to delete device', error)
this.$toast.error('Failed to delete device')
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.deletingDeviceName = null
@ -246,11 +245,11 @@ export default {
this.$axios
.$post('/api/emails/test')
.then(() => {
this.$toast.success('Test Email Sent')
this.$toast.success(this.$strings.ToastDeviceTestEmailSuccess)
})
.catch((error) => {
console.error('Failed to send test email', error)
const errorMsg = error.response.data || 'Failed to send test email'
const errorMsg = error.response.data || this.$strings.ToastDeviceTestEmailFailed
this.$toast.error(errorMsg)
})
.finally(() => {
@ -289,11 +288,11 @@ export default {
this.newSettings = {
...data.settings
}
this.$toast.success('Email settings updated')
this.$toast.success(this.$strings.ToastEmailSettingsUpdateSuccess)
})
.catch((error) => {
console.error('Failed to update email settings', error)
this.$toast.error('Failed to update email settings')
this.$toast.error(this.$strings.ToastEmailSettingsUpdateFailed)
})
.finally(() => {
this.savingSettings = false

View File

@ -130,7 +130,7 @@ export default {
})
.catch((error) => {
console.error('Failed to rename genre', error)
this.$toast.error('Failed to rename genre')
this.$toast.error(this.$strings.ToastRenameFailed)
})
.finally(() => {
this.loading = false
@ -147,7 +147,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove genre', error)
this.$toast.error('Failed to remove genre')
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.loading = false

View File

@ -126,7 +126,7 @@ export default {
})
.catch((error) => {
console.error('Failed to rename tag', error)
this.$toast.error('Failed to rename tag')
this.$toast.error(this.$strings.ToastRenameFailed)
})
.finally(() => {
this.loading = false
@ -143,7 +143,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove tag', error)
this.$toast.error('Failed to remove tag')
this.$toast.error(this.$strings.ToastRemoveFailed)
})
.finally(() => {
this.loading = false

View File

@ -105,12 +105,12 @@ export default {
}
if (isNaN(this.maxNotificationQueue) || this.maxNotificationQueue <= 0) {
this.$toast.error('Max notification queue must be >= 0')
this.$toast.error(this.$strings.ToastNotificationQueueMaximum)
return false
}
if (isNaN(this.maxFailedAttempts) || this.maxFailedAttempts <= 0) {
this.$toast.error('Max failed attempts must be >= 0')
this.$toast.error(this.$strings.ToastNotificationFailedMaximum)
return false
}
@ -128,11 +128,11 @@ export default {
this.$axios
.$patch('/api/notifications', updatePayload)
.then(() => {
this.$toast.success('Notification settings updated')
this.$toast.success(this.$strings.ToastNotificationSettingsUpdateSuccess)
})
.catch((error) => {
console.error('Failed to update notification settings', error)
this.$toast.error('Failed to update notification settings')
this.$toast.error(this.$strings.ToastNotificationSettingsUpdateFailed)
})
.finally(() => {
this.savingSettings = false

View File

@ -290,7 +290,6 @@ export default {
this.$axios
.$post(`/api/sessions/batch/delete`, payload)
.then(() => {
this.$toast.success('Sessions removed')
if (isAllSessions) {
// If all sessions were removed from the current page then go to the previous page
if (this.currentPage > 0) {
@ -303,7 +302,7 @@ export default {
}
})
.catch((error) => {
const errorMsg = error.response?.data || 'Failed to remove sessions'
const errorMsg = error.response?.data || this.$strings.ToastRemoveFailed
this.$toast.error(errorMsg)
})
.finally(() => {
@ -358,12 +357,13 @@ export default {
})
if (!libraryItem) {
this.$toast.error('Failed to get library item')
this.$toast.error(this.$strings.ToastFailedToLoadData)
this.processingGoToTimestamp = false
return
}
if (session.episodeId && !libraryItem.media.episodes.some((ep) => ep.id === session.episodeId)) {
this.$toast.error('Failed to get podcast episode')
console.error('Episode not found in library item', session.episodeId, libraryItem.media.episodes)
this.$toast.error(this.$strings.ToastFailedToLoadData)
this.processingGoToTimestamp = false
return
}
@ -377,7 +377,7 @@ export default {
episodeId: episode.id,
title: episode.title,
subtitle: libraryItem.media.metadata.title,
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: episode.audioFile.duration || null,
coverPath: libraryItem.media.coverPath || null
}

View File

@ -127,12 +127,13 @@ export default {
})
if (!libraryItem) {
this.$toast.error('Failed to get library item')
this.$toast.error(this.$strings.ToastFailedToLoadData)
this.processingGoToTimestamp = false
return
}
if (session.episodeId && !libraryItem.media.episodes.some((ep) => ep.id === session.episodeId)) {
this.$toast.error('Failed to get podcast episode')
console.error('Episode not found in library item', session.episodeId, libraryItem.media.episodes)
this.$toast.error(this.$strings.ToastFailedToLoadData)
this.processingGoToTimestamp = false
return
}
@ -146,7 +147,7 @@ export default {
episodeId: episode.id,
title: episode.title,
subtitle: libraryItem.media.metadata.title,
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: episode.audioFile.duration || null,
coverPath: libraryItem.media.coverPath || null
}

View File

@ -484,23 +484,23 @@ export default {
this.$axios
.$get(`/api/podcasts/${this.libraryItemId}/clear-queue`)
.then(() => {
this.$toast.success('Episode download queue cleared')
this.$toast.success(this.$strings.ToastEpisodeDownloadQueueClearSuccess)
this.episodeDownloadQueued = []
})
.catch((error) => {
console.error('Failed to clear queue', error)
this.$toast.error('Failed to clear queue')
this.$toast.error(this.$strings.ToastEpisodeDownloadQueueClearFailed)
})
}
},
async findEpisodesClick() {
if (!this.mediaMetadata.feedUrl) {
return this.$toast.error('Podcast does not have an RSS Feed')
return this.$toast.error(this.$strings.ToastNoRSSFeed)
}
this.fetchingRSSFeed = true
var payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed: this.mediaMetadata.feedUrl }).catch((error) => {
console.error('Failed to get feed', error)
this.$toast.error('Failed to get podcast feed')
this.$toast.error(this.$strings.ToastPodcastGetFeedFailed)
return null
})
this.fetchingRSSFeed = false
@ -509,7 +509,7 @@ export default {
console.log('Podcast feed', payload)
const podcastfeed = payload.podcast
if (!podcastfeed.episodes || !podcastfeed.episodes.length) {
this.$toast.info('No episodes found in RSS feed')
this.$toast.info(this.$strings.ToastPodcastNoEpisodesInFeed)
return
}
@ -578,7 +578,7 @@ export default {
episodeId: episode.id,
title: episode.title,
subtitle: this.title,
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: episode.audioFile.duration || null,
coverPath: this.libraryItem.media.coverPath || null
})
@ -622,13 +622,12 @@ export default {
},
clearProgressClick() {
if (!this.userMediaProgress) return
if (confirm(`Are you sure you want to reset your progress?`)) {
if (confirm(this.$strings.MessageConfirmResetProgress)) {
this.resettingProgress = true
this.$axios
.$delete(`/api/me/progress/${this.userMediaProgress.id}`)
.then(() => {
console.log('Progress reset complete')
this.$toast.success(`Your progress was reset`)
this.resettingProgress = false
})
.catch((error) => {
@ -722,12 +721,12 @@ export default {
this.$axios
.$delete(`/api/items/${this.libraryItemId}?hard=${hardDelete ? 1 : 0}`)
.then(() => {
this.$toast.success('Item deleted')
this.$toast.success(this.$strings.ToastItemDeletedSuccess)
this.$router.replace(`/library/${this.libraryId}`)
})
.catch((error) => {
console.error('Failed to delete item', error)
this.$toast.error('Failed to delete item')
this.$toast.error(this.$strings.ToastItemDeleteFailed)
})
}
},

View File

@ -138,7 +138,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove narrator', error)
this.$toast.error('Failed to remove narrator')
this.$toast.error(this.$strings.ToastRemoveFailed)
this.loading = false
})
},
@ -158,4 +158,4 @@ export default {
},
beforeDestroy() {}
}
</script>
</script>

View File

@ -111,7 +111,7 @@ export default {
this.processing = true
const queuePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/episode-downloads`).catch((error) => {
console.error('Failed to get download queue', error)
this.$toast.error('Failed to get download queue')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return null
})
this.processing = false

View File

@ -234,7 +234,7 @@ export default {
episodeId: episode.id,
title: episode.title,
subtitle: episode.podcast.metadata.title,
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: episode.duration || null,
coverPath: episode.podcast.coverPath || null
})
@ -251,7 +251,7 @@ export default {
this.processing = true
const episodePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/recent-episodes?limit=25&page=${page}`).catch((error) => {
console.error('Failed to get recent episodes', error)
this.$toast.error('Failed to get recent episodes')
this.$toast.error(this.$strings.ToastFailedToLoadData)
return null
})
this.processing = false
@ -271,7 +271,7 @@ export default {
episodeId: episode.id,
title: episode.title,
subtitle: episode.podcast.metadata.title,
caption: episode.publishedAt ? `Published ${this.$formatDate(episode.publishedAt, this.dateFormat)}` : 'Unknown publish date',
caption: episode.publishedAt ? this.$getString('LabelPublishedDate', [this.$formatDate(episode.publishedAt, this.dateFormat)]) : this.$strings.LabelUnknownPublishDate,
duration: episode.duration || null,
coverPath: episode.podcast.coverPath || null
}

View File

@ -146,7 +146,7 @@ export default {
this.processing = true
var payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed }).catch((error) => {
console.error('Failed to get feed', error)
this.$toast.error('Failed to get podcast feed')
this.$toast.error(this.$strings.ToastPodcastGetFeedFailed)
return null
})
this.processing = false
@ -197,7 +197,7 @@ export default {
this.processing = true
const payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed: podcast.feedUrl }).catch((error) => {
console.error('Failed to get feed', error)
this.$toast.error('Failed to get podcast feed')
this.$toast.error(this.$strings.ToastPodcastGetFeedFailed)
return null
})
this.processing = false

View File

@ -132,11 +132,11 @@ export default {
methods: {
async submitServerSetup() {
if (!this.newRoot.username || !this.newRoot.username.trim()) {
this.$toast.error('Must enter a root username')
this.$toast.error(this.$strings.ToastUserRootRequireName)
return
}
if (this.newRoot.password !== this.confirmPassword) {
this.$toast.error('Password mismatch')
this.$toast.error(this.$strings.ToastUserPasswordMismatch)
return
}
if (!this.newRoot.password) {

View File

@ -19,6 +19,7 @@
"ButtonChooseFiles": "Choose files",
"ButtonClearFilter": "Clear Filter",
"ButtonCloseFeed": "Close Feed",
"ButtonCloseSession": "Close Open Session",
"ButtonCollections": "Collections",
"ButtonConfigureScanner": "Configure Scanner",
"ButtonCreate": "Create",
@ -28,6 +29,9 @@
"ButtonEdit": "Edit",
"ButtonEditChapters": "Edit Chapters",
"ButtonEditPodcast": "Edit Podcast",
"ButtonEnable": "Enable",
"ButtonFireAndFail": "Fire and Fail",
"ButtonFireOnTest": "Fire onTest event",
"ButtonForceReScan": "Force Re-Scan",
"ButtonFullPath": "Full Path",
"ButtonHide": "Hide",
@ -56,6 +60,7 @@
"ButtonPlaylists": "Playlists",
"ButtonPrevious": "Previous",
"ButtonPreviousChapter": "Previous Chapter",
"ButtonProbeAudioFile": "Probe Audio File",
"ButtonPurgeAllCache": "Purge All Cache",
"ButtonPurgeItemsCache": "Purge Items Cache",
"ButtonQueueAddItem": "Add to queue",
@ -93,6 +98,7 @@
"ButtonStats": "Stats",
"ButtonSubmit": "Submit",
"ButtonTest": "Test",
"ButtonUnlinkOpedId": "Unlink OpenID",
"ButtonUpload": "Upload",
"ButtonUploadBackup": "Upload Backup",
"ButtonUploadCover": "Upload Cover",
@ -105,6 +111,7 @@
"ErrorUploadFetchMetadataNoResults": "Could not fetch metadata - try updating title and/or author",
"ErrorUploadLacksTitle": "Must have a title",
"HeaderAccount": "Account",
"HeaderAddCustomMetadataProvider": "Add Custom Metadata Provider",
"HeaderAdvanced": "Advanced",
"HeaderAppriseNotificationSettings": "Apprise Notification Settings",
"HeaderAudioTracks": "Audio Tracks",
@ -150,6 +157,8 @@
"HeaderMetadataToEmbed": "Metadata to embed",
"HeaderNewAccount": "New Account",
"HeaderNewLibrary": "New Library",
"HeaderNotificationCreate": "Create Notification",
"HeaderNotificationUpdate": "Update Notification",
"HeaderNotifications": "Notifications",
"HeaderOpenIDConnectAuthentication": "OpenID Connect Authentication",
"HeaderOpenRSSFeed": "Open RSS Feed",
@ -206,8 +215,8 @@
"LabelAddToCollectionBatch": "Add {0} Books to Collection",
"LabelAddToPlaylist": "Add to Playlist",
"LabelAddToPlaylistBatch": "Add {0} Items to Playlist",
"LabelAdded": "Added",
"LabelAddedAt": "Added At",
"LabelAddedDate": "Added {0}",
"LabelAdminUsersOnly": "Admin users only",
"LabelAll": "All",
"LabelAllUsers": "All Users",
@ -298,6 +307,7 @@
"LabelEpisode": "Episode",
"LabelEpisodeTitle": "Episode Title",
"LabelEpisodeType": "Episode Type",
"LabelEpisodes": "Episodes",
"LabelExample": "Example",
"LabelExpandSeries": "Expand Series",
"LabelExpandSubSeries": "Expand Sub Series",
@ -309,7 +319,9 @@
"LabelFetchingMetadata": "Fetching Metadata",
"LabelFile": "File",
"LabelFileBirthtime": "File Birthtime",
"LabelFileBornDate": "Born {0}",
"LabelFileModified": "File Modified",
"LabelFileModifiedDate": "Modified {0}",
"LabelFilename": "Filename",
"LabelFilterByUser": "Filter by User",
"LabelFindEpisodes": "Find Episodes",
@ -448,8 +460,10 @@
"LabelPrimaryEbook": "Primary ebook",
"LabelProgress": "Progress",
"LabelProvider": "Provider",
"LabelProviderAuthorizationValue": "Authorization Header Value",
"LabelPubDate": "Pub Date",
"LabelPublishYear": "Publish Year",
"LabelPublishedDate": "Published {0}",
"LabelPublisher": "Publisher",
"LabelPublishers": "Publishers",
"LabelRSSFeedCustomOwnerEmail": "Custom owner Email",
@ -595,6 +609,7 @@
"LabelUnabridged": "Unabridged",
"LabelUndo": "Undo",
"LabelUnknown": "Unknown",
"LabelUnknownPublishDate": "Unknown publish date",
"LabelUpdateCover": "Update Cover",
"LabelUpdateCoverHelp": "Allow overwriting of existing covers for the selected books when a match is located",
"LabelUpdateDetails": "Update Details",
@ -643,16 +658,22 @@
"MessageCheckingCron": "Checking cron...",
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
"MessageConfirmDeleteDevice": "Are you sure you want to delete e-reader device \"{0}\"?",
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
"MessageConfirmDeleteLibrary": "Are you sure you want to permanently delete library \"{0}\"?",
"MessageConfirmDeleteLibraryItem": "This will delete the library item from the database and your file system. Are you sure?",
"MessageConfirmDeleteLibraryItems": "This will delete {0} library items from the database and your file system. Are you sure?",
"MessageConfirmDeleteMetadataProvider": "Are you sure you want to delete custom metadata provider \"{0}\"?",
"MessageConfirmDeleteNotification": "Are you sure you want to delete this notification?",
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
"MessageConfirmMarkItemFinished": "Are you sure you want to mark \"{0}\" as finished?",
"MessageConfirmMarkItemNotFinished": "Are you sure you want to mark \"{0}\" as not finished?",
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
"MessageConfirmNotificationTestTrigger": "Trigger this notification with test data?",
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
"MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at <code>/metadata/cache/items</code>.<br />Are you sure?",
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
@ -671,7 +692,9 @@
"MessageConfirmRenameTag": "Are you sure you want to rename tag \"{0}\" to \"{1}\" for all items?",
"MessageConfirmRenameTagMergeNote": "Note: This tag already exists so they will be merged.",
"MessageConfirmRenameTagWarning": "Warning! A similar tag with a different casing already exists \"{0}\".",
"MessageConfirmResetProgress": "Are you sure you want to reset your progress?",
"MessageConfirmSendEbookToDevice": "Are you sure you want to send {0} ebook \"{1}\" to device \"{2}\"?",
"MessageConfirmUnlinkOpenId": "Are you sure you want to unlink this user from OpenID?",
"MessageDownloadingEpisode": "Downloading episode",
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
"MessageEmbedFailed": "Embed Failed!",
@ -706,6 +729,7 @@
"MessageNoCollections": "No Collections",
"MessageNoCoversFound": "No Covers Found",
"MessageNoDescription": "No description",
"MessageNoDevices": "No devices",
"MessageNoDownloadsInProgress": "No downloads currently in progress",
"MessageNoDownloadsQueued": "No downloads queued",
"MessageNoEpisodeMatchesFound": "No episode matches found",
@ -725,7 +749,6 @@
"MessageNoSeries": "No Series",
"MessageNoTags": "No Tags",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNoUpdateNecessary": "No update necessary",
"MessageNoUpdatesWereNecessary": "No updates were necessary",
"MessageNoUserPlaylists": "You have no playlists",
"MessageNotYetImplemented": "Not yet implemented",
@ -734,6 +757,7 @@
"MessagePauseChapter": "Pause chapter playback",
"MessagePlayChapter": "Listen to beginning of chapter",
"MessagePlaylistCreateFromCollection": "Create playlist from collection",
"MessagePleaseWait": "Please wait...",
"MessagePodcastHasNoRSSFeedForMatching": "Podcast has no RSS feed url to use for matching",
"MessageQuickMatchDescription": "Populate empty item details & cover with first match result from '{0}'. Does not overwrite details unless 'Prefer matched metadata' server setting is enabled.",
"MessageRemoveChapter": "Remove chapter",
@ -794,24 +818,32 @@
"StatsYearInReview": "YEAR IN REVIEW",
"ToastAccountUpdateFailed": "Failed to update account",
"ToastAccountUpdateSuccess": "Account updated",
"ToastAuthorImageRemoveFailed": "Failed to remove image",
"ToastAppriseUrlRequired": "Must enter an Apprise URL",
"ToastAuthorImageRemoveSuccess": "Author image removed",
"ToastAuthorNotFound": "Author \"{0}\" not found",
"ToastAuthorRemoveSuccess": "Author removed",
"ToastAuthorSearchNotFound": "Author not found",
"ToastAuthorUpdateFailed": "Failed to update author",
"ToastAuthorUpdateMerged": "Author merged",
"ToastAuthorUpdateSuccess": "Author updated",
"ToastAuthorUpdateSuccessNoImageFound": "Author updated (no image found)",
"ToastBackupAppliedSuccess": "Backup applied",
"ToastBackupCreateFailed": "Failed to create backup",
"ToastBackupCreateSuccess": "Backup created",
"ToastBackupDeleteFailed": "Failed to delete backup",
"ToastBackupDeleteSuccess": "Backup deleted",
"ToastBackupInvalidMaxKeep": "Invalid number of backups to keep",
"ToastBackupInvalidMaxSize": "Invalid maximum backup size",
"ToastBackupPathUpdateFailed": "Failed to update backup path",
"ToastBackupRestoreFailed": "Failed to restore backup",
"ToastBackupUploadFailed": "Failed to upload backup",
"ToastBackupUploadSuccess": "Backup uploaded",
"ToastBatchDeleteFailed": "Batch delete failed",
"ToastBatchDeleteSuccess": "Batch delete success",
"ToastBatchUpdateFailed": "Batch update failed",
"ToastBatchUpdateSuccess": "Batch update success",
"ToastBookmarkCreateFailed": "Failed to create bookmark",
"ToastBookmarkCreateSuccess": "Bookmark added",
"ToastBookmarkRemoveFailed": "Failed to remove bookmark",
"ToastBookmarkRemoveSuccess": "Bookmark removed",
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
"ToastBookmarkUpdateSuccess": "Bookmark updated",
@ -819,25 +851,46 @@
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
"ToastChaptersRemoved": "Chapters removed",
"ToastCollectionItemsAddFailed": "Item(s) added to collection failed",
"ToastCollectionItemsAddSuccess": "Item(s) added to collection success",
"ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
"ToastCollectionRemoveFailed": "Failed to remove collection",
"ToastCollectionRemoveSuccess": "Collection removed",
"ToastCollectionUpdateFailed": "Failed to update collection",
"ToastCollectionUpdateSuccess": "Collection updated",
"ToastCoverUpdateFailed": "Cover update failed",
"ToastDeleteFileFailed": "Failed to delete file",
"ToastDeleteFileSuccess": "File deleted",
"ToastDeviceAddFailed": "Failed to add device",
"ToastDeviceNameAlreadyExists": "Ereader device with that name already exists",
"ToastDeviceTestEmailFailed": "Failed to send test email",
"ToastDeviceTestEmailSuccess": "Test email sent",
"ToastDeviceUpdateFailed": "Failed to update device",
"ToastEmailSettingsUpdateFailed": "Failed to update email settings",
"ToastEmailSettingsUpdateSuccess": "Email settings updated",
"ToastEncodeCancelFailed": "Failed to cancel encode",
"ToastEncodeCancelSucces": "Encode canceled",
"ToastEpisodeDownloadQueueClearFailed": "Failed to clear queue",
"ToastEpisodeDownloadQueueClearSuccess": "Episode download queue cleared",
"ToastErrorCannotShare": "Cannot share natively on this device",
"ToastFailedToLoadData": "Failed to load data",
"ToastFailedToShare": "Failed to share",
"ToastFailedToUpdateAccount": "Failed to update account",
"ToastFailedToUpdateUser": "Failed to update user",
"ToastInvalidImageUrl": "Invalid image URL",
"ToastInvalidUrl": "Invalid URL",
"ToastItemCoverUpdateFailed": "Failed to update item cover",
"ToastItemCoverUpdateSuccess": "Item cover updated",
"ToastItemDeletedFailed": "Failed to delete item",
"ToastItemDeletedSuccess": "Deleted item",
"ToastItemDetailsUpdateFailed": "Failed to update item details",
"ToastItemDetailsUpdateSuccess": "Item details updated",
"ToastItemDetailsUpdateUnneeded": "No updates needed for item details",
"ToastItemMarkedAsFinishedFailed": "Failed to mark as Finished",
"ToastItemMarkedAsFinishedSuccess": "Item marked as Finished",
"ToastItemMarkedAsNotFinishedFailed": "Failed to mark as Not Finished",
"ToastItemMarkedAsNotFinishedSuccess": "Item marked as Not Finished",
"ToastItemUpdateFailed": "Failed to update item",
"ToastItemUpdateSuccess": "Item updated",
"ToastLibraryCreateFailed": "Failed to create library",
"ToastLibraryCreateSuccess": "Library \"{0}\" created",
"ToastLibraryDeleteFailed": "Failed to delete library",
@ -846,32 +899,78 @@
"ToastLibraryScanStarted": "Library scan started",
"ToastLibraryUpdateFailed": "Failed to update library",
"ToastLibraryUpdateSuccess": "Library \"{0}\" updated",
"ToastNameEmailRequired": "Name and email are required",
"ToastNameRequired": "Name is required",
"ToastNewUserCreatedFailed": "Failed to create account: \"{0}\"",
"ToastNewUserCreatedSuccess": "New account created",
"ToastNewUserLibraryError": "Must select at least one library",
"ToastNewUserPasswordError": "Must have a password, only root user can have an empty password",
"ToastNewUserTagError": "Must select at least one tag",
"ToastNewUserUsernameError": "Enter a username",
"ToastNoUpdatesNecessary": "No updates necessary",
"ToastNotificationCreateFailed": "Failed to create notification",
"ToastNotificationDeleteFailed": "Failed to delete notification",
"ToastNotificationFailedMaximum": "Max failed attempts must be >= 0",
"ToastNotificationQueueMaximum": "Max notification queue must be >= 0",
"ToastNotificationSettingsUpdateFailed": "Failed to update notification settings",
"ToastNotificationSettingsUpdateSuccess": "Notification settings updated",
"ToastNotificationTestTriggerFailed": "Failed to trigger test notification",
"ToastNotificationTestTriggerSuccess": "Triggered test notification",
"ToastNotificationUpdateFailed": "Failed to update notification",
"ToastNotificationUpdateSuccess": "Notification updated",
"ToastPlaylistCreateFailed": "Failed to create playlist",
"ToastPlaylistCreateSuccess": "Playlist created",
"ToastPlaylistRemoveFailed": "Failed to remove playlist",
"ToastPlaylistRemoveSuccess": "Playlist removed",
"ToastPlaylistUpdateFailed": "Failed to update playlist",
"ToastPlaylistUpdateSuccess": "Playlist updated",
"ToastPodcastCreateFailed": "Failed to create podcast",
"ToastPodcastCreateSuccess": "Podcast created successfully",
"ToastPodcastGetFeedFailed": "Failed to get podcast feed",
"ToastPodcastNoEpisodesInFeed": "No episodes found in RSS feed",
"ToastPodcastNoRssFeed": "Podcast does not have an RSS feed",
"ToastProviderCreatedFailed": "Failed to add provider",
"ToastProviderCreatedSuccess": "New provider added",
"ToastProviderNameAndUrlRequired": "Name and Url required",
"ToastProviderRemoveSuccess": "Provider removed",
"ToastRSSFeedCloseFailed": "Failed to close RSS feed",
"ToastRSSFeedCloseSuccess": "RSS feed closed",
"ToastRemoveFailed": "Failed to remove",
"ToastRemoveItemFromCollectionFailed": "Failed to remove item from collection",
"ToastRemoveItemFromCollectionSuccess": "Item removed from collection",
"ToastRemoveItemsWithIssuesFailed": "Failed to remove library items with issues",
"ToastRemoveItemsWithIssuesSuccess": "Removed library items with issues",
"ToastRenameFailed": "Failed to rename",
"ToastRescanFailed": "Re-Scan Failed for {0}",
"ToastRescanRemoved": "Re-Scan complete item was removed",
"ToastRescanUpToDate": "Re-Scan complete item was up to date",
"ToastRescanUpdated": "Re-Scan complete item was updated",
"ToastScanFailed": "Failed to scan library item",
"ToastSelectAtLeastOneUser": "Select at least one user",
"ToastSendEbookToDeviceFailed": "Failed to send ebook to device",
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
"ToastSeriesUpdateFailed": "Series update failed",
"ToastSeriesUpdateSuccess": "Series update success",
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
"ToastServerSettingsUpdateSuccess": "Server settings updated",
"ToastSessionCloseFailed": "Failed to close session",
"ToastSessionDeleteFailed": "Failed to delete session",
"ToastSessionDeleteSuccess": "Session deleted",
"ToastSlugMustChange": "Slug contains invalid characters",
"ToastSlugRequired": "Slug is required",
"ToastSocketConnected": "Socket connected",
"ToastSocketDisconnected": "Socket disconnected",
"ToastSocketFailedToConnect": "Socket failed to connect",
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
"ToastTitleRequired": "Title is required",
"ToastUnknownError": "Unknown error",
"ToastUnlinkOpenIdFailed": "Failed to unlink user from OpenID",
"ToastUnlinkOpenIdSuccess": "User unlinked from OpenID",
"ToastUserDeleteFailed": "Failed to delete user",
"ToastUserDeleteSuccess": "User deleted"
"ToastUserDeleteSuccess": "User deleted",
"ToastUserPasswordChangeSuccess": "Password changed successfully",
"ToastUserPasswordMismatch": "Passwords do not match",
"ToastUserPasswordMustChange": "New password cannot match old password",
"ToastUserRootRequireName": "Must enter a root username"
}