diff --git a/client/components/app/ConfigSideNav.vue b/client/components/app/ConfigSideNav.vue index adc99e5ae..108dc42d0 100644 --- a/client/components/app/ConfigSideNav.vue +++ b/client/components/app/ConfigSideNav.vue @@ -19,7 +19,7 @@
{{ Source }}
- Latest: {{ $config.version }} + Latest: {{ versionData.latestVersion }} diff --git a/client/components/app/MediaPlayerContainer.vue b/client/components/app/MediaPlayerContainer.vue index 259e0c98b..1a19f3019 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 1a5e8bd69..597aca99b 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/covers/AuthorImage.vue b/client/components/covers/AuthorImage.vue index af8a394f4..019263633 100644 --- a/client/components/covers/AuthorImage.vue +++ b/client/components/covers/AuthorImage.vue @@ -56,11 +56,7 @@ export default { }, imgSrc() { if (!this.imagePath) return null - if (process.env.NODE_ENV !== 'production') { - // Testing - return `http://localhost:3333${this.$config.routerBasePath}/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}` - } - return `/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}` + return `${this.$config.routerBasePath}/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}` } }, methods: { diff --git a/client/components/modals/AccountModal.vue b/client/components/modals/AccountModal.vue index 1ea24fd04..9c70e728b 100644 --- a/client/components/modals/AccountModal.vue +++ b/client/components/modals/AccountModal.vue @@ -69,6 +69,15 @@ +{{ $strings.LabelPermissionsCreateEreader }}
+{{ $strings.LabelPermissionsAccessExplicitContent }}
@@ -354,7 +363,8 @@ export default { accessExplicitContent: type === 'admin', accessAllLibraries: true, accessAllTags: true, - selectedTagsNotAccessible: false + selectedTagsNotAccessible: false, + createEreader: type === 'admin' } }, init() { @@ -387,7 +397,8 @@ export default { accessAllLibraries: true, accessAllTags: true, accessExplicitContent: false, - selectedTagsNotAccessible: false + selectedTagsNotAccessible: false, + createEreader: false }, librariesAccessible: [], itemTagsSelected: [] diff --git a/client/components/modals/BatchQuickMatchModel.vue b/client/components/modals/BatchQuickMatchModel.vue index 3d2015d6b..ce227c281 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/ShareModal.vue b/client/components/modals/ShareModal.vue index 65ef4fc73..d0487fd38 100644 --- a/client/components/modals/ShareModal.vue +++ b/client/components/modals/ShareModal.vue @@ -112,11 +112,11 @@ export default { return this.$store.state.user.user }, demoShareUrl() { - return `${window.origin}/share/${this.newShareSlug}` + return `${window.origin}${this.$config.routerBasePath}/share/${this.newShareSlug}` }, currentShareUrl() { if (!this.currentShare) return '' - return `${window.origin}/share/${this.currentShare.slug}` + return `${window.origin}${this.$config.routerBasePath}/share/${this.currentShare.slug}` }, currentShareTimeRemaining() { if (!this.currentShare) return 'Error' diff --git a/client/components/modals/emails/UserEReaderDeviceModal.vue b/client/components/modals/emails/UserEReaderDeviceModal.vue new file mode 100644 index 000000000..b1706305c --- /dev/null +++ b/client/components/modals/emails/UserEReaderDeviceModal.vue @@ -0,0 +1,188 @@ + +{{ title }}
+{{ $strings.LabelLimit }}
-- {{ $strings.LabelCurrently }} {{ mediaMetadata.title || '' }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.title || '' }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.subtitle }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.subtitle }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.authorName }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.authorName }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.narratorName }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.narratorName }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.publisher }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.publisher }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.publishedYear }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.publishedYear }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.seriesName }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.seriesName }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.genres.join(', ') }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.genres.join(', ') }}
- {{ $strings.LabelCurrently }} {{ media.tags.join(', ') }} + {{ $strings.LabelCurrently }} {{ media.tags.join(', ') }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.language }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.language }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.isbn }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.isbn }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.asin }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.asin }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.itunesId }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.itunesId }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.feedUrl }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.feedUrl }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.itunesPageUrl }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.itunesPageUrl }}
- {{ $strings.LabelCurrently }} {{ mediaMetadata.releaseDate }} + {{ $strings.LabelCurrently }} {{ mediaMetadata.releaseDate }}
Schedule Automatic Episode Downloads
-{{ $strings.HeaderScheduleEpisodeDownloads }}
+- Max episodes to keep + {{ $strings.LabelMaxEpisodesToKeep }} info
- Max new episodes to download per check + {{ $strings.LabelMaxEpisodesToDownloadPerCheck }} info
- {{ $strings.LabelSettingsSquareBookCovers }} - info -
-{{ $strings.LabelSettingsEnableWatcherForLibrary }}
-*{{ $strings.MessageWatcherIsDisabledGlobally }}
-- {{ $strings.LabelSettingsAudiobooksOnly }} - info -
-{{ $strings.LabelSettingsSkipMatchingBooksWithASIN }}
-{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}
-- {{ $strings.LabelSettingsHideSingleBookSeries }} +
+ {{ $strings.LabelSettingsSquareBookCovers }} info
- {{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }} +
{{ $strings.LabelSettingsEnableWatcherForLibrary }}
+*{{ $strings.MessageWatcherIsDisabledGlobally }}
++ {{ $strings.LabelSettingsAudiobooksOnly }} info
- {{ $strings.LabelSettingsEpubsAllowScriptedContent }} - info -
-{{ $strings.LabelSettingsSkipMatchingBooksWithASIN }}
+{{ $strings.LabelSettingsSkipMatchingBooksWithISBN }}
++ {{ $strings.LabelSettingsHideSingleBookSeries }} + info +
++ {{ $strings.LabelSettingsOnlyShowLaterBooksInContinueSeries }} + info +
++ {{ $strings.LabelSettingsEpubsAllowScriptedContent }} + info +
+Remove metadata files in library item folders
-Remove all metadata.json or metadata.abs files in your {{ mediaType }} folders
+{{ $strings.LabelRemoveMetadataFile }}
+{{ $getString('LabelRemoveMetadataFileHelp', [mediaType]) }}
Episode not linked to RSS feed episode
+{{ $strings.LabelEpisodeNotLinkedToRssFeed }}
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)]) }}
| {{ $strings.LabelName }} | +{{ $strings.LabelEmail }} | ++ |
|---|---|---|
|
+ {{ device.name }} + |
+
+ {{ device.email }} + |
+
+
+
+ |
+
{{ $strings.MessageNoDevices }}
+Page {{ currentPage + 1 }} of {{ numPages }}
+{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}
Open Listening Sessions
+{{ $strings.HeaderOpenListeningSessions }}
Page {{ currentPage + 1 }} of {{ numPages }}
+{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}
{{ mediaItemShare.playbackSession.displayAuthor }}
groups. If configured, the application will automatically assign roles based on the user's group memberships, provided that these groups are named case-insensitively 'admin', 'user', or 'guest' in the claim. The claim should contain a list, and if a user belongs to multiple groups, the application will assign the role corresponding to the highest level of access. If no group matches, access will be denied.",
"LabelOpenRSSFeed": "Open RSS Feed",
"LabelOverwrite": "Overwrite",
+ "LabelPaginationPageXOfY": "Page {0} of {1}",
"LabelPassword": "Password",
"LabelPath": "Path",
"LabelPermanent": "Permanent",
"LabelPermissionsAccessAllLibraries": "Can Access All Libraries",
"LabelPermissionsAccessAllTags": "Can Access All Tags",
"LabelPermissionsAccessExplicitContent": "Can Access Explicit Content",
+ "LabelPermissionsCreateEreader": "Can Create Ereader",
"LabelPermissionsDelete": "Can Delete",
"LabelPermissionsDownload": "Can Download",
"LabelPermissionsUpdate": "Can Update",
@@ -500,18 +517,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",
@@ -540,6 +563,9 @@
"LabelSettingsHideSingleBookSeriesHelp": "Series that have a single book will be hidden from the series page and home page shelves.",
"LabelSettingsHomePageBookshelfView": "Home page use bookshelf view",
"LabelSettingsLibraryBookshelfView": "Library use bookshelf view",
+ "LabelSettingsLibraryMarkAsFinishedPercentComplete": "Percent complete is greater than",
+ "LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Time remaining is less than (seconds)",
+ "LabelSettingsLibraryMarkAsFinishedWhen": "Mark media item as finished when",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Skip earlier books in Continue Series",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "The Continue Series home page shelf shows the first book not started in series that have at least one book finished and no books in progress. Enabling this setting will continue series from the furthest completed book instead of the first book not started.",
"LabelSettingsParseSubtitles": "Parse subtitles",
@@ -604,6 +630,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 +651,7 @@
"LabelTracksMultiTrack": "Multi-track",
"LabelTracksNone": "No tracks",
"LabelTracksSingleTrack": "Single-track",
+ "LabelTrailer": "Trailer",
"LabelType": "Type",
"LabelUnabridged": "Unabridged",
"LabelUndo": "Undo",
@@ -640,6 +668,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 +727,7 @@
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at /metadata/cache. /metadata/cache/items.http://192.168.1.1:8337 trebate upisati http://192.168.1.1:8337/notify.",
- "MessageBackupsDescription": "Backups uključuju korisnike, korisnikov napredak, detalje stavki iz biblioteke, postavke server i slike iz /metadata/items & /metadata/authors. Backups ne uključuju nijedne datoteke koje su u folderima biblioteke.",
+ "MessageBackupsDescription": "Sigurnosne kopije sadrže korisnike, korisnikov napredak medija, pojedinosti knjižničke građe, postavke poslužitelja i slike koje se spremaju u /metadata/items & /metadata/authors. Sigurnosne kopije ne sadrže niti jednu datoteku iz mapa knjižnice.",
"MessageBackupsLocationEditNote": "Napomena: Uređivanje lokacije za sigurnosne kopije ne premješta ili mijenja postojeće sigurnosne kopije",
"MessageBackupsLocationNoEditNote": "Napomena: Lokacija za sigurnosne kopije zadana je kroz varijablu okoline i ovdje se ne može izmijeniti.",
"MessageBackupsLocationPathEmpty": "Putanja do lokacije za sigurnosne kopije ne može ostati prazna",
@@ -669,6 +707,7 @@
"MessageConfirmDeleteMetadataProvider": "Sigurno želite izbrisati prilagođenog pružatelja meta-podataka \"{0}\"?",
"MessageConfirmDeleteNotification": "Sigurno želite izbrisati ovu obavijest?",
"MessageConfirmDeleteSession": "Sigurno želite obrisati ovu sesiju?",
+ "MessageConfirmEmbedMetadataInAudioFiles": "Sigurno želite ugraditi meta-podatke u {0} zvučnih datoteka?",
"MessageConfirmForceReScan": "Sigurno želite ponovno pokrenuti skeniranje?",
"MessageConfirmMarkAllEpisodesFinished": "Sigurno želite označiti sve nastavke dovršenima?",
"MessageConfirmMarkAllEpisodesNotFinished": "Sigurno želite označiti sve nastavke nedovršenima?",
@@ -680,6 +719,7 @@
"MessageConfirmPurgeCache": "Brisanje predmemorije izbrisat će cijelu mapu /metadata/cache. /metadata/cache/items./metadata/cache. /metadata/cache/items./metadata/cache 整个目录. /metadata/cache/items 整个目录.