From f0e70ed27b5185694003133ab37d0904a3470137 Mon Sep 17 00:00:00 2001 From: Nicholas W Date: Fri, 30 Aug 2024 15:47:49 -0700 Subject: [PATCH] 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 --- client/components/app/Appbar.vue | 4 +- client/components/app/BookShelfToolbar.vue | 10 +- client/components/cards/LazyBookCard.vue | 38 +++--- client/components/cards/LazySeriesCard.vue | 2 +- client/components/cards/NotificationCard.vue | 28 ++--- client/components/modals/AccountModal.vue | 22 ++-- .../modals/AddCustomMetadataProviderModal.vue | 10 +- .../components/modals/AudioFileDataModal.vue | 4 +- .../components/modals/BackupScheduleModal.vue | 2 +- client/components/modals/BookmarksModal.vue | 2 +- .../modals/ListeningSessionModal.vue | 5 +- client/components/modals/ShareModal.vue | 2 +- client/components/modals/SleepTimerModal.vue | 2 +- client/components/modals/UploadImageModal.vue | 7 +- .../components/modals/authors/EditModal.vue | 26 ++-- .../modals/collections/AddCreateModal.vue | 14 +-- .../modals/collections/EditModal.vue | 4 +- .../modals/emails/EReaderDeviceModal.vue | 14 +-- client/components/modals/item/tabs/Cover.vue | 15 +-- .../components/modals/item/tabs/Details.vue | 22 ++-- client/components/modals/item/tabs/Match.vue | 4 +- .../components/modals/item/tabs/Schedule.vue | 2 +- .../components/modals/libraries/EditModal.vue | 6 +- .../modals/libraries/LazyFolderChooser.vue | 2 +- .../modals/libraries/LibraryTools.vue | 2 +- .../notification/NotificationEditModal.vue | 13 +- .../modals/playlists/AddCreateModal.vue | 12 +- .../components/modals/playlists/EditModal.vue | 4 +- .../modals/podcast/tabs/EpisodeDetails.vue | 2 +- .../modals/podcast/tabs/EpisodeMatch.vue | 4 +- .../modals/rssfeed/OpenCloseModal.vue | 4 +- client/components/stats/YearInReview.vue | 2 +- .../components/stats/YearInReviewServer.vue | 2 +- client/components/stats/YearInReviewShort.vue | 2 +- client/components/tables/BackupsTable.vue | 2 +- .../tables/CollectionBooksTable.vue | 4 +- .../tables/CustomMetadataProviderTable.vue | 6 +- .../components/tables/PlaylistItemsTable.vue | 4 +- .../tables/playlist/ItemTableRow.vue | 4 +- .../tables/podcast/LazyEpisodesTable.vue | 6 +- client/components/ui/LoadingIndicator.vue | 11 +- client/pages/account.vue | 12 +- client/pages/audiobook/_id/chapters.vue | 6 +- client/pages/audiobook/_id/manage.vue | 4 +- client/pages/batch/index.vue | 4 +- client/pages/config/backups.vue | 6 +- client/pages/config/email.vue | 15 ++- .../config/item-metadata-utils/genres.vue | 4 +- .../pages/config/item-metadata-utils/tags.vue | 4 +- client/pages/config/notifications.vue | 8 +- client/pages/config/sessions.vue | 10 +- client/pages/config/users/_id/sessions.vue | 7 +- client/pages/item/_id/index.vue | 19 ++- client/pages/library/_library/narrators.vue | 4 +- .../_library/podcast/download-queue.vue | 2 +- .../pages/library/_library/podcast/latest.vue | 6 +- .../pages/library/_library/podcast/search.vue | 4 +- client/pages/login.vue | 4 +- client/strings/en-us.json | 117 ++++++++++++++++-- 59 files changed, 330 insertions(+), 237 deletions(-) diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue index 183d58eb..2d557e32 100644 --- a/client/components/app/Appbar.vue +++ b/client/components/app/Appbar.vue @@ -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) diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index 00b0d4a9..ff337428 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -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 diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index ac75407c..4409dbd2 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -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 }) diff --git a/client/components/cards/LazySeriesCard.vue b/client/components/cards/LazySeriesCard.vue index 6711099c..177ec5f3 100644 --- a/client/components/cards/LazySeriesCard.vue +++ b/client/components/cards/LazySeriesCard.vue @@ -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': diff --git a/client/components/cards/NotificationCard.vue b/client/components/cards/NotificationCard.vue index 1506cfb5..cf959df7 100644 --- a/client/components/cards/NotificationCard.vue +++ b/client/components/cards/NotificationCard.vue @@ -4,11 +4,11 @@

{{ eventName }}

- Fire onTest Event - Fire & Fail + {{ this.$strings.ButtonFireOnTest }} + {{ this.$strings.ButtonFireAndFail }} - Test - Enable + {{ this.$strings.ButtonTest }} + {{ this.$strings.ButtonEnable }} @@ -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() {} } - \ No newline at end of file + diff --git a/client/components/modals/AccountModal.vue b/client/components/modals/AccountModal.vue index b22ff245..df1f7cbf 100644 --- a/client/components/modals/AccountModal.vue +++ b/client/components/modals/AccountModal.vue @@ -111,7 +111,7 @@
- Unlink OpenID + {{ $strings.ButtonUnlinkOpenId }} {{ $strings.ButtonChangeRootPassword }}
{{ $strings.ButtonSubmit }} @@ -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 } }) diff --git a/client/components/modals/AddCustomMetadataProviderModal.vue b/client/components/modals/AddCustomMetadataProviderModal.vue index bd870890..a68c63cc 100644 --- a/client/components/modals/AddCustomMetadataProviderModal.vue +++ b/client/components/modals/AddCustomMetadataProviderModal.vue @@ -2,7 +2,7 @@
@@ -20,7 +20,7 @@
- +
@@ -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 diff --git a/client/components/modals/AudioFileDataModal.vue b/client/components/modals/AudioFileDataModal.vue index eeedf295..7e33a980 100644 --- a/client/components/modals/AudioFileDataModal.vue +++ b/client/components/modals/AudioFileDataModal.vue @@ -4,7 +4,7 @@

{{ metadata.filename }}

{{ $strings.ButtonReset }} - Probe Audio File + {{ $strings.ButtonProbeAudioFile }}
@@ -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 diff --git a/client/components/modals/BackupScheduleModal.vue b/client/components/modals/BackupScheduleModal.vue index 71e91959..d0164d76 100644 --- a/client/components/modals/BackupScheduleModal.vue +++ b/client/components/modals/BackupScheduleModal.vue @@ -9,7 +9,7 @@
- {{ isUpdated ? $strings.ButtonSave : $strings.MessageNoUpdateNecessary }} + {{ isUpdated ? $strings.ButtonSave : $strings.MessageNoUpdatesWereNecessary }}
diff --git a/client/components/modals/BookmarksModal.vue b/client/components/modals/BookmarksModal.vue index 723ecc7d..53ed7d07 100644 --- a/client/components/modals/BookmarksModal.vue +++ b/client/components/modals/BookmarksModal.vue @@ -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 diff --git a/client/components/modals/ListeningSessionModal.vue b/client/components/modals/ListeningSessionModal.vue index 0b18980e..df029e56 100644 --- a/client/components/modals/ListeningSessionModal.vue +++ b/client/components/modals/ListeningSessionModal.vue @@ -100,7 +100,7 @@
{{ $strings.ButtonDelete }} - Close Open Session + {{ $strings.ButtonCloseSession }}
@@ -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 diff --git a/client/components/modals/ShareModal.vue b/client/components/modals/ShareModal.vue index 7ad29000..65ef4fc7 100644 --- a/client/components/modals/ShareModal.vue +++ b/client/components/modals/ShareModal.vue @@ -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 = { diff --git a/client/components/modals/SleepTimerModal.vue b/client/components/modals/SleepTimerModal.vue index 43b55217..48210582 100644 --- a/client/components/modals/SleepTimerModal.vue +++ b/client/components/modals/SleepTimerModal.vue @@ -15,7 +15,7 @@ - Set + {{ $strings.ButtonSubmit }}
diff --git a/client/components/modals/UploadImageModal.vue b/client/components/modals/UploadImageModal.vue index d927a046..9a55ae51 100644 --- a/client/components/modals/UploadImageModal.vue +++ b/client/components/modals/UploadImageModal.vue @@ -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 { } } } - \ No newline at end of file + diff --git a/client/components/modals/authors/EditModal.vue b/client/components/modals/authors/EditModal.vue index 174b28d4..d49edd4d 100644 --- a/client/components/modals/authors/EditModal.vue +++ b/client/components/modals/authors/EditModal.vue @@ -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 } diff --git a/client/components/modals/collections/AddCreateModal.vue b/client/components/modals/collections/AddCreateModal.vue index 29c21624..7c22525f 100644 --- a/client/components/modals/collections/AddCreateModal.vue +++ b/client/components/modals/collections/AddCreateModal.vue @@ -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 }) } diff --git a/client/components/modals/collections/EditModal.vue b/client/components/modals/collections/EditModal.vue index e5b3e305..2ab1b939 100644 --- a/client/components/modals/collections/EditModal.vue +++ b/client/components/modals/collections/EditModal.vue @@ -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 diff --git a/client/components/modals/emails/EReaderDeviceModal.vue b/client/components/modals/emails/EReaderDeviceModal.vue index 70b295ae..7476dd4b 100644 --- a/client/components/modals/emails/EReaderDeviceModal.vue +++ b/client/components/modals/emails/EReaderDeviceModal.vue @@ -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 diff --git a/client/components/modals/item/tabs/Cover.vue b/client/components/modals/item/tabs/Cover.vue index fb87bdaa..69c11119 100644 --- a/client/components/modals/item/tabs/Cover.vue +++ b/client/components/modals/item/tabs/Cover.vue @@ -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 { } } } - \ No newline at end of file + diff --git a/client/components/modals/item/tabs/Details.vue b/client/components/modals/item/tabs/Details.vue index 62f08c92..6f814e1b 100644 --- a/client/components/modals/item/tabs/Details.vue +++ b/client/components/modals/item/tabs/Details.vue @@ -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); } - \ No newline at end of file + diff --git a/client/components/modals/item/tabs/Match.vue b/client/components/modals/item/tabs/Match.vue index d11beac0..4cc8d10d 100644 --- a/client/components/modals/item/tabs/Match.vue +++ b/client/components/modals/item/tabs/Match.vue @@ -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') diff --git a/client/components/modals/item/tabs/Schedule.vue b/client/components/modals/item/tabs/Schedule.vue index 51783764..845f77ec 100644 --- a/client/components/modals/item/tabs/Schedule.vue +++ b/client/components/modals/item/tabs/Schedule.vue @@ -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) diff --git a/client/components/modals/libraries/EditModal.vue b/client/components/modals/libraries/EditModal.vue index 27e3ec6d..1dd9c92a 100644 --- a/client/components/modals/libraries/EditModal.vue +++ b/client/components/modals/libraries/EditModal.vue @@ -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; } - \ No newline at end of file + diff --git a/client/components/modals/libraries/LazyFolderChooser.vue b/client/components/modals/libraries/LazyFolderChooser.vue index 061e702f..eb152c28 100644 --- a/client/components/modals/libraries/LazyFolderChooser.vue +++ b/client/components/modals/libraries/LazyFolderChooser.vue @@ -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(() => { diff --git a/client/components/modals/libraries/LibraryTools.vue b/client/components/modals/libraries/LibraryTools.vue index 7297c1ae..a42feeb2 100644 --- a/client/components/modals/libraries/LibraryTools.vue +++ b/client/components/modals/libraries/LibraryTools.vue @@ -78,4 +78,4 @@ export default { }, mounted() {} } - \ No newline at end of file + diff --git a/client/components/modals/notification/NotificationEditModal.vue b/client/components/modals/notification/NotificationEditModal.vue index 2fc10ce6..afd4d1f9 100644 --- a/client/components/modals/notification/NotificationEditModal.vue +++ b/client/components/modals/notification/NotificationEditModal.vue @@ -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 diff --git a/client/components/modals/playlists/AddCreateModal.vue b/client/components/modals/playlists/AddCreateModal.vue index 6d8114fb..3e0c7a9f 100644 --- a/client/components/modals/playlists/AddCreateModal.vue +++ b/client/components/modals/playlists/AddCreateModal.vue @@ -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 }) } diff --git a/client/components/modals/playlists/EditModal.vue b/client/components/modals/playlists/EditModal.vue index 9130c5b8..b26df65a 100644 --- a/client/components/modals/playlists/EditModal.vue +++ b/client/components/modals/playlists/EditModal.vue @@ -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 diff --git a/client/components/modals/podcast/tabs/EpisodeDetails.vue b/client/components/modals/podcast/tabs/EpisodeDetails.vue index 720e1f75..ce5e1b58 100644 --- a/client/components/modals/podcast/tabs/EpisodeDetails.vue +++ b/client/components/modals/podcast/tabs/EpisodeDetails.vue @@ -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) diff --git a/client/components/modals/podcast/tabs/EpisodeMatch.vue b/client/components/modals/podcast/tabs/EpisodeMatch.vue index de58bdf9..403400d1 100644 --- a/client/components/modals/podcast/tabs/EpisodeMatch.vue +++ b/client/components/modals/podcast/tabs/EpisodeMatch.vue @@ -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 diff --git a/client/components/modals/rssfeed/OpenCloseModal.vue b/client/components/modals/rssfeed/OpenCloseModal.vue index b5a2a94f..f15a8e8e 100644 --- a/client/components/modals/rssfeed/OpenCloseModal.vue +++ b/client/components/modals/rssfeed/OpenCloseModal.vue @@ -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 } diff --git a/client/components/stats/YearInReview.vue b/client/components/stats/YearInReview.vue index 57e1e088..56840b9c 100644 --- a/client/components/stats/YearInReview.vue +++ b/client/components/stats/YearInReview.vue @@ -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 { diff --git a/client/components/stats/YearInReviewServer.vue b/client/components/stats/YearInReviewServer.vue index 9843a64d..05bc0910 100644 --- a/client/components/stats/YearInReviewServer.vue +++ b/client/components/stats/YearInReviewServer.vue @@ -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 { diff --git a/client/components/stats/YearInReviewShort.vue b/client/components/stats/YearInReviewShort.vue index dbcd234b..dbba0172 100644 --- a/client/components/stats/YearInReviewShort.vue +++ b/client/components/stats/YearInReviewShort.vue @@ -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 { diff --git a/client/components/tables/BackupsTable.vue b/client/components/tables/BackupsTable.vue index d768407f..6b0c6723 100644 --- a/client/components/tables/BackupsTable.vue +++ b/client/components/tables/BackupsTable.vue @@ -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) } } } diff --git a/client/components/tables/CollectionBooksTable.vue b/client/components/tables/CollectionBooksTable.vue index d7d1225e..ce66de11 100644 --- a/client/components/tables/CollectionBooksTable.vue +++ b/client/components/tables/CollectionBooksTable.vue @@ -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; } - \ No newline at end of file + diff --git a/client/components/tables/CustomMetadataProviderTable.vue b/client/components/tables/CustomMetadataProviderTable.vue index 88ac51dc..25418413 100644 --- a/client/components/tables/CustomMetadataProviderTable.vue +++ b/client/components/tables/CustomMetadataProviderTable.vue @@ -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) diff --git a/client/components/tables/PlaylistItemsTable.vue b/client/components/tables/PlaylistItemsTable.vue index 3a741bfe..09811d4a 100644 --- a/client/components/tables/PlaylistItemsTable.vue +++ b/client/components/tables/PlaylistItemsTable.vue @@ -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; } - \ No newline at end of file + diff --git a/client/components/tables/playlist/ItemTableRow.vue b/client/components/tables/playlist/ItemTableRow.vue index 61fe1451..f7cc7d7b 100644 --- a/client/components/tables/playlist/ItemTableRow.vue +++ b/client/components/tables/playlist/ItemTableRow.vue @@ -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 diff --git a/client/components/tables/podcast/LazyEpisodesTable.vue b/client/components/tables/podcast/LazyEpisodesTable.vue index 27b624b7..2dfe4c55 100644 --- a/client/components/tables/podcast/LazyEpisodesTable.vue +++ b/client/components/tables/podcast/LazyEpisodesTable.vue @@ -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 }) diff --git a/client/components/ui/LoadingIndicator.vue b/client/components/ui/LoadingIndicator.vue index 9762fde7..d984bf35 100644 --- a/client/components/ui/LoadingIndicator.vue +++ b/client/components/ui/LoadingIndicator.vue @@ -7,7 +7,7 @@
-
{{ text }}
+
{{ message }}
@@ -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); } } - \ No newline at end of file + diff --git a/client/pages/account.vue b/client/pages/account.vue index 86be607c..b6c932a0 100644 --- a/client/pages/account.vue +++ b/client/pages/account.vue @@ -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 } } - \ No newline at end of file + diff --git a/client/pages/audiobook/_id/chapters.vue b/client/pages/audiobook/_id/chapters.vue index c5da643a..9dabb59d 100644 --- a/client/pages/audiobook/_id/chapters.vue +++ b/client/pages/audiobook/_id/chapters.vue @@ -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 diff --git a/client/pages/audiobook/_id/manage.vue b/client/pages/audiobook/_id/manage.vue index 6be07349..7de82b51 100644 --- a/client/pages/audiobook/_id/manage.vue +++ b/client/pages/audiobook/_id/manage.vue @@ -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 diff --git a/client/pages/batch/index.vue b/client/pages/batch/index.vue index c73edd40..1f119387 100644 --- a/client/pages/batch/index.vue +++ b/client/pages/batch/index.vue @@ -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; } - \ No newline at end of file + diff --git a/client/pages/config/backups.vue b/client/pages/config/backups.vue index d74dc4d6..0c64ddf6 100644 --- a/client/pages/config/backups.vue +++ b/client/pages/config/backups.vue @@ -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 = { diff --git a/client/pages/config/email.vue b/client/pages/config/email.vue index 3637e312..212c51f3 100644 --- a/client/pages/config/email.vue +++ b/client/pages/config/email.vue @@ -109,7 +109,7 @@
-

No Devices

+

{{ $strings.MessageNoDevices }}

@@ -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 diff --git a/client/pages/config/item-metadata-utils/genres.vue b/client/pages/config/item-metadata-utils/genres.vue index 5a61d51a..e041244c 100644 --- a/client/pages/config/item-metadata-utils/genres.vue +++ b/client/pages/config/item-metadata-utils/genres.vue @@ -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 diff --git a/client/pages/config/item-metadata-utils/tags.vue b/client/pages/config/item-metadata-utils/tags.vue index a98f39b4..0e14f97c 100644 --- a/client/pages/config/item-metadata-utils/tags.vue +++ b/client/pages/config/item-metadata-utils/tags.vue @@ -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 diff --git a/client/pages/config/notifications.vue b/client/pages/config/notifications.vue index ad346a5d..24ea6a6c 100644 --- a/client/pages/config/notifications.vue +++ b/client/pages/config/notifications.vue @@ -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 diff --git a/client/pages/config/sessions.vue b/client/pages/config/sessions.vue index edb14cd2..59ff7558 100644 --- a/client/pages/config/sessions.vue +++ b/client/pages/config/sessions.vue @@ -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 } diff --git a/client/pages/config/users/_id/sessions.vue b/client/pages/config/users/_id/sessions.vue index 8e7ebfb8..6b475677 100644 --- a/client/pages/config/users/_id/sessions.vue +++ b/client/pages/config/users/_id/sessions.vue @@ -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 } diff --git a/client/pages/item/_id/index.vue b/client/pages/item/_id/index.vue index 6b58135b..efc3a462 100644 --- a/client/pages/item/_id/index.vue +++ b/client/pages/item/_id/index.vue @@ -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) }) } }, diff --git a/client/pages/library/_library/narrators.vue b/client/pages/library/_library/narrators.vue index 22d583c7..e2a45da4 100644 --- a/client/pages/library/_library/narrators.vue +++ b/client/pages/library/_library/narrators.vue @@ -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() {} } - \ No newline at end of file + diff --git a/client/pages/library/_library/podcast/download-queue.vue b/client/pages/library/_library/podcast/download-queue.vue index 49b4d4da..777ddfc1 100644 --- a/client/pages/library/_library/podcast/download-queue.vue +++ b/client/pages/library/_library/podcast/download-queue.vue @@ -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 diff --git a/client/pages/library/_library/podcast/latest.vue b/client/pages/library/_library/podcast/latest.vue index d9271ae4..c663b8f5 100644 --- a/client/pages/library/_library/podcast/latest.vue +++ b/client/pages/library/_library/podcast/latest.vue @@ -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 } diff --git a/client/pages/library/_library/podcast/search.vue b/client/pages/library/_library/podcast/search.vue index c7808979..b80ca2f8 100644 --- a/client/pages/library/_library/podcast/search.vue +++ b/client/pages/library/_library/podcast/search.vue @@ -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 diff --git a/client/pages/login.vue b/client/pages/login.vue index d12600c9..a853def4 100644 --- a/client/pages/login.vue +++ b/client/pages/login.vue @@ -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) { diff --git a/client/strings/en-us.json b/client/strings/en-us.json index 7420c1e7..3dc29409 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -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 /metadata/cache.

Are you sure you want to remove the cache directory?", "MessageConfirmPurgeItemsCache": "Purge items cache will delete the entire directory at /metadata/cache/items.
Are you sure?", "MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files.

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