From 9327331ee9c42ace12fbf50102a1a15b1c81e105 Mon Sep 17 00:00:00 2001 From: Nicholas W Date: Thu, 17 Oct 2024 15:03:08 -0700 Subject: [PATCH] Localization updates for 2.15.0 (#3520) * Add: episode edit dropdowns * Update: lazy episode table and row * Various string updates * Batch quick match strings * Author card strings * Update translation key for quick match episodes confirm --------- Co-authored-by: advplyr --- .../components/app/MediaPlayerContainer.vue | 6 +-- client/components/cards/AuthorCard.vue | 11 +++-- .../modals/BatchQuickMatchModel.vue | 4 +- .../components/modals/item/tabs/Episodes.vue | 10 ++--- client/components/modals/item/tabs/Match.vue | 34 +++++++-------- .../components/modals/item/tabs/Schedule.vue | 16 ++++---- .../components/modals/libraries/EditModal.vue | 2 +- .../modals/libraries/LibraryTools.vue | 18 ++++---- client/components/modals/podcast/NewModal.vue | 7 +++- .../modals/podcast/tabs/EpisodeDetails.vue | 15 ++++--- .../tables/podcast/LazyEpisodeRow.vue | 16 ++++---- .../tables/podcast/LazyEpisodesTable.vue | 8 ++-- .../components/widgets/PodcastDetailsEdit.vue | 7 +++- client/pages/audiobook/_id/chapters.vue | 4 +- client/pages/library/_library/narrators.vue | 2 +- client/store/globals.js | 10 ++--- client/strings/en-us.json | 41 +++++++++++++++++++ 17 files changed, 135 insertions(+), 76 deletions(-) diff --git a/client/components/app/MediaPlayerContainer.vue b/client/components/app/MediaPlayerContainer.vue index 259e0c98..1a19f301 100644 --- a/client/components/app/MediaPlayerContainer.vue +++ b/client/components/app/MediaPlayerContainer.vue @@ -167,7 +167,7 @@ export default { }, podcastAuthor() { if (!this.isPodcast) return null - return this.mediaMetadata.author || 'Unknown' + return this.mediaMetadata.author || this.$strings.LabelUnknown }, hasNextItemInQueue() { return this.currentPlayerQueueIndex < this.playerQueueItems.length - 1 @@ -251,7 +251,7 @@ export default { sleepTimerEnd() { this.clearSleepTimer() this.playerHandler.pause() - this.$toast.info('Sleep Timer Done.. zZzzZz') + this.$toast.info(this.$strings.ToastSleepTimerDone) }, cancelSleepTimer() { this.showSleepTimerModal = false @@ -525,7 +525,7 @@ export default { }, showFailedProgressSyncs() { if (!isNaN(this.syncFailedToast)) this.$toast.dismiss(this.syncFailedToast) - this.syncFailedToast = this.$toast('Progress is not being synced. Restart playback', { timeout: false, type: 'error' }) + this.syncFailedToast = this.$toast(this.$strings.ToastProgressIsNotBeingSynced, { timeout: false, type: 'error' }) }, sessionClosedEvent(sessionId) { if (this.playerHandler.currentSessionId === sessionId) { diff --git a/client/components/cards/AuthorCard.vue b/client/components/cards/AuthorCard.vue index 1a5e8bd6..597aca99 100644 --- a/client/components/cards/AuthorCard.vue +++ b/client/components/cards/AuthorCard.vue @@ -125,12 +125,15 @@ export default { return null }) if (!response) { - this.$toast.error(`Author ${this.name} not found`) + this.$toast.error(this.$getString('ToastAuthorNotFound', [this.name])) } else if (response.updated) { - if (response.author.imagePath) this.$toast.success(`Author ${response.author.name} was updated`) - else this.$toast.success(`Author ${response.author.name} was updated (no image found)`) + if (response.author.imagePath) { + this.$toast.success(this.$strings.ToastAuthorUpdateSuccess) + } else { + this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound) + } } else { - this.$toast.info(`No updates were made for Author ${response.author.name}`) + this.$toast.info(this.$strings.ToastNoUpdatesNecessary) } this.searching = false }, diff --git a/client/components/modals/BatchQuickMatchModel.vue b/client/components/modals/BatchQuickMatchModel.vue index 3d2015d6..ce227c28 100644 --- a/client/components/modals/BatchQuickMatchModel.vue +++ b/client/components/modals/BatchQuickMatchModel.vue @@ -116,10 +116,10 @@ export default { libraryItemIds: this.selectedBookIds }) .then(() => { - this.$toast.info('Batch quick match of ' + this.selectedBookIds.length + ' books started!') + this.$toast.info(this.$getString('ToastBatchQuickMatchStarted', [this.selectedBookIds.length])) }) .catch((error) => { - this.$toast.error('Batch quick match failed') + this.$toast.error(this.$strings.ToastBatchQuickMatchFailed) console.error('Failed to batch quick match', error) }) .finally(() => { diff --git a/client/components/modals/item/tabs/Episodes.vue b/client/components/modals/item/tabs/Episodes.vue index 07faee7e..00e62917 100644 --- a/client/components/modals/item/tabs/Episodes.vue +++ b/client/components/modals/item/tabs/Episodes.vue @@ -6,7 +6,7 @@

{{ $strings.LabelLimit }}

- + info
@@ -99,7 +99,7 @@ export default { if (this.maxEpisodesToDownload < 0) { this.maxEpisodesToDownload = 3 - this.$toast.error('Invalid max episodes to download') + this.$toast.error(this.$strings.ToastInvalidMaxEpisodesToDownload) return } @@ -120,9 +120,9 @@ export default { .then((response) => { if (response.episodes && response.episodes.length) { console.log('New episodes', response.episodes.length) - this.$toast.success(`${response.episodes.length} new episodes found!`) + this.$toast.success(this.$getString('ToastNewEpisodesFound', [response.episodes.length])) } else { - this.$toast.info('No new episodes found') + this.$toast.info(this.$strings.ToastNoNewEpisodesFound) } this.checkingNewEpisodes = false }) @@ -141,4 +141,4 @@ export default { this.setLastEpisodeCheckInput() } } - \ 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 6fe05705..c7247d51 100644 --- a/client/components/modals/item/tabs/Match.vue +++ b/client/components/modals/item/tabs/Match.vue @@ -60,7 +60,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.title || '' }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.title || '' }}

@@ -69,7 +69,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.subtitle }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.subtitle }}

@@ -78,7 +78,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.authorName }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.authorName }}

@@ -87,7 +87,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.narratorName }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.narratorName }}

@@ -96,7 +96,7 @@ @@ -105,7 +105,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.publisher }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.publisher }}

@@ -114,7 +114,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.publishedYear }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.publishedYear }}

@@ -124,7 +124,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.seriesName }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.seriesName }}

@@ -133,7 +133,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.genres.join(', ') }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.genres.join(', ') }}

@@ -142,7 +142,7 @@

- {{ $strings.LabelCurrently }} {{ media.tags.join(', ') }} + {{ $strings.LabelCurrently }} {{ media.tags.join(', ') }}

@@ -151,7 +151,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.language }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.language }}

@@ -160,7 +160,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.isbn }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.isbn }}

@@ -169,7 +169,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.asin }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.asin }}

@@ -179,7 +179,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.itunesId }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.itunesId }}

@@ -188,7 +188,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.feedUrl }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.feedUrl }}

@@ -197,7 +197,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.itunesPageUrl }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.itunesPageUrl }}

@@ -206,7 +206,7 @@

- {{ $strings.LabelCurrently }} {{ mediaMetadata.releaseDate }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.releaseDate }}

diff --git a/client/components/modals/item/tabs/Schedule.vue b/client/components/modals/item/tabs/Schedule.vue index 845f77ec..ecbf1c4e 100644 --- a/client/components/modals/item/tabs/Schedule.vue +++ b/client/components/modals/item/tabs/Schedule.vue @@ -2,28 +2,28 @@
@@ -97,7 +97,12 @@ export default { return this.enclosure.url }, episodeTypes() { - return this.$store.state.globals.episodeTypes || [] + return this.$store.state.globals.episodeTypes.map((e) => { + return { + text: this.$strings[e.descriptionKey] || e.text, + value: e.value + } + }) } }, methods: { @@ -152,14 +157,14 @@ export default { const updateResult = await this.$axios.$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, updatedDetails).catch((error) => { console.error('Failed update episode', error) this.isProcessing = false - this.$toast.error(error?.response?.data || 'Failed to update episode') + this.$toast.error(error?.response?.data || this.$strings.ToastFailedToUpdate) return false }) this.isProcessing = false if (updateResult) { if (updateResult) { - this.$toast.success('Podcast episode updated') + this.$toast.success(this.$strings.ToastItemUpdateSuccess) return true } else { this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary) diff --git a/client/components/tables/podcast/LazyEpisodeRow.vue b/client/components/tables/podcast/LazyEpisodeRow.vue index 2284da7f..20e6b9f9 100644 --- a/client/components/tables/podcast/LazyEpisodeRow.vue +++ b/client/components/tables/podcast/LazyEpisodeRow.vue @@ -12,10 +12,10 @@
-

Season #{{ episode.season }}

-

Episode #{{ episode.episode }}

-

{{ episode.chapters.length }} Chapters

-

Published {{ $formatDate(publishedAt, dateFormat) }}

+

{{ $getString('LabelSeasonNumber', [episode.season]) }}

+

{{ $getString('LabelEpisodeNumber', [episode.episode]) }}

+

{{ $getString('LabelChapterCount', [episode.chapters.length]) }}

+

{{ $getString('LabelPublishedDate', [$formatDate(publishedAt, dateFormat)]) }}

@@ -132,13 +132,13 @@ export default { return this.store.state.streamIsPlaying && this.isStreaming }, timeRemaining() { - if (this.streamIsPlaying) return 'Playing' + if (this.streamIsPlaying) return this.$strings.ButtonPlaying if (!this.itemProgress) return this.$elapsedPretty(this.episode?.duration || 0) - if (this.userIsFinished) return 'Finished' + if (this.userIsFinished) return this.$strings.LabelFinished const duration = this.itemProgress.duration || this.episode?.duration || 0 const remaining = Math.floor(duration - this.itemProgress.currentTime) - return `${this.$elapsedPretty(remaining)} left` + return this.$getString('LabelTimeLeft', [this.$elapsedPretty(remaining)]) } }, methods: { @@ -182,7 +182,7 @@ export default { toggleFinished(confirmed = false) { if (!this.userIsFinished && this.itemProgressPercent > 0 && !confirmed) { const payload = { - message: `Are you sure you want to mark "${this.episodeTitle}" as finished?`, + message: this.$getString('MessageConfirmMarkItemFinished', [this.episodeTitle]), callback: (confirmed) => { if (confirmed) { this.toggleFinished(true) diff --git a/client/components/tables/podcast/LazyEpisodesTable.vue b/client/components/tables/podcast/LazyEpisodesTable.vue index 3b0d3cee..963cd7c9 100644 --- a/client/components/tables/podcast/LazyEpisodesTable.vue +++ b/client/components/tables/podcast/LazyEpisodesTable.vue @@ -96,7 +96,7 @@ export default { const menuItems = [] if (this.userIsAdminOrUp) { menuItems.push({ - text: 'Quick match all episodes', + text: this.$strings.MessageQuickMatchAllEpisodes, action: 'quick-match-episodes' }) } @@ -262,21 +262,21 @@ export default { this.processing = true const payload = { - message: 'Quick matching episodes will overwrite details if a match is found. Only unmatched episodes will be updated. Are you sure?', + message: this.$strings.MessageConfirmQuickMatchEpisodes, callback: (confirmed) => { if (confirmed) { this.$axios .$post(`/api/podcasts/${this.libraryItem.id}/match-episodes?override=1`) .then((data) => { if (data.numEpisodesUpdated) { - this.$toast.success(`${data.numEpisodesUpdated} episodes updated`) + this.$toast.success(this.$getString('ToastEpisodeUpdateSuccess', [data.numEpisodesUpdated])) } else { this.$toast.info(this.$strings.ToastNoUpdatesNecessary) } }) .catch((error) => { console.error('Failed to request match episodes', error) - this.$toast.error('Failed to match episodes') + this.$toast.error(this.$strings.ToastFailedToMatch) }) } this.processing = false diff --git a/client/components/widgets/PodcastDetailsEdit.vue b/client/components/widgets/PodcastDetailsEdit.vue index 20513ba5..389ca894 100644 --- a/client/components/widgets/PodcastDetailsEdit.vue +++ b/client/components/widgets/PodcastDetailsEdit.vue @@ -101,7 +101,12 @@ export default { return this.$store.state.libraries.filterData || {} }, podcastTypes() { - return this.$store.state.globals.podcastTypes || [] + return this.$store.state.globals.podcastTypes.map((e) => { + return { + text: this.$strings[e.descriptionKey] || e.text, + value: e.value + } + }) } }, methods: { diff --git a/client/pages/audiobook/_id/chapters.vue b/client/pages/audiobook/_id/chapters.vue index 43d64b90..d7a8f9e1 100644 --- a/client/pages/audiobook/_id/chapters.vue +++ b/client/pages/audiobook/_id/chapters.vue @@ -486,7 +486,7 @@ export default { .then((data) => { this.saving = false if (data.updated) { - this.$toast.success('Chapters updated') + this.$toast.success(this.$strings.ToastChaptersUpdated) if (this.previousRoute) { this.$router.push(this.previousRoute) } else { @@ -533,7 +533,7 @@ export default { }, findChapters() { if (!this.asinInput) { - this.$toast.error('Must input an ASIN') + this.$toast.error(this.$strings.ToastAsinRequired) return } diff --git a/client/pages/library/_library/narrators.vue b/client/pages/library/_library/narrators.vue index e2a45da4..e22de8d0 100644 --- a/client/pages/library/_library/narrators.vue +++ b/client/pages/library/_library/narrators.vue @@ -120,7 +120,7 @@ export default { }) .catch((error) => { console.error('Failed to updated narrator', error) - this.$toast.error('Failed to update narrator') + this.$toast.error(this.$strings.ToastFailedToUpdate) this.loading = false }) }, diff --git a/client/store/globals.js b/client/store/globals.js index 553301f5..c0e7d788 100644 --- a/client/store/globals.js +++ b/client/store/globals.js @@ -72,13 +72,13 @@ export const state = () => ({ } ], podcastTypes: [ - { text: 'Episodic', value: 'episodic' }, - { text: 'Serial', value: 'serial' } + { text: 'Episodic', value: 'episodic', descriptionKey: 'LabelEpisodic' }, + { text: 'Serial', value: 'serial', descriptionKey: 'LabelSerial' } ], episodeTypes: [ - { text: 'Full', value: 'full' }, - { text: 'Trailer', value: 'trailer' }, - { text: 'Bonus', value: 'bonus' } + { text: 'Full', value: 'full', descriptionKey: 'LabelFull' }, + { text: 'Trailer', value: 'trailer', descriptionKey: 'LabelTrailer' }, + { text: 'Bonus', value: 'bonus', descriptionKey: 'LabelBonus' } ], libraryIcons: ['database', 'audiobookshelf', 'books-1', 'books-2', 'book-1', 'microphone-1', 'microphone-3', 'radio', 'podcast', 'rss', 'headphones', 'music', 'file-picture', 'rocket', 'power', 'star', 'heart'] }) diff --git a/client/strings/en-us.json b/client/strings/en-us.json index adfe1001..3cc96451 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -180,6 +180,7 @@ "HeaderRemoveEpisodes": "Remove {0} Episodes", "HeaderSavedMediaProgress": "Saved Media Progress", "HeaderSchedule": "Schedule", + "HeaderScheduleEpisodeDownloads": "Schedule Automatic Episode Downloads", "HeaderScheduleLibraryScans": "Schedule Automatic Library Scans", "HeaderSession": "Session", "HeaderSetBackupSchedule": "Set Backup Schedule", @@ -250,15 +251,18 @@ "LabelBackupsNumberToKeep": "Number of backups to keep", "LabelBackupsNumberToKeepHelp": "Only 1 backup will be removed at a time so if you already have more backups than this you should manually remove them.", "LabelBitrate": "Bitrate", + "LabelBonus": "Bonus", "LabelBooks": "Books", "LabelButtonText": "Button Text", "LabelByAuthor": "by {0}", "LabelChangePassword": "Change Password", "LabelChannels": "Channels", + "LabelChapterCount": "{0} Chapters", "LabelChapterTitle": "Chapter Title", "LabelChapters": "Chapters", "LabelChaptersFound": "chapters found", "LabelClickForMoreInfo": "Click for more info", + "LabelClickToUseCurrentValue": "Click to use current value", "LabelClosePlayer": "Close player", "LabelCodec": "Codec", "LabelCollapseSeries": "Collapse Series", @@ -320,9 +324,13 @@ "LabelEnd": "End", "LabelEndOfChapter": "End of Chapter", "LabelEpisode": "Episode", + "LabelEpisodeNotLinkedToRssFeed": "Episode not linked to RSS feed", + "LabelEpisodeNumber": "Episode #{0}", "LabelEpisodeTitle": "Episode Title", "LabelEpisodeType": "Episode Type", + "LabelEpisodeUrlFromRssFeed": "Episode URL from RSS feed", "LabelEpisodes": "Episodes", + "LabelEpisodic": "Episodic", "LabelExample": "Example", "LabelExpandSeries": "Expand Series", "LabelExpandSubSeries": "Expand Sub Series", @@ -350,6 +358,7 @@ "LabelFontScale": "Font scale", "LabelFontStrikethrough": "Strikethrough", "LabelFormat": "Format", + "LabelFull": "Full", "LabelGenre": "Genre", "LabelGenres": "Genres", "LabelHardDeleteFile": "Hard delete file", @@ -405,6 +414,10 @@ "LabelLowestPriority": "Lowest Priority", "LabelMatchExistingUsersBy": "Match existing users by", "LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider", + "LabelMaxEpisodesToDownload": "Max # of episodes to download. Use 0 for unlimited.", + "LabelMaxEpisodesToDownloadPerCheck": "Max # of new episodes to download per check", + "LabelMaxEpisodesToKeep": "Max # of episodes to keep", + "LabelMaxEpisodesToKeepHelp": "Value of 0 sets no max limit. After a new episode is auto-downloaded this will delete the oldest episode if you have more than X episodes. This will only delete 1 episode per new download.", "LabelMediaPlayer": "Media Player", "LabelMediaType": "Media Type", "LabelMetaTag": "Meta Tag", @@ -500,18 +513,24 @@ "LabelRedo": "Redo", "LabelRegion": "Region", "LabelReleaseDate": "Release Date", + "LabelRemoveAllMetadataAbs": "Remove all metadata.abs files", + "LabelRemoveAllMetadataJson": "Remove all metadata.json files", "LabelRemoveCover": "Remove cover", + "LabelRemoveMetadataFile": "Remove metadata files in library item folders", + "LabelRemoveMetadataFileHelp": "Remove all metadata.json and metadata.abs files in your {0} folders.", "LabelRowsPerPage": "Rows per page", "LabelSearchTerm": "Search Term", "LabelSearchTitle": "Search Title", "LabelSearchTitleOrASIN": "Search Title or ASIN", "LabelSeason": "Season", + "LabelSeasonNumber": "Season #{0}", "LabelSelectAll": "Select all", "LabelSelectAllEpisodes": "Select all episodes", "LabelSelectEpisodesShowing": "Select {0} episodes showing", "LabelSelectUsers": "Select users", "LabelSendEbookToDevice": "Send Ebook to...", "LabelSequence": "Sequence", + "LabelSerial": "Serial", "LabelSeries": "Series", "LabelSeriesName": "Series Name", "LabelSeriesProgress": "Series Progress", @@ -604,6 +623,7 @@ "LabelTimeDurationXMinutes": "{0} minutes", "LabelTimeDurationXSeconds": "{0} seconds", "LabelTimeInMinutes": "Time in minutes", + "LabelTimeLeft": "{0} left", "LabelTimeListened": "Time Listened", "LabelTimeListenedToday": "Time Listened Today", "LabelTimeRemaining": "{0} remaining", @@ -624,6 +644,7 @@ "LabelTracksMultiTrack": "Multi-track", "LabelTracksNone": "No tracks", "LabelTracksSingleTrack": "Single-track", + "LabelTrailer": "Trailer", "LabelType": "Type", "LabelUnabridged": "Unabridged", "LabelUndo": "Undo", @@ -640,6 +661,7 @@ "LabelUseAdvancedOptions": "Use Advanced Options", "LabelUseChapterTrack": "Use chapter track", "LabelUseFullTrack": "Use full track", + "LabelUseZeroForUnlimited": "Use 0 for unlimited", "LabelUser": "User", "LabelUsername": "Username", "LabelValue": "Value", @@ -698,6 +720,7 @@ "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?", + "MessageConfirmQuickMatchEpisodes": "Quick matching episodes will overwrite details if a match is found. Only unmatched episodes will be updated. Are you sure?", "MessageConfirmReScanLibraryItems": "Are you sure you want to re-scan {0} items?", "MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?", "MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?", @@ -705,6 +728,7 @@ "MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?", "MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?", "MessageConfirmRemoveListeningSessions": "Are you sure you want to remove {0} listening sessions?", + "MessageConfirmRemoveMetadataFiles": "Are you sure you want to remove all metadata.{0} files in your library item folders?", "MessageConfirmRemoveNarrator": "Are you sure you want to remove narrator \"{0}\"?", "MessageConfirmRemovePlaylist": "Are you sure you want to remove your playlist \"{0}\"?", "MessageConfirmRenameGenre": "Are you sure you want to rename genre \"{0}\" to \"{1}\" for all items?", @@ -785,6 +809,7 @@ "MessagePodcastSearchField": "Enter search term or RSS feed URL", "MessageQuickEmbedInProgress": "Quick embed in progress", "MessageQuickEmbedQueue": "Queued for quick embed ({0} in queue)", + "MessageQuickMatchAllEpisodes": "Quick Match All Episodes", "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", "MessageRemoveEpisodes": "Remove {0} episode(s)", @@ -883,6 +908,7 @@ "StatsYearInReview": "YEAR IN REVIEW", "ToastAccountUpdateSuccess": "Account updated", "ToastAppriseUrlRequired": "Must enter an Apprise URL", + "ToastAsinRequired": "ASIN is required", "ToastAuthorImageRemoveSuccess": "Author image removed", "ToastAuthorNotFound": "Author \"{0}\" not found", "ToastAuthorRemoveSuccess": "Author removed", @@ -902,6 +928,8 @@ "ToastBackupUploadSuccess": "Backup uploaded", "ToastBatchDeleteFailed": "Batch delete failed", "ToastBatchDeleteSuccess": "Batch delete success", + "ToastBatchQuickMatchFailed": "Batch Quick Match failed!", + "ToastBatchQuickMatchStarted": "Batch Quick Match of {0} books started!", "ToastBatchUpdateFailed": "Batch update failed", "ToastBatchUpdateSuccess": "Batch update success", "ToastBookmarkCreateFailed": "Failed to create bookmark", @@ -913,6 +941,7 @@ "ToastChaptersHaveErrors": "Chapters have errors", "ToastChaptersMustHaveTitles": "Chapters must have titles", "ToastChaptersRemoved": "Chapters removed", + "ToastChaptersUpdated": "Chapters updated", "ToastCollectionItemsAddFailed": "Item(s) added to collection failed", "ToastCollectionItemsAddSuccess": "Item(s) added to collection success", "ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection", @@ -930,11 +959,14 @@ "ToastEncodeCancelSucces": "Encode canceled", "ToastEpisodeDownloadQueueClearFailed": "Failed to clear queue", "ToastEpisodeDownloadQueueClearSuccess": "Episode download queue cleared", + "ToastEpisodeUpdateSuccess": "{0} episodes updated", "ToastErrorCannotShare": "Cannot share natively on this device", "ToastFailedToLoadData": "Failed to load data", + "ToastFailedToMatch": "Failed to match", "ToastFailedToShare": "Failed to share", "ToastFailedToUpdate": "Failed to update", "ToastInvalidImageUrl": "Invalid image URL", + "ToastInvalidMaxEpisodesToDownload": "Invalid max episodes to download", "ToastInvalidUrl": "Invalid URL", "ToastItemCoverUpdateSuccess": "Item cover updated", "ToastItemDeletedFailed": "Failed to delete item", @@ -953,14 +985,21 @@ "ToastLibraryScanStarted": "Library scan started", "ToastLibraryUpdateSuccess": "Library \"{0}\" updated", "ToastMatchAllAuthorsFailed": "Failed to match all authors", + "ToastMetadataFilesRemovedError": "Error removing metadata.{0} files", + "ToastMetadataFilesRemovedNoneFound": "No metadata.{0} files found in library", + "ToastMetadataFilesRemovedNoneRemoved": "No metadata.{0} files removed", + "ToastMetadataFilesRemovedSuccess": "{0} metadata.{1} files removed", + "ToastMustHaveAtLeastOnePath": "Must have at least one path", "ToastNameEmailRequired": "Name and email are required", "ToastNameRequired": "Name is required", + "ToastNewEpisodesFound": "{0} new episodes found", "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", + "ToastNoNewEpisodesFound": "No new episodes found", "ToastNoUpdatesNecessary": "No updates necessary", "ToastNotificationCreateFailed": "Failed to create notification", "ToastNotificationDeleteFailed": "Failed to delete notification", @@ -979,6 +1018,7 @@ "ToastPodcastGetFeedFailed": "Failed to get podcast feed", "ToastPodcastNoEpisodesInFeed": "No episodes found in RSS feed", "ToastPodcastNoRssFeed": "Podcast does not have an RSS feed", + "ToastProgressIsNotBeingSynced": "Progress is not being synced, restart playback", "ToastProviderCreatedFailed": "Failed to add provider", "ToastProviderCreatedSuccess": "New provider added", "ToastProviderNameAndUrlRequired": "Name and Url required", @@ -1005,6 +1045,7 @@ "ToastSessionCloseFailed": "Failed to close session", "ToastSessionDeleteFailed": "Failed to delete session", "ToastSessionDeleteSuccess": "Session deleted", + "ToastSleepTimerDone": "Sleep timer done... zZzzZz", "ToastSlugMustChange": "Slug contains invalid characters", "ToastSlugRequired": "Slug is required", "ToastSocketConnected": "Socket connected",