-
00:00:00
-
/ {{ progressPercent }}%
-
-
- {{ currentChapterName }} ({{ $getString('LabelPlayerChapterNumberMarker', [currentChapterIndex + 1, chapters.length]) }})
-
-
-
{{ timeRemainingPretty }}
+
+
+
00:00:00
+
/ {{ progressPercent }}%
+
+
+
+
{{ timeRemainingPretty }}
+
@@ -224,6 +228,12 @@ export default {
this.playbackRate = Number((this.playbackRate - 0.1).toFixed(1))
this.setPlaybackRate(this.playbackRate)
},
+ playbackRateChanged(playbackRate) {
+ this.setPlaybackRate(playbackRate)
+ this.$store.dispatch('user/updateUserSettings', { playbackRate }).catch((err) => {
+ console.error('Failed to update settings', err)
+ })
+ },
setPlaybackRate(playbackRate) {
this.$emit('setPlaybackRate', playbackRate)
},
diff --git a/client/components/stats/DailyListeningChart.vue b/client/components/stats/DailyListeningChart.vue
index d96813243..ca2cd714b 100644
--- a/client/components/stats/DailyListeningChart.vue
+++ b/client/components/stats/DailyListeningChart.vue
@@ -35,22 +35,22 @@
{{ $strings.LabelStatsWeekListening }}
-
{{ totalMinutesListeningThisWeek }}
+
{{ $formatNumber(totalMinutesListeningThisWeek) }}
{{ $strings.LabelStatsMinutes }}
{{ $strings.LabelStatsDailyAverage }}
-
{{ averageMinutesPerDay }}
+
{{ $formatNumber(averageMinutesPerDay) }}
{{ $strings.LabelStatsMinutes }}
{{ $strings.LabelStatsBestDay }}
-
{{ mostListenedDay }}
+
{{ $formatNumber(mostListenedDay) }}
{{ $strings.LabelStatsMinutes }}
{{ $strings.LabelStatsDays }}
-
{{ daysInARow }}
+
{{ $formatNumber(daysInARow) }}
{{ $strings.LabelStatsInARow }}
diff --git a/client/components/tables/CollectionBooksTable.vue b/client/components/tables/CollectionBooksTable.vue
index ce66de11b..5803889f1 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(this.$strings.ToastCollectionUpdateFailed)
+ this.$toast.error(this.$strings.ToastFailedToUpdate)
})
},
editBook(book) {
diff --git a/client/components/tables/PlaylistItemsTable.vue b/client/components/tables/PlaylistItemsTable.vue
index 09811d4a9..a30125d6a 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(this.$strings.ToastPlaylistUpdateFailed)
+ this.$toast.error(this.$strings.ToastFailedToUpdate)
})
},
init() {
diff --git a/client/components/tables/playlist/ItemTableRow.vue b/client/components/tables/playlist/ItemTableRow.vue
index f7cc7d7b7..9f1a7e87f 100644
--- a/client/components/tables/playlist/ItemTableRow.vue
+++ b/client/components/tables/playlist/ItemTableRow.vue
@@ -223,7 +223,7 @@ export default {
})
.catch((error) => {
console.error('Failed to remove item from playlist', error)
- this.$toast.error(this.$strings.ToastPlaylistUpdateFailed)
+ this.$toast.error(this.$strings.ToastFailedToUpdate)
})
.finally(() => {
this.processingRemove = false
diff --git a/client/components/widgets/ItemSlider.vue b/client/components/widgets/ItemSlider.vue
index 15051fb1f..fd14a2024 100644
--- a/client/components/widgets/ItemSlider.vue
+++ b/client/components/widgets/ItemSlider.vue
@@ -65,7 +65,7 @@ export default {
},
authors: {
component: 'cards-author-card',
- itemPropName: 'author',
+ itemPropName: 'author-mount',
itemIdFunc: (item) => item.id
},
narrators: {
diff --git a/client/cypress/tests/components/cards/AuthorCard.cy.js b/client/cypress/tests/components/cards/AuthorCard.cy.js
index 420678aba..21c638e18 100644
--- a/client/cypress/tests/components/cards/AuthorCard.cy.js
+++ b/client/cypress/tests/components/cards/AuthorCard.cy.js
@@ -5,14 +5,14 @@ import Tooltip from '@/components/ui/Tooltip.vue'
import LoadingSpinner from '@/components/widgets/LoadingSpinner.vue'
describe('AuthorCard', () => {
- const author = {
+ const authorMount = {
id: 1,
name: 'John Doe',
numBooks: 5
}
const propsData = {
- author,
+ authorMount,
nameBelow: false
}
diff --git a/client/mixins/bookshelfCardsHelpers.js b/client/mixins/bookshelfCardsHelpers.js
index 31fa76ba0..802b2cc8a 100644
--- a/client/mixins/bookshelfCardsHelpers.js
+++ b/client/mixins/bookshelfCardsHelpers.js
@@ -4,6 +4,7 @@ import LazySeriesCard from '@/components/cards/LazySeriesCard'
import LazyCollectionCard from '@/components/cards/LazyCollectionCard'
import LazyPlaylistCard from '@/components/cards/LazyPlaylistCard'
import LazyAlbumCard from '@/components/cards/LazyAlbumCard'
+import AuthorCard from '@/components/cards/AuthorCard'
export default {
data() {
@@ -20,6 +21,7 @@ export default {
if (this.entityName === 'collections') return Vue.extend(LazyCollectionCard)
if (this.entityName === 'playlists') return Vue.extend(LazyPlaylistCard)
if (this.entityName === 'albums') return Vue.extend(LazyAlbumCard)
+ if (this.entityName === 'authors') return Vue.extend(AuthorCard)
return Vue.extend(LazyBookCard)
},
getComponentName() {
@@ -27,6 +29,7 @@ export default {
if (this.entityName === 'collections') return 'cards-lazy-collection-card'
if (this.entityName === 'playlists') return 'cards-lazy-playlist-card'
if (this.entityName === 'albums') return 'cards-lazy-album-card'
+ if (this.entityName === 'authors') return 'cards-author-card'
return 'cards-lazy-book-card'
},
async setCardSize() {
@@ -46,13 +49,14 @@ export default {
props.orderBy = this.seriesSortBy
}
const instance = new ComponentClass({
- propsData: props
+ propsData: props,
+ parent: this
})
instance.$mount()
this.resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
- this.cardWidth = entry.contentRect.width
- this.cardHeight = entry.contentRect.height
+ this.cardWidth = entry.borderBoxSize[0].inlineSize
+ this.cardHeight = entry.borderBoxSize[0].blockSize
this.resizeObserver.disconnect()
this.$refs.bookshelf.removeChild(instance.$el)
}
@@ -72,7 +76,7 @@ export default {
})
const timeAfter = performance.now()
},
- async mountEntityCard(index) {
+ mountEntityCard(index) {
var shelf = Math.floor(index / this.entitiesPerShelf)
var shelfEl = document.getElementById(`shelf-${shelf}`)
if (!shelfEl) {
@@ -114,6 +118,7 @@ export default {
const _this = this
const instance = new ComponentClass({
propsData: props,
+ parent: this,
created() {
this.$on('edit', (entity) => {
if (_this.editEntity) _this.editEntity(entity)
diff --git a/client/package-lock.json b/client/package-lock.json
index 48506f925..4c6df7952 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "audiobookshelf-client",
- "version": "2.13.4",
+ "version": "2.14.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf-client",
- "version": "2.13.4",
+ "version": "2.14.0",
"license": "ISC",
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
diff --git a/client/package.json b/client/package.json
index 53487bc91..ab70a30b2 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
- "version": "2.13.4",
+ "version": "2.14.0",
"buildNumber": 1,
"description": "Self-hosted audiobook and podcast client",
"main": "index.js",
diff --git a/client/pages/audiobook/_id/chapters.vue b/client/pages/audiobook/_id/chapters.vue
index 5da4813d6..3448479b9 100644
--- a/client/pages/audiobook/_id/chapters.vue
+++ b/client/pages/audiobook/_id/chapters.vue
@@ -499,7 +499,7 @@ export default {
.catch((error) => {
this.saving = false
console.error('Failed to update chapters', error)
- this.$toast.error('Failed to update chapters')
+ this.$toast.error(this.$strings.ToastFailedToUpdate)
})
},
applyChapterNamesOnly() {
diff --git a/client/pages/author/_id.vue b/client/pages/author/_id.vue
index 0618f8a76..950312ab2 100644
--- a/client/pages/author/_id.vue
+++ b/client/pages/author/_id.vue
@@ -53,7 +53,7 @@ export default {
})
if (!author) {
- return redirect(`/library/${store.state.libraries.currentLibraryId}/authors`)
+ return redirect(`/library/${store.state.libraries.currentLibraryId}/bookshelf/authors`)
}
if (store.state.libraries.currentLibraryId !== author.libraryId || !store.state.libraries.filterData) {
@@ -109,7 +109,7 @@ export default {
authorRemoved(author) {
if (author.id === this.author.id) {
console.warn('Author was removed')
- this.$router.replace(`/library/${this.currentLibraryId}/authors`)
+ this.$router.replace(`/library/${this.currentLibraryId}/bookshelf/authors`)
}
}
},
diff --git a/client/pages/config/authentication.vue b/client/pages/config/authentication.vue
index 50fa50a41..1f934c88e 100644
--- a/client/pages/config/authentication.vue
+++ b/client/pages/config/authentication.vue
@@ -317,7 +317,7 @@ export default {
})
.catch((error) => {
console.error('Failed to update server settings', error)
- this.$toast.error(this.$strings.ToastServerSettingsUpdateFailed)
+ this.$toast.error(this.$strings.ToastFailedToUpdate)
})
.finally(() => {
this.savingSettings = false
diff --git a/client/pages/config/backups.vue b/client/pages/config/backups.vue
index 0c64ddf6f..d1dce014c 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 || this.$strings.ToastBackupPathUpdateFailed
+ const errorMsg = error.response?.data || this.$strings.ToastFailedToUpdate
this.$toast.error(errorMsg)
})
.finally(() => {
diff --git a/client/pages/config/email.vue b/client/pages/config/email.vue
index 212c51f31..e5fd03baa 100644
--- a/client/pages/config/email.vue
+++ b/client/pages/config/email.vue
@@ -292,7 +292,7 @@ export default {
})
.catch((error) => {
console.error('Failed to update email settings', error)
- this.$toast.error(this.$strings.ToastEmailSettingsUpdateFailed)
+ this.$toast.error(this.$strings.ToastFailedToUpdate)
})
.finally(() => {
this.savingSettings = false
diff --git a/client/pages/config/index.vue b/client/pages/config/index.vue
index 3d525142a..4ab913336 100644
--- a/client/pages/config/index.vue
+++ b/client/pages/config/index.vue
@@ -290,7 +290,7 @@ export default {
})
.catch((error) => {
console.error('Failed to update prefixes', error)
- this.$toast.error(this.$strings.ToastSortingPrefixesUpdateFailed)
+ this.$toast.error(this.$strings.ToastFailedToUpdate)
})
.finally(() => {
this.savingPrefixes = false
@@ -328,7 +328,6 @@ export default {
.dispatch('updateServerSettings', payload)
.then(() => {
this.updatingServerSettings = false
- this.$toast.success(this.$strings.ToastServerSettingsUpdateSuccess)
if (payload.language) {
// Updating language after save allows for re-rendering
@@ -338,7 +337,7 @@ export default {
.catch((error) => {
console.error('Failed to update server settings', error)
this.updatingServerSettings = false
- this.$toast.error(this.$strings.ToastServerSettingsUpdateFailed)
+ this.$toast.error(this.$strings.ToastFailedToUpdate)
})
},
initServerSettings() {
diff --git a/client/pages/config/notifications.vue b/client/pages/config/notifications.vue
index 24ea6a6cc..a43a9aeda 100644
--- a/client/pages/config/notifications.vue
+++ b/client/pages/config/notifications.vue
@@ -132,7 +132,7 @@ export default {
})
.catch((error) => {
console.error('Failed to update notification settings', error)
- this.$toast.error(this.$strings.ToastNotificationSettingsUpdateFailed)
+ this.$toast.error(this.$strings.ToastFailedToUpdate)
})
.finally(() => {
this.savingSettings = false
diff --git a/client/pages/library/_library/authors/index.vue b/client/pages/library/_library/authors/index.vue
deleted file mode 100644
index 9820fbce6..000000000
--- a/client/pages/library/_library/authors/index.vue
+++ /dev/null
@@ -1,115 +0,0 @@
-
-
-
-
-
diff --git a/client/pages/library/_library/bookshelf/_id.vue b/client/pages/library/_library/bookshelf/_id.vue
index 7037a012f..72cfba4a9 100644
--- a/client/pages/library/_library/bookshelf/_id.vue
+++ b/client/pages/library/_library/bookshelf/_id.vue
@@ -27,7 +27,7 @@ export default {
// Redirect podcast libraries
const library = libraryData.library
- if (library.mediaType === 'podcast' && (params.id === 'collections' || params.id === 'series')) {
+ if (library.mediaType === 'podcast' && (params.id === 'collections' || params.id === 'series' || params.id === 'authors')) {
return redirect(`/library/${libraryId}`)
}
diff --git a/client/pages/upload/index.vue b/client/pages/upload/index.vue
index 2d0aa1313..0efa1456d 100644
--- a/client/pages/upload/index.vue
+++ b/client/pages/upload/index.vue
@@ -384,12 +384,6 @@ export default {
else itemsFailed++
this.updateItemCardStatus(item.index, result ? 'success' : 'failed')
}
- if (itemsUploaded) {
- this.$toast.success(`Successfully uploaded ${itemsUploaded} item${itemsUploaded > 1 ? 's' : ''}`)
- }
- if (itemsFailed) {
- this.$toast.success(`Failed to upload ${itemsFailed} item${itemsFailed > 1 ? 's' : ''}`)
- }
this.processing = false
this.uploadFinished = true
}
diff --git a/client/plugins/i18n.js b/client/plugins/i18n.js
index 2eb6b123c..0ec5cccee 100644
--- a/client/plugins/i18n.js
+++ b/client/plugins/i18n.js
@@ -89,10 +89,10 @@ Vue.prototype.$strings = { ...enUsStrings }
* Get string and substitute
*
* @param {string} key
- * @param {string[]} subs
+ * @param {string[]} [subs=[]]
* @returns {string}
*/
-Vue.prototype.$getString = (key, subs) => {
+Vue.prototype.$getString = (key, subs = []) => {
if (!Vue.prototype.$strings[key]) return ''
if (subs?.length && Array.isArray(subs)) {
return supplant(Vue.prototype.$strings[key], subs)
diff --git a/client/plugins/utils.js b/client/plugins/utils.js
index 160ff9439..ad08ebf6a 100644
--- a/client/plugins/utils.js
+++ b/client/plugins/utils.js
@@ -18,7 +18,10 @@ Vue.prototype.$bytesPretty = (bytes, decimals = 2) => {
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}
-Vue.prototype.$elapsedPretty = (seconds, useFullNames = false) => {
+Vue.prototype.$elapsedPretty = (seconds, useFullNames = false, useMilliseconds = false) => {
+ if (useMilliseconds && seconds > 0 && seconds < 1) {
+ return `${Math.floor(seconds * 1000)} ms`
+ }
if (seconds < 60) {
return `${Math.floor(seconds)} sec${useFullNames ? 'onds' : ''}`
}
diff --git a/client/store/libraries.js b/client/store/libraries.js
index b92800baf..81d325774 100644
--- a/client/store/libraries.js
+++ b/client/store/libraries.js
@@ -240,7 +240,8 @@ export const mutations = {
series: [],
narrators: [],
languages: [],
- publishers: []
+ publishers: [],
+ publishedDecades: []
}
*/
const mediaMetadata = libraryItem.media.metadata
@@ -307,6 +308,16 @@ export const mutations = {
state.filterData.publishers.sort((a, b) => a.localeCompare(b))
}
+ // Add publishedDecades
+ if (mediaMetadata.publishedYear) {
+ const publishedYear = parseInt(mediaMetadata.publishedYear, 10)
+ const decade = Math.floor(publishedYear / 10) * 10
+ if (!state.filterData.publishedDecades.includes(decade)) {
+ state.filterData.publishedDecades.push(decade)
+ state.filterData.publishedDecades.sort((a, b) => a - b)
+ }
+ }
+
// Add language
if (mediaMetadata.language && !state.filterData.languages.includes(mediaMetadata.language)) {
state.filterData.languages.push(mediaMetadata.language)
diff --git a/client/store/user.js b/client/store/user.js
index 10dc8ef66..0e4cc0ccf 100644
--- a/client/store/user.js
+++ b/client/store/user.js
@@ -90,7 +90,7 @@ export const actions = {
if (state.settings.orderBy == 'media.metadata.publishedYear') {
settingsUpdate.orderBy = 'media.metadata.title'
}
- const invalidFilters = ['series', 'authors', 'narrators', 'publishers', 'languages', 'progress', 'issues', 'ebooks', 'abridged']
+ const invalidFilters = ['series', 'authors', 'narrators', 'publishers', 'publishedDecades', 'languages', 'progress', 'issues', 'ebooks', 'abridged']
const filterByFirstPart = (state.settings.filterBy || '').split('.').shift()
if (invalidFilters.includes(filterByFirstPart)) {
settingsUpdate.filterBy = 'all'
diff --git a/client/strings/bg.json b/client/strings/bg.json
index 3c934d893..8e124d063 100644
--- a/client/strings/bg.json
+++ b/client/strings/bg.json
@@ -711,10 +711,8 @@
"PlaceholderNewPlaylist": "Ново име на плейлиста",
"PlaceholderSearch": "Търсене...",
"PlaceholderSearchEpisode": "Търсене на Епизоди...",
- "ToastAccountUpdateFailed": "Неуспешно обновяване на акаунта",
"ToastAccountUpdateSuccess": "Успешно обновяване на акаунта",
"ToastAuthorImageRemoveSuccess": "Авторската снимка е премахната",
- "ToastAuthorUpdateFailed": "Неуспешно обновяване на автора",
"ToastAuthorUpdateMerged": "Обновяване на автора сливано",
"ToastAuthorUpdateSuccess": "Автора обновен",
"ToastAuthorUpdateSuccessNoImageFound": "Автор обновен (не е намерена снимка)",
@@ -728,17 +726,13 @@
"ToastBookmarkCreateFailed": "Неуспешно създаване на отметка",
"ToastBookmarkCreateSuccess": "Отметката е създадена",
"ToastBookmarkRemoveSuccess": "Отметката е премахната",
- "ToastBookmarkUpdateFailed": "Неуспешно обновяване на отметка",
"ToastBookmarkUpdateSuccess": "Отметката е обновена",
"ToastChaptersHaveErrors": "Главите имат грешки",
"ToastChaptersMustHaveTitles": "Главите трябва да имат заглавия",
"ToastCollectionItemsRemoveSuccess": "Елемент(и) премахнати от колекция",
"ToastCollectionRemoveSuccess": "Колекцията е премахната",
- "ToastCollectionUpdateFailed": "Неуспешно обновяване на колекция",
"ToastCollectionUpdateSuccess": "Колекцията е обновена",
- "ToastItemCoverUpdateFailed": "Неуспешно обновяване на корица на елемент",
"ToastItemCoverUpdateSuccess": "Корицата на елемента е обновена",
- "ToastItemDetailsUpdateFailed": "Неуспешно обновяване на детайли на елемент",
"ToastItemDetailsUpdateSuccess": "Детайлите на елемента са обновени",
"ToastItemMarkedAsFinishedFailed": "Неуспешно маркиране като завършено",
"ToastItemMarkedAsFinishedSuccess": "Елементът е маркиран като завършен",
@@ -750,12 +744,10 @@
"ToastLibraryDeleteSuccess": "Библиотеката е изтрита",
"ToastLibraryScanFailedToStart": "Неуспешно стартиране на сканиране",
"ToastLibraryScanStarted": "Сканирането на библиотеката е стартирано",
- "ToastLibraryUpdateFailed": "Неуспешно обновяване на библиотека",
"ToastLibraryUpdateSuccess": "Библиотеката \"{0}\" е обновена",
"ToastPlaylistCreateFailed": "Неуспешно създаване на плейлист",
"ToastPlaylistCreateSuccess": "Плейлистът е създаден",
"ToastPlaylistRemoveSuccess": "Плейлистът е премахнат",
- "ToastPlaylistUpdateFailed": "Неуспешно обновяване на плейлист",
"ToastPlaylistUpdateSuccess": "Плейлистът е обновен",
"ToastPodcastCreateFailed": "Неуспешно създаване на подкаст",
"ToastPodcastCreateSuccess": "Подкастът е създаден",
diff --git a/client/strings/bn.json b/client/strings/bn.json
index d5856c114..a76b40461 100644
--- a/client/strings/bn.json
+++ b/client/strings/bn.json
@@ -8,7 +8,7 @@
"ButtonAddYourFirstLibrary": "আপনার প্রথম লাইব্রেরি যোগ করুন",
"ButtonApply": "প্রয়োগ করুন",
"ButtonApplyChapters": "অধ্যায় প্রয়োগ করুন",
- "ButtonAuthors": "লেখক",
+ "ButtonAuthors": "লেখকগণ",
"ButtonBack": "পেছনে যান",
"ButtonBrowseForFolder": "ফোল্ডারের জন্য ব্রাউজ করুন",
"ButtonCancel": "বাতিল করুন",
@@ -56,6 +56,7 @@
"ButtonOpenManager": "ম্যানেজার খুলুন",
"ButtonPause": "বিরতি",
"ButtonPlay": "বাজান",
+ "ButtonPlayAll": "সব চালান",
"ButtonPlaying": "বাজছে",
"ButtonPlaylists": "প্লেলিস্ট",
"ButtonPrevious": "পূর্ববর্তী",
@@ -702,7 +703,7 @@
"MessageEpisodesQueuedForDownload": "{0} পর্ব(গুলি) ডাউনলোডের জন্য সারিবদ্ধ",
"MessageEreaderDevices": "ই-বুক সরবরাহ নিশ্চিত করতে, আপনাকে নীচে তালিকাভুক্ত প্রতিটি ডিভাইসের জন্য একটি বৈধ প্রেরক হিসাবে উপরের ইমেল ঠিকানাটি যুক্ত করতে হতে পারে।",
"MessageFeedURLWillBe": "ফিড URL হবে {0}",
- "MessageFetching": "আনয় হচ্ছে...",
+ "MessageFetching": "আনয় হচ্ছে.।",
"MessageForceReScanDescription": "সকল ফাইল আবার নতুন স্ক্যানের মত স্ক্যান করবে। অডিও ফাইল ID3 ট্যাগ, OPF ফাইল, এবং টেক্সট ফাইলগুলি নতুন হিসাবে স্ক্যান করা হবে।",
"MessageImportantNotice": "গুরুত্বপূর্ণ বিজ্ঞপ্তি!",
"MessageInsertChapterBelow": "নীচে অধ্যায় ঢোকান",
@@ -710,7 +711,7 @@
"MessageItemsUpdated": "{0}টি আইটেম আপডেট করা হয়েছে",
"MessageJoinUsOn": "আমাদের সাথে যোগ দিন",
"MessageListeningSessionsInTheLastYear": "গত বছরে {0}টি শোনার সেশন",
- "MessageLoading": "লোড হচ্ছে...",
+ "MessageLoading": "লোড হচ্ছে.।",
"MessageLoadingFolders": "ফোল্ডার লোড হচ্ছে...",
"MessageLogsDescription": "লগগুলি JSON ফাইল হিসাবে
/metadata/logs-এ সংরক্ষণ করা হয়। ক্র্যাশ লগগুলি
/metadata/logs/crash_logs.txt-এ সংরক্ষণ করা হয়।",
"MessageM4BFailed": "M4B ব্যর্থ!",
@@ -776,6 +777,38 @@
"MessageShareExpiresIn": "মেয়াদ শেষ হবে {0}",
"MessageShareURLWillBe": "শেয়ার করা ইউআরএল হবে
{0}",
"MessageStartPlaybackAtTime": "\"{0}\" এর জন্য {1} এ প্লেব্যাক শুরু করবেন?",
+ "MessageTaskAudioFileNotWritable": "অডিও ফাইল \"{0}\" লেখার যোগ্য নয়",
+ "MessageTaskCanceledByUser": "ব্যবহারকারী দ্বারা টাস্ক বাতিল করা হয়েছে",
+ "MessageTaskDownloadingEpisodeDescription": "\"{0}\" পর্ব ডাউনলোড করা হচ্ছে",
+ "MessageTaskEmbeddingMetadata": "মেটাডেটা এম্বেড করা হচ্ছে",
+ "MessageTaskEmbeddingMetadataDescription": "অডিওবুক \"{0}\" এ মেটাডেটা এম্বেড করা হচ্ছে",
+ "MessageTaskEncodingM4b": "এনকোডিং M4B",
+ "MessageTaskEncodingM4bDescription": "একটি একক m4b ফাইলে অডিওবুক \"{0}\" এনকোড করা হচ্ছে",
+ "MessageTaskFailed": "ব্যর্থ হয়েছে",
+ "MessageTaskFailedToBackupAudioFile": "অডিও ফাইল \"{0}\" ব্যাকআপ করতে ব্যর্থ হয়েছে",
+ "MessageTaskFailedToCreateCacheDirectory": "ক্যাশে ডিরেক্টরি তৈরি করতে ব্যর্থ হয়েছে",
+ "MessageTaskFailedToEmbedMetadataInFile": "\"{0}\" ফাইলে মেটাডেটা এম্বেড করতে ব্যর্থ হয়েছে",
+ "MessageTaskFailedToMergeAudioFiles": "অডিও ফাইল মার্জ করতে ব্যর্থ হয়েছে",
+ "MessageTaskFailedToMoveM4bFile": "m4b ফাইল সরাতে ব্যর্থ হয়েছে",
+ "MessageTaskFailedToWriteMetadataFile": "মেটাডেটা ফাইল লিখতে ব্যর্থ হয়েছে",
+ "MessageTaskMatchingBooksInLibrary": "লাইব্রেরি \"{0}\"-এ বই মিলানো হচ্ছে",
+ "MessageTaskNoFilesToScan": "স্ক্যান করার জন্য কোন ফাইল নেই",
+ "MessageTaskOpmlImport": "OPML আমদানি",
+ "MessageTaskOpmlImportDescription": "{0} RSS ফিড থেকে পডকাস্ট তৈরি করা হচ্ছে",
+ "MessageTaskOpmlImportFeed": "OPML ফিড আমদানি",
+ "MessageTaskOpmlImportFeedDescription": "RSS ফিড \"{0}\" আমদানি করা হচ্ছে",
+ "MessageTaskOpmlImportFeedFailed": "পডকাস্ট ফিড পেতে ব্যর্থ হয়েছে",
+ "MessageTaskOpmlImportFeedPodcastDescription": "পডকাস্ট তৈরি করা হচ্ছে \"{0}\"",
+ "MessageTaskOpmlImportFeedPodcastExists": "পডকাস্ট আগে থেকেই পাথে বিদ্যমান",
+ "MessageTaskOpmlImportFeedPodcastFailed": "পডকাস্ট তৈরি করতে ব্যর্থ",
+ "MessageTaskOpmlImportFinished": "{0}টি পডকাস্ট যোগ করা হয়েছে",
+ "MessageTaskScanItemsAdded": "{0}টি করা হয়েছে",
+ "MessageTaskScanItemsMissing": "{0}টি অনুপস্থিত",
+ "MessageTaskScanItemsUpdated": "{0} টি আপডেট করা হয়েছে",
+ "MessageTaskScanNoChangesNeeded": "কোন পরিবর্তন প্রয়োজন নেই",
+ "MessageTaskScanningFileChanges": "\"{0}\" এ ফাইলের পরিবর্তন স্ক্যান করা হচ্ছে",
+ "MessageTaskScanningLibrary": "\"{0}\" লাইব্রেরি স্ক্যান করা হচ্ছে",
+ "MessageTaskTargetDirectoryNotWritable": "টার্গেট ডিরেক্টরি লেখার যোগ্য নয়",
"MessageThinking": "চিন্তা করছি...",
"MessageUploaderItemFailed": "আপলোড করতে ব্যর্থ",
"MessageUploaderItemSuccess": "সফলভাবে আপলোড হয়েছে!",
@@ -816,14 +849,12 @@
"StatsTopNarrators": "শীর্ষ কথকগণ",
"StatsTotalDuration": "মোট সময়কাল…",
"StatsYearInReview": "বাৎসরিক পর্যালোচনা",
- "ToastAccountUpdateFailed": "অ্যাকাউন্ট আপডেট করতে ব্যর্থ",
"ToastAccountUpdateSuccess": "অ্যাকাউন্ট আপডেট করা হয়েছে",
"ToastAppriseUrlRequired": "একটি Apprise ইউআরএল লিখতে হবে",
"ToastAuthorImageRemoveSuccess": "লেখকের ছবি সরানো হয়েছে",
"ToastAuthorNotFound": "লেখক \"{0}\" খুঁজে পাওয়া যায়নি",
"ToastAuthorRemoveSuccess": "লেখক সরানো হয়েছে",
"ToastAuthorSearchNotFound": "লেখক পাওয়া যায়নি",
- "ToastAuthorUpdateFailed": "লেখক আপডেট করতে ব্যর্থ",
"ToastAuthorUpdateMerged": "লেখক একত্রিত হয়েছে",
"ToastAuthorUpdateSuccess": "লেখক আপডেট করেছেন",
"ToastAuthorUpdateSuccessNoImageFound": "লেখক আপডেট করেছেন (কোন ছবি পাওয়া যায়নি)",
@@ -834,7 +865,6 @@
"ToastBackupDeleteSuccess": "ব্যাকআপ মুছে ফেলা হয়েছে",
"ToastBackupInvalidMaxKeep": "রাখার জন্য অকার্যকর ব্যাকআপের সংখ্যা",
"ToastBackupInvalidMaxSize": "অকার্যকর সর্বোচ্চ ব্যাকআপ আকার",
- "ToastBackupPathUpdateFailed": "ব্যাকআপ পথ আপডেট করতে ব্যর্থ হয়েছে",
"ToastBackupRestoreFailed": "ব্যাকআপ পুনরুদ্ধার করতে ব্যর্থ",
"ToastBackupUploadFailed": "ব্যাকআপ আপলোড করতে ব্যর্থ",
"ToastBackupUploadSuccess": "ব্যাকআপ আপলোড হয়েছে",
@@ -845,7 +875,6 @@
"ToastBookmarkCreateFailed": "বুকমার্ক তৈরি করতে ব্যর্থ",
"ToastBookmarkCreateSuccess": "বুকমার্ক যোগ করা হয়েছে",
"ToastBookmarkRemoveSuccess": "বুকমার্ক সরানো হয়েছে",
- "ToastBookmarkUpdateFailed": "বুকমার্ক আপডেট করতে ব্যর্থ",
"ToastBookmarkUpdateSuccess": "বুকমার্ক আপডেট করা হয়েছে",
"ToastCachePurgeFailed": "ক্যাশে পরিষ্কার করতে ব্যর্থ হয়েছে",
"ToastCachePurgeSuccess": "ক্যাশে সফলভাবে পরিষ্কার করা হয়েছে",
@@ -856,7 +885,6 @@
"ToastCollectionItemsAddSuccess": "আইটেম(গুলি) সংগ্রহে যোগ করা সফল হয়েছে",
"ToastCollectionItemsRemoveSuccess": "আইটেম(গুলি) সংগ্রহ থেকে সরানো হয়েছে",
"ToastCollectionRemoveSuccess": "সংগ্রহ সরানো হয়েছে",
- "ToastCollectionUpdateFailed": "সংগ্রহ আপডেট করতে ব্যর্থ",
"ToastCollectionUpdateSuccess": "সংগ্রহ আপডেট করা হয়েছে",
"ToastCoverUpdateFailed": "কভার আপডেট ব্যর্থ হয়েছে",
"ToastDeleteFileFailed": "ফাইল মুছে ফেলতে ব্যর্থ হয়েছে",
@@ -865,8 +893,6 @@
"ToastDeviceNameAlreadyExists": "এই নামের ইরিডার ডিভাইস ইতিমধ্যেই বিদ্যমান",
"ToastDeviceTestEmailFailed": "পরীক্ষামূলক ইমেল পাঠাতে ব্যর্থ হয়েছে",
"ToastDeviceTestEmailSuccess": "পরীক্ষামূলক ইমেল পাঠানো হয়েছে",
- "ToastDeviceUpdateFailed": "ডিভাইস আপডেট করতে ব্যর্থ হয়েছে",
- "ToastEmailSettingsUpdateFailed": "ইমেল সেটিংস আপডেট করতে ব্যর্থ হয়েছে",
"ToastEmailSettingsUpdateSuccess": "ইমেল সেটিংস আপডেট করা হয়েছে",
"ToastEncodeCancelFailed": "এনকোড বাতিল করতে ব্যর্থ হয়েছে",
"ToastEncodeCancelSucces": "এনকোড বাতিল করা হয়েছে",
@@ -875,21 +901,16 @@
"ToastErrorCannotShare": "এই ডিভাইসে স্থানীয়ভাবে শেয়ার করা যাবে না",
"ToastFailedToLoadData": "ডেটা লোড করা যায়নি",
"ToastFailedToShare": "শেয়ার করতে ব্যর্থ",
- "ToastFailedToUpdateAccount": "অ্যাকাউন্ট আপডেট করতে ব্যর্থ",
- "ToastFailedToUpdateUser": "ব্যবহারকারী আপডেট করতে ব্যর্থ",
"ToastInvalidImageUrl": "অকার্যকর ছবির ইউআরএল",
"ToastInvalidUrl": "অকার্যকর ইউআরএল",
- "ToastItemCoverUpdateFailed": "আইটেম কভার আপডেট করতে ব্যর্থ হয়েছে",
"ToastItemCoverUpdateSuccess": "আইটেম কভার আপডেট করা হয়েছে",
"ToastItemDeletedFailed": "আইটেম মুছে ফেলতে ব্যর্থ",
"ToastItemDeletedSuccess": "মুছে ফেলা আইটেম",
- "ToastItemDetailsUpdateFailed": "আইটেমের বিবরণ আপডেট করতে ব্যর্থ",
"ToastItemDetailsUpdateSuccess": "আইটেমের বিবরণ আপডেট করা হয়েছে",
"ToastItemMarkedAsFinishedFailed": "সমাপ্ত হিসাবে চিহ্নিত করতে ব্যর্থ",
"ToastItemMarkedAsFinishedSuccess": "আইটেম সমাপ্ত হিসাবে চিহ্নিত",
"ToastItemMarkedAsNotFinishedFailed": "সমাপ্ত হয়নি হিসাবে চিহ্নিত করতে ব্যর্থ",
"ToastItemMarkedAsNotFinishedSuccess": "আইটেম সমাপ্ত হয়নি বলে চিহ্নিত",
- "ToastItemUpdateFailed": "আইটেম আপডেট করতে ব্যর্থ",
"ToastItemUpdateSuccess": "আইটেম আপডেট করা হয়েছে",
"ToastLibraryCreateFailed": "লাইব্রেরি তৈরি করতে ব্যর্থ",
"ToastLibraryCreateSuccess": "লাইব্রেরি \"{0}\" তৈরি করা হয়েছে",
@@ -897,7 +918,6 @@
"ToastLibraryDeleteSuccess": "লাইব্রেরি মুছে ফেলা হয়েছে",
"ToastLibraryScanFailedToStart": "স্ক্যান শুরু করতে ব্যর্থ",
"ToastLibraryScanStarted": "লাইব্রেরি স্ক্যান শুরু হয়েছে",
- "ToastLibraryUpdateFailed": "লাইব্রেরি আপডেট করতে ব্যর্থ",
"ToastLibraryUpdateSuccess": "লাইব্রেরি \"{0}\" আপডেট করা হয়েছে",
"ToastNameEmailRequired": "নাম এবং ইমেইল আবশ্যক",
"ToastNameRequired": "নাম আবশ্যক",
@@ -912,16 +932,13 @@
"ToastNotificationDeleteFailed": "বিজ্ঞপ্তি মুছে ফেলতে ব্যর্থ",
"ToastNotificationFailedMaximum": "সর্বাধিক ব্যর্থ প্রচেষ্টা >= 0 হতে হবে",
"ToastNotificationQueueMaximum": "সর্বাধিক বিজ্ঞপ্তি সারি >= 0 হতে হবে",
- "ToastNotificationSettingsUpdateFailed": "বিজ্ঞপ্তি সেটিংস আপডেট করতে ব্যর্থ",
"ToastNotificationSettingsUpdateSuccess": "বিজ্ঞপ্তি সেটিংস আপডেট করা হয়েছে",
"ToastNotificationTestTriggerFailed": "পরীক্ষামূলক বিজ্ঞপ্তি ট্রিগার করতে ব্যর্থ হয়েছে",
"ToastNotificationTestTriggerSuccess": "পরীক্ষামুলক বিজ্ঞপ্তি ট্রিগার হয়েছে",
- "ToastNotificationUpdateFailed": "বিজ্ঞপ্তি আপডেট করতে ব্যর্থ",
"ToastNotificationUpdateSuccess": "বিজ্ঞপ্তি আপডেট হয়েছে",
"ToastPlaylistCreateFailed": "প্লেলিস্ট তৈরি করতে ব্যর্থ",
"ToastPlaylistCreateSuccess": "প্লেলিস্ট তৈরি করা হয়েছে",
"ToastPlaylistRemoveSuccess": "প্লেলিস্ট সরানো হয়েছে",
- "ToastPlaylistUpdateFailed": "প্লেলিস্ট আপডেট করতে ব্যর্থ",
"ToastPlaylistUpdateSuccess": "প্লেলিস্ট আপডেট করা হয়েছে",
"ToastPodcastCreateFailed": "পডকাস্ট তৈরি করতে ব্যর্থ",
"ToastPodcastCreateSuccess": "পডকাস্ট সফলভাবে তৈরি করা হয়েছে",
@@ -950,7 +967,6 @@
"ToastSendEbookToDeviceSuccess": "ইবুক \"{0}\" ডিভাইসে পাঠানো হয়েছে",
"ToastSeriesUpdateFailed": "সিরিজ আপডেট ব্যর্থ হয়েছে",
"ToastSeriesUpdateSuccess": "সিরিজ আপডেট সাফল্য",
- "ToastServerSettingsUpdateFailed": "সার্ভার সেটিংস আপডেট করতে ব্যর্থ হয়েছে",
"ToastServerSettingsUpdateSuccess": "সার্ভার সেটিংস আপডেট করা হয়েছে",
"ToastSessionCloseFailed": "অধিবেশন বন্ধ করতে ব্যর্থ হয়েছে",
"ToastSessionDeleteFailed": "সেশন মুছে ফেলতে ব্যর্থ",
@@ -961,7 +977,6 @@
"ToastSocketDisconnected": "সকেট সংযোগ বিচ্ছিন্ন",
"ToastSocketFailedToConnect": "সকেট সংযোগ করতে ব্যর্থ হয়েছে",
"ToastSortingPrefixesEmptyError": "কমপক্ষে ১ টি সাজানোর উপসর্গ থাকতে হবে",
- "ToastSortingPrefixesUpdateFailed": "বাছাই উপসর্গ আপডেট করতে ব্যর্থ হয়েছে",
"ToastSortingPrefixesUpdateSuccess": "বাছাই করা উপসর্গ আপডেট করা হয়েছে ({0}টি আইটেম)",
"ToastTitleRequired": "শিরোনাম আবশ্যক",
"ToastUnknownError": "অজানা ত্রুটি",
diff --git a/client/strings/cs.json b/client/strings/cs.json
index 1f6237fa0..9b429f5e6 100644
--- a/client/strings/cs.json
+++ b/client/strings/cs.json
@@ -28,6 +28,7 @@
"ButtonEdit": "Upravit",
"ButtonEditChapters": "Upravit kapitoly",
"ButtonEditPodcast": "Upravit podcast",
+ "ButtonEnable": "Povolit",
"ButtonForceReScan": "Vynutit opětovné prohledání",
"ButtonFullPath": "Úplná cesta",
"ButtonHide": "Skrýt",
@@ -44,10 +45,15 @@
"ButtonMatchAllAuthors": "Spárovat všechny autory",
"ButtonMatchBooks": "Spárovat Knihy",
"ButtonNevermind": "Nevadí",
+ "ButtonNext": "Další",
"ButtonNextChapter": "Další Kapitola",
+ "ButtonNextItemInQueue": "Žádná další položka ve frontě",
+ "ButtonOk": "Ok",
"ButtonOpenFeed": "Otevřít kanál",
"ButtonOpenManager": "Otevřít správce",
+ "ButtonPause": "Pozastavit",
"ButtonPlay": "Přehrát",
+ "ButtonPlayAll": "Přehrát vše",
"ButtonPlaying": "Hraje",
"ButtonPlaylists": "Seznamy skladeb",
"ButtonPrevious": "Předchozí",
@@ -88,6 +94,8 @@
"ButtonStartMetadataEmbed": "Spustit vkládání metadat",
"ButtonStats": "Statistiky",
"ButtonSubmit": "Odeslat",
+ "ButtonTest": "Test",
+ "ButtonUnlinkOpenId": "Odpojit OpenID",
"ButtonUpload": "Nahrát",
"ButtonUploadBackup": "Nahrát zálohu",
"ButtonUploadCover": "Nahrát obálku",
@@ -100,10 +108,12 @@
"ErrorUploadFetchMetadataNoResults": "Nepodařilo se načíst metadata - zkuste aktualizovat název a/nebo autora",
"ErrorUploadLacksTitle": "Musí mít titul",
"HeaderAccount": "Účet",
+ "HeaderAddCustomMetadataProvider": "Přidat vlastního poskytovatele metadat",
"HeaderAdvanced": "Pokročilé",
"HeaderAppriseNotificationSettings": "Nastavení oznámení Apprise",
"HeaderAudioTracks": "Zvukové stopy",
"HeaderAudiobookTools": "Nástroje pro správu souborů audioknih",
+ "HeaderAuthentication": "Autentizace",
"HeaderBackups": "Zálohy",
"HeaderChangePassword": "Změnit heslo",
"HeaderChapters": "Kapitoly",
@@ -144,10 +154,13 @@
"HeaderMetadataToEmbed": "Metadata k vložení",
"HeaderNewAccount": "Nový účet",
"HeaderNewLibrary": "Nová knihovna",
+ "HeaderNotificationCreate": "Vytvořit notifikaci",
+ "HeaderNotificationUpdate": "Aktualizovat notifikaci",
"HeaderNotifications": "Oznámení",
"HeaderOpenIDConnectAuthentication": "Ověřování pomocí OpenID Connect",
"HeaderOpenRSSFeed": "Otevřít RSS kanál",
"HeaderOtherFiles": "Ostatní soubory",
+ "HeaderPasswordAuthentication": "Autentizace heslem",
"HeaderPermissions": "Oprávnění",
"HeaderPlayerQueue": "Fronta přehrávače",
"HeaderPlayerSettings": "Nastavení přehrávače",
@@ -200,6 +213,7 @@
"LabelAddToPlaylist": "Přidat do seznamu přehrávání",
"LabelAddToPlaylistBatch": "Přidat {0} položky do seznamu přehrávání",
"LabelAddedAt": "Přidáno v",
+ "LabelAddedDate": "Přidáno {0}",
"LabelAdminUsersOnly": "Pouze administrátoři",
"LabelAll": "Vše",
"LabelAllUsers": "Všichni uživatelé",
@@ -229,6 +243,7 @@
"LabelBitrate": "Datový tok",
"LabelBooks": "Knihy",
"LabelButtonText": "Text tlačítka",
+ "LabelByAuthor": "od {0}",
"LabelChangePassword": "Změnit heslo",
"LabelChannels": "Kanály",
"LabelChapterTitle": "Název kapitoly",
@@ -238,6 +253,7 @@
"LabelClosePlayer": "Zavřít přehrávač",
"LabelCodec": "Kodek",
"LabelCollapseSeries": "Sbalit sérii",
+ "LabelCollapseSubSeries": "Sbalit podsérie",
"LabelCollection": "Kolekce",
"LabelCollections": "Kolekce",
"LabelComplete": "Dokončeno",
@@ -288,16 +304,21 @@
"LabelEpisode": "Epizoda",
"LabelEpisodeTitle": "Název epizody",
"LabelEpisodeType": "Typ epizody",
+ "LabelEpisodes": "Epizody",
"LabelExample": "Příklad",
"LabelExpandSeries": "Rozbalit série",
+ "LabelExpandSubSeries": "Rozbalit podsérie",
"LabelExplicit": "Explicitní",
"LabelExplicitChecked": "Explicitní (zaškrtnuto)",
"LabelExplicitUnchecked": "Není explicitní (nezaškrtnuto)",
+ "LabelExportOPML": "Export OPML",
"LabelFeedURL": "URL zdroje",
"LabelFetchingMetadata": "Získávání metadat",
"LabelFile": "Soubor",
"LabelFileBirthtime": "Čas vzniku souboru",
+ "LabelFileBornDate": "Vytvořeno {0}",
"LabelFileModified": "Soubor změněn",
+ "LabelFileModifiedDate": "Změněno {0}",
"LabelFilename": "Název souboru",
"LabelFilterByUser": "Filtrovat podle uživatele",
"LabelFindEpisodes": "Najít epizody",
@@ -307,6 +328,7 @@
"LabelFontBold": "Tučně",
"LabelFontBoldness": "Výraznost písma",
"LabelFontFamily": "Rodina písem",
+ "LabelFontItalic": "Kurzíva",
"LabelFontScale": "Měřítko písma",
"LabelFontStrikethrough": "Přeškrtnutí",
"LabelFormat": "Formát",
@@ -325,6 +347,7 @@
"LabelInProgress": "Probíhá",
"LabelIncludeInTracklist": "Zahrnout do seznamu stop",
"LabelIncomplete": "Neúplné",
+ "LabelInterval": "Interval",
"LabelIntervalCustomDailyWeekly": "Vlastní denně/týdně",
"LabelIntervalEvery12Hours": "Každých 12 hodin",
"LabelIntervalEvery15Minutes": "Každých 15 minut",
@@ -421,17 +444,22 @@
"LabelPersonalYearReview": "Váš přehled roku ({0})",
"LabelPhotoPathURL": "Cesta k fotografii/URL",
"LabelPlayMethod": "Metoda přehrávání",
+ "LabelPlayerChapterNumberMarker": "{0} z {1}",
"LabelPlaylists": "Seznamy skladeb",
+ "LabelPodcast": "Podcast",
"LabelPodcastSearchRegion": "Oblast vyhledávání podcastu",
"LabelPodcastType": "Typ podcastu",
"LabelPodcasts": "Podcasty",
+ "LabelPort": "Port",
"LabelPrefixesToIgnore": "Předpony, které se mají ignorovat (nerozlišují se malá a velká písmena)",
"LabelPreventIndexing": "Zabránit indexování vašeho kanálu v adresářích podcastů iTunes a Google",
"LabelPrimaryEbook": "Hlavní e-kniha",
"LabelProgress": "Průběh",
"LabelProvider": "Poskytovatel",
+ "LabelProviderAuthorizationValue": "Hodnota autorizačního headeru",
"LabelPubDate": "Datum vydání",
"LabelPublishYear": "Rok vydání",
+ "LabelPublishedDate": "Vydáno {0}",
"LabelPublisher": "Vydavatel",
"LabelPublishers": "Vydavatelé",
"LabelRSSFeedCustomOwnerEmail": "Vlastní e-mail vlastníka",
@@ -441,6 +469,7 @@
"LabelRSSFeedSlug": "RSS kanál Slug",
"LabelRSSFeedURL": "URL RSS kanálu",
"LabelRandomly": "Náhodně",
+ "LabelReAddSeriesToContinueListening": "Znovu přidat sérii k pokračování poslechu",
"LabelRead": "Číst",
"LabelReadAgain": "Číst znovu",
"LabelReadEbookWithoutProgress": "Číst e-knihu bez zachování průběhu",
@@ -448,6 +477,7 @@
"LabelRecentlyAdded": "Nedávno přidané",
"LabelRecommended": "Doporučeno",
"LabelRedo": "Přepracovat",
+ "LabelRegion": "Region",
"LabelReleaseDate": "Datum vydání",
"LabelRemoveCover": "Odstranit obálku",
"LabelRowsPerPage": "Řádky na stránku",
@@ -539,6 +569,7 @@
"LabelTagsNotAccessibleToUser": "Značky nepřístupné uživateli",
"LabelTasks": "Spuštěné Úlohy",
"LabelTextEditorBulletedList": "Seznam s odrážkami",
+ "LabelTextEditorLink": "Odkaz",
"LabelTextEditorNumberedList": "Seznam s čísly",
"LabelTextEditorUnlink": "Zrušit odkaz",
"LabelTheme": "Téma",
@@ -572,6 +603,7 @@
"LabelUnabridged": "Nezkráceno",
"LabelUndo": "Zpět",
"LabelUnknown": "Neznámý",
+ "LabelUnknownPublishDate": "Neznámé datum vydání",
"LabelUpdateCover": "Aktualizovat obálku",
"LabelUpdateCoverHelp": "Povolit přepsání existujících obálek pro vybrané knihy, pokud je nalezena shoda",
"LabelUpdateDetails": "Aktualizovat podrobnosti",
@@ -620,14 +652,19 @@
"MessageCheckingCron": "Kontrola cronu...",
"MessageConfirmCloseFeed": "Opravdu chcete zavřít tento kanál?",
"MessageConfirmDeleteBackup": "Opravdu chcete smazat zálohu pro {0}?",
+ "MessageConfirmDeleteDevice": "Opravdu chcete vymazat zařízení e-reader \"{0}\"?",
"MessageConfirmDeleteFile": "Tento krok smaže soubor ze souborového systému. Jsi si jisti?",
"MessageConfirmDeleteLibrary": "Opravdu chcete trvale smazat knihovnu \"{0}\"?",
"MessageConfirmDeleteLibraryItem": "Tento krok odstraní položku knihovny z databáze a vašeho souborového systému. Jste si jisti?",
"MessageConfirmDeleteLibraryItems": "Tímto smažete {0} položkek knihovny z databáze a vašeho souborového systému. Jsi si jisti?",
+ "MessageConfirmDeleteMetadataProvider": "Opravdu chcete vymazat vlastního poskytovatele metadat \"{0}\"?",
+ "MessageConfirmDeleteNotification": "Opravdu chcete vymazat tuto notifikaci?",
"MessageConfirmDeleteSession": "Opravdu chcete smazat tuto relaci?",
"MessageConfirmForceReScan": "Opravdu chcete vynutit opětovné prohledání?",
"MessageConfirmMarkAllEpisodesFinished": "Opravdu chcete označit všechny epizody jako dokončené?",
"MessageConfirmMarkAllEpisodesNotFinished": "Opravdu chcete označit všechny epizody jako nedokončené?",
+ "MessageConfirmMarkItemFinished": "Opravdu chcete označit \"{0}\" jako dokončené?",
+ "MessageConfirmMarkItemNotFinished": "Opravdu chcete označit \"{0}\" jako nedokončené?",
"MessageConfirmMarkSeriesFinished": "Opravdu chcete označit všechny knihy z této série jako dokončené?",
"MessageConfirmMarkSeriesNotFinished": "Opravdu chcete označit všechny knihy z této série jako nedokončené?",
"MessageConfirmPurgeCache": "Vyčistit mezipaměť odstraní celý adresář na adrese
/metadata/cache.
Určitě chcete odstranit adresář mezipaměti?",
@@ -648,7 +685,9 @@
"MessageConfirmRenameTag": "Opravdu chcete přejmenovat tag \"{0}\" na \"{1}\" pro všechny položky?",
"MessageConfirmRenameTagMergeNote": "Poznámka: Tato značka již existuje, takže budou sloučeny.",
"MessageConfirmRenameTagWarning": "Varování! Podobná značka s jinými velkými a malými písmeny již existuje \"{0}\".",
+ "MessageConfirmResetProgress": "Opravdu chcete zahodit svůj pokrok?",
"MessageConfirmSendEbookToDevice": "Opravdu chcete odeslat e-knihu {0} {1}\" do zařízení \"{2}\"?",
+ "MessageConfirmUnlinkOpenId": "Opravdu chcete odpojit tohoto uživatele z OpenID?",
"MessageDownloadingEpisode": "Stahuji epizodu",
"MessageDragFilesIntoTrackOrder": "Přetáhněte soubory do správného pořadí stop",
"MessageEmbedFailed": "Vložení selhalo!",
@@ -656,7 +695,7 @@
"MessageEpisodesQueuedForDownload": "{0} Epizody zařazené do fronty ke stažení",
"MessageEreaderDevices": "Aby bylo zajištěno doručení elektronických knih, může být nutné přidat výše uvedenou e-mailovou adresu jako platného odesílatele pro každé zařízení uvedené níže.",
"MessageFeedURLWillBe": "URL zdroje bude {0}",
- "MessageFetching": "Stahování...",
+ "MessageFetching": "Načítání...",
"MessageForceReScanDescription": "znovu prohledá všechny soubory jako při novém skenování. ID3 tagy zvukových souborů OPF soubory a textové soubory budou skenovány jako nové.",
"MessageImportantNotice": "Důležité upozornění!",
"MessageInsertChapterBelow": "Vložit kapitolu níže",
@@ -683,6 +722,7 @@
"MessageNoCollections": "Žádné kolekce",
"MessageNoCoversFound": "Nebyly nalezeny žádné obálky",
"MessageNoDescription": "Bez popisu",
+ "MessageNoDevices": "Žádná zařízení",
"MessageNoDownloadsInProgress": "Momentálně neprobíhá žádné stahování",
"MessageNoDownloadsQueued": "Žádné stahování ve frontě",
"MessageNoEpisodeMatchesFound": "Nebyly nalezeny žádné odpovídající epizody",
@@ -710,6 +750,7 @@
"MessagePauseChapter": "Pozastavit přehrávání kapitoly",
"MessagePlayChapter": "Poslechnout si začátek kapitoly",
"MessagePlaylistCreateFromCollection": "Vytvořit seznam skladeb z kolekce",
+ "MessagePleaseWait": "Čekejte prosím...",
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nemá žádnou adresu URL kanálu RSS, kterou by mohl použít pro porovnávání",
"MessageQuickMatchDescription": "Vyplňte prázdné detaily položky a obálku prvním výsledkem shody z '{0}'. Nepřepisuje podrobnosti, pokud není povoleno nastavení serveru \"Preferovat párování metadata\".",
"MessageRemoveChapter": "Odstranit kapitolu",
@@ -728,17 +769,46 @@
"MessageShareExpiresIn": "Expiruje za {0}",
"MessageShareURLWillBe": "Sdílené URL bude
{0}",
"MessageStartPlaybackAtTime": "Spustit přehrávání pro \"{0}\" v {1}?",
+ "MessageTaskAudioFileNotWritable": "Nelze zapisovat do audio souboru \"{0}\"",
+ "MessageTaskCanceledByUser": "Task zrušen uživatelem",
+ "MessageTaskDownloadingEpisodeDescription": "Stahování epizody \"{0}\"",
+ "MessageTaskEmbeddingMetadata": "Vkládání metadat",
+ "MessageTaskEmbeddingMetadataDescription": "Vkládání metadat do audioknihy \"{0}\"",
+ "MessageTaskEncodingM4b": "Kódování M4B",
+ "MessageTaskEncodingM4bDescription": "Kódování audioknihy \"{0}\" do jednoho m4b souboru",
+ "MessageTaskFailed": "Selhalo",
+ "MessageTaskFailedToBackupAudioFile": "Zálohování audio souboru \"{0}\" se selhalo",
+ "MessageTaskFailedToCreateCacheDirectory": "Vytvoření cache adresáře selhalo",
+ "MessageTaskFailedToEmbedMetadataInFile": "Vkládání metadat do souboru \"{0}\" selhalo",
+ "MessageTaskFailedToMergeAudioFiles": "Spojení audio souborů selhalo",
+ "MessageTaskFailedToMoveM4bFile": "Přesunutí m4b souboru selhalo",
+ "MessageTaskFailedToWriteMetadataFile": "Zápis souboru metadat selhal",
+ "MessageTaskNoFilesToScan": "Žádné soubory ke skenování",
+ "MessageTaskOpmlImport": "Import OPML",
+ "MessageTaskOpmlImportDescription": "Vytváření podcastů z {0} RSS feedů",
+ "MessageTaskOpmlImportFeedDescription": "Importování RSS feedu \"{0}\"",
+ "MessageTaskOpmlImportFeedPodcastDescription": "Vytváření podcastu \"{0}\"",
+ "MessageTaskOpmlImportFeedPodcastExists": "Podcast se stejnou cestou již existuje",
+ "MessageTaskOpmlImportFeedPodcastFailed": "Vytváření podcastu selhalo",
+ "MessageTaskOpmlImportFinished": "Přidáno {0} podcastů",
+ "MessageTaskScanItemsAdded": "{0} přidáno",
+ "MessageTaskScanItemsMissing": "{0} chybí",
+ "MessageTaskScanItemsUpdated": "{0} aktualizováno",
+ "MessageTaskScanNoChangesNeeded": "Žádné změny nejsou nutné",
+ "MessageTaskScanningFileChanges": "Skenování změn souborů v \"{0}\"",
+ "MessageTaskScanningLibrary": "Skenování \"{0}\" knihovny",
+ "MessageTaskTargetDirectoryNotWritable": "Do cílové složky nelze zapisovat",
"MessageThinking": "Přemýšlení...",
- "MessageUploaderItemFailed": "Nahrávání se nezdařilo",
- "MessageUploaderItemSuccess": "Nahráno bylo úspěšně!",
- "MessageUploading": "Odesílám...",
+ "MessageUploaderItemFailed": "Nahrávání selhalo",
+ "MessageUploaderItemSuccess": "Úspěšně nahráno!",
+ "MessageUploading": "Nahrávám...",
"MessageValidCronExpression": "Platný výraz cronu",
"MessageWatcherIsDisabledGlobally": "Hlídač je globálně zakázán v nastavení serveru",
"MessageXLibraryIsEmpty": "{0} knihovna je prázdná!",
- "MessageYourAudiobookDurationIsLonger": "Doba trvání audioknihy je delší než nalezená délka",
+ "MessageYourAudiobookDurationIsLonger": "Délka audioknihy je delší, než byla nalezena",
"MessageYourAudiobookDurationIsShorter": "Délka audioknihy je kratší, než byla nalezena",
"NoteChangeRootPassword": "Uživatel root je jediný uživatel, který může mít prázdné heslo",
- "NoteChapterEditorTimes": "Poznámka: Čas začátku první kapitoly musí zůstat v 0:00 a čas začátku poslední kapitoly nesmí překročit tuto dobu trvání audioknihy.",
+ "NoteChapterEditorTimes": "Poznámka: Čas začátku první kapitoly musí zůstat na 0:00 a čas začátku poslední kapitoly nesmí překročit dobu trvání audioknihy.",
"NoteFolderPicker": "Poznámka: složky, které jsou již namapovány, nebudou zobrazeny",
"NoteRSSFeedPodcastAppsHttps": "Upozornění: Většina aplikací pro podcasty bude vyžadovat, aby adresa URL kanálu RSS používala protokol HTTPS",
"NoteRSSFeedPodcastAppsPubDate": "Upozornění: 1 nebo více epizod nemá datum vydání. Některé podcastové aplikace to vyžadují.",
@@ -752,8 +822,10 @@
"PlaceholderSearchEpisode": "Hledat epizodu..",
"StatsAuthorsAdded": "autoři přidáni",
"StatsBooksAdded": "knihy přidány",
+ "StatsBooksAdditional": "Některé další zahrnují…",
"StatsBooksFinished": "dokončené knihy",
"StatsBooksFinishedThisYear": "Některé knihy dokončené tento rok…",
+ "StatsCollectionGrewTo": "Vaše kolekce knih se rozrostla na…",
"StatsSessions": "sezení",
"StatsSpentListening": "stráveno posloucháním",
"StatsTopAuthor": "TOP AUTOR",
@@ -763,59 +835,75 @@
"StatsTopMonth": "TOP MĚSÍC",
"StatsTotalDuration": "S celkovou dobou…",
"StatsYearInReview": "ROK V PŘEHLEDU",
- "ToastAccountUpdateFailed": "Aktualizace účtu se nezdařila",
"ToastAccountUpdateSuccess": "Účet aktualizován",
+ "ToastAppriseUrlRequired": "Je nutné zadat Apprise URL",
"ToastAuthorImageRemoveSuccess": "Obrázek autora odstraněn",
- "ToastAuthorUpdateFailed": "Aktualizace autora se nezdařila",
+ "ToastAuthorNotFound": "Author \"{0}\" nenalezen",
+ "ToastAuthorRemoveSuccess": "Autor odstraněn",
+ "ToastAuthorSearchNotFound": "Autor nenalezen",
"ToastAuthorUpdateMerged": "Autor sloučen",
"ToastAuthorUpdateSuccess": "Autor aktualizován",
"ToastAuthorUpdateSuccessNoImageFound": "Autor aktualizován (nebyl nalezen žádný obrázek)",
+ "ToastBackupAppliedSuccess": "Záloha obnovena",
"ToastBackupCreateFailed": "Vytvoření zálohy se nezdařilo",
"ToastBackupCreateSuccess": "Záloha vytvořena",
"ToastBackupDeleteFailed": "Nepodařilo se smazat zálohu",
"ToastBackupDeleteSuccess": "Záloha smazána",
+ "ToastBackupInvalidMaxKeep": "Neplatný počet záloh k zachování",
+ "ToastBackupInvalidMaxSize": "Neplatná maximální velikost zálohy",
"ToastBackupRestoreFailed": "Nepodařilo se obnovit zálohu",
"ToastBackupUploadFailed": "Nepodařilo se nahrát zálohu",
"ToastBackupUploadSuccess": "Záloha nahrána",
+ "ToastBatchDeleteFailed": "Hromadné smazání selhalo",
+ "ToastBatchDeleteSuccess": "Hromadné smazání proběhlo úspěšně",
"ToastBatchUpdateFailed": "Dávková aktualizace se nezdařila",
"ToastBatchUpdateSuccess": "Dávková aktualizace proběhla úspěšně",
"ToastBookmarkCreateFailed": "Vytvoření záložky se nezdařilo",
"ToastBookmarkCreateSuccess": "Přidána záložka",
"ToastBookmarkRemoveSuccess": "Záložka odstraněna",
- "ToastBookmarkUpdateFailed": "Aktualizace záložky se nezdařila",
"ToastBookmarkUpdateSuccess": "Záložka aktualizována",
"ToastCachePurgeFailed": "Nepodařilo se vyčistit mezipaměť",
"ToastCachePurgeSuccess": "Vyrovnávací paměť úspěšně vyčištěna",
"ToastChaptersHaveErrors": "Kapitoly obsahují chyby",
"ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy",
+ "ToastChaptersRemoved": "Kapitoly odstraněny",
"ToastCollectionItemsRemoveSuccess": "Položky odstraněny z kolekce",
"ToastCollectionRemoveSuccess": "Kolekce odstraněna",
- "ToastCollectionUpdateFailed": "Aktualizace kolekce se nezdařila",
"ToastCollectionUpdateSuccess": "Kolekce aktualizována",
+ "ToastCoverUpdateFailed": "Aktualizace obálky selhala",
"ToastDeleteFileFailed": "Nepodařilo se smazat soubor",
"ToastDeleteFileSuccess": "Soubor smazán",
+ "ToastDeviceAddFailed": "Přidání zařízení selhalo",
+ "ToastDeviceNameAlreadyExists": "Zařízení se stejným jménem již existuje",
+ "ToastDeviceTestEmailFailed": "Odeslání testovacího emailu selhalo",
+ "ToastDeviceTestEmailSuccess": "Testovací email byl odeslán",
+ "ToastEmailSettingsUpdateSuccess": "Nastavení emailu aktualizována",
+ "ToastEpisodeDownloadQueueClearFailed": "Vyčištění fronty selhalo",
"ToastErrorCannotShare": "Na tomto zařízení nelze nativně sdílet",
"ToastFailedToLoadData": "Nepodařilo se načíst data",
- "ToastItemCoverUpdateFailed": "Aktualizace obálky se nezdařila",
+ "ToastFailedToShare": "Sdílení selhalo",
+ "ToastFailedToUpdate": "Aktualizace selhala",
+ "ToastInvalidImageUrl": "Neplatná URL obrázku",
+ "ToastInvalidUrl": "Neplatná URL",
"ToastItemCoverUpdateSuccess": "Obálka předmětu byl aktualizována",
- "ToastItemDetailsUpdateFailed": "Nepodařilo se aktualizovat podrobnosti o položce",
+ "ToastItemDeletedFailed": "Smazání položky selhalo",
+ "ToastItemDeletedSuccess": "Položka smazána",
"ToastItemDetailsUpdateSuccess": "Podrobnosti o položce byly aktualizovány",
"ToastItemMarkedAsFinishedFailed": "Nepodařilo se označit jako dokončené",
"ToastItemMarkedAsFinishedSuccess": "Položka označena jako dokončená",
"ToastItemMarkedAsNotFinishedFailed": "Nepodařilo se označit jako nedokončené",
"ToastItemMarkedAsNotFinishedSuccess": "Položka označena jako nedokončená",
+ "ToastItemUpdateSuccess": "Položka aktualizována",
"ToastLibraryCreateFailed": "Vytvoření knihovny se nezdařilo",
"ToastLibraryCreateSuccess": "Knihovna \"{0}\" vytvořena",
"ToastLibraryDeleteFailed": "Nepodařilo se smazat knihovnu",
"ToastLibraryDeleteSuccess": "Knihovna smazána",
"ToastLibraryScanFailedToStart": "Nepodařilo se spustit kontrolu",
"ToastLibraryScanStarted": "Kontrola knihovny spuštěna",
- "ToastLibraryUpdateFailed": "Aktualizace knihovny se nezdařila",
"ToastLibraryUpdateSuccess": "Knihovna \"{0}\" aktualizována",
"ToastPlaylistCreateFailed": "Vytvoření seznamu přehrávání se nezdařilo",
"ToastPlaylistCreateSuccess": "Seznam přehrávání vytvořen",
"ToastPlaylistRemoveSuccess": "Seznam přehrávání odstraněn",
- "ToastPlaylistUpdateFailed": "Aktualizace seznamu přehrávání se nezdařila",
"ToastPlaylistUpdateSuccess": "Seznam přehrávání aktualizován",
"ToastPodcastCreateFailed": "Vytvoření podcastu se nezdařilo",
"ToastPodcastCreateSuccess": "Podcast byl úspěšně vytvořen",
@@ -827,7 +915,6 @@
"ToastSendEbookToDeviceSuccess": "E-kniha odeslána do zařízení \"{0}\"",
"ToastSeriesUpdateFailed": "Aktualizace série se nezdařila",
"ToastSeriesUpdateSuccess": "Aktualizace série byla úspěšná",
- "ToastServerSettingsUpdateFailed": "Nepodařilo se aktualizovat nastavení serveru",
"ToastServerSettingsUpdateSuccess": "Nastavení serveru aktualizováno",
"ToastSessionDeleteFailed": "Nepodařilo se smazat relaci",
"ToastSessionDeleteSuccess": "Relace smazána",
@@ -835,7 +922,6 @@
"ToastSocketDisconnected": "Socket odpojen",
"ToastSocketFailedToConnect": "Socket se nepodařilo připojit",
"ToastSortingPrefixesEmptyError": "Musí mít alespoň 1 třídicí předponu",
- "ToastSortingPrefixesUpdateFailed": "Nepodařilo se aktualizovat třídicí předpony",
"ToastSortingPrefixesUpdateSuccess": "Aktualizovány předpony třídění ({0} položek)",
"ToastUserDeleteFailed": "Nepodařilo se smazat uživatele",
"ToastUserDeleteSuccess": "Uživatel smazán"
diff --git a/client/strings/da.json b/client/strings/da.json
index 2e055b42a..b4b92bc86 100644
--- a/client/strings/da.json
+++ b/client/strings/da.json
@@ -1,7 +1,10 @@
{
"ButtonAdd": "Tilføj",
"ButtonAddChapters": "Tilføj kapitler",
+ "ButtonAddDevice": "Tilføj enhed",
+ "ButtonAddLibrary": "Tilføj Bibliotek",
"ButtonAddPodcasts": "Tilføj podcasts",
+ "ButtonAddUser": "Tilføj bruger",
"ButtonAddYourFirstLibrary": "Tilføj din første bibliotek",
"ButtonApply": "Anvend",
"ButtonApplyChapters": "Anvend kapitler",
@@ -25,6 +28,7 @@
"ButtonEdit": "Rediger",
"ButtonEditChapters": "Rediger kapitler",
"ButtonEditPodcast": "Rediger podcast",
+ "ButtonEnable": "Aktiver",
"ButtonForceReScan": "Tvungen genindlæsning",
"ButtonFullPath": "Fuld sti",
"ButtonHide": "Skjul",
@@ -42,6 +46,7 @@
"ButtonOk": "OK",
"ButtonOpenFeed": "Åbn feed",
"ButtonOpenManager": "Åbn manager",
+ "ButtonPause": "Pause",
"ButtonPlay": "Afspil",
"ButtonPlaying": "Afspiller",
"ButtonPlaylists": "Afspilningslister",
@@ -66,7 +71,7 @@
"ButtonScanLibrary": "Scan Bibliotek",
"ButtonSearch": "Søg",
"ButtonSelectFolderPath": "Vælg Mappen Sti",
- "ButtonSeries": "Serie",
+ "ButtonSeries": "Serier",
"ButtonSetChaptersFromTracks": "Sæt kapitler fra spor",
"ButtonShiftTimes": "Skift Tider",
"ButtonShow": "Vis",
@@ -188,14 +193,14 @@
"LabelChapters": "Kapitler",
"LabelChaptersFound": "fundne kapitler",
"LabelClosePlayer": "Luk afspiller",
- "LabelCollapseSeries": "Fold Serie Sammen",
+ "LabelCollapseSeries": "Fold Serier Sammen",
"LabelCollection": "Samling",
"LabelCollections": "Samlinger",
"LabelComplete": "Fuldfør",
"LabelConfirmPassword": "Bekræft Adgangskode",
- "LabelContinueListening": "Fortsæt Lytning",
- "LabelContinueReading": "Fortsæt Læsning",
- "LabelContinueSeries": "Fortsæt Serie",
+ "LabelContinueListening": "Fortsæt med at lytte",
+ "LabelContinueReading": "Fortsæt med at læse",
+ "LabelContinueSeries": "Fortsæt Serien",
"LabelCover": "Omslag",
"LabelCoverImageURL": "Omslagsbillede URL",
"LabelCreatedAt": "Oprettet Kl.",
@@ -212,6 +217,7 @@
"LabelDiscFromFilename": "Disk fra Filnavn",
"LabelDiscFromMetadata": "Disk fra Metadata",
"LabelDiscover": "Opdag",
+ "LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episoder",
"LabelDuration": "Varighed",
"LabelDurationFound": "Fundet varighed:",
@@ -225,12 +231,15 @@
"LabelEmbeddedCover": "Indlejret Omslag",
"LabelEnable": "Aktivér",
"LabelEnd": "Slut",
+ "LabelEndOfChapter": "Slutningen af kapitel",
+ "LabelEpisode": "Episode",
"LabelEpisodeTitle": "Episodetitel",
"LabelEpisodeType": "Episodetype",
"LabelExample": "Eksempel",
"LabelExplicit": "Eksplisit",
+ "LabelFeedURL": "Feed URL",
"LabelFile": "Fil",
- "LabelFileBirthtime": "Fødselstidspunkt for fil",
+ "LabelFileBirthtime": "Oprettelsestidspunkt for fil",
"LabelFileModified": "Fil ændret",
"LabelFilename": "Filnavn",
"LabelFilterByUser": "Filtrér efter bruger",
@@ -238,8 +247,10 @@
"LabelFinished": "Færdig",
"LabelFolder": "Mappe",
"LabelFolders": "Mapper",
+ "LabelFontBoldness": "Skrift tykkelse",
"LabelFontFamily": "Fontfamilie",
"LabelFontScale": "Skriftstørrelse",
+ "LabelGenre": "Genre",
"LabelGenres": "Genrer",
"LabelHardDeleteFile": "Permanent slet fil",
"LabelHasEbook": "Har e-bog",
@@ -267,6 +278,7 @@
"LabelLastSeen": "Sidst set",
"LabelLastTime": "Sidste gang",
"LabelLastUpdate": "Seneste opdatering",
+ "LabelLayout": "Layout",
"LabelLayoutSinglePage": "Enkeltside",
"LabelLayoutSplitPage": "Opdelt side",
"LabelLess": "Mindre",
@@ -344,10 +356,11 @@
"LabelRSSFeedPreventIndexing": "Forhindrer indeksering",
"LabelRSSFeedSlug": "RSS-feed-slug",
"LabelRSSFeedURL": "RSS-feed-URL",
+ "LabelRandomly": "Tilfældigt",
"LabelRead": "Læst",
"LabelReadAgain": "Læs igen",
"LabelReadEbookWithoutProgress": "Læs e-bog uden at følge fremskridt",
- "LabelRecentSeries": "Seneste serie",
+ "LabelRecentSeries": "Seneste serier",
"LabelRecentlyAdded": "Senest tilføjet",
"LabelRecommended": "Anbefalet",
"LabelReleaseDate": "Udgivelsesdato",
@@ -604,10 +617,8 @@
"PlaceholderNewPlaylist": "Nyt afspilningslistnavn",
"PlaceholderSearch": "Søg..",
"PlaceholderSearchEpisode": "Søg efter episode..",
- "ToastAccountUpdateFailed": "Mislykkedes opdatering af konto",
"ToastAccountUpdateSuccess": "Konto opdateret",
"ToastAuthorImageRemoveSuccess": "Forfatterbillede fjernet",
- "ToastAuthorUpdateFailed": "Mislykkedes opdatering af forfatter",
"ToastAuthorUpdateMerged": "Forfatter fusioneret",
"ToastAuthorUpdateSuccess": "Forfatter opdateret",
"ToastAuthorUpdateSuccessNoImageFound": "Forfatter opdateret (ingen billede fundet)",
@@ -623,17 +634,13 @@
"ToastBookmarkCreateFailed": "Mislykkedes oprettelse af bogmærke",
"ToastBookmarkCreateSuccess": "Bogmærke tilføjet",
"ToastBookmarkRemoveSuccess": "Bogmærke fjernet",
- "ToastBookmarkUpdateFailed": "Mislykkedes opdatering af bogmærke",
"ToastBookmarkUpdateSuccess": "Bogmærke opdateret",
"ToastChaptersHaveErrors": "Kapitler har fejl",
"ToastChaptersMustHaveTitles": "Kapitler skal have titler",
"ToastCollectionItemsRemoveSuccess": "Element(er) fjernet fra samlingen",
"ToastCollectionRemoveSuccess": "Samling fjernet",
- "ToastCollectionUpdateFailed": "Mislykkedes opdatering af samling",
"ToastCollectionUpdateSuccess": "Samling opdateret",
- "ToastItemCoverUpdateFailed": "Mislykkedes opdatering af varens omslag",
"ToastItemCoverUpdateSuccess": "Varens omslag opdateret",
- "ToastItemDetailsUpdateFailed": "Mislykkedes opdatering af varedetaljer",
"ToastItemDetailsUpdateSuccess": "Varedetaljer opdateret",
"ToastItemMarkedAsFinishedFailed": "Mislykkedes markering som afsluttet",
"ToastItemMarkedAsFinishedSuccess": "Vare markeret som afsluttet",
@@ -645,12 +652,10 @@
"ToastLibraryDeleteSuccess": "Bibliotek slettet",
"ToastLibraryScanFailedToStart": "Mislykkedes start af skanning",
"ToastLibraryScanStarted": "Biblioteksskanning startet",
- "ToastLibraryUpdateFailed": "Mislykkedes opdatering af bibliotek",
"ToastLibraryUpdateSuccess": "Bibliotek \"{0}\" opdateret",
"ToastPlaylistCreateFailed": "Mislykkedes oprettelse af afspilningsliste",
"ToastPlaylistCreateSuccess": "Afspilningsliste oprettet",
"ToastPlaylistRemoveSuccess": "Afspilningsliste fjernet",
- "ToastPlaylistUpdateFailed": "Mislykkedes opdatering af afspilningsliste",
"ToastPlaylistUpdateSuccess": "Afspilningsliste opdateret",
"ToastPodcastCreateFailed": "Mislykkedes oprettelse af podcast",
"ToastPodcastCreateSuccess": "Podcast oprettet med succes",
diff --git a/client/strings/de.json b/client/strings/de.json
index 9aec24f36..25c34c7ed 100644
--- a/client/strings/de.json
+++ b/client/strings/de.json
@@ -56,6 +56,7 @@
"ButtonOpenManager": "Manager öffnen",
"ButtonPause": "Pausieren",
"ButtonPlay": "Abspielen",
+ "ButtonPlayAll": "Alles abspielen",
"ButtonPlaying": "Spielt",
"ButtonPlaylists": "Wiedergabelisten",
"ButtonPrevious": "Zurück",
@@ -566,7 +567,7 @@
"LabelStatsMinutesListening": "Gehörte Minuten",
"LabelStatsOverallDays": "Gesamte Tage",
"LabelStatsOverallHours": "Gesamte Stunden",
- "LabelStatsWeekListening": "Gehörte Wochen",
+ "LabelStatsWeekListening": "Wochenhördauer",
"LabelSubtitle": "Untertitel",
"LabelSupportedFileTypes": "Unterstützte Dateitypen",
"LabelTag": "Schlagwort",
@@ -776,6 +777,31 @@
"MessageShareExpiresIn": "Läuft in {0} ab",
"MessageShareURLWillBe": "Der Freigabe Link wird
{0} sein.",
"MessageStartPlaybackAtTime": "Start der Wiedergabe für \"{0}\" bei {1}?",
+ "MessageTaskAudioFileNotWritable": "Die Audiodatei \"{0}\" ist schreibgeschützt",
+ "MessageTaskCanceledByUser": "Aufgabe vom Benutzer abgebrochen",
+ "MessageTaskDownloadingEpisodeDescription": "Folge \"{0}\" wird heruntergeladen",
+ "MessageTaskEmbeddingMetadata": "Metadaten werden eingebettet",
+ "MessageTaskEmbeddingMetadataDescription": "Metadaten werden in Hörbuch \"{0}\" eingebettet",
+ "MessageTaskEncodingM4b": "M4B wird encodiert",
+ "MessageTaskEncodingM4bDescription": "Hörbuch \"{0}\" wird in eine einzelne m4b Datei encodiert",
+ "MessageTaskFailed": "Fehlgeschlagen",
+ "MessageTaskFailedToBackupAudioFile": "Sicherung der Audiodatei \"{0}\" fehlgeschlagen",
+ "MessageTaskFailedToCreateCacheDirectory": "Fehler beim erstellen des Cache-Verzeichnisses",
+ "MessageTaskFailedToEmbedMetadataInFile": "Einbetten der Metadaten in die Datei \"{0}\" fehlgeschlagen",
+ "MessageTaskFailedToMergeAudioFiles": "Fehler beim zusammenführen der Audiodateien",
+ "MessageTaskFailedToMoveM4bFile": "Fehler beim verschieben der m4b Datei",
+ "MessageTaskFailedToWriteMetadataFile": "Fehler beim schreiben der Metadaten-Datei",
+ "MessageTaskNoFilesToScan": "Keine Dateien zum scannen",
+ "MessageTaskOpmlImportDescription": "Podcasts von {0} RSS-Feeds werden ersrtellt",
+ "MessageTaskOpmlImportFeedDescription": "RSS-Feed \"{0}\" wird importiert",
+ "MessageTaskOpmlImportFeedFailed": "Podcast Feed konnte nicht geladen werden",
+ "MessageTaskOpmlImportFeedPodcastDescription": "Podcast \"{0}\" wird erstellt",
+ "MessageTaskOpmlImportFeedPodcastFailed": "Erstellen des Podcasts fehlgeschlagen",
+ "MessageTaskOpmlImportFinished": "{0} Podcasts hinzugefügt",
+ "MessageTaskScanItemsAdded": "{0} hinzugefügt",
+ "MessageTaskScanItemsMissing": "{0} fehlend",
+ "MessageTaskScanItemsUpdated": "{0} aktualisiert",
+ "MessageTaskScanNoChangesNeeded": "Keine Änderungen nötig",
"MessageThinking": "Nachdenken...",
"MessageUploaderItemFailed": "Hochladen fehlgeschlagen",
"MessageUploaderItemSuccess": "Erfolgreich hochgeladen!",
@@ -816,14 +842,12 @@
"StatsTopNarrators": "TOP SPRECHER",
"StatsTotalDuration": "Mit einer totalen Dauer von…",
"StatsYearInReview": "DAS JAHR IM RÜCKBLICK",
- "ToastAccountUpdateFailed": "Aktualisierung des Kontos fehlgeschlagen",
"ToastAccountUpdateSuccess": "Konto aktualisiert",
"ToastAppriseUrlRequired": "Eine Apprise-URL ist notwendig",
"ToastAuthorImageRemoveSuccess": "Autorenbild entfernt",
"ToastAuthorNotFound": "Autor \"{0}\" nicht gefunden",
"ToastAuthorRemoveSuccess": "Autor entfernt",
"ToastAuthorSearchNotFound": "Autor nicht gefunden",
- "ToastAuthorUpdateFailed": "Aktualisierung des Autors fehlgeschlagen",
"ToastAuthorUpdateMerged": "Autor zusammengeführt",
"ToastAuthorUpdateSuccess": "Autor aktualisiert",
"ToastAuthorUpdateSuccessNoImageFound": "Autor aktualisiert (kein Bild gefunden)",
@@ -834,7 +858,6 @@
"ToastBackupDeleteSuccess": "Sicherung gelöscht",
"ToastBackupInvalidMaxKeep": "Ungültige Anzahl aufzubewahrender Backups",
"ToastBackupInvalidMaxSize": "Ungültige maximale Backupgröße",
- "ToastBackupPathUpdateFailed": "Der Backuppfad konnte nicht aktualisiert werden",
"ToastBackupRestoreFailed": "Sicherung konnte nicht wiederhergestellt werden",
"ToastBackupUploadFailed": "Sicherung konnte nicht hochgeladen werden",
"ToastBackupUploadSuccess": "Sicherung hochgeladen",
@@ -845,7 +868,6 @@
"ToastBookmarkCreateFailed": "Lesezeichen konnte nicht erstellt werden",
"ToastBookmarkCreateSuccess": "Lesezeichen hinzugefügt",
"ToastBookmarkRemoveSuccess": "Lesezeichen entfernt",
- "ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
"ToastBookmarkUpdateSuccess": "Lesezeichen aktualisiert",
"ToastCachePurgeFailed": "Cache leeren fehlgeschlagen",
"ToastCachePurgeSuccess": "Cache geleert",
@@ -856,7 +878,6 @@
"ToastCollectionItemsAddSuccess": "Element(e) erfolgreich zur Sammlung hinzugefügt",
"ToastCollectionItemsRemoveSuccess": "Medien aus der Sammlung entfernt",
"ToastCollectionRemoveSuccess": "Sammlung entfernt",
- "ToastCollectionUpdateFailed": "Sammlung konnte nicht aktualisiert werden",
"ToastCollectionUpdateSuccess": "Sammlung aktualisiert",
"ToastCoverUpdateFailed": "Cover-Update fehlgeschlagen",
"ToastDeleteFileFailed": "Die Datei konnte nicht gelöscht werden",
@@ -865,8 +886,6 @@
"ToastDeviceNameAlreadyExists": "E-Reader-Gerät mit diesem Namen existiert bereits",
"ToastDeviceTestEmailFailed": "Senden der Test-E-Mail fehlgeschlagen",
"ToastDeviceTestEmailSuccess": "Test-E-Mail gesendet",
- "ToastDeviceUpdateFailed": "Das Gerät konnte nicht aktualisiert werden",
- "ToastEmailSettingsUpdateFailed": "E-Mail-Einstellungen konnten nicht aktualisiert werden",
"ToastEmailSettingsUpdateSuccess": "E-Mail-Einstellungen aktualisiert",
"ToastEncodeCancelFailed": "Das Encoding konnte nicht abgebrochen werden",
"ToastEncodeCancelSucces": "Encoding abgebrochen",
@@ -875,21 +894,16 @@
"ToastErrorCannotShare": "Das kann nicht nativ auf diesem Gerät freigegeben werden",
"ToastFailedToLoadData": "Daten laden fehlgeschlagen",
"ToastFailedToShare": "Fehler beim Teilen",
- "ToastFailedToUpdateAccount": "Fehler beim ändern des Accounts",
- "ToastFailedToUpdateUser": "Fehler beim ändern des Benutzers",
"ToastInvalidImageUrl": "Ungültiger Bild URL",
"ToastInvalidUrl": "Ungültiger URL",
- "ToastItemCoverUpdateFailed": "Fehler bei der Aktualisierung des Titelbildes",
"ToastItemCoverUpdateSuccess": "Titelbild aktualisiert",
"ToastItemDeletedFailed": "Fehler beim löschen des Artikels",
"ToastItemDeletedSuccess": "Artikel gelöscht",
- "ToastItemDetailsUpdateFailed": "Fehler bei der Aktualisierung der Artikeldetails",
"ToastItemDetailsUpdateSuccess": "Artikeldetails aktualisiert",
"ToastItemMarkedAsFinishedFailed": "Fehler bei der Markierung des Artikels als \"Beendet\"",
"ToastItemMarkedAsFinishedSuccess": "Artikel als \"Beendet\" markiert",
"ToastItemMarkedAsNotFinishedFailed": "Fehler bei der Markierung des Artikels als \"Nicht Beendet\"",
"ToastItemMarkedAsNotFinishedSuccess": "Artikel als \"Nicht Beendet\" markiert",
- "ToastItemUpdateFailed": "Fehler beim ändern des Artikels",
"ToastItemUpdateSuccess": "Artikel wurde verändert",
"ToastLibraryCreateFailed": "Bibliothek konnte nicht erstellt werden",
"ToastLibraryCreateSuccess": "Bibliothek \"{0}\" erstellt",
@@ -897,7 +911,6 @@
"ToastLibraryDeleteSuccess": "Bibliothek gelöscht",
"ToastLibraryScanFailedToStart": "Scan konnte nicht gestartet werden",
"ToastLibraryScanStarted": "Bibliotheksscan gestartet",
- "ToastLibraryUpdateFailed": "Aktualisierung der Bibliothek fehlgeschlagen",
"ToastLibraryUpdateSuccess": "Bibliothek \"{0}\" aktualisiert",
"ToastNameEmailRequired": "Name und E-Mail sind erforderlich",
"ToastNameRequired": "Name ist erforderlich",
@@ -912,16 +925,13 @@
"ToastNotificationDeleteFailed": "Fehler beim löschen der Benachrichtigung",
"ToastNotificationFailedMaximum": "Maximale Fehlversuche muss >= 0 sein",
"ToastNotificationQueueMaximum": "Maximale Benachrichtigungswarteschlange muss >= 0 sein",
- "ToastNotificationSettingsUpdateFailed": "Fehler beim ändern der Benachrichtigungseinstellungen",
"ToastNotificationSettingsUpdateSuccess": "Benachrichtigungseinstellungen geändert",
"ToastNotificationTestTriggerFailed": "Fehler beim Auslösen der Testbenachrichtigung",
"ToastNotificationTestTriggerSuccess": "Testbenachrichtigung ausgelöst",
- "ToastNotificationUpdateFailed": "Fehler bein ändern der Benachrichtigung",
"ToastNotificationUpdateSuccess": "Benachrichtigung geändert",
"ToastPlaylistCreateFailed": "Erstellen der Wiedergabeliste fehlgeschlagen",
"ToastPlaylistCreateSuccess": "Wiedergabeliste erstellt",
"ToastPlaylistRemoveSuccess": "Wiedergabeliste gelöscht",
- "ToastPlaylistUpdateFailed": "Aktualisieren der Wiedergabeliste fehlgeschlagen",
"ToastPlaylistUpdateSuccess": "Wiedergabeliste aktualisiert",
"ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden",
"ToastPodcastCreateSuccess": "Podcast erstellt",
@@ -950,7 +960,6 @@
"ToastSendEbookToDeviceSuccess": "E-Buch an Gerät „{0}“ gesendet",
"ToastSeriesUpdateFailed": "Aktualisierung der Serien fehlgeschlagen",
"ToastSeriesUpdateSuccess": "Serien aktualisiert",
- "ToastServerSettingsUpdateFailed": "Die Server-Einstellungen wurden nicht gespeichert",
"ToastServerSettingsUpdateSuccess": "Die Server-Einstellungen wurden geupdated",
"ToastSessionCloseFailed": "Fehler beim schließen der Sitzung",
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
@@ -961,7 +970,6 @@
"ToastSocketDisconnected": "Verbindung zum WebSocket verloren",
"ToastSocketFailedToConnect": "Verbindung zum WebSocket fehlgeschlagen",
"ToastSortingPrefixesEmptyError": "Es muss mindestens ein Sortier-Prefix vorhanden sein",
- "ToastSortingPrefixesUpdateFailed": "Update der Sortier-Prefixe ist fehlgeschlagen",
"ToastSortingPrefixesUpdateSuccess": "Die Sortier-Prefixe wirden geupdated ({0} Einträge)",
"ToastTitleRequired": "Titel erforderlich",
"ToastUnknownError": "Unbekannter Fehler",
diff --git a/client/strings/en-us.json b/client/strings/en-us.json
index 9e1643e17..34b014dcf 100644
--- a/client/strings/en-us.json
+++ b/client/strings/en-us.json
@@ -465,6 +465,8 @@
"LabelPubDate": "Pub Date",
"LabelPublishYear": "Publish Year",
"LabelPublishedDate": "Published {0}",
+ "LabelPublishedDecade": "Published Decade",
+ "LabelPublishedDecades": "Published Decades",
"LabelPublisher": "Publisher",
"LabelPublishers": "Publishers",
"LabelRSSFeedCustomOwnerEmail": "Custom owner Email",
@@ -777,6 +779,38 @@
"MessageShareExpiresIn": "Expires in {0}",
"MessageShareURLWillBe": "Share URL will be
{0}",
"MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
+ "MessageTaskAudioFileNotWritable": "Audio file \"{0}\" is not writable",
+ "MessageTaskCanceledByUser": "Task canceled by user",
+ "MessageTaskDownloadingEpisodeDescription": "Downloading episode \"{0}\"",
+ "MessageTaskEmbeddingMetadata": "Embedding metadata",
+ "MessageTaskEmbeddingMetadataDescription": "Embedding metadata in audiobook \"{0}\"",
+ "MessageTaskEncodingM4b": "Encoding M4B",
+ "MessageTaskEncodingM4bDescription": "Encoding audiobook \"{0}\" into a single m4b file",
+ "MessageTaskFailed": "Failed",
+ "MessageTaskFailedToBackupAudioFile": "Failed to backup audio file \"{0}\"",
+ "MessageTaskFailedToCreateCacheDirectory": "Failed to create cache directory",
+ "MessageTaskFailedToEmbedMetadataInFile": "Failed to embed metadata in file \"{0}\"",
+ "MessageTaskFailedToMergeAudioFiles": "Failed to merge audio files",
+ "MessageTaskFailedToMoveM4bFile": "Failed to move m4b file",
+ "MessageTaskFailedToWriteMetadataFile": "Failed to write metadata file",
+ "MessageTaskMatchingBooksInLibrary": "Matching books in library \"{0}\"",
+ "MessageTaskNoFilesToScan": "No files to scan",
+ "MessageTaskOpmlImport": "OPML import",
+ "MessageTaskOpmlImportDescription": "Creating podcasts from {0} RSS feeds",
+ "MessageTaskOpmlImportFeed": "OPML import feed",
+ "MessageTaskOpmlImportFeedDescription": "Importing RSS feed \"{0}\"",
+ "MessageTaskOpmlImportFeedFailed": "Failed to get podcast feed",
+ "MessageTaskOpmlImportFeedPodcastDescription": "Creating podcast \"{0}\"",
+ "MessageTaskOpmlImportFeedPodcastExists": "Podcast already exists at path",
+ "MessageTaskOpmlImportFeedPodcastFailed": "Failed to create podcast",
+ "MessageTaskOpmlImportFinished": "Added {0} podcasts",
+ "MessageTaskScanItemsAdded": "{0} added",
+ "MessageTaskScanItemsMissing": "{0} missing",
+ "MessageTaskScanItemsUpdated": "{0} updated",
+ "MessageTaskScanNoChangesNeeded": "No changes needed",
+ "MessageTaskScanningFileChanges": "Scanning file changes in \"{0}\"",
+ "MessageTaskScanningLibrary": "Scanning \"{0}\" library",
+ "MessageTaskTargetDirectoryNotWritable": "Target directory is not writable",
"MessageThinking": "Thinking...",
"MessageUploaderItemFailed": "Failed to upload",
"MessageUploaderItemSuccess": "Successfully Uploaded!",
@@ -817,14 +851,12 @@
"StatsTopNarrators": "TOP NARRATORS",
"StatsTotalDuration": "With a total duration of…",
"StatsYearInReview": "YEAR IN REVIEW",
- "ToastAccountUpdateFailed": "Failed to update account",
"ToastAccountUpdateSuccess": "Account updated",
"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)",
@@ -835,7 +867,6 @@
"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",
@@ -846,7 +877,6 @@
"ToastBookmarkCreateFailed": "Failed to create bookmark",
"ToastBookmarkCreateSuccess": "Bookmark added",
"ToastBookmarkRemoveSuccess": "Bookmark removed",
- "ToastBookmarkUpdateFailed": "Failed to update bookmark",
"ToastBookmarkUpdateSuccess": "Bookmark updated",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
@@ -857,7 +887,6 @@
"ToastCollectionItemsAddSuccess": "Item(s) added to collection success",
"ToastCollectionItemsRemoveSuccess": "Item(s) removed from collection",
"ToastCollectionRemoveSuccess": "Collection removed",
- "ToastCollectionUpdateFailed": "Failed to update collection",
"ToastCollectionUpdateSuccess": "Collection updated",
"ToastCoverUpdateFailed": "Cover update failed",
"ToastDeleteFileFailed": "Failed to delete file",
@@ -866,8 +895,6 @@
"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",
@@ -876,21 +903,17 @@
"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",
+ "ToastFailedToUpdate": "Failed to update",
"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",
"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",
@@ -898,8 +921,8 @@
"ToastLibraryDeleteSuccess": "Library deleted",
"ToastLibraryScanFailedToStart": "Failed to start scan",
"ToastLibraryScanStarted": "Library scan started",
- "ToastLibraryUpdateFailed": "Failed to update library",
"ToastLibraryUpdateSuccess": "Library \"{0}\" updated",
+ "ToastMatchAllAuthorsFailed": "Failed to match all authors",
"ToastNameEmailRequired": "Name and email are required",
"ToastNameRequired": "Name is required",
"ToastNewUserCreatedFailed": "Failed to create account: \"{0}\"",
@@ -913,16 +936,13 @@
"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",
"ToastPlaylistRemoveSuccess": "Playlist removed",
- "ToastPlaylistUpdateFailed": "Failed to update playlist",
"ToastPlaylistUpdateSuccess": "Playlist updated",
"ToastPodcastCreateFailed": "Failed to create podcast",
"ToastPodcastCreateSuccess": "Podcast created successfully",
@@ -951,7 +971,6 @@
"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",
@@ -962,7 +981,6 @@
"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",
diff --git a/client/strings/es.json b/client/strings/es.json
index 2f7781db0..2510d809e 100644
--- a/client/strings/es.json
+++ b/client/strings/es.json
@@ -30,6 +30,7 @@
"ButtonEditChapters": "Editar Capítulo",
"ButtonEditPodcast": "Editar Podcast",
"ButtonEnable": "Permitir",
+ "ButtonFireAndFail": "Ejecutado y fallido",
"ButtonFireOnTest": "Activar evento de prueba",
"ButtonForceReScan": "Forzar Re-Escaneo",
"ButtonFullPath": "Ruta de Acceso Completa",
@@ -55,6 +56,7 @@
"ButtonOpenManager": "Abrir Editor",
"ButtonPause": "Pausar",
"ButtonPlay": "Reproducir",
+ "ButtonPlayAll": "Reproducir todo",
"ButtonPlaying": "Reproduciendo",
"ButtonPlaylists": "Listas de reproducción",
"ButtonPrevious": "Anterior",
@@ -459,6 +461,7 @@
"LabelPrimaryEbook": "Ebook principal",
"LabelProgress": "Progreso",
"LabelProvider": "Proveedor",
+ "LabelProviderAuthorizationValue": "Valor del encabezado de autorización",
"LabelPubDate": "Fecha de publicación",
"LabelPublishYear": "Año de publicación",
"LabelPublishedDate": "Publicado {0}",
@@ -774,6 +777,38 @@
"MessageShareExpiresIn": "Caduduca en {0}",
"MessageShareURLWillBe": "La URL para compartir será
{0} ",
"MessageStartPlaybackAtTime": "Iniciar reproducción para \"{0}\" en {1}?",
+ "MessageTaskAudioFileNotWritable": "El archivo de audio \"{0}\" no se puede grabar",
+ "MessageTaskCanceledByUser": "Tarea cancelada por el usuario",
+ "MessageTaskDownloadingEpisodeDescription": "Descargando el episodio \"{0}\"",
+ "MessageTaskEmbeddingMetadata": "Inserción de metadatos",
+ "MessageTaskEmbeddingMetadataDescription": "Inserción de metadatos en el audiolibro \"{0}\"",
+ "MessageTaskEncodingM4b": "Codificación M4B",
+ "MessageTaskEncodingM4bDescription": "Codificación del audiolibro \"{0}\" en un único archivo m4b",
+ "MessageTaskFailed": "Fallida",
+ "MessageTaskFailedToBackupAudioFile": "Error en la copia de seguridad del archivo de audio \"{0}\"",
+ "MessageTaskFailedToCreateCacheDirectory": "Error al crear el directorio de la caché",
+ "MessageTaskFailedToEmbedMetadataInFile": "Error al incrustar metadatos en el archivo \"{0}\"",
+ "MessageTaskFailedToMergeAudioFiles": "Error al fusionar archivos de audio",
+ "MessageTaskFailedToMoveM4bFile": "Error al mover el archivo m4b",
+ "MessageTaskFailedToWriteMetadataFile": "Error al escribir el archivo de metadatos",
+ "MessageTaskMatchingBooksInLibrary": "Libros coincidentes en la biblioteca \"{0}\"",
+ "MessageTaskNoFilesToScan": "Sin archivos para escanear",
+ "MessageTaskOpmlImport": "Importar OPML",
+ "MessageTaskOpmlImportDescription": "Creando podcasts a partir de {0} fuentes RSS",
+ "MessageTaskOpmlImportFeed": "Feed de importación OPML",
+ "MessageTaskOpmlImportFeedDescription": "Importando el feed RSS \"{0}\"",
+ "MessageTaskOpmlImportFeedFailed": "No se puede obtener el podcast",
+ "MessageTaskOpmlImportFeedPodcastDescription": "Creando podcast \"{0}\"",
+ "MessageTaskOpmlImportFeedPodcastExists": "Podcast ya existe en la ruta",
+ "MessageTaskOpmlImportFeedPodcastFailed": "Error al crear podcast",
+ "MessageTaskOpmlImportFinished": "Añadido {0} podcasts",
+ "MessageTaskScanItemsAdded": "{0} añadido",
+ "MessageTaskScanItemsMissing": "Falta {0}",
+ "MessageTaskScanItemsUpdated": "{0} actualizado",
+ "MessageTaskScanNoChangesNeeded": "No se necesitan cambios",
+ "MessageTaskScanningFileChanges": "Escaneando cambios en el archivo en \"{0}\"",
+ "MessageTaskScanningLibrary": "Escaneando la biblioteca \"{0}\"",
+ "MessageTaskTargetDirectoryNotWritable": "El directorio de destino no se puede escribir",
"MessageThinking": "Pensando...",
"MessageUploaderItemFailed": "Error al Subir",
"MessageUploaderItemSuccess": "¡Éxito al Subir!",
@@ -814,14 +849,12 @@
"StatsTopNarrators": "NARRADORES DESTACADOS",
"StatsTotalDuration": "Con una duración total de…",
"StatsYearInReview": "RESEÑA DEL AÑO",
- "ToastAccountUpdateFailed": "Error al actualizar cuenta",
"ToastAccountUpdateSuccess": "Cuenta actualizada",
"ToastAppriseUrlRequired": "Debes ingresar una URL de Apprise",
"ToastAuthorImageRemoveSuccess": "Se eliminó la imagen del autor",
"ToastAuthorNotFound": "No se encontró el autor \"{0}\"",
"ToastAuthorRemoveSuccess": "Autor eliminado",
"ToastAuthorSearchNotFound": "No se encontró al autor",
- "ToastAuthorUpdateFailed": "Error al actualizar el autor",
"ToastAuthorUpdateMerged": "Autor combinado",
"ToastAuthorUpdateSuccess": "Autor actualizado",
"ToastAuthorUpdateSuccessNoImageFound": "Autor actualizado (Imagen no encontrada)",
@@ -832,7 +865,6 @@
"ToastBackupDeleteSuccess": "Respaldo eliminado",
"ToastBackupInvalidMaxKeep": "Número no válido de copias de seguridad a conservar",
"ToastBackupInvalidMaxSize": "Tamaño máximo de copia de seguridad no válido",
- "ToastBackupPathUpdateFailed": "Error al actualizar la ruta de la copia de seguridad",
"ToastBackupRestoreFailed": "Error al restaurar el respaldo",
"ToastBackupUploadFailed": "Error al subir el respaldo",
"ToastBackupUploadSuccess": "Respaldo cargado",
@@ -843,7 +875,6 @@
"ToastBookmarkCreateFailed": "Error al crear marcador",
"ToastBookmarkCreateSuccess": "Marcador Agregado",
"ToastBookmarkRemoveSuccess": "Marcador eliminado",
- "ToastBookmarkUpdateFailed": "Error al actualizar el marcador",
"ToastBookmarkUpdateSuccess": "Marcador actualizado",
"ToastCachePurgeFailed": "Error al purgar el caché",
"ToastCachePurgeSuccess": "Caché purgado de manera exitosa",
@@ -854,7 +885,6 @@
"ToastCollectionItemsAddSuccess": "Artículo(s) añadido(s) a la colección correctamente",
"ToastCollectionItemsRemoveSuccess": "Elementos(s) removidos de la colección",
"ToastCollectionRemoveSuccess": "Colección removida",
- "ToastCollectionUpdateFailed": "Error al actualizar la colección",
"ToastCollectionUpdateSuccess": "Colección actualizada",
"ToastCoverUpdateFailed": "Error al actualizar la cubierta",
"ToastDeleteFileFailed": "Error el eliminar archivo",
@@ -863,8 +893,6 @@
"ToastDeviceNameAlreadyExists": "Un libro electrónico ya existe con ese nombre",
"ToastDeviceTestEmailFailed": "Error al enviar correo de prueba",
"ToastDeviceTestEmailSuccess": "Correo electrónico de prueba enviado",
- "ToastDeviceUpdateFailed": "Error al actualizar el dispositivo",
- "ToastEmailSettingsUpdateFailed": "Error al actualizar la configuración del correo electrónico",
"ToastEmailSettingsUpdateSuccess": "Configuración del correo electrónico actualizada",
"ToastEncodeCancelFailed": "No se pudo cancelar la codificación",
"ToastEncodeCancelSucces": "Codificación cancelada",
@@ -873,21 +901,17 @@
"ToastErrorCannotShare": "No se puede compartir de forma nativa en este dispositivo",
"ToastFailedToLoadData": "Error al cargar data",
"ToastFailedToShare": "Error al compartir",
- "ToastFailedToUpdateAccount": "Error al actualizar la cuenta",
- "ToastFailedToUpdateUser": "Error al actualizar el usuario",
+ "ToastFailedToUpdate": "Error al actualizar",
"ToastInvalidImageUrl": "URL de la imagen no válida",
"ToastInvalidUrl": "URL no válida",
- "ToastItemCoverUpdateFailed": "Error al actualizar la portada del elemento",
"ToastItemCoverUpdateSuccess": "Portada del elemento actualizada",
"ToastItemDeletedFailed": "Error al eliminar el elemento",
"ToastItemDeletedSuccess": "Elemento borrado",
- "ToastItemDetailsUpdateFailed": "Error al actualizar los detalles del elemento",
"ToastItemDetailsUpdateSuccess": "Detalles del Elemento Actualizados",
"ToastItemMarkedAsFinishedFailed": "Error al marcar como terminado",
"ToastItemMarkedAsFinishedSuccess": "Elemento marcado como terminado",
"ToastItemMarkedAsNotFinishedFailed": "No se ha podido marcar como no finalizado",
"ToastItemMarkedAsNotFinishedSuccess": "Elemento marcado como No Terminado",
- "ToastItemUpdateFailed": "Error al actualizar el elemento",
"ToastItemUpdateSuccess": "Elemento actualizado",
"ToastLibraryCreateFailed": "Error al crear biblioteca",
"ToastLibraryCreateSuccess": "Biblioteca \"{0}\" creada",
@@ -895,7 +919,6 @@
"ToastLibraryDeleteSuccess": "Biblioteca eliminada",
"ToastLibraryScanFailedToStart": "Error al iniciar el escaneo",
"ToastLibraryScanStarted": "Se inició el escaneo de la biblioteca",
- "ToastLibraryUpdateFailed": "Error al actualizar la biblioteca",
"ToastLibraryUpdateSuccess": "Biblioteca \"{0}\" actualizada",
"ToastNameEmailRequired": "Nombre y correo electrónico obligatorios",
"ToastNameRequired": "Nombre obligatorio",
@@ -910,16 +933,13 @@
"ToastNotificationDeleteFailed": "Error al borrar la notificación",
"ToastNotificationFailedMaximum": "El número máximo de intentos fallidos debe ser ≥ 0",
"ToastNotificationQueueMaximum": "La cola de notificación máxima debe ser ≥ 0",
- "ToastNotificationSettingsUpdateFailed": "Error al actualizar los ajustes de la notificación",
"ToastNotificationSettingsUpdateSuccess": "Ajustes de la notificación actualizados",
"ToastNotificationTestTriggerFailed": "No se ha podido activar la notificación de prueba",
"ToastNotificationTestTriggerSuccess": "Notificación de prueba activada",
- "ToastNotificationUpdateFailed": "No se ha podido actualizar la notificación",
"ToastNotificationUpdateSuccess": "Notificación actualizada",
"ToastPlaylistCreateFailed": "Error al crear la lista de reproducción",
"ToastPlaylistCreateSuccess": "Lista de reproducción creada",
"ToastPlaylistRemoveSuccess": "Lista de reproducción eliminada",
- "ToastPlaylistUpdateFailed": "Error al actualizar la lista de reproducción",
"ToastPlaylistUpdateSuccess": "Lista de reproducción actualizada",
"ToastPodcastCreateFailed": "Error al crear podcast",
"ToastPodcastCreateSuccess": "Podcast creado",
@@ -948,7 +968,6 @@
"ToastSendEbookToDeviceSuccess": "Ebook enviado al dispositivo \"{0}\"",
"ToastSeriesUpdateFailed": "Error al actualizar la serie",
"ToastSeriesUpdateSuccess": "Serie actualizada",
- "ToastServerSettingsUpdateFailed": "Error al actualizar configuración del servidor",
"ToastServerSettingsUpdateSuccess": "Configuración del servidor actualizada",
"ToastSessionCloseFailed": "Error al cerrar la sesión",
"ToastSessionDeleteFailed": "Error al eliminar sesión",
@@ -959,7 +978,6 @@
"ToastSocketDisconnected": "Socket desconectado",
"ToastSocketFailedToConnect": "Error al conectar al Socket",
"ToastSortingPrefixesEmptyError": "Debe tener por lo menos 1 prefijo para ordenar",
- "ToastSortingPrefixesUpdateFailed": "Error al actualizar los prefijos de ordenar",
"ToastSortingPrefixesUpdateSuccess": "Prefijos de ordenar actualizaron ({0} items)",
"ToastTitleRequired": "Título obligatorio",
"ToastUnknownError": "Error desconocido",
diff --git a/client/strings/et.json b/client/strings/et.json
index 45da89895..8d256c011 100644
--- a/client/strings/et.json
+++ b/client/strings/et.json
@@ -9,6 +9,7 @@
"ButtonApply": "Rakenda",
"ButtonApplyChapters": "Rakenda peatükid",
"ButtonAuthors": "Autorid",
+ "ButtonBack": "Tagasi",
"ButtonBrowseForFolder": "Sirvi kausta",
"ButtonCancel": "Tühista",
"ButtonCancelEncode": "Tühista kodeerimine",
@@ -18,6 +19,7 @@
"ButtonChooseFiles": "Vali failid",
"ButtonClearFilter": "Tühista filter",
"ButtonCloseFeed": "Sulge voog",
+ "ButtonCloseSession": "Sulge avatud sessioon",
"ButtonCollections": "Kogud",
"ButtonConfigureScanner": "Konfigureeri skanner",
"ButtonCreate": "Loo",
@@ -27,6 +29,7 @@
"ButtonEdit": "Muuda",
"ButtonEditChapters": "Muuda peatükke",
"ButtonEditPodcast": "Muuda podcasti",
+ "ButtonEnable": "Aktiveeri",
"ButtonForceReScan": "Sunnitud uuestiskaneerimine",
"ButtonFullPath": "Täielik asukoht",
"ButtonHide": "Peida",
@@ -43,13 +46,18 @@
"ButtonMatchAllAuthors": "Sobita kõik autorid",
"ButtonMatchBooks": "Sobita raamatud",
"ButtonNevermind": "Pole tähtis",
+ "ButtonNext": "Järgmine",
"ButtonNextChapter": "Järgmine peatükk",
+ "ButtonNextItemInQueue": "Järgmine kirje järjekorras",
+ "ButtonOk": "Ok",
"ButtonOpenFeed": "Ava voog",
"ButtonOpenManager": "Ava haldur",
"ButtonPause": "Peata",
"ButtonPlay": "Mängi",
+ "ButtonPlayAll": "Mängi kõik",
"ButtonPlaying": "Mängib",
"ButtonPlaylists": "Esitusloendid",
+ "ButtonPrevious": "Eelmine",
"ButtonPreviousChapter": "Eelmine peatükk",
"ButtonPurgeAllCache": "Tühjenda kogu vahemälu",
"ButtonPurgeItemsCache": "Tühjenda esemete vahemälu",
@@ -58,6 +66,9 @@
"ButtonQuickMatch": "Kiire sobitamine",
"ButtonReScan": "Uuestiskaneeri",
"ButtonRead": "Loe",
+ "ButtonReadLess": "Loe vähem",
+ "ButtonReadMore": "Loe rohkem",
+ "ButtonRefresh": "Värskenda",
"ButtonRemove": "Eemalda",
"ButtonRemoveAll": "Eemalda kõik",
"ButtonRemoveAllLibraryItems": "Eemalda kõik raamatukogu esemed",
@@ -211,7 +222,7 @@
"LabelBackupLocation": "Varukoopia asukoht",
"LabelBackupsEnableAutomaticBackups": "Luba automaatsed varukoopiad",
"LabelBackupsEnableAutomaticBackupsHelp": "Varukoopiad salvestatakse /metadata/backups kausta",
- "LabelBackupsMaxBackupSize": "Maksimaalne varukoopia suurus (GB-des)",
+ "LabelBackupsMaxBackupSize": "Maksimaalne varukoopia suurus (GB-des) (0 lõpmatu suuruse jaoks)",
"LabelBackupsMaxBackupSizeHelp": "Kaitsena valesti seadistamise vastu ebaõnnestuvad varukoopiad, kui need ületavad seadistatud suuruse.",
"LabelBackupsNumberToKeep": "Varukoopiate arv, mida hoida",
"LabelBackupsNumberToKeepHelp": "Ühel ajal eemaldatakse ainult 1 varukoopia, seega kui teil on juba rohkem varukoopiaid kui siin määratud, peaksite need käsitsi eemaldama.",
@@ -449,7 +460,7 @@
"LabelSettingsHomePageBookshelfView": "Avaleht kasutage raamatukoguvaadet",
"LabelSettingsLibraryBookshelfView": "Raamatukogu kasutamiseks kasutage raamatukoguvaadet",
"LabelSettingsParseSubtitles": "Lugege subtiitreid",
- "LabelSettingsParseSubtitlesHelp": "Eraldage subtiitrid heliraamatu kaustade nimedest.
Subtiitrid peavad olema eraldatud \" - \".
Näiteks: \"Raamatu pealkiri - Siin on alapealkiri\" alapealkiri on \"Siin on alapealkiri\"",
+ "LabelSettingsParseSubtitlesHelp": "Eraldage subtiitrid heliraamatu kaustade nimedest.
Subtiitrid peavad olema eraldatud kasutades \" - \".
Näiteks: \"Raamatu pealkiri - Siin on alapealkiri\" alapealkiri on \"Siin on alapealkiri\"",
"LabelSettingsPreferMatchedMetadata": "Eelista sobitatud metaandmeid",
"LabelSettingsPreferMatchedMetadataHelp": "Sobitatud andmed kirjutavad Kiir Sobitamise kasutamisel üle üksikasjad.",
"LabelSettingsSkipMatchingBooksWithASIN": "Jätke ASIN-iga sobituvad raamatud vahele",
@@ -682,10 +693,8 @@
"PlaceholderNewPlaylist": "Uue esitusloendi nimi",
"PlaceholderSearch": "Otsi...",
"PlaceholderSearchEpisode": "Otsi episoodi...",
- "ToastAccountUpdateFailed": "Konto värskendamine ebaõnnestus",
"ToastAccountUpdateSuccess": "Konto on värskendatud",
"ToastAuthorImageRemoveSuccess": "Autori pilt on eemaldatud",
- "ToastAuthorUpdateFailed": "Autori värskendamine ebaõnnestus",
"ToastAuthorUpdateMerged": "Autor liidetud",
"ToastAuthorUpdateSuccess": "Autor värskendatud",
"ToastAuthorUpdateSuccessNoImageFound": "Autor värskendatud (pilti ei leitud)",
@@ -701,17 +710,13 @@
"ToastBookmarkCreateFailed": "Järjehoidja loomine ebaõnnestus",
"ToastBookmarkCreateSuccess": "Järjehoidja lisatud",
"ToastBookmarkRemoveSuccess": "Järjehoidja eemaldatud",
- "ToastBookmarkUpdateFailed": "Järjehoidja värskendamine ebaõnnestus",
"ToastBookmarkUpdateSuccess": "Järjehoidja värskendatud",
"ToastChaptersHaveErrors": "Peatükkidel on vigu",
"ToastChaptersMustHaveTitles": "Peatükkidel peab olema pealkiri",
"ToastCollectionItemsRemoveSuccess": "Üksus(ed) eemaldatud kogumist",
"ToastCollectionRemoveSuccess": "Kogum eemaldatud",
- "ToastCollectionUpdateFailed": "Kogumi värskendamine ebaõnnestus",
"ToastCollectionUpdateSuccess": "Kogum värskendatud",
- "ToastItemCoverUpdateFailed": "Üksuse kaane värskendamine ebaõnnestus",
"ToastItemCoverUpdateSuccess": "Üksuse kaas värskendatud",
- "ToastItemDetailsUpdateFailed": "Üksuse üksikasjade värskendamine ebaõnnestus",
"ToastItemDetailsUpdateSuccess": "Üksuse üksikasjad värskendatud",
"ToastItemMarkedAsFinishedFailed": "Märgistamine kui lõpetatud ebaõnnestus",
"ToastItemMarkedAsFinishedSuccess": "Üksus märgitud kui lõpetatud",
@@ -723,12 +728,10 @@
"ToastLibraryDeleteSuccess": "Raamatukogu kustutatud",
"ToastLibraryScanFailedToStart": "Skanneerimine ei käivitunud",
"ToastLibraryScanStarted": "Raamatukogu skaneerimine alustatud",
- "ToastLibraryUpdateFailed": "Raamatukogu värskendamine ebaõnnestus",
"ToastLibraryUpdateSuccess": "Raamatukogu \"{0}\" värskendatud",
"ToastPlaylistCreateFailed": "Esitusloendi loomine ebaõnnestus",
"ToastPlaylistCreateSuccess": "Esitusloend loodud",
"ToastPlaylistRemoveSuccess": "Esitusloend eemaldatud",
- "ToastPlaylistUpdateFailed": "Esitusloendi värskendamine ebaõnnestus",
"ToastPlaylistUpdateSuccess": "Esitusloend värskendatud",
"ToastPodcastCreateFailed": "Podcasti loomine ebaõnnestus",
"ToastPodcastCreateSuccess": "Podcast loodud edukalt",
diff --git a/client/strings/fi.json b/client/strings/fi.json
index b1840bb06..9795139b4 100644
--- a/client/strings/fi.json
+++ b/client/strings/fi.json
@@ -19,6 +19,7 @@
"ButtonChooseFiles": "Valitse tiedostot",
"ButtonClearFilter": "Poista suodatus",
"ButtonCloseFeed": "Sulje syöte",
+ "ButtonCloseSession": "Sulje Avoin Sessio",
"ButtonCollections": "Kokoelmat",
"ButtonConfigureScanner": "Skannerin asetukset",
"ButtonCreate": "Luo",
@@ -28,6 +29,9 @@
"ButtonEdit": "Muokkaa",
"ButtonEditChapters": "Muokkaa lukuja",
"ButtonEditPodcast": "Muokkaa podcastia",
+ "ButtonEnable": "Aktivoi",
+ "ButtonFireAndFail": "Laukaise ja epäonnistu",
+ "ButtonFireOnTest": "Laukaise onTest tapahtuma",
"ButtonForceReScan": "Pakota uudelleenskannaus",
"ButtonFullPath": "Koko polku",
"ButtonHide": "Piilota",
@@ -46,10 +50,13 @@
"ButtonNevermind": "Ei sittenkään",
"ButtonNext": "Seuraava",
"ButtonNextChapter": "Seuraava luku",
+ "ButtonNextItemInQueue": "Seuraava jonossa",
+ "ButtonOk": "Ok",
"ButtonOpenFeed": "Avaa syöte",
"ButtonOpenManager": "Avaa hallinta",
"ButtonPause": "Pysäytä",
"ButtonPlay": "Toista",
+ "ButtonPlayAll": "Toista kaikki",
"ButtonPlaying": "Toistetaan",
"ButtonPlaylists": "Soittolistat",
"ButtonPrevious": "Edellinen",
@@ -90,6 +97,7 @@
"ButtonStats": "Tilastot",
"ButtonSubmit": "Lähetä",
"ButtonTest": "Testi",
+ "ButtonUnlinkOpenId": "Poista OpenID linkitys",
"ButtonUpload": "Lähetä palvelimelle",
"ButtonUploadBackup": "Lähetä varmuuskopio",
"ButtonUploadCover": "Lähetä kansikuva",
@@ -102,6 +110,7 @@
"ErrorUploadFetchMetadataNoResults": "Metadatan haku epäonnistui, yritä päivittää Teoksen nimi ja/tai Tekijä",
"ErrorUploadLacksTitle": "Pitää sisältää Nimi",
"HeaderAccount": "Tili",
+ "HeaderAddCustomMetadataProvider": "Lisää mukautettu metadata tarjoaja",
"HeaderAdvanced": "Edistynyt",
"HeaderAppriseNotificationSettings": "Apprise-ilmoitusasetukset",
"HeaderAudioTracks": "Ääniraidat",
@@ -126,38 +135,60 @@
"HeaderEreaderDevices": "E-lukijalaitteet",
"HeaderEreaderSettings": "E-lukijan asetukset",
"HeaderFiles": "Tiedostot",
+ "HeaderFindChapters": "Etsi kappaleet",
"HeaderIgnoredFiles": "Ohitetut tiedostot",
+ "HeaderLastListeningSession": "Edellinen kuuntelukerta",
"HeaderLatestEpisodes": "Viimeisimmät jaksot",
"HeaderLibraries": "Kirjastot",
"HeaderLibraryFiles": "Kirjaston tiedostot",
"HeaderLibraryStats": "Kirjaston tilastot",
+ "HeaderListeningSessions": "Kuuntelukerrat",
"HeaderListeningStats": "Kuuntelutilastot",
+ "HeaderLogin": "Kirjaudu",
"HeaderLogs": "Lokit",
+ "HeaderManageGenres": "Hallitse lajityyppejä",
+ "HeaderManageTags": "Hallitse tageja",
+ "HeaderMetadataToEmbed": "Sisällytettävä metadata",
"HeaderNewAccount": "Uusi tili",
"HeaderNewLibrary": "Uusi kirjasto",
+ "HeaderNotificationCreate": "Luo ilmoitus",
+ "HeaderNotificationUpdate": "Päivitä ilmoitus",
"HeaderNotifications": "Ilmoitukset",
"HeaderOpenRSSFeed": "Avaa RSS-syöte",
"HeaderOtherFiles": "Muut tiedostot",
"HeaderPermissions": "Käyttöoikeudet",
+ "HeaderPlayerQueue": "Soittimen jono",
+ "HeaderPlayerSettings": "Soittimen asetukset",
"HeaderPlaylist": "Soittolista",
"HeaderPlaylistItems": "Soittolistan kohteet",
+ "HeaderPodcastsToAdd": "Lisättävät podcastit",
+ "HeaderPreviewCover": "Esikatsele kansikuvaa",
"HeaderRSSFeedGeneral": "RSS yksityiskohdat",
"HeaderRSSFeedIsOpen": "RSS syöte on avoinna",
+ "HeaderRSSFeeds": "RSS syötteet",
"HeaderRemoveEpisode": "Poista jakso",
"HeaderRemoveEpisodes": "Poista {0} jaksoa",
"HeaderSchedule": "Ajoita",
"HeaderScheduleLibraryScans": "Ajoita automaattiset kirjastoskannaukset",
+ "HeaderSession": "Istunto",
"HeaderSetBackupSchedule": "Aseta varmuuskopiointiaikataulu",
"HeaderSettings": "Asetukset",
"HeaderSettingsExperimental": "Kokeelliset ominaisuudet",
+ "HeaderSettingsGeneral": "Yleiset",
"HeaderSleepTimer": "Uniajastin",
"HeaderStatsMinutesListeningChart": "Kuunteluminuutit (viim. 7 pv)",
"HeaderStatsRecentSessions": "Viimeaikaiset istunnot",
+ "HeaderStatsTop5Genres": "Top 5 lajityypit",
"HeaderTableOfContents": "Sisällysluettelo",
"HeaderTools": "Työkalut",
+ "HeaderUpdateAccount": "Päivitä tili",
+ "HeaderUpdateAuthor": "Päivitä kirjailija",
+ "HeaderUpdateLibrary": "Päivitä kirjasto",
"HeaderUsers": "Käyttäjät",
"HeaderYourStats": "Tilastosi",
+ "LabelAbridged": "Lyhennetty",
"LabelAccountType": "Tilin tyyppi",
+ "LabelAccountTypeAdmin": "Järjestelmänvalvoja",
"LabelAccountTypeGuest": "Vieras",
"LabelAccountTypeUser": "Käyttäjä",
"LabelActivity": "Toiminta",
@@ -166,22 +197,29 @@
"LabelAddToPlaylist": "Lisää soittolistaan",
"LabelAddToPlaylistBatch": "Lisää {0} kohdetta soittolistaan",
"LabelAddedAt": "Lisätty listalle",
+ "LabelAddedDate": "Lisätty {0}",
+ "LabelAdminUsersOnly": "Vain järjestelmänvalvojat",
"LabelAll": "Kaikki",
"LabelAllUsers": "Kaikki käyttäjät",
"LabelAllUsersExcludingGuests": "Kaikki käyttäjät vieraita lukuun ottamatta",
"LabelAllUsersIncludingGuests": "Kaikki käyttäjät mukaan lukien vieraat",
+ "LabelAlreadyInYourLibrary": "Jo kirjastossasi",
"LabelAuthor": "Tekijä",
"LabelAuthorFirstLast": "Tekijä (Etunimi Sukunimi)",
"LabelAuthorLastFirst": "Tekijä (Sukunimi, Etunimi)",
"LabelAuthors": "Tekijät",
"LabelAutoDownloadEpisodes": "Lataa jaksot automaattisesti",
+ "LabelBackToUser": "Takaisin käyttäjään",
+ "LabelBackupLocation": "Varmuuskopiointipaikka",
"LabelBackupsEnableAutomaticBackups": "Ota automaattinen varmuuskopiointi käyttöön",
"LabelBackupsEnableAutomaticBackupsHelp": "Varmuuskopiot tallennettu kansioon /metadata/backups",
"LabelBackupsMaxBackupSize": "Varmuuskopion enimmäiskoko (Gt) (0 rajaton)",
"LabelBackupsNumberToKeep": "Säilytettävien varmuuskopioiden määrä",
+ "LabelBitrate": "Bittinopeus",
"LabelBooks": "Kirjat",
"LabelButtonText": "Painikkeen teksti",
"LabelChangePassword": "Vaihda salasana",
+ "LabelChannels": "Kanavat",
"LabelChapters": "Luvut",
"LabelClickForMoreInfo": "Napsauta saadaksesi lisätietoja",
"LabelClosePlayer": "Sulje soitin",
@@ -196,79 +234,205 @@
"LabelContinueSeries": "Jatka sarjoja",
"LabelCover": "Kansikuva",
"LabelCoverImageURL": "Kansikuvan URL-osoite",
+ "LabelCreatedAt": "Luotu",
"LabelCurrent": "Nykyinen",
+ "LabelDays": "Päivää",
"LabelDescription": "Kuvaus",
"LabelDevice": "Laite",
"LabelDeviceInfo": "Laitteen tiedot",
+ "LabelDiscover": "Löydä",
"LabelDownload": "Lataa",
"LabelDownloadNEpisodes": "Lataa {0} jaksoa",
"LabelDuration": "Kesto",
+ "LabelDurationComparisonLonger": "({0} pidempi)",
+ "LabelDurationComparisonShorter": "({0} lyhyempi)",
"LabelEbook": "E-kirja",
"LabelEbooks": "E-kirjat",
"LabelEdit": "Muokkaa",
"LabelEmail": "Sähköposti",
+ "LabelEmailSettingsTestAddress": "Testiosoite",
+ "LabelEmbeddedCover": "Upotettu kansikuva",
"LabelEnable": "Ota käyttöön",
+ "LabelEnd": "Loppu",
"LabelEndOfChapter": "Luvun loppu",
"LabelEpisode": "Jakso",
+ "LabelEpisodes": "Jaksot",
+ "LabelExample": "Esimerkki",
+ "LabelFeedURL": "Syötteen URL",
"LabelFile": "Tiedosto",
"LabelFileBirthtime": "Tiedoston syntymäaika",
+ "LabelFileBornDate": "Syntynyt {0}",
"LabelFileModified": "Muutettu tiedosto",
+ "LabelFileModifiedDate": "Muokattu {0}",
"LabelFilename": "Tiedostonimi",
+ "LabelFindEpisodes": "Etsi jaksoja",
+ "LabelFinished": "Valmis",
"LabelFolder": "Kansio",
+ "LabelFolders": "Kansiot",
+ "LabelGenre": "Lajityyppi",
+ "LabelGenres": "Lajityypit",
+ "LabelHost": "Isäntä",
+ "LabelHours": "Tunnit",
"LabelInProgress": "Kesken",
"LabelIncomplete": "Keskeneräinen",
+ "LabelIntervalEvery12Hours": "12 tunnin välein",
+ "LabelIntervalEvery15Minutes": "15 minuutin välein",
+ "LabelIntervalEvery2Hours": "2 tunnin välein",
+ "LabelIntervalEvery30Minutes": "30 minuutin välein",
+ "LabelIntervalEvery6Hours": "6 tunnin välein",
+ "LabelIntervalEveryDay": "Joka päivä",
+ "LabelIntervalEveryHour": "Joka tunti",
+ "LabelItem": "Kohde",
"LabelLanguage": "Kieli",
+ "LabelLanguageDefaultServer": "Palvelimen oletuskieli",
+ "LabelLanguages": "Kielet",
+ "LabelLastBookAdded": "Viimeisin lisätty kirja",
+ "LabelLibrary": "Kirjasto",
+ "LabelLineSpacing": "Riviväli",
"LabelListenAgain": "Kuuntele uudelleen",
"LabelMediaType": "Mediatyyppi",
+ "LabelMinute": "Minuutti",
+ "LabelMinutes": "Minuutit",
"LabelMore": "Lisää",
"LabelMoreInfo": "Lisätietoja",
"LabelName": "Nimi",
"LabelNarrator": "Lukija",
"LabelNarrators": "Lukijat",
+ "LabelNew": "Uusi",
+ "LabelNewPassword": "Uusi salasana",
"LabelNewestAuthors": "Uusimmat kirjailijat",
"LabelNewestEpisodes": "Uusimmat jaksot",
+ "LabelNotStarted": "Ei aloitettu",
"LabelPassword": "Salasana",
"LabelPath": "Polku",
+ "LabelPermanent": "Pysyvä",
+ "LabelPermissionsAccessAllLibraries": "Käyttöoikeudet kaikkiin kirjastoihin",
+ "LabelPermissionsDelete": "Voi poistaa",
+ "LabelPermissionsDownload": "Voi ladata",
+ "LabelPermissionsUpdate": "Voi päivittää",
+ "LabelPermissionsUpload": "Voi lähettää",
+ "LabelPlaylists": "Soittolistat",
+ "LabelPodcast": "Podcast",
"LabelPodcasts": "Podcastit",
+ "LabelPort": "Portti",
"LabelPublishYear": "Julkaisuvuosi",
+ "LabelPublisher": "Julkaisija",
+ "LabelPublishers": "Julkaisijat",
"LabelRSSFeedPreventIndexing": "Estä indeksointi",
+ "LabelRandomly": "Satunnaisesti",
"LabelRead": "Lue",
"LabelReadAgain": "Lue uudelleen",
"LabelRecentSeries": "Viimeisimmät sarjat",
"LabelRecentlyAdded": "Viimeeksi lisätyt",
+ "LabelRecommended": "Suositeltu",
+ "LabelRegion": "Alue",
+ "LabelRemoveCover": "Poista kansikuva",
"LabelSeason": "Kausi",
+ "LabelSelectAll": "Valitse kaikki",
+ "LabelSelectUsers": "Valitse käyttäjät",
+ "LabelSeries": "Sarja",
+ "LabelSeriesName": "Sarjan nimi",
"LabelSetEbookAsPrimary": "Aseta ensisijaiseksi",
"LabelSetEbookAsSupplementary": "Aseta täydentäväksi",
+ "LabelSettingsAudiobooksOnly": "Vain äänikirjat",
+ "LabelSettingsChromecastSupport": "Chromecast-tuki",
+ "LabelSettingsExperimentalFeatures": "Kokeelliset ominaisuudet",
+ "LabelSettingsFindCovers": "Etsi kansikuvia",
+ "LabelShare": "Jaa",
"LabelShowAll": "Näytä kaikki",
+ "LabelShowSeconds": "Näytä sekunnit",
"LabelSize": "Koko",
"LabelSleepTimer": "Uniajastin",
+ "LabelStart": "Aloita",
+ "LabelStartTime": "Aloitusaika",
+ "LabelStatsAudioTracks": "Ääniraidat",
+ "LabelStatsBestDay": "Paras päivä",
"LabelStatsDailyAverage": "Päivittäinen keskiarvo",
+ "LabelStatsDays": "Päivää",
+ "LabelStatsDaysListened": "Päivää kuunneltu",
+ "LabelStatsHours": "Tunnit",
"LabelStatsInARow": "peräjälkeen",
"LabelStatsMinutes": "minuuttia",
+ "LabelStatsMinutesListening": "Minuuttia kuunneltu",
+ "LabelStatsWeekListening": "Viikon aikana kuunneltu",
+ "LabelTag": "Tägi",
+ "LabelTags": "Tägit",
"LabelTheme": "Teema",
"LabelThemeDark": "Tumma",
"LabelThemeLight": "Kirkas",
"LabelTimeRemaining": "{0} jäljellä",
+ "LabelTitle": "Nimi",
+ "LabelTotalDuration": "Kokonaiskesto",
+ "LabelTracks": "Raidat",
"LabelType": "Tyyppi",
+ "LabelUnknown": "Tuntematon",
+ "LabelUpdateCover": "Päivitä kansikuva",
"LabelUser": "Käyttäjä",
"LabelUsername": "Käyttäjätunnus",
+ "LabelValue": "Arvo",
+ "LabelVersion": "Versio",
"LabelYourBookmarks": "Kirjanmerkkisi",
"LabelYourProgress": "Edistymisesi",
"MessageDownloadingEpisode": "Ladataan jaksoa",
"MessageEpisodesQueuedForDownload": "{0} jaksoa on latausjonossa",
+ "MessageFeedURLWillBe": "Syötteen URL tulee olemaan {0}",
"MessageFetching": "Haetaan...",
"MessageLoading": "Ladataan...",
"MessageMarkAsFinished": "Merkitse valmiiksi",
"MessageNoBookmarks": "Ei kirjanmerkkejä",
+ "MessageNoChapters": "Ei kappaleita",
+ "MessageNoCoversFound": "Kansikuvia ei löydetty",
+ "MessageNoGenres": "Ei lajityyppejä",
"MessageNoItems": "Ei kohteita",
"MessageNoItemsFound": "Kohteita ei löytynyt",
+ "MessageNoListeningSessions": "Ei kuunteluistuntoja",
"MessageNoPodcastsFound": "Podcasteja ei löytynyt",
+ "MessageNoUpdatesWereNecessary": "Päivityksiä ei tarvittu",
"MessageNoUserPlaylists": "Sinulla ei ole soittolistoja",
+ "MessageOr": "tai",
"MessageReportBugsAndContribute": "Ilmoita virheistä, toivo ominaisuuksia ja osallistu",
+ "MessageTaskFailed": "Epäonnistunut",
+ "StatsSessions": "istunnot",
+ "ToastAccountUpdateSuccess": "Tili päivitetty",
"ToastBookmarkCreateFailed": "Kirjanmerkin luominen epäonnistui",
- "ToastBookmarkUpdateFailed": "Kirjanmerkin päivittäminen epäonnistui",
+ "ToastCoverUpdateFailed": "Kansikuvan päivitys epäonnistui",
+ "ToastItemCoverUpdateSuccess": "Kohteen kansikuva päivitetty",
"ToastItemMarkedAsFinishedFailed": "Valmiiksi merkitseminen epäonnistui",
+ "ToastItemMarkedAsNotFinishedFailed": "Valmiiksi merkitsemisen poisto epäonnistui",
+ "ToastItemUpdateSuccess": "Kohde päivitetty",
+ "ToastLibraryCreateFailed": "Kirjaston luominen epäonnistui",
+ "ToastLibraryCreateSuccess": "Kirjasto \"{0}\" luotu",
+ "ToastLibraryDeleteFailed": "Kirjaston poistaminen epäonnistui",
+ "ToastLibraryDeleteSuccess": "Kirjasto poistettu",
+ "ToastLibraryUpdateSuccess": "Kirjasto \"{0}\" päivitetty",
+ "ToastNewUserCreatedFailed": "Tilin \"{0}\" luominen epäonnistui",
+ "ToastNewUserCreatedSuccess": "Uusi tili luotu",
"ToastPlaylistCreateFailed": "Soittolistan luominen epäonnistui",
+ "ToastPlaylistCreateSuccess": "Soittolista luotu",
+ "ToastPlaylistRemoveSuccess": "Soittolista poistettu",
+ "ToastPlaylistUpdateSuccess": "Soittolista päivitetty",
"ToastPodcastCreateFailed": "Podcastin luominen epäonnistui",
- "ToastPodcastCreateSuccess": "Podcastin luominen onnistui"
+ "ToastPodcastCreateSuccess": "Podcastin luominen onnistui",
+ "ToastRSSFeedCloseFailed": "RSS syötteen sulkeminen epäonnistui",
+ "ToastRSSFeedCloseSuccess": "RSS syöte suljettu",
+ "ToastRemoveFailed": "Poistaminen epäonnistui",
+ "ToastRemoveItemFromCollectionFailed": "Kohteen poistaminen kokoelmasta epäonnistui",
+ "ToastRemoveItemFromCollectionSuccess": "Kohde poistettu kokoelmasta",
+ "ToastRenameFailed": "Uudelleennimeäminen epäonnistui",
+ "ToastSelectAtLeastOneUser": "Valitse ainakin yksi käyttäjä",
+ "ToastServerSettingsUpdateSuccess": "Palvelimen asetukset päivitetty",
+ "ToastSessionCloseFailed": "Istunnon sulkeminen epäonnistui",
+ "ToastSessionDeleteFailed": "Istunnon poistaminen epäonnistui",
+ "ToastSessionDeleteSuccess": "Istunto poistettu",
+ "ToastSocketConnected": "Yhteys saatu",
+ "ToastSocketDisconnected": "Yhteys katkaistu",
+ "ToastSocketFailedToConnect": "Yhteyden muodostus epäonnistui",
+ "ToastTitleRequired": "Otsikko on pakollinen",
+ "ToastUnknownError": "Tuntematon virhe",
+ "ToastUserDeleteFailed": "Käyttäjän poisto epäonnistui",
+ "ToastUserDeleteSuccess": "Käyttäjä poistettu",
+ "ToastUserPasswordChangeSuccess": "Salasana vaihdettu onnistuneesti",
+ "ToastUserPasswordMismatch": "Salasanat eivät täsmää",
+ "ToastUserPasswordMustChange": "Uusi salasana ei voi olla sama kuin vanha salasana",
+ "ToastUserRootRequireName": "Pääkäyttäjän nimi on pakollinen"
}
diff --git a/client/strings/fr.json b/client/strings/fr.json
index a1b5a58e5..3c5e40021 100644
--- a/client/strings/fr.json
+++ b/client/strings/fr.json
@@ -9,7 +9,7 @@
"ButtonApply": "Appliquer",
"ButtonApplyChapters": "Appliquer aux chapitres",
"ButtonAuthors": "Auteurs",
- "ButtonBack": "Reculer",
+ "ButtonBack": "Retour",
"ButtonBrowseForFolder": "Naviguer vers le répertoire",
"ButtonCancel": "Annuler",
"ButtonCancelEncode": "Annuler l’encodage",
@@ -56,6 +56,7 @@
"ButtonOpenManager": "Ouvrir le gestionnaire",
"ButtonPause": "Pause",
"ButtonPlay": "Lire",
+ "ButtonPlayAll": "Lire tout",
"ButtonPlaying": "En lecture",
"ButtonPlaylists": "Listes de lecture",
"ButtonPrevious": "Précédent",
@@ -212,11 +213,11 @@
"LabelAccountTypeUser": "Utilisateur",
"LabelActivity": "Activité",
"LabelAddToCollection": "Ajouter à la collection",
- "LabelAddToCollectionBatch": "Ajout de {0} livres à la lollection",
+ "LabelAddToCollectionBatch": "Ajout de {0} livres à la collection",
"LabelAddToPlaylist": "Ajouter à la liste de lecture",
"LabelAddToPlaylistBatch": "{0} éléments ajoutés à la liste de lecture",
"LabelAddedAt": "Date d’ajout",
- "LabelAddedDate": "{0} ajoutés",
+ "LabelAddedDate": "Ajouté le {0}",
"LabelAdminUsersOnly": "Administrateurs uniquement",
"LabelAll": "Tout",
"LabelAllUsers": "Tous les utilisateurs",
@@ -319,7 +320,7 @@
"LabelFetchingMetadata": "Récupération des métadonnées",
"LabelFile": "Fichier",
"LabelFileBirthtime": "Création du fichier",
- "LabelFileBornDate": "Créé {0}",
+ "LabelFileBornDate": "Créé le {0}",
"LabelFileModified": "Modification du fichier",
"LabelFileModifiedDate": "Modifié le {0}",
"LabelFilename": "Nom de fichier",
@@ -463,7 +464,7 @@
"LabelProviderAuthorizationValue": "Valeur de l’en-tête d’autorisation",
"LabelPubDate": "Date de publication",
"LabelPublishYear": "Année de publication",
- "LabelPublishedDate": "{0} publiés",
+ "LabelPublishedDate": "Publié en {0}",
"LabelPublisher": "Éditeur",
"LabelPublishers": "Éditeurs",
"LabelRSSFeedCustomOwnerEmail": "Courriel personnalisée du propriétaire",
@@ -491,7 +492,7 @@
"LabelSeason": "Saison",
"LabelSelectAll": "Tout sélectionner",
"LabelSelectAllEpisodes": "Sélectionner tous les épisodes",
- "LabelSelectEpisodesShowing": "Sélectionner {0} episode(s) en cours",
+ "LabelSelectEpisodesShowing": "Sélectionner {0} épisode(s) en cours",
"LabelSelectUsers": "Sélectionner les utilisateurs",
"LabelSendEbookToDevice": "Envoyer le livre numérique à…",
"LabelSequence": "Séquence",
@@ -776,6 +777,38 @@
"MessageShareExpiresIn": "Expire dans {0}",
"MessageShareURLWillBe": "L’adresse de partage sera
{0}",
"MessageStartPlaybackAtTime": "Démarrer la lecture pour « {0} » à {1} ?",
+ "MessageTaskAudioFileNotWritable": "Le fichier audio « {0} » n’est pas accessible en écriture",
+ "MessageTaskCanceledByUser": "Tâche annulée par l’utilisateur",
+ "MessageTaskDownloadingEpisodeDescription": "Téléchargement de l'épisode « {0} »",
+ "MessageTaskEmbeddingMetadata": "Intégration de métadonnées",
+ "MessageTaskEmbeddingMetadataDescription": "Intégration de métadonnées dans le livre audio « {0} »",
+ "MessageTaskEncodingM4b": "Encodage M4B",
+ "MessageTaskEncodingM4bDescription": "Encodage du livre audio « {0} » dans un seul fichier M4B",
+ "MessageTaskFailed": "Échec",
+ "MessageTaskFailedToBackupAudioFile": "Échec de la sauvegarde du fichier audio « {0} »",
+ "MessageTaskFailedToCreateCacheDirectory": "Échec de la création du répertoire de cache",
+ "MessageTaskFailedToEmbedMetadataInFile": "Échec de l'intégration des métadonnées dans le fichier « {0} »",
+ "MessageTaskFailedToMergeAudioFiles": "Échec de la fusion des fichiers audio",
+ "MessageTaskFailedToMoveM4bFile": "Échec du déplacement du fichier M4B",
+ "MessageTaskFailedToWriteMetadataFile": "Échec de l’écriture du fichier de métadonnées",
+ "MessageTaskMatchingBooksInLibrary": "Livres correspondants dans la bibliothèque « {0} »",
+ "MessageTaskNoFilesToScan": "Aucun fichier à analyser",
+ "MessageTaskOpmlImport": "Importation OPML",
+ "MessageTaskOpmlImportDescription": "Création de podcasts à partir de {0} flux RSS",
+ "MessageTaskOpmlImportFeed": "Flux d’importation OPML",
+ "MessageTaskOpmlImportFeedDescription": "Importation du flux RSS « {0} »",
+ "MessageTaskOpmlImportFeedFailed": "Échec de l’obtention du flux de podcast",
+ "MessageTaskOpmlImportFeedPodcastDescription": "Création du podcast « {0} »",
+ "MessageTaskOpmlImportFeedPodcastExists": "Le podcast existe déjà à cet emplacement",
+ "MessageTaskOpmlImportFeedPodcastFailed": "Échec de la création du podcast",
+ "MessageTaskOpmlImportFinished": "Ajout de {0} podcasts",
+ "MessageTaskScanItemsAdded": "{0} ajouté",
+ "MessageTaskScanItemsMissing": "{0} manquant",
+ "MessageTaskScanItemsUpdated": "{0} mis à jour",
+ "MessageTaskScanNoChangesNeeded": "Aucun changement nécessaire",
+ "MessageTaskScanningFileChanges": "Analyse des modifications du fichier dans « {0} »",
+ "MessageTaskScanningLibrary": "Analyse de la bibliothèque « {0} »",
+ "MessageTaskTargetDirectoryNotWritable": "Le répertoire cible n’est pas accessible en écriture",
"MessageThinking": "Je cherche…",
"MessageUploaderItemFailed": "Échec du téléversement",
"MessageUploaderItemSuccess": "Téléversement effectué !",
@@ -816,14 +849,12 @@
"StatsTopNarrators": "TOP NARRATEURS",
"StatsTotalDuration": "Pour une durée totale de…",
"StatsYearInReview": "BILAN DE L’ANNÉE",
- "ToastAccountUpdateFailed": "Échec de la mise à jour du compte",
"ToastAccountUpdateSuccess": "Compte mis à jour",
"ToastAppriseUrlRequired": "Vous devez entrer une URL Apprise",
"ToastAuthorImageRemoveSuccess": "Image de l’auteur supprimée",
"ToastAuthorNotFound": "Auteur \"{0}\" non trouvé",
"ToastAuthorRemoveSuccess": "Auteur supprimé",
"ToastAuthorSearchNotFound": "Auteur non trouvé",
- "ToastAuthorUpdateFailed": "Échec de la mise à jour de l’auteur",
"ToastAuthorUpdateMerged": "Auteur fusionné",
"ToastAuthorUpdateSuccess": "Auteur mis à jour",
"ToastAuthorUpdateSuccessNoImageFound": "Auteur mis à jour (aucune image trouvée)",
@@ -834,7 +865,6 @@
"ToastBackupDeleteSuccess": "Sauvegarde supprimée",
"ToastBackupInvalidMaxKeep": "Nombre de sauvegardes à conserver invalide",
"ToastBackupInvalidMaxSize": "Taille maximale de sauvegarde invalide",
- "ToastBackupPathUpdateFailed": "Échec de la mise à jour du chemin de sauvegarde",
"ToastBackupRestoreFailed": "Échec de la restauration de sauvegarde",
"ToastBackupUploadFailed": "Échec du téléversement de sauvegarde",
"ToastBackupUploadSuccess": "Sauvegarde téléversée",
@@ -845,7 +875,6 @@
"ToastBookmarkCreateFailed": "Échec de la création de signet",
"ToastBookmarkCreateSuccess": "Signet ajouté",
"ToastBookmarkRemoveSuccess": "Signet supprimé",
- "ToastBookmarkUpdateFailed": "Échec de la mise à jour de signet",
"ToastBookmarkUpdateSuccess": "Signet mis à jour",
"ToastCachePurgeFailed": "Échec de la purge du cache",
"ToastCachePurgeSuccess": "Cache purgé avec succès",
@@ -856,7 +885,6 @@
"ToastCollectionItemsAddSuccess": "Ajout de(s) élément(s) à la collection réussi",
"ToastCollectionItemsRemoveSuccess": "Élément(s) supprimé(s) de la collection",
"ToastCollectionRemoveSuccess": "Collection supprimée",
- "ToastCollectionUpdateFailed": "Échec de la mise à jour de la collection",
"ToastCollectionUpdateSuccess": "Collection mise à jour",
"ToastCoverUpdateFailed": "Échec de la mise à jour de la couverture",
"ToastDeleteFileFailed": "Échec de la suppression du fichier",
@@ -865,8 +893,6 @@
"ToastDeviceNameAlreadyExists": "Un appareil de lecture avec ce nom existe déjà",
"ToastDeviceTestEmailFailed": "Échec de l’envoi du courriel de test",
"ToastDeviceTestEmailSuccess": "Courriel de test envoyé",
- "ToastDeviceUpdateFailed": "Échec de la mise à jour",
- "ToastEmailSettingsUpdateFailed": "Échec de la mise à jour des paramètres de messagerie",
"ToastEmailSettingsUpdateSuccess": "Paramètres de messagerie mis à jour",
"ToastEncodeCancelFailed": "Échec de l’annulation de l’encodage",
"ToastEncodeCancelSucces": "Encodage annulé",
@@ -875,21 +901,16 @@
"ToastErrorCannotShare": "Impossible de partager nativement sur cet appareil",
"ToastFailedToLoadData": "Échec du chargement des données",
"ToastFailedToShare": "Échec du partage",
- "ToastFailedToUpdateAccount": "Échec de la mise à jour du compte",
- "ToastFailedToUpdateUser": "La mise a jour de l'utilisateur à échouée",
"ToastInvalidImageUrl": "URL de l'image invalide",
"ToastInvalidUrl": "URL invalide",
- "ToastItemCoverUpdateFailed": "Échec de la mise à jour de la couverture de l’élément",
"ToastItemCoverUpdateSuccess": "Couverture mise à jour",
"ToastItemDeletedFailed": "La suppression de l'élément à échouée",
"ToastItemDeletedSuccess": "Élément supprimé",
- "ToastItemDetailsUpdateFailed": "Échec de la mise à jour des détails de l’élément",
"ToastItemDetailsUpdateSuccess": "Détails de l’élément mis à jour",
"ToastItemMarkedAsFinishedFailed": "Échec de l’annotation terminée",
"ToastItemMarkedAsFinishedSuccess": "Article marqué comme terminé",
"ToastItemMarkedAsNotFinishedFailed": "Échec de l’annotation non-terminée",
"ToastItemMarkedAsNotFinishedSuccess": "Article marqué comme non-terminé",
- "ToastItemUpdateFailed": "La mise a jour de l’élément à échoué",
"ToastItemUpdateSuccess": "Élément mis a jour",
"ToastLibraryCreateFailed": "Échec de la création de bibliothèque",
"ToastLibraryCreateSuccess": "Bibliothèque « {0} » créée",
@@ -897,7 +918,6 @@
"ToastLibraryDeleteSuccess": "Bibliothèque supprimée",
"ToastLibraryScanFailedToStart": "Échec du démarrage de l’analyse",
"ToastLibraryScanStarted": "Analyse de la bibliothèque démarrée",
- "ToastLibraryUpdateFailed": "Échec de la mise à jour de la bibliothèque",
"ToastLibraryUpdateSuccess": "Bibliothèque « {0} » mise à jour",
"ToastNameEmailRequired": "Le nom et le courriel sont requis",
"ToastNameRequired": "Le nom est requis",
@@ -912,16 +932,13 @@
"ToastNotificationDeleteFailed": "La suppression de la notification à échouée",
"ToastNotificationFailedMaximum": "Le nombre maximum de tentatives échouées doit être >= 0",
"ToastNotificationQueueMaximum": "Le nombre de notification maximum doit être >= 0",
- "ToastNotificationSettingsUpdateFailed": "La mise a jour des paramètres de notification a échouée",
"ToastNotificationSettingsUpdateSuccess": "Paramètres de notification mis à jour",
"ToastNotificationTestTriggerFailed": "L'envoi de la notification de test à échoué",
"ToastNotificationTestTriggerSuccess": "Notification de test déclenchée",
- "ToastNotificationUpdateFailed": "Échec de la mise à jour de la notification",
"ToastNotificationUpdateSuccess": "Notification mise à jour",
"ToastPlaylistCreateFailed": "Échec de la création de la liste de lecture",
"ToastPlaylistCreateSuccess": "Liste de lecture créée",
"ToastPlaylistRemoveSuccess": "Liste de lecture supprimée",
- "ToastPlaylistUpdateFailed": "Échec de la mise à jour de la liste de lecture",
"ToastPlaylistUpdateSuccess": "Liste de lecture mise à jour",
"ToastPodcastCreateFailed": "Échec de la création du podcast",
"ToastPodcastCreateSuccess": "Podcast créé avec succès",
@@ -950,7 +967,6 @@
"ToastSendEbookToDeviceSuccess": "Livre numérique envoyé à l’appareil : {0}",
"ToastSeriesUpdateFailed": "Échec de la mise à jour de la série",
"ToastSeriesUpdateSuccess": "Mise à jour de la série réussie",
- "ToastServerSettingsUpdateFailed": "Échec de la mise à jour des paramètres du serveur",
"ToastServerSettingsUpdateSuccess": "Mise à jour des paramètres du serveur",
"ToastSessionCloseFailed": "Échec de la fermeture de la session",
"ToastSessionDeleteFailed": "Échec de la suppression de session",
@@ -961,7 +977,6 @@
"ToastSocketDisconnected": "WebSocket déconnecté",
"ToastSocketFailedToConnect": "Échec de la connexion WebSocket",
"ToastSortingPrefixesEmptyError": "Doit avoir au moins 1 préfixe de tri",
- "ToastSortingPrefixesUpdateFailed": "Échec de la mise à jour des préfixes de tri",
"ToastSortingPrefixesUpdateSuccess": "Mise à jour des préfixes de tri ({0} élément)",
"ToastTitleRequired": "Le titre est requis",
"ToastUnknownError": "Erreur inconnue",
diff --git a/client/strings/he.json b/client/strings/he.json
index 0b584f916..9f7822b9b 100644
--- a/client/strings/he.json
+++ b/client/strings/he.json
@@ -701,10 +701,8 @@
"PlaceholderNewPlaylist": "שם רשימת השמעה חדשה",
"PlaceholderSearch": "חיפוש..",
"PlaceholderSearchEpisode": "חיפוש פרק..",
- "ToastAccountUpdateFailed": "עדכון חשבון נכשל",
"ToastAccountUpdateSuccess": "חשבון עודכן בהצלחה",
"ToastAuthorImageRemoveSuccess": "תמונת המחבר הוסרה בהצלחה",
- "ToastAuthorUpdateFailed": "עדכון המחבר נכשל",
"ToastAuthorUpdateMerged": "המחבר מוזג",
"ToastAuthorUpdateSuccess": "המחבר עודכן בהצלחה",
"ToastAuthorUpdateSuccessNoImageFound": "המחבר עודכן (תמונה לא נמצאה)",
@@ -720,17 +718,13 @@
"ToastBookmarkCreateFailed": "יצירת סימניה נכשלה",
"ToastBookmarkCreateSuccess": "הסימניה נוספה בהצלחה",
"ToastBookmarkRemoveSuccess": "הסימניה הוסרה בהצלחה",
- "ToastBookmarkUpdateFailed": "עדכון הסימניה נכשל",
"ToastBookmarkUpdateSuccess": "הסימניה עודכנה בהצלחה",
"ToastChaptersHaveErrors": "פרקים מכילים שגיאות",
"ToastChaptersMustHaveTitles": "פרקים חייבים לכלול כותרות",
"ToastCollectionItemsRemoveSuccess": "הפריט(ים) הוסרו מהאוסף בהצלחה",
"ToastCollectionRemoveSuccess": "האוסף הוסר בהצלחה",
- "ToastCollectionUpdateFailed": "עדכון האוסף נכשל",
"ToastCollectionUpdateSuccess": "האוסף עודכן בהצלחה",
- "ToastItemCoverUpdateFailed": "עדכון כריכת הפריט נכשל",
"ToastItemCoverUpdateSuccess": "כריכת הפריט עודכנה בהצלחה",
- "ToastItemDetailsUpdateFailed": "עדכון פרטי הפריט נכשל",
"ToastItemDetailsUpdateSuccess": "פרטי הפריט עודכנו בהצלחה",
"ToastItemMarkedAsFinishedFailed": "סימון כפריט כהושלם נכשל",
"ToastItemMarkedAsFinishedSuccess": "הפריט סומן כהושלם בהצלחה",
@@ -742,12 +736,10 @@
"ToastLibraryDeleteSuccess": "הספרייה נמחקה בהצלחה",
"ToastLibraryScanFailedToStart": "הפעלת הסריקה נכשלה",
"ToastLibraryScanStarted": "הסריקה של הספרייה החלה",
- "ToastLibraryUpdateFailed": "עדכון הספרייה נכשל",
"ToastLibraryUpdateSuccess": "הספרייה \"{0}\" עודכנה בהצלחה",
"ToastPlaylistCreateFailed": "יצירת רשימת השמעה נכשלה",
"ToastPlaylistCreateSuccess": "רשימת השמעה נוצרה בהצלחה",
"ToastPlaylistRemoveSuccess": "רשימת השמעה הוסרה בהצלחה",
- "ToastPlaylistUpdateFailed": "עדכון רשימת השמעה נכשל",
"ToastPlaylistUpdateSuccess": "רשימת השמעה עודכנה בהצלחה",
"ToastPodcastCreateFailed": "יצירת הפודקאסט נכשלה",
"ToastPodcastCreateSuccess": "הפודקאסט נוצר בהצלחה",
@@ -759,7 +751,6 @@
"ToastSendEbookToDeviceSuccess": "הספר נשלח אל המכשיר \"{0}\"",
"ToastSeriesUpdateFailed": "עדכון הסדרה נכשל",
"ToastSeriesUpdateSuccess": "הסדרה עודכנה בהצלחה",
- "ToastServerSettingsUpdateFailed": "כשל בעדכון הגדרות שרת",
"ToastServerSettingsUpdateSuccess": "הגדרות שרת עודכנו בהצלחה",
"ToastSessionDeleteFailed": "מחיקת הפעולה נכשלה",
"ToastSessionDeleteSuccess": "הפעולה נמחקה בהצלחה",
diff --git a/client/strings/hr.json b/client/strings/hr.json
index 0b6fc2285..9d0ed0d56 100644
--- a/client/strings/hr.json
+++ b/client/strings/hr.json
@@ -56,6 +56,7 @@
"ButtonOpenManager": "Otvori Upravitelja",
"ButtonPause": "Pauziraj",
"ButtonPlay": "Reproduciraj",
+ "ButtonPlayAll": "Reproduciraj sve",
"ButtonPlaying": "Izvodi se",
"ButtonPlaylists": "Popisi za izvođenje",
"ButtonPrevious": "Prethodno",
@@ -96,7 +97,7 @@
"ButtonStartM4BEncode": "Pokreni M4B kodiranje",
"ButtonStartMetadataEmbed": "Pokreni ugradnju meta-podataka",
"ButtonStats": "Statistika",
- "ButtonSubmit": "Podnesi",
+ "ButtonSubmit": "Pošalji",
"ButtonTest": "Test",
"ButtonUnlinkOpenId": "Prekini vezu s OpenID-jem",
"ButtonUpload": "Učitaj",
@@ -266,7 +267,7 @@
"LabelContinueSeries": "Nastavi serijal",
"LabelCover": "Naslovnica",
"LabelCoverImageURL": "URL naslovnice",
- "LabelCreatedAt": "Stvoreno",
+ "LabelCreatedAt": "Izrađen",
"LabelCronExpression": "Cron izraz",
"LabelCurrent": "Trenutan",
"LabelCurrently": "Trenutno:",
@@ -368,7 +369,7 @@
"LabelLanguages": "Jezici",
"LabelLastBookAdded": "Zadnja dodana knjiga",
"LabelLastBookUpdated": "Zadnja ažurirana knjiga",
- "LabelLastSeen": "Zadnje pogledano",
+ "LabelLastSeen": "Zadnji puta viđen",
"LabelLastTime": "Zadnji puta",
"LabelLastUpdate": "Zadnje ažuriranje",
"LabelLayout": "Prikaz",
@@ -411,7 +412,7 @@
"LabelNew": "Novo",
"LabelNewPassword": "Nova zaporka",
"LabelNewestAuthors": "Najnoviji autori",
- "LabelNewestEpisodes": "Najnoviji nastavci",
+ "LabelNewestEpisodes": "Najnovije epizode",
"LabelNextBackupDate": "Sljedeće izrada sigurnosne kopije",
"LabelNextScheduledRun": "Sljedeće zakazano izvođenje",
"LabelNoCustomMetadataProviders": "Nema prilagođenih pružatelja meta-podataka",
@@ -654,7 +655,7 @@
"MessageChapterErrorFirstNotZero": "Prvo poglavlje mora započeti u 0",
"MessageChapterErrorStartGteDuration": "Netočno vrijeme početka, mora biti manje od trajanja zvučne knjige",
"MessageChapterErrorStartLtPrev": "Netočno vrijeme početka, mora biti veće ili jednako vremenu početka prethodnog poglavlja",
- "MessageChapterStartIsAfter": "Početak poglavlja je nakon kraja zvučne knjige.",
+ "MessageChapterStartIsAfter": "Početak poglavlja je nakon kraja zvučne knjige",
"MessageCheckingCron": "Provjeravam cron...",
"MessageConfirmCloseFeed": "Sigurno želite zatvoriti ovaj izvor?",
"MessageConfirmDeleteBackup": "Jeste li sigurni da želite obrisati backup za {0}?",
@@ -776,6 +777,38 @@
"MessageShareExpiresIn": "Istječe za {0}",
"MessageShareURLWillBe": "URL za dijeljenje bit će
{0}",
"MessageStartPlaybackAtTime": "Pokreni reprodukciju za \"{0}\" na {1}?",
+ "MessageTaskAudioFileNotWritable": "U zvučnu datoteku \"{0}\" nije moguće pisati",
+ "MessageTaskCanceledByUser": "Korisnik je otkazao izvršavanje zadatka",
+ "MessageTaskDownloadingEpisodeDescription": "Preuzimanje nastavka \"{0}\"",
+ "MessageTaskEmbeddingMetadata": "Ugrađivanje meta-podataka",
+ "MessageTaskEmbeddingMetadataDescription": "Ugrađivanje meta-podataka u zvučnu knjigu \"{0}\"",
+ "MessageTaskEncodingM4b": "Kodiranje M4B datoteke",
+ "MessageTaskEncodingM4bDescription": "Kodiranje zvučne knjige \"{0}\" u jedinstvenu m4b datoteku",
+ "MessageTaskFailed": "Nije uspjelo",
+ "MessageTaskFailedToBackupAudioFile": "Izrada sigurnosne kopije zvučne datoteke \"{0}\" nije uspjela",
+ "MessageTaskFailedToCreateCacheDirectory": "Izrada mape predmemorije nije uspjela",
+ "MessageTaskFailedToEmbedMetadataInFile": "Ugradnja meta-podataka u datoteku \"{0}\" nije uspjela",
+ "MessageTaskFailedToMergeAudioFiles": "Spajanje zvučnih datoteka nije uspjelo",
+ "MessageTaskFailedToMoveM4bFile": "Premještanje m4b datoteke nije uspjelo",
+ "MessageTaskFailedToWriteMetadataFile": "Pisanje datoteke s meta-podatcima nije uspjelo",
+ "MessageTaskMatchingBooksInLibrary": "Prepoznavanje knjiga u knjižnici \"{0}\"",
+ "MessageTaskNoFilesToScan": "Nema datoteka za skeniranje",
+ "MessageTaskOpmlImport": "Uvoz OPML-a",
+ "MessageTaskOpmlImportDescription": "Stvaram podcaste od {0} RSS izvora",
+ "MessageTaskOpmlImportFeed": "Uvoz OPML izvora",
+ "MessageTaskOpmlImportFeedDescription": "Uvoz RSS izvora \"{0}\"",
+ "MessageTaskOpmlImportFeedFailed": "Izvor podcasta nije dohvaćen",
+ "MessageTaskOpmlImportFeedPodcastDescription": "Stvaranje podcasta \"{0}\"",
+ "MessageTaskOpmlImportFeedPodcastExists": "Podcast već postoji u putanji",
+ "MessageTaskOpmlImportFeedPodcastFailed": "Stvaranje podcasta nije uspjelo",
+ "MessageTaskOpmlImportFinished": "Dodano {0} podcasta",
+ "MessageTaskScanItemsAdded": "{0} dodan(o)",
+ "MessageTaskScanItemsMissing": "{0} nedostaje",
+ "MessageTaskScanItemsUpdated": "{0} ažurirano",
+ "MessageTaskScanNoChangesNeeded": "Nisu potrebne izmjene",
+ "MessageTaskScanningFileChanges": "Skeniranje izmijenjenih datoteka u \"{0}\"",
+ "MessageTaskScanningLibrary": "Skeniranje knjižnice \"{0}\"",
+ "MessageTaskTargetDirectoryNotWritable": "U odredišnu mapu nije moguće pisati",
"MessageThinking": "Razmišljam...",
"MessageUploaderItemFailed": "Učitavanje nije uspjelo",
"MessageUploaderItemSuccess": "Uspješno učitano!",
@@ -816,14 +849,12 @@
"StatsTopNarrators": "NAJPOPULARNIJI PRIPOVJEDAČI",
"StatsTotalDuration": "S ukupnim trajanjem od…",
"StatsYearInReview": "PREGLED GODINE",
- "ToastAccountUpdateFailed": "Ažuriranje računa nije uspjelo",
"ToastAccountUpdateSuccess": "Račun ažuriran",
"ToastAppriseUrlRequired": "Obavezno upisati Apprise URL",
"ToastAuthorImageRemoveSuccess": "Slika autora uklonjena",
"ToastAuthorNotFound": "Autor \"{0}\" nije pronađen",
"ToastAuthorRemoveSuccess": "Autor uklonjen",
"ToastAuthorSearchNotFound": "Autor nije pronađen",
- "ToastAuthorUpdateFailed": "Ažuriranje autora nije uspjelo",
"ToastAuthorUpdateMerged": "Autor pripojen",
"ToastAuthorUpdateSuccess": "Autor ažuriran",
"ToastAuthorUpdateSuccessNoImageFound": "Autor ažuriran (slika nije pronađena)",
@@ -834,7 +865,6 @@
"ToastBackupDeleteSuccess": "Sigurnosna kopija izbrisana",
"ToastBackupInvalidMaxKeep": "Neispravan broj sigurnosnih kopija za čuvanje",
"ToastBackupInvalidMaxSize": "Neispravna najveća veličina sigurnosne kopije",
- "ToastBackupPathUpdateFailed": "Ažuriranje putanje za sigurnosne kopije nije uspjelo",
"ToastBackupRestoreFailed": "Vraćanje sigurnosne kopije nije uspjelo",
"ToastBackupUploadFailed": "Učitavanje sigurnosne kopije nije uspjelo",
"ToastBackupUploadSuccess": "Sigurnosna kopija učitana",
@@ -845,7 +875,6 @@
"ToastBookmarkCreateFailed": "Izrada knjižne oznake nije uspjela",
"ToastBookmarkCreateSuccess": "Knjižna oznaka dodana",
"ToastBookmarkRemoveSuccess": "Knjižna oznaka uklonjena",
- "ToastBookmarkUpdateFailed": "Ažuriranje knjižne oznake nije uspjelo",
"ToastBookmarkUpdateSuccess": "Knjižna oznaka ažurirana",
"ToastCachePurgeFailed": "Čišćenje predmemorije nije uspjelo",
"ToastCachePurgeSuccess": "Predmemorija uspješno očišćena",
@@ -856,7 +885,6 @@
"ToastCollectionItemsAddSuccess": "Uspješno dodavanje stavki u zbirku",
"ToastCollectionItemsRemoveSuccess": "Stavke izbrisane iz zbirke",
"ToastCollectionRemoveSuccess": "Zbirka izbrisana",
- "ToastCollectionUpdateFailed": "Ažuriranje zbirke nije uspjelo",
"ToastCollectionUpdateSuccess": "Zbirka ažurirana",
"ToastCoverUpdateFailed": "Ažuriranje naslovnice nije uspjelo",
"ToastDeleteFileFailed": "Brisanje datoteke nije uspjelo",
@@ -865,8 +893,6 @@
"ToastDeviceNameAlreadyExists": "E-čitač s tim nazivom već postoji",
"ToastDeviceTestEmailFailed": "Slanje probne poruke e-pošte nije uspjelo",
"ToastDeviceTestEmailSuccess": "Probna poruka e-pošte poslana",
- "ToastDeviceUpdateFailed": "Ažuriranje uređaja nije uspjelo",
- "ToastEmailSettingsUpdateFailed": "Ažuriranje postavki e-pošte nije uspjelo",
"ToastEmailSettingsUpdateSuccess": "Postavke e-pošte ažurirane",
"ToastEncodeCancelFailed": "Kodiranje nije uspješno otkazano",
"ToastEncodeCancelSucces": "Kodiranje otkazano",
@@ -875,21 +901,17 @@
"ToastErrorCannotShare": "Dijeljenje na ovaj uređaj nije moguće",
"ToastFailedToLoadData": "Učitavanje podataka nije uspjelo",
"ToastFailedToShare": "Dijeljenje nije uspjelo",
- "ToastFailedToUpdateAccount": "Ažuriranje računa nije uspjelo",
- "ToastFailedToUpdateUser": "Ažuriranje korisnika nije uspjelo",
+ "ToastFailedToUpdate": "Ažuriranje nije uspjelo",
"ToastInvalidImageUrl": "Neispravan URL slike",
"ToastInvalidUrl": "Neispravan URL",
- "ToastItemCoverUpdateFailed": "Ažuriranje naslovnice stavke nije uspjelo",
"ToastItemCoverUpdateSuccess": "Naslovnica stavke ažurirana",
"ToastItemDeletedFailed": "Brisanje stavke nije uspjelo",
"ToastItemDeletedSuccess": "Stavka je izbrisana",
- "ToastItemDetailsUpdateFailed": "Ažuriranje podataka stavke nije uspjelo",
"ToastItemDetailsUpdateSuccess": "Pojedinosti stavke su ažurirane",
"ToastItemMarkedAsFinishedFailed": "Označavanje kao Dovršeno nije uspjelo",
"ToastItemMarkedAsFinishedSuccess": "Stavka označena kao dovršena",
"ToastItemMarkedAsNotFinishedFailed": "Označavanje kao Nije dovršeno nije uspjelo",
"ToastItemMarkedAsNotFinishedSuccess": "Stavka označena kao nedovršena",
- "ToastItemUpdateFailed": "Ažuriranje stavke nije uspjelo",
"ToastItemUpdateSuccess": "Stavka ažurirana",
"ToastLibraryCreateFailed": "Stvaranje knjižnice nije uspjelo",
"ToastLibraryCreateSuccess": "Knjižnica \"{0}\" stvorena",
@@ -897,7 +919,6 @@
"ToastLibraryDeleteSuccess": "Knjižnica izbrisana",
"ToastLibraryScanFailedToStart": "Skeniranje nije uspjelo",
"ToastLibraryScanStarted": "Skeniranje knjižnice započelo",
- "ToastLibraryUpdateFailed": "Ažuriranje knjižnice nije uspjelo",
"ToastLibraryUpdateSuccess": "Knjižnica \"{0}\" ažurirana",
"ToastNameEmailRequired": "Ime i adresa e-pošte su obavezni",
"ToastNameRequired": "Ime je obavezno",
@@ -912,16 +933,13 @@
"ToastNotificationDeleteFailed": "Brisanje obavijesti nije uspjelo",
"ToastNotificationFailedMaximum": "Najveći broj neuspješnih pokušaja mora biti >= 0",
"ToastNotificationQueueMaximum": "Najveći broj obavijesti u redu mora biti >= 0",
- "ToastNotificationSettingsUpdateFailed": "Ažuriranje postavki obavijesti nije uspjelo",
"ToastNotificationSettingsUpdateSuccess": "Postavke obavijesti ažurirane",
"ToastNotificationTestTriggerFailed": "Okidanje probne obavijesti nije uspjelo",
"ToastNotificationTestTriggerSuccess": "Okinuta je probna obavijest",
- "ToastNotificationUpdateFailed": "Ažuriranje obavijesti nije uspjelo",
"ToastNotificationUpdateSuccess": "Obavijest ažurirana",
"ToastPlaylistCreateFailed": "Popis za izvođenje nije izrađen",
"ToastPlaylistCreateSuccess": "Popis za izvođenje izrađen",
"ToastPlaylistRemoveSuccess": "Popis za izvođenje uklonjen",
- "ToastPlaylistUpdateFailed": "Ažuriranje popisa za izvođenje nije uspjelo",
"ToastPlaylistUpdateSuccess": "Popis za izvođenje ažuriran",
"ToastPodcastCreateFailed": "Podcast nije izrađen",
"ToastPodcastCreateSuccess": "Podcast uspješno izrađen",
@@ -950,7 +968,6 @@
"ToastSendEbookToDeviceSuccess": "E-knjiga poslana uređaju \"{0}\"",
"ToastSeriesUpdateFailed": "Ažuriranje serijala nije uspjelo",
"ToastSeriesUpdateSuccess": "Serijal uspješno ažuriran",
- "ToastServerSettingsUpdateFailed": "Ažuriranje postavki poslužitelja nije uspjelo",
"ToastServerSettingsUpdateSuccess": "Postavke poslužitelja ažurirane",
"ToastSessionCloseFailed": "Zatvaranje sesije nije uspjelo",
"ToastSessionDeleteFailed": "Neuspješno brisanje serije",
@@ -961,7 +978,6 @@
"ToastSocketDisconnected": "Veza sa socketom je prekinuta",
"ToastSocketFailedToConnect": "Priključivanje na socket nije uspjelo",
"ToastSortingPrefixesEmptyError": "Mora imati najmanje jedan prefiks za sortiranje",
- "ToastSortingPrefixesUpdateFailed": "Ažuriranje prefiksa za sortiranje nije uspjelo",
"ToastSortingPrefixesUpdateSuccess": "Prefiksi za sortiranje ažurirani ({0} stavki)",
"ToastTitleRequired": "Naslov je obavezan",
"ToastUnknownError": "Nepoznata pogreška",
diff --git a/client/strings/hu.json b/client/strings/hu.json
index a50fce1b3..79852a9cd 100644
--- a/client/strings/hu.json
+++ b/client/strings/hu.json
@@ -9,6 +9,7 @@
"ButtonApply": "Alkalmaz",
"ButtonApplyChapters": "Fejezetek alkalmazása",
"ButtonAuthors": "Szerzők",
+ "ButtonBack": "Vissza",
"ButtonBrowseForFolder": "Mappa keresése",
"ButtonCancel": "Mégse",
"ButtonCancelEncode": "Kódolás megszakítása",
@@ -18,7 +19,8 @@
"ButtonChooseFiles": "Fájlok kiválasztása",
"ButtonClearFilter": "Szűrő törlése",
"ButtonCloseFeed": "Hírcsatorna bezárása",
- "ButtonCollections": "Gyűjtemény",
+ "ButtonCloseSession": "Nyitott munkamenet bezárása",
+ "ButtonCollections": "Gyűjtemények",
"ButtonConfigureScanner": "Szkenner konfigurálása",
"ButtonCreate": "Létrehozás",
"ButtonCreateBackup": "Biztonsági másolat készítése",
@@ -27,6 +29,9 @@
"ButtonEdit": "Szerkesztés",
"ButtonEditChapters": "Fejezetek szerkesztése",
"ButtonEditPodcast": "Podcast szerkesztése",
+ "ButtonEnable": "Engedélyezés",
+ "ButtonFireAndFail": "Küldés és összeomlás",
+ "ButtonFireOnTest": "onTest esemény küldése",
"ButtonForceReScan": "Újraszkennelés kényszerítése",
"ButtonFullPath": "Teljes útvonal",
"ButtonHide": "Elrejtés",
@@ -43,22 +48,31 @@
"ButtonMatchAllAuthors": "Minden szerző egyeztetése",
"ButtonMatchBooks": "Könyvek egyeztetése",
"ButtonNevermind": "Mindegy",
+ "ButtonNext": "Következő",
"ButtonNextChapter": "Következő fejezet",
+ "ButtonNextItemInQueue": "Következő elem a sorban",
"ButtonOk": "Oké",
"ButtonOpenFeed": "Hírcsatorna megnyitása",
"ButtonOpenManager": "Kezelő megnyitása",
"ButtonPause": "Szünet",
"ButtonPlay": "Lejátszás",
+ "ButtonPlayAll": "Összes lejátszása",
"ButtonPlaying": "Lejátszás folyamatban",
"ButtonPlaylists": "Lejátszási listák",
+ "ButtonPrevious": "Előző",
"ButtonPreviousChapter": "Előző fejezet",
+ "ButtonProbeAudioFile": "Hangfájl vizsgálata",
"ButtonPurgeAllCache": "Összes gyorsítótár törlése",
"ButtonPurgeItemsCache": "Elemek gyorsítótárának törlése",
"ButtonQueueAddItem": "Hozzáadás a sorhoz",
"ButtonQueueRemoveItem": "Eltávolítás a sorból",
+ "ButtonQuickEmbedMetadata": "Metaadat gyors beágyazása",
"ButtonQuickMatch": "Gyors egyeztetés",
"ButtonReScan": "Újraszkennelés",
"ButtonRead": "Olvasás",
+ "ButtonReadLess": "Kevesebb mutatása",
+ "ButtonReadMore": "Mutass többet",
+ "ButtonRefresh": "Frissítés",
"ButtonRemove": "Eltávolítás",
"ButtonRemoveAll": "Összes eltávolítása",
"ButtonRemoveAllLibraryItems": "Összes könyvtárelem eltávolítása",
@@ -77,12 +91,15 @@
"ButtonSelectFolderPath": "Mappa útvonalának kiválasztása",
"ButtonSeries": "Sorozatok",
"ButtonSetChaptersFromTracks": "Fejezetek beállítása sávokból",
+ "ButtonShare": "Megosztás",
"ButtonShiftTimes": "Idők eltolása",
"ButtonShow": "Megjelenítés",
"ButtonStartM4BEncode": "M4B kódolás indítása",
"ButtonStartMetadataEmbed": "Metaadatok beágyazásának indítása",
+ "ButtonStats": "Statisztikák",
"ButtonSubmit": "Beküldés",
"ButtonTest": "Teszt",
+ "ButtonUnlinkOpenId": "OpenID szétkapcsolása",
"ButtonUpload": "Feltöltés",
"ButtonUploadBackup": "Biztonsági másolat feltöltése",
"ButtonUploadCover": "Borító feltöltése",
@@ -95,6 +112,7 @@
"ErrorUploadFetchMetadataNoResults": "Nem sikerült a metaadatok lekérése - próbálja meg frissíteni a címet és/vagy a szerzőt",
"ErrorUploadLacksTitle": "Cím szükséges",
"HeaderAccount": "Fiók",
+ "HeaderAddCustomMetadataProvider": "Egyedi metaadat szolgáltató hozzáadása",
"HeaderAdvanced": "Haladó",
"HeaderAppriseNotificationSettings": "Apprise értesítési beállítások",
"HeaderAudioTracks": "Audiósávok",
@@ -108,6 +126,7 @@
"HeaderCollectionItems": "Gyűjtemény elemek",
"HeaderCover": "Borító",
"HeaderCurrentDownloads": "Jelenlegi letöltések",
+ "HeaderCustomMessageOnLogin": "Egyedi üzenet bejelentkezéskor",
"HeaderCustomMetadataProviders": "Egyéni metaadat-szolgáltatók",
"HeaderDetails": "Részletek",
"HeaderDownloadQueue": "Letöltési sor",
@@ -139,6 +158,8 @@
"HeaderMetadataToEmbed": "Beágyazandó metaadatok",
"HeaderNewAccount": "Új fiók",
"HeaderNewLibrary": "Új könyvtár",
+ "HeaderNotificationCreate": "Értesítés készítése",
+ "HeaderNotificationUpdate": "Értesítés frissítése",
"HeaderNotifications": "Értesítések",
"HeaderOpenIDConnectAuthentication": "OpenID Connect hitelesítés",
"HeaderOpenRSSFeed": "RSS hírcsatorna megnyitása",
@@ -146,12 +167,13 @@
"HeaderPasswordAuthentication": "Jelszó hitelesítés",
"HeaderPermissions": "Engedélyek",
"HeaderPlayerQueue": "Lejátszó sor",
+ "HeaderPlayerSettings": "Lejátszó beállításai",
"HeaderPlaylist": "Lejátszási lista",
"HeaderPlaylistItems": "Lejátszási lista elemek",
"HeaderPodcastsToAdd": "Hozzáadandó podcastok",
"HeaderPreviewCover": "Borító előnézete",
"HeaderRSSFeedGeneral": "RSS részletek",
- "HeaderRSSFeedIsOpen": "RSS hírcsatorna nyitva",
+ "HeaderRSSFeedIsOpen": "RSS hírcsatorna nyitva van",
"HeaderRSSFeeds": "RSS hírcsatornák",
"HeaderRemoveEpisode": "Epizód eltávolítása",
"HeaderRemoveEpisodes": "{0} epizód eltávolítása",
@@ -168,7 +190,7 @@
"HeaderSleepTimer": "Alvásidőzítő",
"HeaderStatsLargestItems": "Legnagyobb elemek",
"HeaderStatsLongestItems": "Leghosszabb elemek (órákban)",
- "HeaderStatsMinutesListeningChart": "Hallgatási percek (az utolsó 7 napban)",
+ "HeaderStatsMinutesListeningChart": "Hallgatási grafikon percekben (az elmúlt 7 napból)",
"HeaderStatsRecentSessions": "Legutóbbi munkamenetek",
"HeaderStatsTop10Authors": "Top 10 szerzők",
"HeaderStatsTop5Genres": "Top 5 műfajok",
@@ -179,9 +201,14 @@
"HeaderUpdateDetails": "Részletek frissítése",
"HeaderUpdateLibrary": "Könyvtár frissítése",
"HeaderUsers": "Felhasználók",
+ "HeaderYearReview": "{0} év áttekintése",
"HeaderYourStats": "Saját statisztikák",
"LabelAbridged": "Tömörített",
+ "LabelAbridgedChecked": "Rövidített (ellenőrizve)",
+ "LabelAbridgedUnchecked": "Teljes (nem ellenőrzött)",
+ "LabelAccessibleBy": "Hozzáférhető",
"LabelAccountType": "Fióktípus",
+ "LabelAccountTypeAdmin": "Adminisztrátor",
"LabelAccountTypeGuest": "Vendég",
"LabelAccountTypeUser": "Felhasználó",
"LabelActivity": "Tevékenység",
@@ -190,8 +217,9 @@
"LabelAddToPlaylist": "Hozzáadás a lejátszási listához",
"LabelAddToPlaylistBatch": "{0} elem hozzáadása a lejátszási listához",
"LabelAddedAt": "Hozzáadás ideje",
+ "LabelAddedDate": "{0} Hozzáadva",
"LabelAdminUsersOnly": "Csak admin felhasználók",
- "LabelAll": "Minden",
+ "LabelAll": "Összes",
"LabelAllUsers": "Minden felhasználó",
"LabelAllUsersExcludingGuests": "Minden felhasználó, vendégek kivételével",
"LabelAllUsersIncludingGuests": "Minden felhasználó, beleértve a vendégeket is",
@@ -212,13 +240,14 @@
"LabelBackupLocation": "Biztonsági másolat helye",
"LabelBackupsEnableAutomaticBackups": "Automatikus biztonsági másolatok engedélyezése",
"LabelBackupsEnableAutomaticBackupsHelp": "Biztonsági másolatok mentése a /metadata/backups mappába",
- "LabelBackupsMaxBackupSize": "Maximális biztonsági másolat méret (GB-ban)",
+ "LabelBackupsMaxBackupSize": "Maximális biztonsági másolat méret (GB-ban) (0-tól végtelenig)",
"LabelBackupsMaxBackupSizeHelp": "A rossz konfiguráció elleni védelem érdekében a biztonsági másolatok meghiúsulnak, ha meghaladják a beállított méretet.",
"LabelBackupsNumberToKeep": "Megtartandó biztonsági másolatok száma",
"LabelBackupsNumberToKeepHelp": "Egyszerre csak 1 biztonsági másolat kerül eltávolításra, tehát ha már több biztonsági másolat van, mint ez a szám, akkor manuálisan kell eltávolítani őket.",
"LabelBitrate": "Bitráta",
"LabelBooks": "Könyvek",
"LabelButtonText": "Gomb szövege",
+ "LabelByAuthor": "{} által",
"LabelChangePassword": "Jelszó megváltoztatása",
"LabelChannels": "Csatornák",
"LabelChapterTitle": "Fejezet címe",
@@ -228,9 +257,10 @@
"LabelClosePlayer": "Lejátszó bezárása",
"LabelCodec": "Kodek",
"LabelCollapseSeries": "Sorozat összecsukása",
+ "LabelCollapseSubSeries": "Alszéria összecsukása",
"LabelCollection": "Gyűjtemény",
"LabelCollections": "Gyűjtemények",
- "LabelComplete": "Teljes",
+ "LabelComplete": "Kész",
"LabelConfirmPassword": "Jelszó megerősítése",
"LabelContinueListening": "Hallgatás folytatása",
"LabelContinueReading": "Olvasás folytatása",
@@ -243,6 +273,7 @@
"LabelCurrently": "Jelenleg:",
"LabelCustomCronExpression": "Egyéni Cron kifejezés:",
"LabelDatetime": "Dátumidő",
+ "LabelDays": "Napok",
"LabelDeleteFromFileSystemCheckbox": "Törlés a fájlrendszerről (ne jelölje be, ha csak az adatbázisból szeretné eltávolítani)",
"LabelDescription": "Leírás",
"LabelDeselectAll": "Minden kijelölés megszüntetése",
@@ -256,27 +287,42 @@
"LabelDownload": "Letöltés",
"LabelDownloadNEpisodes": "{0} epizód letöltése",
"LabelDuration": "Időtartam",
+ "LabelDurationComparisonExactMatch": "(pontos egyezés)",
+ "LabelDurationComparisonLonger": "({0}-val hosszabb)",
+ "LabelDurationComparisonShorter": "({0}-val rövidebb)",
"LabelDurationFound": "Megtalált időtartam:",
"LabelEbook": "E-könyv",
"LabelEbooks": "E-könyvek",
"LabelEdit": "Szerkesztés",
"LabelEmail": "E-mail",
"LabelEmailSettingsFromAddress": "Feladó címe",
+ "LabelEmailSettingsRejectUnauthorized": "Nem hitelesített tanúsítványok elutasítása",
+ "LabelEmailSettingsRejectUnauthorizedHelp": "Az SSL tanúsítványok érvényesítésének letiltása biztonsági kockázatoknak, például man-in-the-middle támadásoknak teheti ki a kapcsolatot. Csak akkor tiltsd le ezt az opciót, ha tisztában vagy a következményekkel, és megbízol az e-mail szerverben, amelyhez csatlakozol.",
"LabelEmailSettingsSecure": "Biztonságos",
"LabelEmailSettingsSecureHelp": "Ha igaz, a kapcsolat TLS-t használ a szerverhez való csatlakozáskor. Ha hamis, akkor TLS-t használ, ha a szerver támogatja a STARTTLS kiterjesztést. A legtöbb esetben állítsa ezt az értéket igazra, ha a 465-ös portra csatlakozik. A 587-es vagy 25-ös port esetében tartsa hamis értéken. (a nodemailer.com/smtp/#authentication oldalról)",
"LabelEmailSettingsTestAddress": "Teszt cím",
"LabelEmbeddedCover": "Beágyazott borító",
"LabelEnable": "Engedélyezés",
"LabelEnd": "Vége",
+ "LabelEndOfChapter": "Fejezet vége",
"LabelEpisode": "Epizód",
"LabelEpisodeTitle": "Epizód címe",
"LabelEpisodeType": "Epizód típusa",
+ "LabelEpisodes": "Epizódok",
"LabelExample": "Példa",
+ "LabelExpandSeries": "Sorozat kinyitása",
+ "LabelExpandSubSeries": "Alsorozat kinyitása",
+ "LabelExplicit": "Explicit",
+ "LabelExplicitChecked": "Explicit (ellenőrizve)",
+ "LabelExplicitUnchecked": "Nem explicit (nem ellenőrzött)",
+ "LabelExportOPML": "OPML exportálása",
"LabelFeedURL": "Hírcsatorna URL",
"LabelFetchingMetadata": "Metaadatok lekérése",
"LabelFile": "Fájl",
"LabelFileBirthtime": "Fájl létrehozásának ideje",
+ "LabelFileBornDate": "Született {0}",
"LabelFileModified": "Fájl módosításának ideje",
+ "LabelFileModifiedDate": "Módosítva {0}",
"LabelFilename": "Fájlnév",
"LabelFilterByUser": "Szűrés felhasználó szerint",
"LabelFindEpisodes": "Epizódok keresése",
@@ -284,6 +330,7 @@
"LabelFolder": "Mappa",
"LabelFolders": "Mappák",
"LabelFontBold": "Félkövér",
+ "LabelFontBoldness": "Betű vastagság",
"LabelFontFamily": "Betűtípus család",
"LabelFontItalic": "Dőlt",
"LabelFontScale": "Betűméret skála",
@@ -294,9 +341,11 @@
"LabelHardDeleteFile": "Fájl végleges törlése",
"LabelHasEbook": "Van e-könyve",
"LabelHasSupplementaryEbook": "Van kiegészítő e-könyve",
+ "LabelHideSubtitles": "Alcím elrejtése",
"LabelHighestPriority": "Legmagasabb prioritás",
- "LabelHost": "Hoszt",
+ "LabelHost": "Házigazda",
"LabelHour": "Óra",
+ "LabelHours": "Órák",
"LabelIcon": "Ikon",
"LabelImageURLFromTheWeb": "Kép URL a weben",
"LabelInProgress": "Folyamatban",
@@ -313,8 +362,11 @@
"LabelIntervalEveryHour": "Minden órában",
"LabelInvert": "Megfordítás",
"LabelItem": "Elem",
+ "LabelJumpBackwardAmount": "Visszafelé ugrás mennyisége",
+ "LabelJumpForwardAmount": "Előre ugrás mennyisége",
"LabelLanguage": "Nyelv",
"LabelLanguageDefaultServer": "Szerver alapértelmezett nyelve",
+ "LabelLanguages": "Nyelvek",
"LabelLastBookAdded": "Utolsó hozzáadott könyv",
"LabelLastBookUpdated": "Utolsó frissített könyv",
"LabelLastSeen": "Utolsó látogatás",
@@ -326,11 +378,13 @@
"LabelLess": "Kevesebb",
"LabelLibrariesAccessibleToUser": "A felhasználó számára elérhető könyvtárak",
"LabelLibrary": "Könyvtár",
+ "LabelLibraryFilterSublistEmpty": "Nem {0}",
"LabelLibraryItem": "Könyvtári elem",
"LabelLibraryName": "Könyvtár neve",
"LabelLimit": "Korlát",
"LabelLineSpacing": "Sorköz",
"LabelListenAgain": "Újrahallgatás",
+ "LabelLogLevelDebug": "Hibakeresés",
"LabelLogLevelInfo": "Információ",
"LabelLogLevelWarn": "Figyelmeztetés",
"LabelLookForNewEpisodesAfterDate": "Új epizódok keresése ezen a dátum után",
@@ -344,7 +398,10 @@
"LabelMetadataOrderOfPrecedenceDescription": "A magasabb prioritású metaadat-források felülírják az alacsonyabb prioritásúakat",
"LabelMetadataProvider": "Metaadat-szolgáltató",
"LabelMinute": "Perc",
+ "LabelMinutes": "Percek",
"LabelMissing": "Hiányzó",
+ "LabelMissingEbook": "Nincs e-könyve",
+ "LabelMissingSupplementaryEbook": "Nincs kiegészítő e-könyve",
"LabelMobileRedirectURIs": "Engedélyezett mobil átirányítási URI-k",
"LabelMobileRedirectURIsDescription": "Ez egy fehérlista az érvényes mobilalkalmazás-átirányítási URI-k számára. Az alapértelmezett
audiobookshelf://oauth, amely eltávolítható vagy kiegészíthető további URI-kkal harmadik féltől származó alkalmazásintegráció érdekében. Ha az egyetlen bejegyzés egy csillag (
*), akkor bármely URI engedélyezett.",
"LabelMore": "Több",
@@ -358,6 +415,7 @@
"LabelNewestEpisodes": "Legújabb epizódok",
"LabelNextBackupDate": "Következő biztonsági másolat dátuma",
"LabelNextScheduledRun": "Következő ütemezett futtatás",
+ "LabelNoCustomMetadataProviders": "Nincsenek egyedi metaadat szolgáltatók",
"LabelNoEpisodesSelected": "Nincsenek kiválasztott epizódok",
"LabelNotFinished": "Nem befejezett",
"LabelNotStarted": "Nem indult el",
@@ -373,10 +431,14 @@
"LabelNotificationsMaxQueueSizeHelp": "Az események korlátozva vannak, hogy másodpercenként 1-szer történjenek. Ha a sor maximális méretű, akkor az események figyelmen kívül lesznek hagyva. Ez megakadályozza az értesítések spamelését.",
"LabelNumberOfBooks": "Könyvek száma",
"LabelNumberOfEpisodes": "Epizódok száma",
+ "LabelOpenIDAdvancedPermsClaimDescription": "Az OpenID-igény neve, amely a felhasználói műveletekre vonatkozó haladó jogosultságokat tartalmazza az alkalmazáson belül, és amely a nem adminisztrátori szerepkörökre vonatkozik (
ha konfigurálva van). Ha az igény hiányzik a válaszból, az ABS-hez való hozzáférés megtagadásra kerül. Ha egyetlen opció hiányzik, azt
false-ként fogja kezelni. Győződj meg arról, hogy az identitásszolgáltató igénye megfelel a várt struktúrának:",
+ "LabelOpenIDClaims": "Hagyd üresen a következő opciókat, hogy letiltsd a haladó csoport- és jogosultság-hozzárendelést, ekkor automatikusan a ‘Felhasználó’ csoport kerül hozzárendelésre.",
+ "LabelOpenIDGroupClaimDescription": "Az OpenID-igény neve, amely a felhasználó csoportjainak listáját tartalmazza. Általában groups néven hivatkoznak rá. Ha konfigurálva van, az alkalmazás automatikusan hozzárendeli a szerepköröket a felhasználó csoporttagságai alapján, feltéve, hogy ezek a csoportok az igényben kis- és nagybetűkre érzéketlenül ‘admin’, ‘user’ vagy ‘guest’ néven szerepelnek. Az igénynek egy listát kell tartalmaznia, és ha egy felhasználó több csoport tagja, az alkalmazás a legmagasabb szintű hozzáféréssel rendelkező szerepkört rendeli hozzá. Ha egyetlen csoport sem felel meg, a hozzáférés megtagadásra kerül.",
"LabelOpenRSSFeed": "RSS hírcsatorna megnyitása",
"LabelOverwrite": "Felülírás",
"LabelPassword": "Jelszó",
"LabelPath": "Útvonal",
+ "LabelPermanent": "Végleges",
"LabelPermissionsAccessAllLibraries": "Hozzáférhet az összes könyvtárhoz",
"LabelPermissionsAccessAllTags": "Hozzáférhet az összes címkéhez",
"LabelPermissionsAccessExplicitContent": "Hozzáférhet explicit tartalomhoz",
@@ -384,26 +446,34 @@
"LabelPermissionsDownload": "Letölthet",
"LabelPermissionsUpdate": "Frissíthet",
"LabelPermissionsUpload": "Feltölthet",
+ "LabelPersonalYearReview": "Az éved áttekintése ({0})",
"LabelPhotoPathURL": "Fénykép útvonal/URL",
"LabelPlayMethod": "Lejátszási módszer",
+ "LabelPlayerChapterNumberMarker": "{0} a {1} -ből",
"LabelPlaylists": "Lejátszási listák",
+ "LabelPodcast": "Podcast",
"LabelPodcastSearchRegion": "Podcast keresési régió",
"LabelPodcastType": "Podcast típus",
"LabelPodcasts": "Podcastok",
+ "LabelPort": "Port",
"LabelPrefixesToIgnore": "Figyelmen kívül hagyandó előtagok (nem érzékeny a kis- és nagybetűkre)",
- "LabelPreventIndexing": "A hírcsatorna indexelésének megakadályozása az iTunes és a Google podcast könyvtáraiban",
+ "LabelPreventIndexing": "Megakadályozza a hírcsatornájának indexelését az iTunes és a Google podcast könyvtárakban",
"LabelPrimaryEbook": "Elsődleges e-könyv",
"LabelProgress": "Haladás",
"LabelProvider": "Szolgáltató",
+ "LabelProviderAuthorizationValue": "Authorization fejléc értéke",
"LabelPubDate": "Kiadás dátuma",
"LabelPublishYear": "Kiadás éve",
+ "LabelPublishedDate": "Kiadva {0}",
"LabelPublisher": "Kiadó",
+ "LabelPublishers": "Kiadók",
"LabelRSSFeedCustomOwnerEmail": "Egyéni tulajdonos e-mail",
"LabelRSSFeedCustomOwnerName": "Egyéni tulajdonos neve",
"LabelRSSFeedOpen": "RSS hírcsatorna nyitva",
"LabelRSSFeedPreventIndexing": "Indexelés megakadályozása",
"LabelRSSFeedSlug": "RSS hírcsatorna slug",
"LabelRSSFeedURL": "RSS hírcsatorna URL",
+ "LabelRandomly": "Véletlenszerűen",
"LabelRead": "Olvasás",
"LabelReadAgain": "Újraolvasás",
"LabelReadEbookWithoutProgress": "E-könyv olvasása haladás nélkül",
@@ -592,9 +662,9 @@
"MessageDownloadingEpisode": "Epizód letöltése",
"MessageDragFilesIntoTrackOrder": "Húzza a fájlokat a helyes sávrendbe",
"MessageEmbedFinished": "Beágyazás befejeződött!",
- "MessageEpisodesQueuedForDownload": "{0} Epizód letöltésre várakozik",
+ "MessageEpisodesQueuedForDownload": "{0} epizód letöltésre vár",
"MessageFeedURLWillBe": "A hírcsatorna URL-je {0} lesz",
- "MessageFetching": "Lekérés...",
+ "MessageFetching": "Lekérdezés...",
"MessageForceReScanDescription": "minden fájlt újra szkennel, mint egy friss szkennelés. Az audiofájlok ID3 címkéi, OPF fájlok és szövegfájlok újként lesznek szkennelve.",
"MessageImportantNotice": "Fontos közlemény!",
"MessageInsertChapterBelow": "Fejezet beszúrása alulra",
@@ -628,7 +698,7 @@
"MessageNoGenres": "Nincsenek műfajok",
"MessageNoIssues": "Nincsenek problémák",
"MessageNoItems": "Nincsenek elemek",
- "MessageNoItemsFound": "Nem találhatóak elemek",
+ "MessageNoItemsFound": "Nincs találat",
"MessageNoListeningSessions": "Nincsenek hallgatási munkamenetek",
"MessageNoLogs": "Nincsenek naplók",
"MessageNoMediaProgress": "Nincs előrehaladás a médialejátszásban",
@@ -683,10 +753,8 @@
"PlaceholderNewPlaylist": "Új lejátszási lista neve",
"PlaceholderSearch": "Keresés..",
"PlaceholderSearchEpisode": "Epizód keresése..",
- "ToastAccountUpdateFailed": "A fiók frissítése sikertelen",
"ToastAccountUpdateSuccess": "Fiók frissítve",
"ToastAuthorImageRemoveSuccess": "Szerző képe eltávolítva",
- "ToastAuthorUpdateFailed": "A szerző frissítése sikertelen",
"ToastAuthorUpdateMerged": "Szerző összevonva",
"ToastAuthorUpdateSuccess": "Szerző frissítve",
"ToastAuthorUpdateSuccessNoImageFound": "Szerző frissítve (nem található kép)",
@@ -702,21 +770,17 @@
"ToastBookmarkCreateFailed": "Könyvjelző létrehozása sikertelen",
"ToastBookmarkCreateSuccess": "Könyvjelző hozzáadva",
"ToastBookmarkRemoveSuccess": "Könyvjelző eltávolítva",
- "ToastBookmarkUpdateFailed": "Könyvjelző frissítése sikertelen",
"ToastBookmarkUpdateSuccess": "Könyvjelző frissítve",
"ToastChaptersHaveErrors": "A fejezetek hibákat tartalmaznak",
"ToastChaptersMustHaveTitles": "A fejezeteknek címekkel kell rendelkezniük",
"ToastCollectionItemsRemoveSuccess": "Elem(ek) eltávolítva a gyűjteményből",
"ToastCollectionRemoveSuccess": "Gyűjtemény eltávolítva",
- "ToastCollectionUpdateFailed": "Gyűjtemény frissítése sikertelen",
"ToastCollectionUpdateSuccess": "Gyűjtemény frissítve",
- "ToastItemCoverUpdateFailed": "Elem borítójának frissítése sikertelen",
"ToastItemCoverUpdateSuccess": "Elem borítója frissítve",
- "ToastItemDetailsUpdateFailed": "Elem részleteinek frissítése sikertelen",
"ToastItemDetailsUpdateSuccess": "Elem részletei frissítve",
"ToastItemMarkedAsFinishedFailed": "Megjelölés Befejezettként sikertelen",
"ToastItemMarkedAsFinishedSuccess": "Elem megjelölve Befejezettként",
- "ToastItemMarkedAsNotFinishedFailed": "Nem sikerült Nem Befejezettként megjelölni az elemet",
+ "ToastItemMarkedAsNotFinishedFailed": "Az elem befejezetlennek jelölése sikertelen",
"ToastItemMarkedAsNotFinishedSuccess": "Elem megjelölve Nem Befejezettként",
"ToastLibraryCreateFailed": "Könyvtár létrehozása sikertelen",
"ToastLibraryCreateSuccess": "\"{0}\" könyvtár létrehozva",
@@ -724,16 +788,14 @@
"ToastLibraryDeleteSuccess": "Könyvtár törölve",
"ToastLibraryScanFailedToStart": "A beolvasás elindítása sikertelen",
"ToastLibraryScanStarted": "Könyvtár beolvasása elindítva",
- "ToastLibraryUpdateFailed": "Könyvtár frissítése sikertelen",
"ToastLibraryUpdateSuccess": "\"{0}\" könyvtár frissítve",
"ToastPlaylistCreateFailed": "Lejátszási lista létrehozása sikertelen",
"ToastPlaylistCreateSuccess": "Lejátszási lista létrehozva",
"ToastPlaylistRemoveSuccess": "Lejátszási lista eltávolítva",
- "ToastPlaylistUpdateFailed": "Lejátszási lista frissítése sikertelen",
"ToastPlaylistUpdateSuccess": "Lejátszási lista frissítve",
"ToastPodcastCreateFailed": "Podcast létrehozása sikertelen",
- "ToastPodcastCreateSuccess": "Podcast sikeresen létrehozva",
- "ToastRSSFeedCloseFailed": "RSS feed bezárása sikertelen",
+ "ToastPodcastCreateSuccess": "A podcast sikeresen létrehozva",
+ "ToastRSSFeedCloseFailed": "Az RSS hírcsatorna bezárása sikertelen",
"ToastRSSFeedCloseSuccess": "RSS feed bezárva",
"ToastRemoveItemFromCollectionFailed": "Tétel eltávolítása a gyűjteményből sikertelen",
"ToastRemoveItemFromCollectionSuccess": "Tétel eltávolítva a gyűjteményből",
diff --git a/client/strings/it.json b/client/strings/it.json
index ac0bec4e7..3cffc1eb6 100644
--- a/client/strings/it.json
+++ b/client/strings/it.json
@@ -30,6 +30,8 @@
"ButtonEditChapters": "Modifica Capitoli",
"ButtonEditPodcast": "Modifica Podcast",
"ButtonEnable": "Abilita",
+ "ButtonFireAndFail": "Fire and Fail",
+ "ButtonFireOnTest": "Fire onTest event",
"ButtonForceReScan": "Forza Re-Scan",
"ButtonFullPath": "Percorso Completo",
"ButtonHide": "Nascondi",
@@ -54,6 +56,7 @@
"ButtonOpenManager": "Apri Manager",
"ButtonPause": "Pausa",
"ButtonPlay": "Riproduci",
+ "ButtonPlayAll": "Riproduci tutto",
"ButtonPlaying": "In riproduzione",
"ButtonPlaylists": "Playlist",
"ButtonPrevious": "Precendente",
@@ -96,6 +99,7 @@
"ButtonStats": "Statistische",
"ButtonSubmit": "Invia",
"ButtonTest": "Test",
+ "ButtonUnlinkOpenId": "Disattiva OpenID",
"ButtonUpload": "Carica",
"ButtonUploadBackup": "Carica il backup",
"ButtonUploadCover": "Carica una copertina",
@@ -184,7 +188,7 @@
"HeaderSettingsGeneral": "Generale",
"HeaderSettingsScanner": "Scanner",
"HeaderSleepTimer": "Sveglia",
- "HeaderStatsLargestItems": "Oggetti Grandi",
+ "HeaderStatsLargestItems": "File pesanti",
"HeaderStatsLongestItems": "libri più lunghi (ore)",
"HeaderStatsMinutesListeningChart": "Minuti ascoltati (Ultimi 7 Giorni)",
"HeaderStatsRecentSessions": "Sessioni Recenti",
@@ -253,6 +257,7 @@
"LabelClosePlayer": "Chiudi player",
"LabelCodec": "Codec",
"LabelCollapseSeries": "Comprimi Serie",
+ "LabelCollapseSubSeries": "Comprimi subserie",
"LabelCollection": "Raccolta",
"LabelCollections": "Raccolte",
"LabelComplete": "Completo",
@@ -293,7 +298,7 @@
"LabelEmailSettingsFromAddress": "Da Indirizzo",
"LabelEmailSettingsRejectUnauthorized": "Rifiuta i certificati non autorizzati",
"LabelEmailSettingsRejectUnauthorizedHelp": "La disattivazione della convalida del certificato SSL può esporre la tua connessione a rischi per la sicurezza, come attacchi man-in-the-middle. Disattiva questa opzione solo se ne comprendi le implicazioni e ti fidi del server di posta a cui ti stai connettendo.",
- "LabelEmailSettingsSecure": "Sicuro",
+ "LabelEmailSettingsSecure": "SSL",
"LabelEmailSettingsSecureHelp": "Se vero, la connessione utilizzerà TLS durante la connessione al server. Se false, viene utilizzato TLS se il server supporta l'estensione STARTTLS. Nella maggior parte dei casi impostare questo valore su true se ci si connette alla porta 465. Per la porta 587 o 25 mantenerlo false. (da nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Indirizzo di test",
"LabelEmbeddedCover": "Cover Integrata",
@@ -306,6 +311,7 @@
"LabelEpisodes": "Episodi",
"LabelExample": "Esempio",
"LabelExpandSeries": "Espandi Serie",
+ "LabelExpandSubSeries": "Espandi Sub Serie",
"LabelExplicit": "Esplicito",
"LabelExplicitChecked": "Esplicito (selezionato)",
"LabelExplicitUnchecked": "Non Esplicito (selezionato)",
@@ -798,27 +804,25 @@
"StatsBooksAdditional": "Alcune aggiunte includono…",
"StatsBooksFinished": "Libri Finiti",
"StatsBooksFinishedThisYear": "Alcuni libri terminati quest'anno…",
- "StatsBooksListenedTo": "libri ascoltati",
- "StatsCollectionGrewTo": "La tua collezione di libri è cresciuta fino a…",
- "StatsSessions": "sessioni",
- "StatsSpentListening": "trascorso ad ascoltare",
+ "StatsBooksListenedTo": "Libri ascoltati",
+ "StatsCollectionGrewTo": "La tua collezione è aumentata di…",
+ "StatsSessions": "Sessioni",
+ "StatsSpentListening": "Tempo di Ascolto",
"StatsTopAuthor": "MIGLIOR AUTORE",
- "StatsTopAuthors": "MIGLIORI AUTORI",
+ "StatsTopAuthors": "AUTORI MIGLIORI",
"StatsTopGenre": "MIGLIOR GENERE",
- "StatsTopGenres": "MIGLIORI GENERI",
+ "StatsTopGenres": "GENERI MIGLIORI",
"StatsTopMonth": "MIGLIOR MESE",
"StatsTopNarrator": "MIGLIOR NARRATORE",
- "StatsTopNarrators": "MIGLIORI NARRATORI",
- "StatsTotalDuration": "Con una durata totale di…",
+ "StatsTopNarrators": "NARRATORI MIGLIORI",
+ "StatsTotalDuration": "Per una durata totale di…",
"StatsYearInReview": "ANNO IN RASSEGNA",
- "ToastAccountUpdateFailed": "Aggiornamento Account Fallito",
"ToastAccountUpdateSuccess": "Account Aggiornato",
"ToastAppriseUrlRequired": "È necessario immettere un indirizzo Apprise",
"ToastAuthorImageRemoveSuccess": "Immagine Autore Rimossa",
"ToastAuthorNotFound": "Autore\"{0}\" non trovato",
"ToastAuthorRemoveSuccess": "Autore rimosso",
"ToastAuthorSearchNotFound": "Autore non trovato",
- "ToastAuthorUpdateFailed": "Aggiornamento Autore Fallito",
"ToastAuthorUpdateMerged": "Autore unito",
"ToastAuthorUpdateSuccess": "Autore aggiornato",
"ToastAuthorUpdateSuccessNoImageFound": "Autore aggiornato (nessuna immagine trovata)",
@@ -829,7 +833,6 @@
"ToastBackupDeleteSuccess": "backup Eliminato",
"ToastBackupInvalidMaxKeep": "Numero non valido di backup da conservare",
"ToastBackupInvalidMaxSize": "Dimensione massima del backup non valida",
- "ToastBackupPathUpdateFailed": "Impossibile aggiornare il percorso di backup",
"ToastBackupRestoreFailed": "Ripristino fallito",
"ToastBackupUploadFailed": "Caricamento backup fallito",
"ToastBackupUploadSuccess": "Backup caricato",
@@ -840,7 +843,6 @@
"ToastBookmarkCreateFailed": "Creazione segnalibro fallita",
"ToastBookmarkCreateSuccess": "Segnalibro creato",
"ToastBookmarkRemoveSuccess": "Segnalibro Rimosso",
- "ToastBookmarkUpdateFailed": "Aggiornamento segnalibro fallito",
"ToastBookmarkUpdateSuccess": "Segnalibro aggiornato",
"ToastCachePurgeFailed": "Impossibile eliminare la cache",
"ToastCachePurgeSuccess": "Cache eliminata correttamente",
@@ -851,7 +853,6 @@
"ToastCollectionItemsAddSuccess": "L'aggiunta dell'elemento(i) alla raccolta è riuscito",
"ToastCollectionItemsRemoveSuccess": "Oggetto(i) rimossi dalla Raccolta",
"ToastCollectionRemoveSuccess": "Collezione rimossa",
- "ToastCollectionUpdateFailed": "Errore aggiornamento Raccolta",
"ToastCollectionUpdateSuccess": "Raccolta aggiornata",
"ToastCoverUpdateFailed": "Aggiornamento cover fallito",
"ToastDeleteFileFailed": "Impossibile eliminare il file",
@@ -860,49 +861,99 @@
"ToastDeviceNameAlreadyExists": "Esiste già un dispositivo e-reader con quel nome",
"ToastDeviceTestEmailFailed": "Impossibile inviare l'e-mail di prova",
"ToastDeviceTestEmailSuccess": "Test invio mail completato",
+ "ToastEmailSettingsUpdateSuccess": "Impostazioni e-mail aggiornate",
+ "ToastEncodeCancelFailed": "Impossibile annullare la codifica",
+ "ToastEncodeCancelSucces": "Codifica annullata",
+ "ToastEpisodeDownloadQueueClearFailed": "Impossibile cancellare la coda",
+ "ToastEpisodeDownloadQueueClearSuccess": "Coda di download degli episodi cancellata",
"ToastErrorCannotShare": "Impossibile condividere in modo nativo su questo dispositivo",
"ToastFailedToLoadData": "Impossibile caricare i dati",
- "ToastItemCoverUpdateFailed": "Errore Aggiornamento cover",
+ "ToastFailedToShare": "Impossibile condividere",
+ "ToastInvalidImageUrl": "URL dell'immagine non valido",
+ "ToastInvalidUrl": "URL non valido",
"ToastItemCoverUpdateSuccess": "Cover aggiornata",
- "ToastItemDetailsUpdateFailed": "Errore Aggiornamento dettagli file",
+ "ToastItemDeletedFailed": "Impossibile eliminare l'elemento",
+ "ToastItemDeletedSuccess": "Elemento eliminato",
"ToastItemDetailsUpdateSuccess": "Dettagli file Aggiornata",
"ToastItemMarkedAsFinishedFailed": "Errore nel segnare il file come finito",
"ToastItemMarkedAsFinishedSuccess": "File segnato come finito",
"ToastItemMarkedAsNotFinishedFailed": "Errore nel segnare il file come non completo",
"ToastItemMarkedAsNotFinishedSuccess": "File segnato come non completo",
+ "ToastItemUpdateSuccess": "Articolo aggiornato",
"ToastLibraryCreateFailed": "Errore creazione libreria",
"ToastLibraryCreateSuccess": "Libreria \"{0}\" creata",
"ToastLibraryDeleteFailed": "Errore cancellazione libreria",
"ToastLibraryDeleteSuccess": "Libreria Cancellata",
"ToastLibraryScanFailedToStart": "Errore inizio scansione",
"ToastLibraryScanStarted": "Scansione Libreria iniziata",
- "ToastLibraryUpdateFailed": "Errore Aggiornamento libreria",
"ToastLibraryUpdateSuccess": "Libreria \"{0}\" aggiornata",
+ "ToastNameEmailRequired": "Nome ed email sono obbligatori",
+ "ToastNameRequired": "Il nome è obbligatorio",
+ "ToastNewUserCreatedFailed": "Impossibile creare l'account: \"{0}\"",
+ "ToastNewUserCreatedSuccess": "Nuovo account creato",
+ "ToastNewUserLibraryError": "È necessario selezionare almeno una libreria",
+ "ToastNewUserPasswordError": "Deve avere una password, solo l'utente root può avere una password vuota",
+ "ToastNewUserTagError": "Devi selezionare almeno un tag",
+ "ToastNewUserUsernameError": "Inserisci un nome utente",
+ "ToastNoUpdatesNecessary": "Nessun aggiornamento necessario",
+ "ToastNotificationCreateFailed": "Impossibile creare la notifica",
+ "ToastNotificationDeleteFailed": "Impossibile eliminare la notifica",
+ "ToastNotificationFailedMaximum": "Il numero massimo di tentativi falliti deve essere >= 0",
+ "ToastNotificationQueueMaximum": "La coda di notifica massima deve essere >= 0",
+ "ToastNotificationSettingsUpdateSuccess": "Impostazioni di notifica aggiornate",
+ "ToastNotificationTestTriggerFailed": "Impossibile attivare la notifica del test",
+ "ToastNotificationTestTriggerSuccess": "Notifica di test attivata",
+ "ToastNotificationUpdateSuccess": "Notifica aggiornata",
"ToastPlaylistCreateFailed": "Errore creazione playlist",
"ToastPlaylistCreateSuccess": "Playlist creata",
"ToastPlaylistRemoveSuccess": "Playlist rimossa",
- "ToastPlaylistUpdateFailed": "Aggiornamento Playlist Fallita",
"ToastPlaylistUpdateSuccess": "Playlist Aggiornata",
"ToastPodcastCreateFailed": "Errore creazione podcast",
"ToastPodcastCreateSuccess": "Podcast creato correttamente",
+ "ToastPodcastGetFeedFailed": "Impossibile ottenere il feed del podcast",
+ "ToastPodcastNoEpisodesInFeed": "Nessun episodio trovato nel feed RSS",
+ "ToastPodcastNoRssFeed": "Il podcast non ha un feed RSS",
+ "ToastProviderCreatedFailed": "Impossibile aggiungere il provider",
+ "ToastProviderCreatedSuccess": "Aggiunto nuovo provider",
+ "ToastProviderNameAndUrlRequired": "Nome e URL richiesti",
+ "ToastProviderRemoveSuccess": "Provider rimosso",
"ToastRSSFeedCloseFailed": "Errore chiusura flusso RSS",
"ToastRSSFeedCloseSuccess": "Flusso RSS chiuso",
+ "ToastRemoveFailed": "Impossibile rimuovere",
"ToastRemoveItemFromCollectionFailed": "Errore rimozione file dalla Raccolta",
"ToastRemoveItemFromCollectionSuccess": "Oggetto rimosso dalla Raccolta",
+ "ToastRemoveItemsWithIssuesFailed": "Impossibile rimuovere gli elementi della libreria con problemi",
+ "ToastRemoveItemsWithIssuesSuccess": "Rimossi gli elementi della libreria con problemi",
+ "ToastRenameFailed": "Impossibile rinominare",
+ "ToastRescanFailed": "Nuova scansione non riuscita per {0}",
+ "ToastRescanRemoved": "L'articolo completo di Re-Scan è stato rimosso",
+ "ToastRescanUpToDate": "La nuova scansione dell'articolo completo è stata aggiornata",
+ "ToastRescanUpdated": "L'articolo completo di Re-Scan è stato aggiornato",
+ "ToastScanFailed": "Impossibile eseguire la scansione dell'elemento della libreria",
+ "ToastSelectAtLeastOneUser": "Seleziona almeno un utente",
"ToastSendEbookToDeviceFailed": "Impossibile inviare il libro al dispositivo",
"ToastSendEbookToDeviceSuccess": "Libro inviato al dispositivo «{0}»",
"ToastSeriesUpdateFailed": "Aggiornamento Serie Fallito",
"ToastSeriesUpdateSuccess": "Serie Aggiornate",
- "ToastServerSettingsUpdateFailed": "Impossibile aggiornare le impostazioni del server",
"ToastServerSettingsUpdateSuccess": "Impostazioni del server aggiornate",
+ "ToastSessionCloseFailed": "Disconnessione Fallita",
"ToastSessionDeleteFailed": "Errore eliminazione sessione",
"ToastSessionDeleteSuccess": "Sessione cancellata",
+ "ToastSlugMustChange": "Lo slug contiene caratteri non validi",
+ "ToastSlugRequired": "È richiesto lo slug",
"ToastSocketConnected": "Socket connesso",
"ToastSocketDisconnected": "Socket disconnesso",
"ToastSocketFailedToConnect": "Socket non riesce a connettersi",
"ToastSortingPrefixesEmptyError": "Deve avere almeno 1 prefisso di ordinamento",
- "ToastSortingPrefixesUpdateFailed": "Impossibile aggiornare i prefissi di ordinamento",
"ToastSortingPrefixesUpdateSuccess": "Prefissi di ordinamento aggiornati ({0} items)",
+ "ToastTitleRequired": "Il titolo è obbligatorio",
+ "ToastUnknownError": "Errore sconosciuto",
+ "ToastUnlinkOpenIdFailed": "Impossibile scollegare l'utente da OpenID",
+ "ToastUnlinkOpenIdSuccess": "Utente scollegato da OpenID",
"ToastUserDeleteFailed": "Errore eliminazione utente",
- "ToastUserDeleteSuccess": "Utente eliminato"
+ "ToastUserDeleteSuccess": "Utente eliminato",
+ "ToastUserPasswordChangeSuccess": "Password modificata con successo",
+ "ToastUserPasswordMismatch": "Le password non corrispondono",
+ "ToastUserPasswordMustChange": "La nuova password non può corrispondere alla vecchia password",
+ "ToastUserRootRequireName": "È necessario immettere un nome utente root"
}
diff --git a/client/strings/lt.json b/client/strings/lt.json
index f9b765d45..6cc7966c8 100644
--- a/client/strings/lt.json
+++ b/client/strings/lt.json
@@ -622,10 +622,8 @@
"PlaceholderNewPlaylist": "Naujas grojaraščio pavadinimas",
"PlaceholderSearch": "Ieškoti..",
"PlaceholderSearchEpisode": "Ieškoti epizodo..",
- "ToastAccountUpdateFailed": "Paskyros atnaujinimas nepavyko",
"ToastAccountUpdateSuccess": "Paskyra atnaujinta",
"ToastAuthorImageRemoveSuccess": "Autoriaus paveiksliukas pašalintas",
- "ToastAuthorUpdateFailed": "Nepavyko atnaujinti autoriaus",
"ToastAuthorUpdateMerged": "Autorius sujungtas",
"ToastAuthorUpdateSuccess": "Autorius atnaujintas",
"ToastAuthorUpdateSuccessNoImageFound": "Autorius atnaujintas (paveiksliukas nerastas)",
@@ -641,17 +639,13 @@
"ToastBookmarkCreateFailed": "Žymos sukurti nepavyko",
"ToastBookmarkCreateSuccess": "Žyma pridėta",
"ToastBookmarkRemoveSuccess": "Žyma pašalinta",
- "ToastBookmarkUpdateFailed": "Žymos atnaujinti nepavyko",
"ToastBookmarkUpdateSuccess": "Žyma atnaujinta",
"ToastChaptersHaveErrors": "Skyriai turi klaidų",
"ToastChaptersMustHaveTitles": "Skyriai turi turėti pavadinimus",
"ToastCollectionItemsRemoveSuccess": "Elementai pašalinti iš kolekcijos",
"ToastCollectionRemoveSuccess": "Kolekcija pašalinta",
- "ToastCollectionUpdateFailed": "Kolekcijos atnaujinti nepavyko",
"ToastCollectionUpdateSuccess": "Kolekcija atnaujinta",
- "ToastItemCoverUpdateFailed": "Elemento viršelio atnaujinti nepavyko",
"ToastItemCoverUpdateSuccess": "Elemento viršelis atnaujintas",
- "ToastItemDetailsUpdateFailed": "Elemento detalių atnaujinti nepavyko",
"ToastItemDetailsUpdateSuccess": "Elemento detalės atnaujintos",
"ToastItemMarkedAsFinishedFailed": "Pažymėti kaip Baigta nepavyko",
"ToastItemMarkedAsFinishedSuccess": "Elementas pažymėtas kaip Baigta",
@@ -663,12 +657,10 @@
"ToastLibraryDeleteSuccess": "Biblioteka ištrinta",
"ToastLibraryScanFailedToStart": "Nepavyko pradėti bibliotekos skenavimo",
"ToastLibraryScanStarted": "Bibliotekos skenavimas pradėtas",
- "ToastLibraryUpdateFailed": "Bibliotekos atnaujinti nepavyko",
"ToastLibraryUpdateSuccess": "Biblioteka \"{0}\" atnaujinta",
"ToastPlaylistCreateFailed": "Grojaraščio sukurti nepavyko",
"ToastPlaylistCreateSuccess": "Grojaraštis sukurtas",
"ToastPlaylistRemoveSuccess": "Grojaraštis pašalintas",
- "ToastPlaylistUpdateFailed": "Grojaraščio atnaujinti nepavyko",
"ToastPlaylistUpdateSuccess": "Grojaraštis atnaujintas",
"ToastPodcastCreateFailed": "Tinklalaidės sukurti nepavyko",
"ToastPodcastCreateSuccess": "Tinklalaidė sėkmingai sukurta",
diff --git a/client/strings/nl.json b/client/strings/nl.json
index 41fd8ef68..cd2c872c4 100644
--- a/client/strings/nl.json
+++ b/client/strings/nl.json
@@ -614,10 +614,8 @@
"PlaceholderNewPlaylist": "Nieuwe naam afspeellijst",
"PlaceholderSearch": "Zoeken..",
"PlaceholderSearchEpisode": "Aflevering zoeken..",
- "ToastAccountUpdateFailed": "Bijwerken account mislukt",
"ToastAccountUpdateSuccess": "Account bijgewerkt",
"ToastAuthorImageRemoveSuccess": "Afbeelding auteur verwijderd",
- "ToastAuthorUpdateFailed": "Bijwerken auteur mislukt",
"ToastAuthorUpdateMerged": "Auteur samengevoegd",
"ToastAuthorUpdateSuccess": "Auteur bijgewerkt",
"ToastAuthorUpdateSuccessNoImageFound": "Auteur bijgewerkt (geen afbeelding gevonden)",
@@ -633,17 +631,13 @@
"ToastBookmarkCreateFailed": "Aanmaken boekwijzer mislukt",
"ToastBookmarkCreateSuccess": "boekwijzer toegevoegd",
"ToastBookmarkRemoveSuccess": "Boekwijzer verwijderd",
- "ToastBookmarkUpdateFailed": "Bijwerken boekwijzer mislukt",
"ToastBookmarkUpdateSuccess": "Boekwijzer bijgewerkt",
"ToastChaptersHaveErrors": "Hoofdstukken bevatten fouten",
"ToastChaptersMustHaveTitles": "Hoofdstukken moeten titels hebben",
"ToastCollectionItemsRemoveSuccess": "Onderdeel (of onderdelen) verwijderd uit collectie",
"ToastCollectionRemoveSuccess": "Collectie verwijderd",
- "ToastCollectionUpdateFailed": "Bijwerken collectie mislukt",
"ToastCollectionUpdateSuccess": "Collectie bijgewerkt",
- "ToastItemCoverUpdateFailed": "Bijwerken cover onderdeel mislukt",
"ToastItemCoverUpdateSuccess": "Cover onderdeel bijgewerkt",
- "ToastItemDetailsUpdateFailed": "Bijwerken details onderdeel mislukt",
"ToastItemDetailsUpdateSuccess": "Details onderdeel bijgewerkt",
"ToastItemMarkedAsFinishedFailed": "Markeren als Voltooid mislukt",
"ToastItemMarkedAsFinishedSuccess": "Onderdeel gemarkeerd als Voltooid",
@@ -655,12 +649,10 @@
"ToastLibraryDeleteSuccess": "Bibliotheek verwijderd",
"ToastLibraryScanFailedToStart": "Starten scan mislukt",
"ToastLibraryScanStarted": "Scannen bibliotheek gestart",
- "ToastLibraryUpdateFailed": "Bijwerken bibliotheek mislukt",
"ToastLibraryUpdateSuccess": "Bibliotheek \"{0}\" bijgewerkt",
"ToastPlaylistCreateFailed": "Aanmaken afspeellijst mislukt",
"ToastPlaylistCreateSuccess": "Afspeellijst aangemaakt",
"ToastPlaylistRemoveSuccess": "Afspeellijst verwijderd",
- "ToastPlaylistUpdateFailed": "Afspeellijst bijwerken mislukt",
"ToastPlaylistUpdateSuccess": "Afspeellijst bijgewerkt",
"ToastPodcastCreateFailed": "Podcast aanmaken mislukt",
"ToastPodcastCreateSuccess": "Podcast aangemaakt",
diff --git a/client/strings/no.json b/client/strings/no.json
index ea2b8f0c5..01be88ef4 100644
--- a/client/strings/no.json
+++ b/client/strings/no.json
@@ -641,10 +641,8 @@
"PlaceholderNewPlaylist": "Ny spillelistenavn",
"PlaceholderSearch": "Søk..",
"PlaceholderSearchEpisode": "Søk episode..",
- "ToastAccountUpdateFailed": "Mislykkes å oppdatere konto",
"ToastAccountUpdateSuccess": "Konto oppdatert",
"ToastAuthorImageRemoveSuccess": "Forfatter bilde fjernet",
- "ToastAuthorUpdateFailed": "Mislykkes å oppdatere forfatter",
"ToastAuthorUpdateMerged": "Forfatter slått sammen",
"ToastAuthorUpdateSuccess": "Forfatter oppdatert",
"ToastAuthorUpdateSuccessNoImageFound": "Forfatter oppdater (ingen bilde funnet)",
@@ -660,17 +658,13 @@
"ToastBookmarkCreateFailed": "Misslykkes å opprette bokmerke",
"ToastBookmarkCreateSuccess": "Bokmerke lagt til",
"ToastBookmarkRemoveSuccess": "Bokmerke fjernet",
- "ToastBookmarkUpdateFailed": "Misslykkes å oppdatere bokmerke",
"ToastBookmarkUpdateSuccess": "Bokmerke oppdatert",
"ToastChaptersHaveErrors": "Kapittel har feil",
"ToastChaptersMustHaveTitles": "Kapittel må ha titler",
"ToastCollectionItemsRemoveSuccess": "Gjenstand(er) fjernet fra samling",
"ToastCollectionRemoveSuccess": "Samling fjernet",
- "ToastCollectionUpdateFailed": "Misslykkes å oppdatere samling",
"ToastCollectionUpdateSuccess": "samlingupdated",
- "ToastItemCoverUpdateFailed": "Misslykkes å oppdatere omslag",
"ToastItemCoverUpdateSuccess": "Omslag oppdatert",
- "ToastItemDetailsUpdateFailed": "Misslykkes å oppdatere detaljer",
"ToastItemDetailsUpdateSuccess": "Detaljer oppdatert",
"ToastItemMarkedAsFinishedFailed": "Misslykkes å markere som Fullført",
"ToastItemMarkedAsFinishedSuccess": "Gjenstand marker som Fullført",
@@ -682,12 +676,10 @@
"ToastLibraryDeleteSuccess": "Bibliotek slettet",
"ToastLibraryScanFailedToStart": "Misslykkes å starte skann",
"ToastLibraryScanStarted": "Bibliotek skann startet",
- "ToastLibraryUpdateFailed": "Misslykkes å oppdatere bibiliotek",
"ToastLibraryUpdateSuccess": "Bibliotek \"{0}\" oppdatert",
"ToastPlaylistCreateFailed": "Misslykkes å opprette spilleliste",
"ToastPlaylistCreateSuccess": "Spilleliste opprettet",
"ToastPlaylistRemoveSuccess": "Spilleliste fjernet",
- "ToastPlaylistUpdateFailed": "Misslykkes å oppdatere spilleliste",
"ToastPlaylistUpdateSuccess": "Spilleliste oppdatert",
"ToastPodcastCreateFailed": "Misslykkes å opprette podcast",
"ToastPodcastCreateSuccess": "Podcast opprettet",
diff --git a/client/strings/pl.json b/client/strings/pl.json
index c786a1188..9fd6c9feb 100644
--- a/client/strings/pl.json
+++ b/client/strings/pl.json
@@ -54,10 +54,12 @@
"ButtonOpenManager": "Otwórz menadżera",
"ButtonPause": "Wstrzymaj",
"ButtonPlay": "Odtwarzaj",
+ "ButtonPlayAll": "Odtwórz wszystko",
"ButtonPlaying": "Odtwarzane",
"ButtonPlaylists": "Listy odtwarzania",
"ButtonPrevious": "Poprzedni",
"ButtonPreviousChapter": "Poprzedni rozdział",
+ "ButtonProbeAudioFile": "Próbka audio",
"ButtonPurgeAllCache": "Wyczyść dane tymczasowe",
"ButtonPurgeItemsCache": "Wyczyść dane tymczasowe pozycji",
"ButtonQueueAddItem": "Dodaj do kolejki",
@@ -95,6 +97,7 @@
"ButtonStats": "Statystyki",
"ButtonSubmit": "Zaloguj",
"ButtonTest": "Test",
+ "ButtonUnlinkOpenId": "Odłącz OpenID",
"ButtonUpload": "Wgraj",
"ButtonUploadBackup": "Wgraj kopię zapasową",
"ButtonUploadCover": "Wgraj okładkę",
@@ -355,7 +358,8 @@
"LabelIntervalEveryHour": "Każdej godziny",
"LabelInvert": "Inversja",
"LabelItem": "Pozycja",
- "LabelJumpBackwardAmount": "Rozmiar skoku do przodu",
+ "LabelJumpBackwardAmount": "Przeskocz do tyłu o:",
+ "LabelJumpForwardAmount": "Przeskocz do przodu o:",
"LabelLanguage": "Język",
"LabelLanguageDefaultServer": "Domyślny język serwera",
"LabelLanguages": "Języki",
@@ -738,10 +742,8 @@
"StatsTopNarrator": "TOPOWY NARRATOR",
"StatsTopNarrators": "TOPOWI NARRATORZY",
"StatsYearInReview": "PRZEGLĄD ROKU",
- "ToastAccountUpdateFailed": "Nie udało się zaktualizować konta",
"ToastAccountUpdateSuccess": "Zaktualizowano konto",
"ToastAuthorImageRemoveSuccess": "Zdjęcie autora usunięte",
- "ToastAuthorUpdateFailed": "nie udało się zaktualizować autora",
"ToastAuthorUpdateMerged": "Autor scalony",
"ToastAuthorUpdateSuccess": "Autor zaktualizowany",
"ToastAuthorUpdateSuccessNoImageFound": "Autor zaktualizowany (nie znaleziono obrazu)",
@@ -757,15 +759,11 @@
"ToastBookmarkCreateFailed": "Nie udało się utworzyć zakładki",
"ToastBookmarkCreateSuccess": "Dodano zakładkę",
"ToastBookmarkRemoveSuccess": "Zakładka została usunięta",
- "ToastBookmarkUpdateFailed": "Nie udało się zaktualizować zakładki",
"ToastBookmarkUpdateSuccess": "Zaktualizowano zakładkę",
"ToastCollectionItemsRemoveSuccess": "Przedmiot(y) zostały usunięte z kolekcji",
"ToastCollectionRemoveSuccess": "Kolekcja usunięta",
- "ToastCollectionUpdateFailed": "Nie udało się zaktualizować kolekcji",
"ToastCollectionUpdateSuccess": "Zaktualizowano kolekcję",
- "ToastItemCoverUpdateFailed": "Nie udało się zaktualizować okładki",
"ToastItemCoverUpdateSuccess": "Zaktualizowano okładkę",
- "ToastItemDetailsUpdateFailed": "Nie udało się zaktualizować szczegółów",
"ToastItemDetailsUpdateSuccess": "Zaktualizowano szczegóły",
"ToastItemMarkedAsFinishedFailed": "Nie udało się oznaczyć jako ukończone",
"ToastItemMarkedAsFinishedSuccess": "Pozycja oznaczona jako ukończona",
@@ -777,12 +775,10 @@
"ToastLibraryDeleteSuccess": "Biblioteka usunięta",
"ToastLibraryScanFailedToStart": "Nie udało się rozpocząć skanowania",
"ToastLibraryScanStarted": "Rozpoczęto skanowanie biblioteki",
- "ToastLibraryUpdateFailed": "Nie udało się zaktualizować biblioteki",
"ToastLibraryUpdateSuccess": "Zaktualizowano \"{0}\" pozycji",
"ToastPlaylistCreateFailed": "Nie udało się utworzyć playlisty",
"ToastPlaylistCreateSuccess": "Playlista utworzona",
"ToastPlaylistRemoveSuccess": "Playlista usunięta",
- "ToastPlaylistUpdateFailed": "Nie udało się zaktualizować playlisty",
"ToastPlaylistUpdateSuccess": "Playlista zaktualizowana",
"ToastPodcastCreateFailed": "Nie udało się utworzyć podcastu",
"ToastPodcastCreateSuccess": "Podcast został pomyślnie utworzony",
diff --git a/client/strings/pt-br.json b/client/strings/pt-br.json
index dba497d44..68fad736e 100644
--- a/client/strings/pt-br.json
+++ b/client/strings/pt-br.json
@@ -710,10 +710,8 @@
"PlaceholderNewPlaylist": "Novo nome da lista de reprodução",
"PlaceholderSearch": "Buscar..",
"PlaceholderSearchEpisode": "Buscar Episódio..",
- "ToastAccountUpdateFailed": "Falha ao atualizar a conta",
"ToastAccountUpdateSuccess": "Conta atualizada",
"ToastAuthorImageRemoveSuccess": "Imagem do autor removida",
- "ToastAuthorUpdateFailed": "Falha ao atualizar o autor",
"ToastAuthorUpdateMerged": "Autor combinado",
"ToastAuthorUpdateSuccess": "Autor atualizado",
"ToastAuthorUpdateSuccessNoImageFound": "Autor atualizado (nenhuma imagem encontrada)",
@@ -729,7 +727,6 @@
"ToastBookmarkCreateFailed": "Falha ao criar marcador",
"ToastBookmarkCreateSuccess": "Marcador adicionado",
"ToastBookmarkRemoveSuccess": "Marcador removido",
- "ToastBookmarkUpdateFailed": "Falha ao atualizar o marcador",
"ToastBookmarkUpdateSuccess": "Marcador atualizado",
"ToastCachePurgeFailed": "Falha ao apagar o cache",
"ToastCachePurgeSuccess": "Cache apagado com sucesso",
@@ -737,14 +734,11 @@
"ToastChaptersMustHaveTitles": "Capítulos precisam ter títulos",
"ToastCollectionItemsRemoveSuccess": "Item(ns) removidos da coleção",
"ToastCollectionRemoveSuccess": "Coleção removida",
- "ToastCollectionUpdateFailed": "Falha ao atualizar coleção",
"ToastCollectionUpdateSuccess": "Coleção atualizada",
"ToastDeleteFileFailed": "Falha ao apagar arquivo",
"ToastDeleteFileSuccess": "Arquivo apagado",
"ToastFailedToLoadData": "Falha ao carregar dados",
- "ToastItemCoverUpdateFailed": "Falha ao atualizar capa do item",
"ToastItemCoverUpdateSuccess": "Capa do item atualizada",
- "ToastItemDetailsUpdateFailed": "Falha ao atualizar detalhes do item",
"ToastItemDetailsUpdateSuccess": "Detalhes do item atualizados",
"ToastItemMarkedAsFinishedFailed": "Falha ao marcar como Concluído",
"ToastItemMarkedAsFinishedSuccess": "Item marcado como Concluído",
@@ -756,12 +750,10 @@
"ToastLibraryDeleteSuccess": "Biblioteca apagada",
"ToastLibraryScanFailedToStart": "Falha ao iniciar verificação",
"ToastLibraryScanStarted": "Verificação da biblioteca iniciada",
- "ToastLibraryUpdateFailed": "Falha ao atualizar a biblioteca",
"ToastLibraryUpdateSuccess": "Biblioteca \"{0}\" atualizada",
"ToastPlaylistCreateFailed": "Falha ao criar lista de reprodução",
"ToastPlaylistCreateSuccess": "Lista de reprodução criada",
"ToastPlaylistRemoveSuccess": "Lista de reprodução removida",
- "ToastPlaylistUpdateFailed": "Falha ao atualizar lista de reprodução",
"ToastPlaylistUpdateSuccess": "Lista de reprodução atualizada",
"ToastPodcastCreateFailed": "Falha ao criar podcast",
"ToastPodcastCreateSuccess": "Podcast criado",
@@ -773,7 +765,6 @@
"ToastSendEbookToDeviceSuccess": "Ebook enviado para o dispositivo \"{0}\"",
"ToastSeriesUpdateFailed": "Falha ao atualizar série",
"ToastSeriesUpdateSuccess": "Série atualizada",
- "ToastServerSettingsUpdateFailed": "Falha ao atualizar configurações do servidor",
"ToastServerSettingsUpdateSuccess": "Configurações do servidor atualizadas",
"ToastSessionDeleteFailed": "Falha ao apagar sessão",
"ToastSessionDeleteSuccess": "Sessão apagada",
@@ -781,7 +772,6 @@
"ToastSocketDisconnected": "Socket desconectado",
"ToastSocketFailedToConnect": "Falha na conexão do socket",
"ToastSortingPrefixesEmptyError": "É preciso ter pelo menos um prefixo de ordenação",
- "ToastSortingPrefixesUpdateFailed": "Falha ao atualizar prefixos de ordenação",
"ToastSortingPrefixesUpdateSuccess": "Prefixos de ordenação atualizados ({0} item(ns))",
"ToastUserDeleteFailed": "Falha ao apagar usuário",
"ToastUserDeleteSuccess": "Usuário apagado"
diff --git a/client/strings/ru.json b/client/strings/ru.json
index bfefb5bd8..04002ebae 100644
--- a/client/strings/ru.json
+++ b/client/strings/ru.json
@@ -56,6 +56,7 @@
"ButtonOpenManager": "Открыть менеджер",
"ButtonPause": "Пауза",
"ButtonPlay": "Слушать",
+ "ButtonPlayAll": "Играть все",
"ButtonPlaying": "Проигрывается",
"ButtonPlaylists": "Плейлисты",
"ButtonPrevious": "Предыдущий",
@@ -816,14 +817,12 @@
"StatsTopNarrators": "ТОП ЧТЕЦЫ",
"StatsTotalDuration": "С общей продолжительностью…",
"StatsYearInReview": "ИТОГИ ГОДА",
- "ToastAccountUpdateFailed": "Не удалось обновить учетную запись",
"ToastAccountUpdateSuccess": "Учетная запись обновлена",
"ToastAppriseUrlRequired": "Необходимо ввести URL-адрес Apprise",
"ToastAuthorImageRemoveSuccess": "Изображение автора удалено",
"ToastAuthorNotFound": "Автор \"{0}\" не найден",
"ToastAuthorRemoveSuccess": "Автор удален",
"ToastAuthorSearchNotFound": "Автор не найден",
- "ToastAuthorUpdateFailed": "Не удалось обновить автора",
"ToastAuthorUpdateMerged": "Автор объединен",
"ToastAuthorUpdateSuccess": "Автор обновлен",
"ToastAuthorUpdateSuccessNoImageFound": "Автор обновлен (изображение не найдено)",
@@ -834,7 +833,6 @@
"ToastBackupDeleteSuccess": "Бэкап удален",
"ToastBackupInvalidMaxKeep": "Недопустимое количество резервных копий для хранения",
"ToastBackupInvalidMaxSize": "Недопустимый максимальный размер резервной копии",
- "ToastBackupPathUpdateFailed": "Не удалось обновить путь к резервному копированию",
"ToastBackupRestoreFailed": "Не удалось восстановить из бэкапа",
"ToastBackupUploadFailed": "Не удалось загрузить бэкап",
"ToastBackupUploadSuccess": "Бэкап загружен",
@@ -845,7 +843,6 @@
"ToastBookmarkCreateFailed": "Не удалось создать закладку",
"ToastBookmarkCreateSuccess": "Добавлена закладка",
"ToastBookmarkRemoveSuccess": "Закладка удалена",
- "ToastBookmarkUpdateFailed": "Не удалось обновить закладку",
"ToastBookmarkUpdateSuccess": "Закладка обновлена",
"ToastCachePurgeFailed": "Не удалось очистить кэш",
"ToastCachePurgeSuccess": "Кэш успешно очищен",
@@ -856,7 +853,6 @@
"ToastCollectionItemsAddSuccess": "Элемент(ы) добавлены в коллекцию",
"ToastCollectionItemsRemoveSuccess": "Элемент(ы), удалены из коллекции",
"ToastCollectionRemoveSuccess": "Коллекция удалена",
- "ToastCollectionUpdateFailed": "Не удалось обновить коллекцию",
"ToastCollectionUpdateSuccess": "Коллекция обновлена",
"ToastCoverUpdateFailed": "Не удалось обновить обложку",
"ToastDeleteFileFailed": "Не удалось удалить файл",
@@ -865,8 +861,6 @@
"ToastDeviceNameAlreadyExists": "Устройство для чтения электронных книг с таким именем уже существует",
"ToastDeviceTestEmailFailed": "Не удалось отправить тестовое электронное письмо",
"ToastDeviceTestEmailSuccess": "Тестовое письмо отправлено",
- "ToastDeviceUpdateFailed": "Не удалось обновить устройство",
- "ToastEmailSettingsUpdateFailed": "Не удалось обновить настройки электронной почты",
"ToastEmailSettingsUpdateSuccess": "Обновлены настройки электронной почты",
"ToastEncodeCancelFailed": "Не удалось отменить кодирование",
"ToastEncodeCancelSucces": "Кодирование отменено",
@@ -875,21 +869,16 @@
"ToastErrorCannotShare": "Невозможно предоставить общий доступ на этом устройстве",
"ToastFailedToLoadData": "Не удалось загрузить данные",
"ToastFailedToShare": "Не удалось поделиться",
- "ToastFailedToUpdateAccount": "Не удалось обновить учетную запись",
- "ToastFailedToUpdateUser": "Не удалось обновить пользователя",
"ToastInvalidImageUrl": "Неверный URL изображения",
"ToastInvalidUrl": "Неверный URL",
- "ToastItemCoverUpdateFailed": "Не удалось обновить обложку элемента",
"ToastItemCoverUpdateSuccess": "Обложка элемента обновлена",
"ToastItemDeletedFailed": "Не удалось удалить элемент",
"ToastItemDeletedSuccess": "Удаленный элемент",
- "ToastItemDetailsUpdateFailed": "Не удалось обновить сведения об элементе",
"ToastItemDetailsUpdateSuccess": "Обновлены сведения об элементе",
"ToastItemMarkedAsFinishedFailed": "Не удалось пометить как Завершенный",
"ToastItemMarkedAsFinishedSuccess": "Элемент помечен как Завершенный",
"ToastItemMarkedAsNotFinishedFailed": "Не удалось пометить как Незавершенный",
"ToastItemMarkedAsNotFinishedSuccess": "Элемент помечен как Незавершенный",
- "ToastItemUpdateFailed": "Не удалось обновить элемент",
"ToastItemUpdateSuccess": "Элемент обновлен",
"ToastLibraryCreateFailed": "Не удалось создать библиотеку",
"ToastLibraryCreateSuccess": "Библиотека \"{0}\" создана",
@@ -897,7 +886,6 @@
"ToastLibraryDeleteSuccess": "Библиотека удалена",
"ToastLibraryScanFailedToStart": "Не удалось запустить сканирование",
"ToastLibraryScanStarted": "Запущено сканирование библиотеки",
- "ToastLibraryUpdateFailed": "Не удалось обновить библиотеку",
"ToastLibraryUpdateSuccess": "Библиотека \"{0}\" обновлена",
"ToastNameEmailRequired": "Имя и адрес электронной почты обязательны",
"ToastNameRequired": "Имя обязательно для заполнения",
@@ -912,16 +900,13 @@
"ToastNotificationDeleteFailed": "Не удалось удалить уведомление",
"ToastNotificationFailedMaximum": "Максимальное количество неудачных попыток должно быть >= 0",
"ToastNotificationQueueMaximum": "Максимальная очередь уведомлений должна быть >= 0",
- "ToastNotificationSettingsUpdateFailed": "Не удалось обновить настройки уведомлений",
"ToastNotificationSettingsUpdateSuccess": "Обновлены настройки уведомлений",
"ToastNotificationTestTriggerFailed": "Не удалось активировать тестовое уведомление",
"ToastNotificationTestTriggerSuccess": "Сработавшее уведомление о тестировании",
- "ToastNotificationUpdateFailed": "Не удалось обновить уведомление",
"ToastNotificationUpdateSuccess": "Уведомление обновлено",
"ToastPlaylistCreateFailed": "Не удалось создать плейлист",
"ToastPlaylistCreateSuccess": "Плейлист создан",
"ToastPlaylistRemoveSuccess": "Плейлист удален",
- "ToastPlaylistUpdateFailed": "Не удалось обновить плейлист",
"ToastPlaylistUpdateSuccess": "Плейлист обновлен",
"ToastPodcastCreateFailed": "Не удалось создать подкаст",
"ToastPodcastCreateSuccess": "Подкаст успешно создан",
@@ -950,7 +935,6 @@
"ToastSendEbookToDeviceSuccess": "E-книга отправлена на устройство \"{0}\"",
"ToastSeriesUpdateFailed": "Не удалось обновить серию",
"ToastSeriesUpdateSuccess": "Успешное обновление серии",
- "ToastServerSettingsUpdateFailed": "Не удалось обновить настройки сервера",
"ToastServerSettingsUpdateSuccess": "Обновлены настройки сервера",
"ToastSessionCloseFailed": "Не удалось закрыть сеанс",
"ToastSessionDeleteFailed": "Не удалось удалить сеанс",
@@ -961,7 +945,6 @@
"ToastSocketDisconnected": "Сокет отключен",
"ToastSocketFailedToConnect": "Не удалось подключить сокет",
"ToastSortingPrefixesEmptyError": "Должен быть хотя бы 1 префикс сортировки",
- "ToastSortingPrefixesUpdateFailed": "Не удалось обновить префиксы сортировки",
"ToastSortingPrefixesUpdateSuccess": "Обновлены префиксы сортировки ({0} элементов)",
"ToastTitleRequired": "Название обязательно",
"ToastUnknownError": "Неизвестная ошибка",
diff --git a/client/strings/sl.json b/client/strings/sl.json
index b6ae7f521..28655c520 100644
--- a/client/strings/sl.json
+++ b/client/strings/sl.json
@@ -56,6 +56,7 @@
"ButtonOpenManager": "Odpri upravljanje",
"ButtonPause": "Premor",
"ButtonPlay": "Predvajaj",
+ "ButtonPlayAll": "Predvajaj vse",
"ButtonPlaying": "Predvajam",
"ButtonPlaylists": "Seznami predvajanj",
"ButtonPrevious": "Prejšnje",
@@ -96,7 +97,7 @@
"ButtonStartM4BEncode": "Zaženi M4B prekodiranje",
"ButtonStartMetadataEmbed": "Začni vdelavo metapodatkov",
"ButtonStats": "Statistika",
- "ButtonSubmit": "Posreduj",
+ "ButtonSubmit": "Potrdi",
"ButtonTest": "Test",
"ButtonUnlinkOpenId": "Prekini povezavo OpenID",
"ButtonUpload": "Naloži",
@@ -776,6 +777,38 @@
"MessageShareExpiresIn": "Poteče čez {0}",
"MessageShareURLWillBe": "URL za skupno rabo bo
{0}",
"MessageStartPlaybackAtTime": "Začni predvajanje za \"{0}\" ob {1}?",
+ "MessageTaskAudioFileNotWritable": "Zvočna datoteka \"{0}\" ni zapisljiva",
+ "MessageTaskCanceledByUser": "Nalogo je preklical uporabnik",
+ "MessageTaskDownloadingEpisodeDescription": "Prenašanje epizode \"{0}\"",
+ "MessageTaskEmbeddingMetadata": "Vdelujem metapodatke",
+ "MessageTaskEmbeddingMetadataDescription": "Vdelujem metapodatke v zvočno knjigo \"{0}\"",
+ "MessageTaskEncodingM4b": "Enkodiranje M4B",
+ "MessageTaskEncodingM4bDescription": "Enkodiranje zvočne knjige \"{0}\" v samo eno datoteko m4b",
+ "MessageTaskFailed": "Neuspešno",
+ "MessageTaskFailedToBackupAudioFile": "Varnostno kopiranje zvočne datoteke \"{0}\" ni uspelo",
+ "MessageTaskFailedToCreateCacheDirectory": "Imenika predpomnilnika ni bilo mogoče ustvariti",
+ "MessageTaskFailedToEmbedMetadataInFile": "Metapodatkov ni bilo mogoče vdelati v datoteko \"{0}\"",
+ "MessageTaskFailedToMergeAudioFiles": "Zvočnih datotek ni bilo mogoče združiti",
+ "MessageTaskFailedToMoveM4bFile": "Datoteke m4b ni bilo mogoče premakniti",
+ "MessageTaskFailedToWriteMetadataFile": "Metapodatke ni bilo mogoče zapisati v datoteke",
+ "MessageTaskMatchingBooksInLibrary": "Ujemam knjige v knjižnici \"{0}\"",
+ "MessageTaskNoFilesToScan": "Ni datotek za pregledovanje",
+ "MessageTaskOpmlImport": "Uvoz OPML",
+ "MessageTaskOpmlImportDescription": "Ustvarjanje podcastov iz {0} virov RSS",
+ "MessageTaskOpmlImportFeed": "Vir za uvoz OPML",
+ "MessageTaskOpmlImportFeedDescription": "Uvažanje vira RSS \"{0}\"",
+ "MessageTaskOpmlImportFeedFailed": "Vira podcasta ni bilo mogoče pridobiti",
+ "MessageTaskOpmlImportFeedPodcastDescription": "Ustvarjanje podcasta \"{0}\"",
+ "MessageTaskOpmlImportFeedPodcastExists": "Podcast že obstaja na tej poti",
+ "MessageTaskOpmlImportFeedPodcastFailed": "Podcasta ni bilo mogoče ustvariti",
+ "MessageTaskOpmlImportFinished": "Dodanih {0} podcastov",
+ "MessageTaskScanItemsAdded": "{0} dodano",
+ "MessageTaskScanItemsMissing": "{0} manjka",
+ "MessageTaskScanItemsUpdated": "{0} posodobljeno",
+ "MessageTaskScanNoChangesNeeded": "Spremembe niso potrebne",
+ "MessageTaskScanningFileChanges": "Pregledovanje sprememb v datoteki \"{0}\"",
+ "MessageTaskScanningLibrary": "Pregled knjižnice \"{0}\"",
+ "MessageTaskTargetDirectoryNotWritable": "Ciljni imenik ni zapisljiv",
"MessageThinking": "Razmišljam...",
"MessageUploaderItemFailed": "Nalaganje ni uspelo",
"MessageUploaderItemSuccess": "Uspešno naloženo!",
@@ -816,14 +849,12 @@
"StatsTopNarrators": "TOP BRALCI",
"StatsTotalDuration": "S skupnim trajanjem…",
"StatsYearInReview": "PREGLED LETA",
- "ToastAccountUpdateFailed": "Računa ni bilo mogoče posodobiti",
"ToastAccountUpdateSuccess": "Račun posodobljen",
"ToastAppriseUrlRequired": "Vnesti morate Apprise URL",
"ToastAuthorImageRemoveSuccess": "Slika avtorja je odstranjena",
"ToastAuthorNotFound": "Avtor \"{0}\" ni bil najden",
"ToastAuthorRemoveSuccess": "Avtor odstranjen",
"ToastAuthorSearchNotFound": "Ne najdem avtorja",
- "ToastAuthorUpdateFailed": "Avtorja ni bilo mogoče posodobiti",
"ToastAuthorUpdateMerged": "Avtor združen",
"ToastAuthorUpdateSuccess": "Avtor posodobljen",
"ToastAuthorUpdateSuccessNoImageFound": "Avtor posodobljen (ne najdem slike)",
@@ -834,7 +865,6 @@
"ToastBackupDeleteSuccess": "Varnostna kopija izbrisana",
"ToastBackupInvalidMaxKeep": "Neveljavno število varnostnih kopij za ohranjanje",
"ToastBackupInvalidMaxSize": "Neveljavna največja velikost varnostne kopije",
- "ToastBackupPathUpdateFailed": "Posodobitev poti varnostnih kopij ni uspela",
"ToastBackupRestoreFailed": "Varnostne kopije ni bilo mogoče obnoviti",
"ToastBackupUploadFailed": "Nalaganje varnostne kopije ni uspelo",
"ToastBackupUploadSuccess": "Varnostna kopija je naložena",
@@ -845,7 +875,6 @@
"ToastBookmarkCreateFailed": "Zaznamka ni bilo mogoče ustvariti",
"ToastBookmarkCreateSuccess": "Zaznamek dodan",
"ToastBookmarkRemoveSuccess": "Zaznamek odstranjen",
- "ToastBookmarkUpdateFailed": "Zaznamka ni bilo mogoče posodobiti",
"ToastBookmarkUpdateSuccess": "Zaznamek posodobljen",
"ToastCachePurgeFailed": "Čiščenje predpomnilnika ni uspelo",
"ToastCachePurgeSuccess": "Predpomnilnik je bil uspešno očiščen",
@@ -856,7 +885,6 @@
"ToastCollectionItemsAddSuccess": "Dodajanje elementov v zbirko je bilo uspešno",
"ToastCollectionItemsRemoveSuccess": "Elementi so bili odstranjeni iz zbirke",
"ToastCollectionRemoveSuccess": "Zbirka je bila odstranjena",
- "ToastCollectionUpdateFailed": "Zbirke ni bilo mogoče posodobiti",
"ToastCollectionUpdateSuccess": "Zbirka je bila posodobljena",
"ToastCoverUpdateFailed": "Posodobitev naslovnice ni uspela",
"ToastDeleteFileFailed": "Brisanje datoteke ni uspelo",
@@ -865,8 +893,6 @@
"ToastDeviceNameAlreadyExists": "Elektronska naprava s tem imenom že obstaja",
"ToastDeviceTestEmailFailed": "Pošiljanje testnega e-poštnega sporočila ni uspelo",
"ToastDeviceTestEmailSuccess": "Testno e-poštno sporočilo je poslano",
- "ToastDeviceUpdateFailed": "Naprave ni bilo mogoče posodobiti",
- "ToastEmailSettingsUpdateFailed": "E-poštnih nastavitev ni bilo mogoče posodobiti",
"ToastEmailSettingsUpdateSuccess": "E-poštne nastavitve so bile posodobljene",
"ToastEncodeCancelFailed": "Napaka pri preklicu prekodiranja",
"ToastEncodeCancelSucces": "Prekodiranje prekinjeno",
@@ -875,21 +901,17 @@
"ToastErrorCannotShare": "V tej napravi ni mogoče dati v skupno rabo",
"ToastFailedToLoadData": "Podatkov ni bilo mogoče naložiti",
"ToastFailedToShare": "Skupna raba ni uspela",
- "ToastFailedToUpdateAccount": "Računa ni bilo mogoče posodobiti",
- "ToastFailedToUpdateUser": "Uporabnika ni bilo mogoče posodobiti",
+ "ToastFailedToUpdate": "Napaka pri posodobitvi",
"ToastInvalidImageUrl": "Neveljaven URL slike",
"ToastInvalidUrl": "Neveljaven URL",
- "ToastItemCoverUpdateFailed": "Naslovnice elementa ni bilo mogoče posodobiti",
"ToastItemCoverUpdateSuccess": "Naslovnica elementa je bila posodobljena",
"ToastItemDeletedFailed": "Elementa ni bilo mogoče izbrisati",
"ToastItemDeletedSuccess": "Element je bil izbrisan",
- "ToastItemDetailsUpdateFailed": "Posodobitev podrobnosti elementa ni uspela",
"ToastItemDetailsUpdateSuccess": "Podrobnosti elementa so bile posodobjene",
"ToastItemMarkedAsFinishedFailed": "Označevanje kot dokončano ni uspelo",
"ToastItemMarkedAsFinishedSuccess": "Element je označen kot dokončan",
"ToastItemMarkedAsNotFinishedFailed": "Ni bilo mogoče označiti kot nedokončano",
"ToastItemMarkedAsNotFinishedSuccess": "Element označen kot nedokončan",
- "ToastItemUpdateFailed": "Elementa ni bilo mogoče posodobiti",
"ToastItemUpdateSuccess": "Element je bil posodobljen",
"ToastLibraryCreateFailed": "Knjižnice ni bilo mogoče ustvariti",
"ToastLibraryCreateSuccess": "Knjižnica \"{0}\" je bila ustvarjena",
@@ -897,7 +919,6 @@
"ToastLibraryDeleteSuccess": "Knjižnica je bila izbrisana",
"ToastLibraryScanFailedToStart": "Pregleda ni bilo mogoče začeti",
"ToastLibraryScanStarted": "Pregled knjižnice se je začel",
- "ToastLibraryUpdateFailed": "Knjižnice ni bilo mogoče posodobiti",
"ToastLibraryUpdateSuccess": "Knjižnica \"{0}\" je bila posodobljena",
"ToastNameEmailRequired": "Ime in e-pošta sta obvezna",
"ToastNameRequired": "Ime je obvezno",
@@ -912,16 +933,13 @@
"ToastNotificationDeleteFailed": "Brisanje obvestila ni uspelo",
"ToastNotificationFailedMaximum": "Največje število neuspelih poskusov mora biti >= 0",
"ToastNotificationQueueMaximum": "Največja čakalna vrsta obvestil mora biti >= 0",
- "ToastNotificationSettingsUpdateFailed": "Nastavitev obvestil ni bilo mogoče posodobiti",
"ToastNotificationSettingsUpdateSuccess": "Nastavitve obvestil so bile posodobljene",
"ToastNotificationTestTriggerFailed": "Sprožitev testnega obvestila ni uspela",
"ToastNotificationTestTriggerSuccess": "Sproženo testno obvestilo",
- "ToastNotificationUpdateFailed": "Obvestila ni bilo mogoče posodobiti",
"ToastNotificationUpdateSuccess": "Obvestilo posodobljeno",
"ToastPlaylistCreateFailed": "Seznama predvajanja ni bilo mogoče ustvariti",
"ToastPlaylistCreateSuccess": "Seznam predvajanja je bil ustvarjen",
"ToastPlaylistRemoveSuccess": "Seznam predvajanja odstranjen",
- "ToastPlaylistUpdateFailed": "Seznama predvajanja ni bilo mogoče posodobiti",
"ToastPlaylistUpdateSuccess": "Seznam predvajanja je bil posodobljen",
"ToastPodcastCreateFailed": "Podcasta ni bilo mogoče ustvariti",
"ToastPodcastCreateSuccess": "Podcast je bil uspešno ustvarjen",
@@ -950,7 +968,6 @@
"ToastSendEbookToDeviceSuccess": "E-knjiga je bila poslana v napravo \"{0}\"",
"ToastSeriesUpdateFailed": "Posodobitev serije ni uspela",
"ToastSeriesUpdateSuccess": "Uspešna posodobitev serije",
- "ToastServerSettingsUpdateFailed": "Nastavitev strežnika ni bilo mogoče posodobiti",
"ToastServerSettingsUpdateSuccess": "Nastavitve strežnika so bile posodobljene",
"ToastSessionCloseFailed": "Seje ni bilo mogoče zapreti",
"ToastSessionDeleteFailed": "Brisanje seje ni uspelo",
@@ -961,7 +978,6 @@
"ToastSocketDisconnected": "Omrežna povezava je odklopljena",
"ToastSocketFailedToConnect": "Omrežna povezava ni uspela vzpostaviti priklopa",
"ToastSortingPrefixesEmptyError": "Imeti mora vsaj 1 predpono za razvrščanje",
- "ToastSortingPrefixesUpdateFailed": "Posodobitev predpon za razvrščanje ni uspela",
"ToastSortingPrefixesUpdateSuccess": "Predpone za razvrščanje so bile posodobljene ({0} elementov)",
"ToastTitleRequired": "Naslov je obvezen",
"ToastUnknownError": "Neznana napaka",
diff --git a/client/strings/sv.json b/client/strings/sv.json
index 0db5cbd3a..0d156efd7 100644
--- a/client/strings/sv.json
+++ b/client/strings/sv.json
@@ -45,6 +45,7 @@
"ButtonOk": "Okej",
"ButtonOpenFeed": "Öppna flöde",
"ButtonOpenManager": "Öppna Manager",
+ "ButtonPause": "Pausa",
"ButtonPlay": "Spela",
"ButtonPlaying": "Spelar",
"ButtonPlaylists": "Spellistor",
@@ -263,8 +264,10 @@
"LabelFinished": "Avslutad",
"LabelFolder": "Mapp",
"LabelFolders": "Mappar",
+ "LabelFontBoldness": "Fetstil",
"LabelFontFamily": "Teckensnittsfamilj",
"LabelFontScale": "Teckensnittsskala",
+ "LabelGenre": "Genre",
"LabelGenres": "Genrer",
"LabelHardDeleteFile": "Hård radering av fil",
"LabelHasEbook": "Har E-bok",
@@ -294,6 +297,7 @@
"LabelLastSeen": "Senast sedd",
"LabelLastTime": "Senaste gången",
"LabelLastUpdate": "Senaste uppdatering",
+ "LabelLayout": "Layout",
"LabelLayoutSinglePage": "En sida",
"LabelLayoutSplitPage": "Dela sida",
"LabelLess": "Mindre",
@@ -322,8 +326,8 @@
"LabelNarrators": "Berättare",
"LabelNew": "Ny",
"LabelNewPassword": "Nytt lösenord",
- "LabelNewestAuthors": "Nyaste författare",
- "LabelNewestEpisodes": "Nyaste avsnitt",
+ "LabelNewestAuthors": "Senast tillagda författare",
+ "LabelNewestEpisodes": "Senast tillagda avsnitt",
"LabelNextBackupDate": "Nästa säkerhetskopia datum",
"LabelNextScheduledRun": "Nästa schemalagda körning",
"LabelNoEpisodesSelected": "Inga avsnitt valda",
@@ -355,8 +359,10 @@
"LabelPhotoPathURL": "Bildsökväg/URL",
"LabelPlayMethod": "Spelläge",
"LabelPlaylists": "Spellistor",
+ "LabelPodcast": "Podcast",
"LabelPodcastSearchRegion": "Podcast-sökområde",
"LabelPodcastType": "Podcasttyp",
+ "LabelPodcasts": "Podcasts",
"LabelPrefixesToIgnore": "Prefix att ignorera (skiftlägesokänsligt)",
"LabelPreventIndexing": "Förhindra att ditt flöde indexeras av iTunes och Google-podcastsökmotorer",
"LabelPrimaryEbook": "Primär e-bok",
@@ -371,6 +377,7 @@
"LabelRSSFeedPreventIndexing": "Förhindra indexering",
"LabelRSSFeedSlug": "RSS-flödesslag",
"LabelRSSFeedURL": "RSS-flöde URL",
+ "LabelRandomly": "Slumpartat",
"LabelRead": "Läst",
"LabelReadAgain": "Läs igen",
"LabelReadEbookWithoutProgress": "Läs e-bok utan att behålla framsteg",
@@ -430,6 +437,7 @@
"LabelShowAll": "Visa alla",
"LabelSize": "Storlek",
"LabelSleepTimer": "Sleeptimer",
+ "LabelStart": "Starta",
"LabelStartTime": "Starttid",
"LabelStarted": "Startad",
"LabelStartedAt": "Startad vid",
@@ -637,10 +645,8 @@
"PlaceholderNewPlaylist": "Nytt spellistanamn",
"PlaceholderSearch": "Sök...",
"PlaceholderSearchEpisode": "Sök avsnitt...",
- "ToastAccountUpdateFailed": "Det gick inte att uppdatera kontot",
"ToastAccountUpdateSuccess": "Kontot uppdaterat",
"ToastAuthorImageRemoveSuccess": "Författarens bild borttagen",
- "ToastAuthorUpdateFailed": "Det gick inte att uppdatera författaren",
"ToastAuthorUpdateMerged": "Författaren sammanslagen",
"ToastAuthorUpdateSuccess": "Författaren uppdaterad",
"ToastAuthorUpdateSuccessNoImageFound": "Författaren uppdaterad (ingen bild hittad)",
@@ -656,17 +662,13 @@
"ToastBookmarkCreateFailed": "Det gick inte att skapa bokmärket",
"ToastBookmarkCreateSuccess": "Bokmärket tillagt",
"ToastBookmarkRemoveSuccess": "Bokmärket borttaget",
- "ToastBookmarkUpdateFailed": "Det gick inte att uppdatera bokmärket",
"ToastBookmarkUpdateSuccess": "Bokmärket uppdaterat",
"ToastChaptersHaveErrors": "Kapitlen har fel",
"ToastChaptersMustHaveTitles": "Kapitel måste ha titlar",
"ToastCollectionItemsRemoveSuccess": "Objekt borttagna från samlingen",
"ToastCollectionRemoveSuccess": "Samlingen borttagen",
- "ToastCollectionUpdateFailed": "Det gick inte att uppdatera samlingen",
"ToastCollectionUpdateSuccess": "Samlingen uppdaterad",
- "ToastItemCoverUpdateFailed": "Det gick inte att uppdatera objektets omslag",
"ToastItemCoverUpdateSuccess": "Objektets omslag uppdaterat",
- "ToastItemDetailsUpdateFailed": "Det gick inte att uppdatera objektdetaljerna",
"ToastItemDetailsUpdateSuccess": "Objektdetaljer uppdaterade",
"ToastItemMarkedAsFinishedFailed": "Misslyckades med att markera som färdig",
"ToastItemMarkedAsFinishedSuccess": "Objekt markerat som färdig",
@@ -678,12 +680,10 @@
"ToastLibraryDeleteSuccess": "Biblioteket borttaget",
"ToastLibraryScanFailedToStart": "Misslyckades med att starta skanningen",
"ToastLibraryScanStarted": "Skanning av biblioteket påbörjad",
- "ToastLibraryUpdateFailed": "Det gick inte att uppdatera biblioteket",
"ToastLibraryUpdateSuccess": "Biblioteket \"{0}\" uppdaterat",
"ToastPlaylistCreateFailed": "Det gick inte att skapa spellistan",
"ToastPlaylistCreateSuccess": "Spellistan skapad",
"ToastPlaylistRemoveSuccess": "Spellistan borttagen",
- "ToastPlaylistUpdateFailed": "Det gick inte att uppdatera spellistan",
"ToastPlaylistUpdateSuccess": "Spellistan uppdaterad",
"ToastPodcastCreateFailed": "Misslyckades med att skapa podcasten",
"ToastPodcastCreateSuccess": "Podcasten skapad framgångsrikt",
diff --git a/client/strings/uk.json b/client/strings/uk.json
index d1a161c85..4c1941d4e 100644
--- a/client/strings/uk.json
+++ b/client/strings/uk.json
@@ -785,10 +785,8 @@
"StatsTopNarrators": "УЛЮБЛЕНІ ЧИТЦІ",
"StatsTotalDuration": "Загальною довжиною…",
"StatsYearInReview": "ОГЛЯД РОКУ",
- "ToastAccountUpdateFailed": "Не вдалося оновити профіль",
"ToastAccountUpdateSuccess": "Профіль оновлено",
"ToastAuthorImageRemoveSuccess": "Фото автора видалено",
- "ToastAuthorUpdateFailed": "Не вдалося оновити автора",
"ToastAuthorUpdateMerged": "Автора об'єднано",
"ToastAuthorUpdateSuccess": "Автора оновлено",
"ToastAuthorUpdateSuccessNoImageFound": "Автора оновлено (фото не знайдено)",
@@ -804,7 +802,6 @@
"ToastBookmarkCreateFailed": "Не вдалося створити закладку",
"ToastBookmarkCreateSuccess": "Закладку додано",
"ToastBookmarkRemoveSuccess": "Закладку видалено",
- "ToastBookmarkUpdateFailed": "Не вдалося оновити закладку",
"ToastBookmarkUpdateSuccess": "Закладку оновлено",
"ToastCachePurgeFailed": "Не вдалося очистити кеш",
"ToastCachePurgeSuccess": "Кеш очищено",
@@ -812,15 +809,12 @@
"ToastChaptersMustHaveTitles": "Глави повинні мати назви",
"ToastCollectionItemsRemoveSuccess": "Елемент(и) видалено з добірки",
"ToastCollectionRemoveSuccess": "Добірку видалено",
- "ToastCollectionUpdateFailed": "Не вдалося оновити добірку",
"ToastCollectionUpdateSuccess": "Добірку оновлено",
"ToastDeleteFileFailed": "Не вдалося видалити файл",
"ToastDeleteFileSuccess": "Файл видалено",
"ToastErrorCannotShare": "Не можна типово поширити на цей пристрій",
"ToastFailedToLoadData": "Не вдалося завантажити дані",
- "ToastItemCoverUpdateFailed": "Не вдалося оновити обкладинку",
"ToastItemCoverUpdateSuccess": "Обкладинку елемента оновлено",
- "ToastItemDetailsUpdateFailed": "Не вдалося оновити подробиці елемента",
"ToastItemDetailsUpdateSuccess": "Подробиці про елемент оновлено",
"ToastItemMarkedAsFinishedFailed": "Не вдалося позначити як завершене",
"ToastItemMarkedAsFinishedSuccess": "Елемент позначено як завершений",
@@ -832,12 +826,10 @@
"ToastLibraryDeleteSuccess": "Бібліотеку видалено",
"ToastLibraryScanFailedToStart": "Не вдалося розпочати сканування",
"ToastLibraryScanStarted": "Почалося сканування бібліотеки",
- "ToastLibraryUpdateFailed": "Не вдалося оновити бібліотеку",
"ToastLibraryUpdateSuccess": "Бібліотеку \"{0}\" оновлено",
"ToastPlaylistCreateFailed": "Не вдалося створити список",
"ToastPlaylistCreateSuccess": "Список відтворення створено",
"ToastPlaylistRemoveSuccess": "Список відтворення видалено",
- "ToastPlaylistUpdateFailed": "Не вдалося оновити список",
"ToastPlaylistUpdateSuccess": "Список відтворення оновлено",
"ToastPodcastCreateFailed": "Не вдалося створити подкаст",
"ToastPodcastCreateSuccess": "Подкаст успішно створено",
@@ -849,7 +841,6 @@
"ToastSendEbookToDeviceSuccess": "Електронну книгу надіслано на пристрій \"{0}\"",
"ToastSeriesUpdateFailed": "Не вдалося оновити серію",
"ToastSeriesUpdateSuccess": "Серію успішно оновлено",
- "ToastServerSettingsUpdateFailed": "Не вдалося оновити налаштування сервера",
"ToastServerSettingsUpdateSuccess": "Налаштування сервера оновлено",
"ToastSessionDeleteFailed": "Не вдалося видалити сесію",
"ToastSessionDeleteSuccess": "Сесію видалено",
@@ -857,7 +848,6 @@
"ToastSocketDisconnected": "Сокет від'єднано",
"ToastSocketFailedToConnect": "Не вдалося під'єднатися до сокета",
"ToastSortingPrefixesEmptyError": "Мусить мати хоча б 1 префікс сортування",
- "ToastSortingPrefixesUpdateFailed": "Не вдалося оновити префікси сортування",
"ToastSortingPrefixesUpdateSuccess": "Префікси сортування оновлено ({0})",
"ToastUserDeleteFailed": "Не вдалося видалити користувача",
"ToastUserDeleteSuccess": "Користувача видалено"
diff --git a/client/strings/vi-vn.json b/client/strings/vi-vn.json
index 02fa75f58..8b0ca165c 100644
--- a/client/strings/vi-vn.json
+++ b/client/strings/vi-vn.json
@@ -663,10 +663,8 @@
"PlaceholderNewPlaylist": "Tên danh sách phát mới",
"PlaceholderSearch": "Tìm kiếm..",
"PlaceholderSearchEpisode": "Tìm kiếm tập..",
- "ToastAccountUpdateFailed": "Cập nhật tài khoản thất bại",
"ToastAccountUpdateSuccess": "Tài khoản đã được cập nhật",
"ToastAuthorImageRemoveSuccess": "Ảnh tác giả đã được xóa",
- "ToastAuthorUpdateFailed": "Cập nhật tác giả thất bại",
"ToastAuthorUpdateMerged": "Tác giả đã được hợp nhất",
"ToastAuthorUpdateSuccess": "Cập nhật tác giả thành công",
"ToastAuthorUpdateSuccessNoImageFound": "Cập nhật tác giả thành công (không tìm thấy ảnh)",
@@ -682,17 +680,13 @@
"ToastBookmarkCreateFailed": "Tạo đánh dấu thất bại",
"ToastBookmarkCreateSuccess": "Đã thêm đánh dấu",
"ToastBookmarkRemoveSuccess": "Đánh dấu đã được xóa",
- "ToastBookmarkUpdateFailed": "Cập nhật đánh dấu thất bại",
"ToastBookmarkUpdateSuccess": "Đánh dấu đã được cập nhật",
"ToastChaptersHaveErrors": "Các chương có lỗi",
"ToastChaptersMustHaveTitles": "Các chương phải có tiêu đề",
"ToastCollectionItemsRemoveSuccess": "Mục đã được xóa khỏi bộ sưu tập",
"ToastCollectionRemoveSuccess": "Bộ sưu tập đã được xóa",
- "ToastCollectionUpdateFailed": "Cập nhật bộ sưu tập thất bại",
"ToastCollectionUpdateSuccess": "Bộ sưu tập đã được cập nhật",
- "ToastItemCoverUpdateFailed": "Cập nhật ảnh bìa mục thất bại",
"ToastItemCoverUpdateSuccess": "Ảnh bìa mục đã được cập nhật",
- "ToastItemDetailsUpdateFailed": "Cập nhật chi tiết mục thất bại",
"ToastItemDetailsUpdateSuccess": "Chi tiết mục đã được cập nhật",
"ToastItemMarkedAsFinishedFailed": "Đánh dấu mục là Hoàn thành thất bại",
"ToastItemMarkedAsFinishedSuccess": "Mục đã được đánh dấu là Hoàn thành",
@@ -704,12 +698,10 @@
"ToastLibraryDeleteSuccess": "Thư viện đã được xóa",
"ToastLibraryScanFailedToStart": "Không thể bắt đầu quét thư viện",
"ToastLibraryScanStarted": "Quét thư viện đã được bắt đầu",
- "ToastLibraryUpdateFailed": "Cập nhật thư viện thất bại",
"ToastLibraryUpdateSuccess": "Thư viện \"{0}\" đã được cập nhật",
"ToastPlaylistCreateFailed": "Tạo danh sách phát thất bại",
"ToastPlaylistCreateSuccess": "Danh sách phát đã được tạo",
"ToastPlaylistRemoveSuccess": "Danh sách phát đã được xóa",
- "ToastPlaylistUpdateFailed": "Cập nhật danh sách phát thất bại",
"ToastPlaylistUpdateSuccess": "Danh sách phát đã được cập nhật",
"ToastPodcastCreateFailed": "Tạo podcast thất bại",
"ToastPodcastCreateSuccess": "Podcast đã được tạo thành công",
diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json
index df0d77f7c..e1b1cbbc0 100644
--- a/client/strings/zh-cn.json
+++ b/client/strings/zh-cn.json
@@ -1,5 +1,5 @@
{
- "ButtonAdd": "增加",
+ "ButtonAdd": "添加",
"ButtonAddChapters": "添加章节",
"ButtonAddDevice": "添加设备",
"ButtonAddLibrary": "添加库",
@@ -47,7 +47,7 @@
"ButtonMapChapterTitles": "章节标题结构",
"ButtonMatchAllAuthors": "匹配所有作者",
"ButtonMatchBooks": "匹配图书",
- "ButtonNevermind": "没有关系",
+ "ButtonNevermind": "取消",
"ButtonNext": "下一个",
"ButtonNextChapter": "下一章节",
"ButtonNextItemInQueue": "队列中的下一个项目",
@@ -56,6 +56,7 @@
"ButtonOpenManager": "打开管理器",
"ButtonPause": "暂停",
"ButtonPlay": "播放",
+ "ButtonPlayAll": "播放",
"ButtonPlaying": "正在播放",
"ButtonPlaylists": "播放列表",
"ButtonPrevious": "上一个",
@@ -134,7 +135,7 @@
"HeaderEmailSettings": "邮箱设置",
"HeaderEpisodes": "剧集",
"HeaderEreaderDevices": "Ereader 设备",
- "HeaderEreaderSettings": "Ereader 设置",
+ "HeaderEreaderSettings": "电子阅读器设置",
"HeaderFiles": "文件",
"HeaderFindChapters": "查找章节",
"HeaderIgnoredFiles": "忽略的文件",
@@ -776,6 +777,38 @@
"MessageShareExpiresIn": "到期时间 {0}",
"MessageShareURLWillBe": "分享网址是
{0}",
"MessageStartPlaybackAtTime": "开始播放 \"{0}\" 在 {1}?",
+ "MessageTaskAudioFileNotWritable": "音频文件 \"{0}\" 不可写",
+ "MessageTaskCanceledByUser": "任务已被用户取消",
+ "MessageTaskDownloadingEpisodeDescription": "正在下载剧集 \"{0}\"",
+ "MessageTaskEmbeddingMetadata": "嵌入元数据",
+ "MessageTaskEmbeddingMetadataDescription": "在有声读物 \"{0}\" 中嵌入元数据",
+ "MessageTaskEncodingM4b": "编码 M4B",
+ "MessageTaskEncodingM4bDescription": "将有声读物 \"{0}\" 编码为单个 m4b 文件",
+ "MessageTaskFailed": "失败",
+ "MessageTaskFailedToBackupAudioFile": "无法备份音频文件 \"{0}\"",
+ "MessageTaskFailedToCreateCacheDirectory": "无法创建缓存目录",
+ "MessageTaskFailedToEmbedMetadataInFile": "无法将元数据嵌入文件 \"{0}\"",
+ "MessageTaskFailedToMergeAudioFiles": "无法合并音频文件",
+ "MessageTaskFailedToMoveM4bFile": "无法移动 m4b 文件",
+ "MessageTaskFailedToWriteMetadataFile": "无法写入元数据文件",
+ "MessageTaskMatchingBooksInLibrary": "在库中匹配图书 \"{0}\"",
+ "MessageTaskNoFilesToScan": "没有要扫描的文件",
+ "MessageTaskOpmlImport": "OPML 导入",
+ "MessageTaskOpmlImportDescription": "通过 {0} RSS 源创建播客",
+ "MessageTaskOpmlImportFeed": "OPML 导入源",
+ "MessageTaskOpmlImportFeedDescription": "正在导入 RSS 源 \"{0}\"",
+ "MessageTaskOpmlImportFeedFailed": "无法获取播客信息",
+ "MessageTaskOpmlImportFeedPodcastDescription": "正在创建播客 \"{0}\"",
+ "MessageTaskOpmlImportFeedPodcastExists": "播客已存在于路径中",
+ "MessageTaskOpmlImportFeedPodcastFailed": "无法创建播客",
+ "MessageTaskOpmlImportFinished": "已添加 {0} 播客",
+ "MessageTaskScanItemsAdded": "{0} 已添加",
+ "MessageTaskScanItemsMissing": "{0} 已缺失",
+ "MessageTaskScanItemsUpdated": "{0} 已更新",
+ "MessageTaskScanNoChangesNeeded": "无需改变",
+ "MessageTaskScanningFileChanges": "正在扫描文件更改 \"{0}\"",
+ "MessageTaskScanningLibrary": "扫描 \"{0}\" 库",
+ "MessageTaskTargetDirectoryNotWritable": "目标目录不可写",
"MessageThinking": "正在查找...",
"MessageUploaderItemFailed": "上传失败",
"MessageUploaderItemSuccess": "上传成功!",
@@ -816,14 +849,12 @@
"StatsTopNarrators": "最佳叙述者",
"StatsTotalDuration": "总时长为…",
"StatsYearInReview": "年度回顾",
- "ToastAccountUpdateFailed": "账户更新失败",
"ToastAccountUpdateSuccess": "帐户已更新",
"ToastAppriseUrlRequired": "必须输入 Apprise URL",
"ToastAuthorImageRemoveSuccess": "作者图像已删除",
"ToastAuthorNotFound": "未找到作者 \"{0}\"",
"ToastAuthorRemoveSuccess": "作者已删除",
"ToastAuthorSearchNotFound": "未找到作者",
- "ToastAuthorUpdateFailed": "作者更新失败",
"ToastAuthorUpdateMerged": "作者已合并",
"ToastAuthorUpdateSuccess": "作者已更新",
"ToastAuthorUpdateSuccessNoImageFound": "作者已更新 (未找到图像)",
@@ -834,7 +865,6 @@
"ToastBackupDeleteSuccess": "备份已删除",
"ToastBackupInvalidMaxKeep": "要保留的备份数无效",
"ToastBackupInvalidMaxSize": "最大备份大小无效",
- "ToastBackupPathUpdateFailed": "无法更新备份路径",
"ToastBackupRestoreFailed": "备份还原失败",
"ToastBackupUploadFailed": "上传备份失败",
"ToastBackupUploadSuccess": "备份已上传",
@@ -845,7 +875,6 @@
"ToastBookmarkCreateFailed": "创建书签失败",
"ToastBookmarkCreateSuccess": "书签已添加",
"ToastBookmarkRemoveSuccess": "书签已删除",
- "ToastBookmarkUpdateFailed": "书签更新失败",
"ToastBookmarkUpdateSuccess": "书签已更新",
"ToastCachePurgeFailed": "清除缓存失败",
"ToastCachePurgeSuccess": "缓存清除成功",
@@ -856,7 +885,6 @@
"ToastCollectionItemsAddSuccess": "项目添加到收藏夹成功",
"ToastCollectionItemsRemoveSuccess": "项目从收藏夹移除",
"ToastCollectionRemoveSuccess": "收藏夹已删除",
- "ToastCollectionUpdateFailed": "更新收藏夹失败",
"ToastCollectionUpdateSuccess": "收藏夹已更新",
"ToastCoverUpdateFailed": "封面更新失败",
"ToastDeleteFileFailed": "删除文件失败",
@@ -865,8 +893,6 @@
"ToastDeviceNameAlreadyExists": "同名的电子阅读器设备已存在",
"ToastDeviceTestEmailFailed": "无法发送测试电子邮件",
"ToastDeviceTestEmailSuccess": "测试邮件已发送",
- "ToastDeviceUpdateFailed": "无法更新设备",
- "ToastEmailSettingsUpdateFailed": "无法更新电子邮件设置",
"ToastEmailSettingsUpdateSuccess": "电子邮件设置已更新",
"ToastEncodeCancelFailed": "取消编码失败",
"ToastEncodeCancelSucces": "编码已取消",
@@ -875,21 +901,17 @@
"ToastErrorCannotShare": "无法在此设备上本地共享",
"ToastFailedToLoadData": "加载数据失败",
"ToastFailedToShare": "分享失败",
- "ToastFailedToUpdateAccount": "无法更新账户",
- "ToastFailedToUpdateUser": "无法更新用户",
+ "ToastFailedToUpdate": "更新失败",
"ToastInvalidImageUrl": "图片网址无效",
"ToastInvalidUrl": "网址无效",
- "ToastItemCoverUpdateFailed": "更新项目封面失败",
"ToastItemCoverUpdateSuccess": "项目封面已更新",
"ToastItemDeletedFailed": "删除项目失败",
"ToastItemDeletedSuccess": "已删除项目",
- "ToastItemDetailsUpdateFailed": "更新项目详细信息失败",
"ToastItemDetailsUpdateSuccess": "项目详细信息已更新",
"ToastItemMarkedAsFinishedFailed": "无法标记为已听完",
"ToastItemMarkedAsFinishedSuccess": "标记为已听完的项目",
"ToastItemMarkedAsNotFinishedFailed": "无法标记为未听完",
"ToastItemMarkedAsNotFinishedSuccess": "标记为未听完的项目",
- "ToastItemUpdateFailed": "更新项目失败",
"ToastItemUpdateSuccess": "项目已更新",
"ToastLibraryCreateFailed": "创建媒体库失败",
"ToastLibraryCreateSuccess": "媒体库 \"{0}\" 创建成功",
@@ -897,7 +919,6 @@
"ToastLibraryDeleteSuccess": "媒体库已删除",
"ToastLibraryScanFailedToStart": "无法启动扫描",
"ToastLibraryScanStarted": "媒体库扫描已启动",
- "ToastLibraryUpdateFailed": "更新图书库失败",
"ToastLibraryUpdateSuccess": "媒体库 \"{0}\" 已更新",
"ToastNameEmailRequired": "姓名和电子邮件为必填项",
"ToastNameRequired": "姓名为必填项",
@@ -912,16 +933,13 @@
"ToastNotificationDeleteFailed": "删除通知失败",
"ToastNotificationFailedMaximum": "最大失败尝试次数必须 >= 0",
"ToastNotificationQueueMaximum": "最大通知队列必须 >= 0",
- "ToastNotificationSettingsUpdateFailed": "无法更新通知设置",
"ToastNotificationSettingsUpdateSuccess": "通知设置已更新",
"ToastNotificationTestTriggerFailed": "无法触发测试通知",
"ToastNotificationTestTriggerSuccess": "触发测试通知",
- "ToastNotificationUpdateFailed": "更新通知失败",
"ToastNotificationUpdateSuccess": "通知已更新",
"ToastPlaylistCreateFailed": "创建播放列表失败",
"ToastPlaylistCreateSuccess": "已成功创建播放列表",
"ToastPlaylistRemoveSuccess": "播放列表已删除",
- "ToastPlaylistUpdateFailed": "更新播放列表失败",
"ToastPlaylistUpdateSuccess": "播放列表已更新",
"ToastPodcastCreateFailed": "创建播客失败",
"ToastPodcastCreateSuccess": "已成功创建播客",
@@ -950,7 +968,6 @@
"ToastSendEbookToDeviceSuccess": "电子书已经发送到设备 \"{0}\"",
"ToastSeriesUpdateFailed": "更新系列失败",
"ToastSeriesUpdateSuccess": "系列已更新",
- "ToastServerSettingsUpdateFailed": "无法更新服务器设置",
"ToastServerSettingsUpdateSuccess": "服务器设置已更新",
"ToastSessionCloseFailed": "关闭会话失败",
"ToastSessionDeleteFailed": "删除会话失败",
@@ -961,7 +978,6 @@
"ToastSocketDisconnected": "网络已断开",
"ToastSocketFailedToConnect": "网络连接失败",
"ToastSortingPrefixesEmptyError": "必须至少有 1 个排序前缀",
- "ToastSortingPrefixesUpdateFailed": "无法更新排序前缀",
"ToastSortingPrefixesUpdateSuccess": "排序前缀已更新 ({0} 项)",
"ToastTitleRequired": "标题为必填项",
"ToastUnknownError": "未知错误",
diff --git a/client/strings/zh-tw.json b/client/strings/zh-tw.json
index be023813b..f6c4b6c8c 100644
--- a/client/strings/zh-tw.json
+++ b/client/strings/zh-tw.json
@@ -9,6 +9,7 @@
"ButtonApply": "應用",
"ButtonApplyChapters": "應用到章節",
"ButtonAuthors": "作者",
+ "ButtonBack": "返回",
"ButtonBrowseForFolder": "瀏覽資料夾",
"ButtonCancel": "取消",
"ButtonCancelEncode": "取消編碼",
@@ -18,6 +19,7 @@
"ButtonChooseFiles": "選擇檔案",
"ButtonClearFilter": "清除過濾器",
"ButtonCloseFeed": "關閉源",
+ "ButtonCloseSession": "關閉開放會話",
"ButtonCollections": "收藏",
"ButtonConfigureScanner": "配置掃描",
"ButtonCreate": "創建",
@@ -27,6 +29,7 @@
"ButtonEdit": "編輯",
"ButtonEditChapters": "編輯章節",
"ButtonEditPodcast": "編輯播客",
+ "ButtonEnable": "啟用",
"ButtonForceReScan": "強制重新掃描",
"ButtonFullPath": "完整路徑",
"ButtonHide": "隱藏",
@@ -688,10 +691,8 @@
"PlaceholderNewPlaylist": "輸入播放列表名稱",
"PlaceholderSearch": "查找..",
"PlaceholderSearchEpisode": "搜尋劇集..",
- "ToastAccountUpdateFailed": "帳號更新失敗",
"ToastAccountUpdateSuccess": "帳號已更新",
"ToastAuthorImageRemoveSuccess": "作者圖像已刪除",
- "ToastAuthorUpdateFailed": "作者更新失敗",
"ToastAuthorUpdateMerged": "作者已合併",
"ToastAuthorUpdateSuccess": "作者已更新",
"ToastAuthorUpdateSuccessNoImageFound": "作者已更新 (未找到圖像)",
@@ -707,17 +708,13 @@
"ToastBookmarkCreateFailed": "創建書籤失敗",
"ToastBookmarkCreateSuccess": "書籤已新增",
"ToastBookmarkRemoveSuccess": "書籤已刪除",
- "ToastBookmarkUpdateFailed": "書籤更新失敗",
"ToastBookmarkUpdateSuccess": "書籤已更新",
"ToastChaptersHaveErrors": "章節有錯誤",
"ToastChaptersMustHaveTitles": "章節必須有標題",
"ToastCollectionItemsRemoveSuccess": "項目從收藏夾移除",
"ToastCollectionRemoveSuccess": "收藏夾已刪除",
- "ToastCollectionUpdateFailed": "更新收藏夾失敗",
"ToastCollectionUpdateSuccess": "收藏夾已更新",
- "ToastItemCoverUpdateFailed": "更新項目封面失敗",
"ToastItemCoverUpdateSuccess": "項目封面已更新",
- "ToastItemDetailsUpdateFailed": "更新項目詳細信息失敗",
"ToastItemDetailsUpdateSuccess": "項目詳細信息已更新",
"ToastItemMarkedAsFinishedFailed": "標記為聽完失敗",
"ToastItemMarkedAsFinishedSuccess": "標記為聽完的項目",
@@ -729,12 +726,10 @@
"ToastLibraryDeleteSuccess": "媒體庫已刪除",
"ToastLibraryScanFailedToStart": "無法啟動掃描",
"ToastLibraryScanStarted": "媒體庫掃描已啟動",
- "ToastLibraryUpdateFailed": "更新圖書庫失敗",
"ToastLibraryUpdateSuccess": "媒體庫 \"{0}\" 已更新",
"ToastPlaylistCreateFailed": "創建播放列表失敗",
"ToastPlaylistCreateSuccess": "已成功創建播放列表",
"ToastPlaylistRemoveSuccess": "播放列表已刪除",
- "ToastPlaylistUpdateFailed": "更新播放列表失敗",
"ToastPlaylistUpdateSuccess": "播放列表已更新",
"ToastPodcastCreateFailed": "創建播客失敗",
"ToastPodcastCreateSuccess": "已成功創建播客",
diff --git a/index.js b/index.js
index 141c5826e..de1ed5c30 100644
--- a/index.js
+++ b/index.js
@@ -9,6 +9,7 @@ if (isDev) {
if (devEnv.MetadataPath) process.env.METADATA_PATH = devEnv.MetadataPath
if (devEnv.FFmpegPath) process.env.FFMPEG_PATH = devEnv.FFmpegPath
if (devEnv.FFProbePath) process.env.FFPROBE_PATH = devEnv.FFProbePath
+ if (devEnv.NunicodePath) process.env.NUSQLITE3_PATH = devEnv.NunicodePath
if (devEnv.SkipBinariesCheck) process.env.SKIP_BINARIES_CHECK = '1'
if (devEnv.BackupPath) process.env.BACKUP_PATH = devEnv.BackupPath
process.env.SOURCE = 'local'
diff --git a/package-lock.json b/package-lock.json
index 6f0a3587f..7c1798ed9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "audiobookshelf",
- "version": "2.13.4",
+ "version": "2.14.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf",
- "version": "2.13.4",
+ "version": "2.14.0",
"license": "GPL-3.0",
"dependencies": {
"axios": "^0.27.2",
diff --git a/package.json b/package.json
index 752b2f8df..69bc41ddd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
- "version": "2.13.4",
+ "version": "2.14.0",
"buildNumber": 1,
"description": "Self-hosted audiobook and podcast server",
"main": "index.js",
diff --git a/server/Database.js b/server/Database.js
index e7bad49ba..9bce26050 100644
--- a/server/Database.js
+++ b/server/Database.js
@@ -28,6 +28,9 @@ class Database {
this.notificationSettings = null
/** @type {import('./objects/settings/EmailSettings')} */
this.emailSettings = null
+
+ this.supportsUnaccent = false
+ this.supportsUnicodeFoldings = false
}
get models() {
@@ -223,6 +226,12 @@ class Database {
try {
await this.sequelize.authenticate()
+ if (process.env.NUSQLITE3_PATH) {
+ await this.loadExtension(process.env.NUSQLITE3_PATH)
+ Logger.info(`[Database] Db supports unaccent and unicode foldings`)
+ this.supportsUnaccent = true
+ this.supportsUnicodeFoldings = true
+ }
Logger.info(`[Database] Db connection was successful`)
return true
} catch (error) {
@@ -232,10 +241,9 @@ class Database {
}
/**
- * TODO: Temporarily disabled
- * @param {string[]} extensions paths to extension binaries
+ * @param {string} extension paths to extension binary
*/
- async loadExtensions(extensions) {
+ async loadExtension(extension) {
// This is a hack to get the db connection for loading extensions.
// The proper way would be to use the 'afterConnect' hook, but that hook is never called for sqlite due to a bug in sequelize.
// See https://github.com/sequelize/sequelize/issues/12487
@@ -243,20 +251,18 @@ class Database {
const db = await this.sequelize.dialect.connectionManager.getConnection()
if (typeof db?.loadExtension !== 'function') throw new Error('Failed to get db connection for loading extensions')
- for (const ext of extensions) {
- Logger.info(`[Database] Loading extension ${ext}`)
- await new Promise((resolve, reject) => {
- db.loadExtension(ext, (err) => {
- if (err) {
- Logger.error(`[Database] Failed to load extension ${ext}`, err)
- reject(err)
- return
- }
- Logger.info(`[Database] Successfully loaded extension ${ext}`)
- resolve()
- })
+ Logger.info(`[Database] Loading extension ${extension}`)
+ await new Promise((resolve, reject) => {
+ db.loadExtension(extension, (err) => {
+ if (err) {
+ Logger.error(`[Database] Failed to load extension ${extension}`, err)
+ reject(err)
+ return
+ }
+ Logger.info(`[Database] Successfully loaded extension ${extension}`)
+ resolve()
})
- }
+ })
}
/**
@@ -601,6 +607,11 @@ class Database {
this.libraryFilterData[libraryId].publishers.push(publisher)
}
+ addPublishedDecadeToFilterData(libraryId, decade) {
+ if (!this.libraryFilterData[libraryId] || !decade || this.libraryFilterData[libraryId].publishedDecades.includes(decade)) return
+ this.libraryFilterData[libraryId].publishedDecades.push(decade)
+ }
+
addLanguageToFilterData(libraryId, language) {
if (!this.libraryFilterData[libraryId] || !language || this.libraryFilterData[libraryId].languages.includes(language)) return
this.libraryFilterData[libraryId].languages.push(language)
@@ -745,37 +756,57 @@ class Database {
}
}
- /**
- * TODO: Temporarily unused
- * @param {string} value
- * @returns {string}
- */
- normalize(value) {
- return `lower(unaccent(${value}))`
+ async createTextSearchQuery(query) {
+ const textQuery = new this.TextSearchQuery(this.sequelize, this.supportsUnaccent, query)
+ await textQuery.init()
+ return textQuery
}
- /**
- * TODO: Temporarily unused
- * @param {string} query
- * @returns {Promise
}
- */
- async getNormalizedQuery(query) {
- const escapedQuery = this.sequelize.escape(query)
- const normalizedQuery = this.normalize(escapedQuery)
- const normalizedQueryResult = await this.sequelize.query(`SELECT ${normalizedQuery} as normalized_query`)
- return normalizedQueryResult[0][0].normalized_query
- }
+ TextSearchQuery = class {
+ constructor(sequelize, supportsUnaccent, query) {
+ this.sequelize = sequelize
+ this.supportsUnaccent = supportsUnaccent
+ this.query = query
+ this.hasAccents = false
+ }
- /**
- *
- * @param {string} column
- * @param {string} normalizedQuery
- * @returns {string}
- */
- matchExpression(column, normalizedQuery) {
- const normalizedPattern = this.sequelize.escape(`%${normalizedQuery}%`)
- const normalizedColumn = column
- return `${normalizedColumn} LIKE ${normalizedPattern}`
+ /**
+ * Returns a normalized (accents-removed) expression for the specified value.
+ *
+ * @param {string} value
+ * @returns {string}
+ */
+ normalize(value) {
+ return `unaccent(${value})`
+ }
+
+ /**
+ * Initialize the text query.
+ *
+ */
+ async init() {
+ if (!this.supportsUnaccent) return
+ const escapedQuery = this.sequelize.escape(this.query)
+ const normalizedQueryExpression = this.normalize(escapedQuery)
+ const normalizedQueryResult = await this.sequelize.query(`SELECT ${normalizedQueryExpression} as normalized_query`)
+ const normalizedQuery = normalizedQueryResult[0][0].normalized_query
+ this.hasAccents = escapedQuery !== this.sequelize.escape(normalizedQuery)
+ }
+
+ /**
+ * Get match expression for the specified column.
+ * If the query contains accents, match against the column as-is (case-insensitive exact match).
+ * otherwise match against a normalized column (case-insensitive match with accents removed).
+ *
+ * @param {string} column
+ * @returns {string}
+ */
+ matchExpression(column) {
+ const pattern = this.sequelize.escape(`%${this.query}%`)
+ if (!this.supportsUnaccent) return `${column} LIKE ${pattern}`
+ const normalizedColumn = this.hasAccents ? column : this.normalize(column)
+ return `${normalizedColumn} LIKE ${pattern}`
+ }
}
}
diff --git a/server/Server.js b/server/Server.js
index 17466e863..7e541e612 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -23,7 +23,6 @@ const HlsRouter = require('./routers/HlsRouter')
const PublicRouter = require('./routers/PublicRouter')
const LogManager = require('./managers/LogManager')
-const NotificationManager = require('./managers/NotificationManager')
const EmailManager = require('./managers/EmailManager')
const AbMergeManager = require('./managers/AbMergeManager')
const CacheManager = require('./managers/CacheManager')
@@ -67,12 +66,11 @@ class Server {
this.auth = new Auth()
// Managers
- this.notificationManager = new NotificationManager()
this.emailManager = new EmailManager()
- this.backupManager = new BackupManager(this.notificationManager)
+ this.backupManager = new BackupManager()
this.abMergeManager = new AbMergeManager()
this.playbackSessionManager = new PlaybackSessionManager()
- this.podcastManager = new PodcastManager(this.watcher, this.notificationManager)
+ this.podcastManager = new PodcastManager(this.watcher)
this.audioMetadataManager = new AudioMetadataMangaer()
this.rssFeedManager = new RssFeedManager()
this.cronManager = new CronManager(this.podcastManager, this.playbackSessionManager)
diff --git a/server/Watcher.js b/server/Watcher.js
index 83c45234c..0e34fc66b 100644
--- a/server/Watcher.js
+++ b/server/Watcher.js
@@ -301,7 +301,12 @@ class FolderWatcher extends EventEmitter {
libraryId,
libraryName: libwatcher.name
}
- this.pendingTask = TaskManager.createAndAddTask('watcher-scan', `Scanning file changes in "${libwatcher.name}"`, null, true, taskData)
+ const taskTitleString = {
+ text: `Scanning file changes in "${libwatcher.name}"`,
+ key: 'MessageTaskScanningFileChanges',
+ subs: [libwatcher.name]
+ }
+ this.pendingTask = TaskManager.createAndAddTask('watcher-scan', taskTitleString, null, true, taskData)
}
this.pendingFileUpdates.push({
path,
@@ -330,7 +335,11 @@ class FolderWatcher extends EventEmitter {
if (this.pendingFileUpdates.length) {
LibraryScanner.scanFilesChanged(this.pendingFileUpdates, this.pendingTask)
} else {
- this.pendingTask.setFinished('Scan abandoned. No files to scan.')
+ const taskFinishedString = {
+ text: 'No files to scan',
+ key: 'MessageTaskNoFilesToScan'
+ }
+ this.pendingTask.setFinished(taskFinishedString)
TaskManager.taskFinished(this.pendingTask)
}
this.pendingTask = null
diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js
index 65243acc4..a27255435 100644
--- a/server/controllers/LibraryController.js
+++ b/server/controllers/LibraryController.js
@@ -9,7 +9,6 @@ const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilter
const libraryItemFilters = require('../utils/queries/libraryItemFilters')
const seriesFilters = require('../utils/queries/seriesFilters')
const fileUtils = require('../utils/fileUtils')
-const { asciiOnlyToLowerCase } = require('../utils/index')
const { createNewSortInstance } = require('../libs/fastSort')
const naturalSort = createNewSortInstance({
comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare
@@ -493,8 +492,8 @@ class LibraryController {
const payload = {
results: [],
total: undefined,
- limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
- page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
+ limit: req.query.limit,
+ page: req.query.page,
sortBy: req.query.sort,
sortDesc: req.query.desc === '1',
filterBy: req.query.filter,
@@ -503,6 +502,7 @@ class LibraryController {
collapseseries: req.query.collapseseries === '1',
include: include.join(',')
}
+
payload.offset = payload.page * payload.limit
// TODO: Temporary way of handling collapse sub-series. Either remove feature or handle through sql queries
@@ -594,8 +594,8 @@ class LibraryController {
const payload = {
results: [],
total: 0,
- limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
- page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
+ limit: req.query.limit,
+ page: req.query.page,
sortBy: req.query.sort,
sortDesc: req.query.desc === '1',
filterBy: req.query.filter,
@@ -666,8 +666,8 @@ class LibraryController {
const payload = {
results: [],
total: 0,
- limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
- page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0,
+ limit: req.query.limit,
+ page: req.query.page,
sortBy: req.query.sort,
sortDesc: req.query.desc === '1',
filterBy: req.query.filter,
@@ -702,8 +702,8 @@ class LibraryController {
const payload = {
results: [],
total: playlistsForUser.length,
- limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
- page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0
+ limit: req.query.limit,
+ page: req.query.page
}
if (payload.limit) {
@@ -734,7 +734,7 @@ class LibraryController {
* @param {Response} res
*/
async getUserPersonalizedShelves(req, res) {
- const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10
+ const limitPerShelf = req.query.limit || 10
const include = (req.query.include || '')
.split(',')
.map((v) => v.trim().toLowerCase())
@@ -807,8 +807,8 @@ class LibraryController {
return res.status(400).send('Invalid request. Query param "q" must be a string')
}
- const limit = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 12
- const query = asciiOnlyToLowerCase(req.query.q.trim())
+ const limit = req.query.limit || 12
+ const query = req.query.q.trim()
const matches = await libraryItemFilters.search(req.user, req.library, query, limit)
res.json(matches)
@@ -865,8 +865,40 @@ class LibraryController {
* @param {Response} res
*/
async getAuthors(req, res) {
+ const isPaginated = req.query.limit && !isNaN(req.query.limit) && !isNaN(req.query.page)
+
+ const payload = {
+ results: [],
+ total: 0,
+ limit: isPaginated ? Number(req.query.limit) : 0,
+ page: isPaginated ? Number(req.query.page) : 0,
+ sortBy: req.query.sort,
+ sortDesc: req.query.desc === '1',
+ filterBy: req.query.filter,
+ minified: req.query.minified === '1',
+ include: req.query.include
+ }
+
+ // create order, limit and offset for pagination
+ let offset = isPaginated ? payload.page * payload.limit : undefined
+ let limit = isPaginated ? payload.limit : undefined
+ let order = undefined
+ const direction = payload.sortDesc ? 'DESC' : 'ASC'
+ if (payload.sortBy === 'name') {
+ order = [[Sequelize.literal('name COLLATE NOCASE'), direction]]
+ } else if (payload.sortBy === 'lastFirst') {
+ order = [[Sequelize.literal('lastFirst COLLATE NOCASE'), direction]]
+ } else if (payload.sortBy === 'addedAt') {
+ order = [['createdAt', direction]]
+ } else if (payload.sortBy === 'updatedAt') {
+ order = [['updatedAt', direction]]
+ } else if (payload.sortBy === 'numBooks') {
+ offset = undefined
+ limit = undefined
+ }
+
const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user)
- const authors = await Database.authorModel.findAll({
+ const { rows: authors, count } = await Database.authorModel.findAndCountAll({
where: {
libraryId: req.library.id
},
@@ -880,10 +912,13 @@ class LibraryController {
attributes: []
}
},
- order: [[Sequelize.literal('name COLLATE NOCASE'), 'ASC']]
+ order: order,
+ limit: limit,
+ offset: offset,
+ distinct: true
})
- const oldAuthors = []
+ let oldAuthors = []
for (const author of authors) {
const oldAuthor = author.toOldJSONExpanded(author.books.length)
@@ -891,9 +926,25 @@ class LibraryController {
oldAuthors.push(oldAuthor)
}
- res.json({
- authors: oldAuthors
- })
+ // numBooks sort is handled post-query
+ if (payload.sortBy === 'numBooks') {
+ oldAuthors.sort((a, b) => (payload.sortDesc ? b.numBooks - a.numBooks : a.numBooks - b.numBooks))
+ if (isPaginated) {
+ const startIndex = payload.page * payload.limit
+ const endIndex = startIndex + payload.limit
+ oldAuthors = oldAuthors.slice(startIndex, endIndex)
+ }
+ }
+
+ payload.results = oldAuthors
+ if (isPaginated) {
+ payload.total = count
+ res.json(payload)
+ } else {
+ res.json({
+ authors: payload.results
+ })
+ }
}
/**
@@ -1088,8 +1139,8 @@ class LibraryController {
const payload = {
episodes: [],
- limit: req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) : 0,
- page: req.query.page && !isNaN(req.query.page) ? Number(req.query.page) : 0
+ limit: req.query.limit,
+ page: req.query.page
}
const offset = payload.page * payload.limit
@@ -1192,6 +1243,17 @@ class LibraryController {
return res.status(404).send('Library not found')
}
req.library = library
+
+ // Ensure pagination query params are positive integers
+ for (const queryKey of ['limit', 'page']) {
+ if (req.query[queryKey] !== undefined) {
+ req.query[queryKey] = !isNaN(req.query[queryKey]) ? Number(req.query[queryKey]) : 0
+ if (!Number.isInteger(req.query[queryKey]) || req.query[queryKey] < 0) {
+ return res.status(400).send(`Invalid request. ${queryKey} must be a positive integer`)
+ }
+ }
+ }
+
next()
}
}
diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js
index c77e1d3a5..fe8539bc3 100644
--- a/server/controllers/LibraryItemController.js
+++ b/server/controllers/LibraryItemController.js
@@ -480,7 +480,7 @@ class LibraryItemController {
const libraryId = itemsToDelete[0].libraryId
for (const libraryItem of itemsToDelete) {
const libraryItemPath = libraryItem.path
- Logger.info(`[LibraryItemController] Deleting Library Item "${libraryItem.media.metadata.title}"`)
+ Logger.info(`[LibraryItemController] (${hardDelete ? 'Hard' : 'Soft'}) deleting Library Item "${libraryItem.media.metadata.title}" with id "${libraryItem.id}"`)
const mediaItemIds = libraryItem.mediaType === 'podcast' ? libraryItem.media.episodes.map((ep) => ep.id) : [libraryItem.media.id]
await this.handleDeleteLibraryItem(libraryItem.mediaType, libraryItem.id, mediaItemIds)
if (hardDelete) {
diff --git a/server/controllers/NotificationController.js b/server/controllers/NotificationController.js
index 215afe0ab..b3fd72400 100644
--- a/server/controllers/NotificationController.js
+++ b/server/controllers/NotificationController.js
@@ -1,6 +1,7 @@
const { Request, Response, NextFunction } = require('express')
const Database = require('../Database')
const { version } = require('../../package.json')
+const NotificationManager = require('../managers/NotificationManager')
/**
* @typedef RequestUserObject
@@ -23,7 +24,7 @@ class NotificationController {
*/
get(req, res) {
res.json({
- data: this.notificationManager.getData(),
+ data: NotificationManager.getData(),
settings: Database.notificationSettings
})
}
@@ -52,7 +53,7 @@ class NotificationController {
* @param {Response} res
*/
getData(req, res) {
- res.json(this.notificationManager.getData())
+ res.json(NotificationManager.getData())
}
/**
@@ -64,7 +65,7 @@ class NotificationController {
* @param {Response} res
*/
async fireTestEvent(req, res) {
- await this.notificationManager.triggerNotification('onTest', { version: `v${version}` }, req.query.fail === '1')
+ await NotificationManager.triggerNotification('onTest', { version: `v${version}` }, req.query.fail === '1')
res.sendStatus(200)
}
@@ -121,7 +122,7 @@ class NotificationController {
async sendNotificationTest(req, res) {
if (!Database.notificationSettings.isUseable) return res.status(400).send('Apprise is not configured')
- const success = await this.notificationManager.sendTestNotification(req.notification)
+ const success = await NotificationManager.sendTestNotification(req.notification)
if (success) res.sendStatus(200)
else res.sendStatus(500)
}
diff --git a/server/managers/AbMergeManager.js b/server/managers/AbMergeManager.js
index d94e94898..ea70d73c7 100644
--- a/server/managers/AbMergeManager.js
+++ b/server/managers/AbMergeManager.js
@@ -40,7 +40,11 @@ class AbMergeManager {
* @returns {Promise}
*/
cancelEncode(task) {
- task.setFailed('Task canceled by user')
+ const taskFailedString = {
+ text: 'Task canceled by user',
+ key: 'MessageTaskCanceledByUser'
+ }
+ task.setFailed(taskFailedString)
return this.removeTask(task, true)
}
@@ -76,8 +80,17 @@ class AbMergeManager {
duration: libraryItem.media.duration,
encodeOptions: options
}
- const taskDescription = `Encoding audiobook "${libraryItem.media.metadata.title}" into a single m4b file.`
- task.setData('encode-m4b', 'Encoding M4b', taskDescription, false, taskData)
+
+ const taskTitleString = {
+ text: 'Encoding M4b',
+ key: 'MessageTaskEncodingM4b'
+ }
+ const taskDescriptionString = {
+ text: `Encoding audiobook "${libraryItem.media.metadata.title}" into a single m4b file.`,
+ key: 'MessageTaskEncodingM4bDescription',
+ subs: [libraryItem.media.metadata.title]
+ }
+ task.setData('encode-m4b', taskTitleString, taskDescriptionString, false, taskData)
TaskManager.addTask(task)
Logger.info(`Start m4b encode for ${libraryItem.id} - TaskId: ${task.id}`)
@@ -98,7 +111,11 @@ class AbMergeManager {
// Make sure the target directory is writable
if (!(await isWritable(task.data.libraryItemDir))) {
Logger.error(`[AbMergeManager] Target directory is not writable: ${task.data.libraryItemDir}`)
- task.setFailed('Target directory is not writable')
+ const taskFailedString = {
+ text: 'Target directory is not writable',
+ key: 'MessageTaskTargetDirectoryNotWritable'
+ }
+ task.setFailed(taskFailedString)
this.removeTask(task, true)
return
}
@@ -106,7 +123,11 @@ class AbMergeManager {
// Create ffmetadata file
if (!(await ffmpegHelpers.writeFFMetadataFile(task.data.ffmetadataObject, task.data.chapters, task.data.ffmetadataPath))) {
Logger.error(`[AudioMetadataManager] Failed to write ffmetadata file for audiobook "${task.data.libraryItemId}"`)
- task.setFailed('Failed to write metadata file.')
+ const taskFailedString = {
+ text: 'Failed to write metadata file',
+ key: 'MessageTaskFailedToWriteMetadataFile'
+ }
+ task.setFailed(taskFailedString)
this.removeTask(task, true)
return
}
@@ -137,7 +158,11 @@ class AbMergeManager {
Logger.info(`[AbMergeManager] Task cancelled ${task.id}`)
} else {
Logger.error(`[AbMergeManager] mergeAudioFiles failed`, error)
- task.setFailed('Failed to merge audio files')
+ const taskFailedString = {
+ text: 'Failed to merge audio files',
+ key: 'MessageTaskFailedToMergeAudioFiles'
+ }
+ task.setFailed(taskFailedString)
this.removeTask(task, true)
}
return
@@ -163,8 +188,13 @@ class AbMergeManager {
if (error.message === 'FFMPEG_CANCELED') {
Logger.info(`[AbMergeManager] Task cancelled ${task.id}`)
} else {
- Logger.error(`[AbMergeManager] Failed to write metadata to file "${task.data.tempFilepath}"`)
- task.setFailed('Failed to write metadata to m4b file')
+ Logger.error(`[AbMergeManager] Failed to embed metadata in file "${task.data.tempFilepath}"`)
+ const taskFailedString = {
+ text: `Failed to embed metadata in file ${Path.basename(task.data.tempFilepath)}`,
+ key: 'MessageTaskFailedToEmbedMetadataInFile',
+ subs: [Path.basename(task.data.tempFilepath)]
+ }
+ task.setFailed(taskFailedString)
this.removeTask(task, true)
}
return
@@ -196,7 +226,11 @@ class AbMergeManager {
await fs.remove(task.data.tempFilepath)
} catch (err) {
Logger.error(`[AbMergeManager] Failed to move m4b from ${task.data.tempFilepath} to ${task.data.targetFilepath}`, err)
- task.setFailed('Failed to move m4b file')
+ const taskFailedString = {
+ text: 'Failed to move m4b file',
+ key: 'MessageTaskFailedToMoveM4bFile'
+ }
+ task.setFailed(taskFailedString)
this.removeTask(task, true)
return
}
diff --git a/server/managers/AudioMetadataManager.js b/server/managers/AudioMetadataManager.js
index 2dcbb1d44..7911178e3 100644
--- a/server/managers/AudioMetadataManager.js
+++ b/server/managers/AudioMetadataManager.js
@@ -97,8 +97,17 @@ class AudioMetadataMangaer {
},
duration: libraryItem.media.duration
}
- const taskDescription = `Embedding metadata in audiobook "${libraryItem.media.metadata.title}".`
- task.setData('embed-metadata', 'Embedding Metadata', taskDescription, false, taskData)
+
+ const taskTitleString = {
+ text: 'Embedding Metadata',
+ key: 'MessageTaskEmbeddingMetadata'
+ }
+ const taskDescriptionString = {
+ text: `Embedding metadata in audiobook "${libraryItem.media.metadata.title}".`,
+ key: 'MessageTaskEmbeddingMetadataDescription',
+ subs: [libraryItem.media.metadata.title]
+ }
+ task.setData('embed-metadata', taskTitleString, taskDescriptionString, false, taskData)
if (this.tasksRunning.length >= this.MAX_CONCURRENT_TASKS) {
Logger.info(`[AudioMetadataManager] Queueing embed metadata for audiobook "${libraryItem.media.metadata.title}"`)
@@ -112,6 +121,10 @@ class AudioMetadataMangaer {
}
}
+ /**
+ *
+ * @param {import('../objects/Task')} task
+ */
async runMetadataEmbed(task) {
this.tasksRunning.push(task)
TaskManager.addTask(task)
@@ -123,7 +136,11 @@ class AudioMetadataMangaer {
Logger.debug(`[AudioMetadataManager] Target directory ${task.data.libraryItemDir} writable: ${targetDirWritable}`)
if (!targetDirWritable) {
Logger.error(`[AudioMetadataManager] Target directory is not writable: ${task.data.libraryItemDir}`)
- task.setFailed('Target directory is not writable')
+ const taskFailedString = {
+ text: 'Target directory is not writable',
+ key: 'MessageTaskTargetDirectoryNotWritable'
+ }
+ task.setFailed(taskFailedString)
this.handleTaskFinished(task)
return
}
@@ -134,7 +151,12 @@ class AudioMetadataMangaer {
await fs.access(af.path, fs.constants.W_OK)
} catch (err) {
Logger.error(`[AudioMetadataManager] Audio file is not writable: ${af.path}`)
- task.setFailed(`Audio file "${Path.basename(af.path)}" is not writable`)
+ const taskFailedString = {
+ text: `Audio file "${Path.basename(af.path)}" is not writable`,
+ key: 'MessageTaskAudioFileNotWritable',
+ subs: [Path.basename(af.path)]
+ }
+ task.setFailed(taskFailedString)
this.handleTaskFinished(task)
return
}
@@ -148,7 +170,11 @@ class AudioMetadataMangaer {
cacheDirCreated = true
} catch (err) {
Logger.error(`[AudioMetadataManager] Failed to create cache directory ${task.data.itemCachePath}`, err)
- task.setFailed('Failed to create cache directory')
+ const taskFailedString = {
+ text: 'Failed to create cache directory',
+ key: 'MessageTaskFailedToCreateCacheDirectory'
+ }
+ task.setFailed(taskFailedString)
this.handleTaskFinished(task)
return
}
@@ -159,7 +185,11 @@ class AudioMetadataMangaer {
const success = await ffmpegHelpers.writeFFMetadataFile(task.data.metadataObject, task.data.chapters, ffmetadataPath)
if (!success) {
Logger.error(`[AudioMetadataManager] Failed to write ffmetadata file for audiobook "${task.data.libraryItemId}"`)
- task.setFailed('Failed to write metadata file.')
+ const taskFailedString = {
+ text: 'Failed to write metadata file',
+ key: 'MessageTaskFailedToWriteMetadataFile'
+ }
+ task.setFailed(taskFailedString)
this.handleTaskFinished(task)
return
}
@@ -181,7 +211,12 @@ class AudioMetadataMangaer {
Logger.debug(`[AudioMetadataManager] Backed up audio file at "${backupFilePath}"`)
} catch (err) {
Logger.error(`[AudioMetadataManager] Failed to backup audio file "${af.path}"`, err)
- task.setFailed(`Failed to backup audio file "${Path.basename(af.path)}"`)
+ const taskFailedString = {
+ text: `Failed to backup audio file "${Path.basename(af.path)}"`,
+ key: 'MessageTaskFailedToBackupAudioFile',
+ subs: [Path.basename(af.path)]
+ }
+ task.setFailed(taskFailedString)
this.handleTaskFinished(task)
return
}
@@ -195,7 +230,12 @@ class AudioMetadataMangaer {
Logger.info(`[AudioMetadataManager] Successfully tagged audio file "${af.path}"`)
} catch (err) {
Logger.error(`[AudioMetadataManager] Failed to tag audio file "${af.path}"`, err)
- task.setFailed(`Failed to tag audio file "${Path.basename(af.path)}"`)
+ const taskFailedString = {
+ text: `Failed to embed metadata in file "${Path.basename(af.path)}"`,
+ key: 'MessageTaskFailedToEmbedMetadataInFile',
+ subs: [Path.basename(af.path)]
+ }
+ task.setFailed(taskFailedString)
this.handleTaskFinished(task)
return
}
diff --git a/server/managers/BackupManager.js b/server/managers/BackupManager.js
index 54727b6bb..102521b71 100644
--- a/server/managers/BackupManager.js
+++ b/server/managers/BackupManager.js
@@ -15,13 +15,12 @@ const { getFileSize } = require('../utils/fileUtils')
const Backup = require('../objects/Backup')
const CacheManager = require('./CacheManager')
+const NotificationManager = require('./NotificationManager')
class BackupManager {
- constructor(notificationManager) {
+ constructor() {
this.ItemsMetadataPath = Path.join(global.MetadataPath, 'items')
this.AuthorsMetadataPath = Path.join(global.MetadataPath, 'authors')
- /** @type {import('./NotificationManager')} */
- this.notificationManager = notificationManager
this.scheduleTask = null
@@ -301,7 +300,7 @@ class BackupManager {
const sqliteBackupPath = await this.backupSqliteDb(newBackup).catch((error) => {
Logger.error(`[BackupManager] Failed to backup sqlite db`, error)
const errorMsg = error?.message || error || 'Unknown Error'
- this.notificationManager.onBackupFailed(errorMsg)
+ NotificationManager.onBackupFailed(errorMsg)
return false
})
@@ -313,7 +312,7 @@ class BackupManager {
const zipResult = await this.zipBackup(sqliteBackupPath, newBackup).catch((error) => {
Logger.error(`[BackupManager] Backup Failed ${error}`)
const errorMsg = error?.message || error || 'Unknown Error'
- this.notificationManager.onBackupFailed(errorMsg)
+ NotificationManager.onBackupFailed(errorMsg)
return false
})
@@ -344,7 +343,7 @@ class BackupManager {
}
// Notification for backup successfully completed
- this.notificationManager.onBackupCompleted(newBackup, this.backups.length, removeOldest)
+ NotificationManager.onBackupCompleted(newBackup, this.backups.length, removeOldest)
return true
}
diff --git a/server/managers/BinaryManager.js b/server/managers/BinaryManager.js
index 823a4c0e7..f16d47c0b 100644
--- a/server/managers/BinaryManager.js
+++ b/server/managers/BinaryManager.js
@@ -76,18 +76,27 @@ class ZippedAssetDownloader {
async extractFiles(zipPath, filesToExtract, destDir) {
const zip = new StreamZip.async({ file: zipPath })
- for (const file of filesToExtract) {
- const outputPath = path.join(destDir, file.outputFileName)
- await zip.extract(file.pathInsideZip, outputPath)
- Logger.debug(`[ZippedAssetDownloader] Extracted file ${file.pathInsideZip} to ${outputPath}`)
+ try {
+ for (const file of filesToExtract) {
+ const outputPath = path.join(destDir, file.outputFileName)
+ if (!(await zip.entry(file.pathInsideZip))) {
+ Logger.error(`[ZippedAssetDownloader] File ${file.pathInsideZip} not found in zip file ${zipPath}`)
+ continue
+ }
+ await zip.extract(file.pathInsideZip, outputPath)
+ Logger.debug(`[ZippedAssetDownloader] Extracted file ${file.pathInsideZip} to ${outputPath}`)
- // Set executable permission for Linux
- if (process.platform !== 'win32') {
- await fs.chmod(outputPath, 0o755)
+ // Set executable permission for Linux
+ if (process.platform !== 'win32') {
+ await fs.chmod(outputPath, 0o755)
+ }
}
+ } catch (error) {
+ Logger.error('[ZippedAssetDownloader] Error extracting files:', error)
+ throw error
+ } finally {
+ await zip.close()
}
-
- await zip.close()
}
async downloadAndExtractFiles(releaseTag, assetName, filesToExtract, destDir) {
@@ -99,7 +108,6 @@ class ZippedAssetDownloader {
await this.extractFiles(zipPath, filesToExtract, destDir)
} catch (error) {
Logger.error(`[ZippedAssetDownloader] Error downloading or extracting files: ${error.message}`)
- throw error
} finally {
if (zipPath) await fs.remove(zipPath)
}
@@ -164,14 +172,67 @@ class FFBinariesDownloader extends ZippedAssetDownloader {
}
}
+class NunicodeDownloader extends ZippedAssetDownloader {
+ constructor() {
+ super()
+ this.platformSuffix = this.getPlatformSuffix()
+ }
+
+ getPlatformSuffix() {
+ const platform = process.platform
+ const arch = process.arch
+
+ if (platform === 'win32' && arch === 'x64') {
+ return 'win-x64'
+ } else if (platform === 'darwin' && (arch === 'x64' || arch === 'arm64')) {
+ return 'osx-arm64'
+ } else if (platform === 'linux' && arch === 'x64') {
+ return 'linux-x64'
+ } else if (platform === 'linux' && arch === 'arm64') {
+ return 'linux-arm64'
+ }
+
+ return null
+ }
+
+ async getAssetUrl(releaseTag, assetName) {
+ return `https://github.com/mikiher/nunicode-sqlite/releases/download/v${releaseTag}/${assetName}`
+ }
+
+ getAssetName(binaryName, releaseTag) {
+ if (!this.platformSuffix) {
+ throw new Error(`[NunicodeDownloader] Platform ${process.platform}-${process.arch} not supported`)
+ }
+ return `${binaryName}-${this.platformSuffix}.zip`
+ }
+
+ getAssetFileName(binaryName) {
+ if (process.platform === 'win32') {
+ return `${binaryName}.dll`
+ } else if (process.platform === 'darwin') {
+ return `${binaryName}.dylib`
+ } else if (process.platform === 'linux') {
+ return `${binaryName}.so`
+ }
+
+ throw new Error(`[NunicodeDownloader] Platform ${process.platform} not supported`)
+ }
+}
+
class Binary {
- constructor(name, type, envVariable, validVersions, source) {
+ constructor(name, type, envVariable, validVersions, source, required = true) {
+ if (!name) throw new Error('Binary name is required')
this.name = name
+ if (!type) throw new Error('Binary type is required')
this.type = type
+ if (!envVariable) throw new Error('Binary environment variable name is required')
this.envVariable = envVariable
+ if (!validVersions || !validVersions.length) throw new Error(`No valid versions specified for ${type} ${name}. At least one version is required.`)
this.validVersions = validVersions
+ if (!source || !(source instanceof ZippedAssetDownloader)) throw new Error('Binary source is required, and must be an instance of ZippedAssetDownloader')
this.source = source
this.fileName = this.getFileName()
+ this.required = required
this.exec = exec
}
@@ -205,37 +266,65 @@ class Binary {
}
}
- async isGood(binaryPath) {
- if (!binaryPath || !(await fs.pathExists(binaryPath))) return false
- if (!this.validVersions.length) return true
- if (this.type === 'library') return true
+ async isLibraryVersionValid(libraryPath) {
try {
- const { stdout } = await this.exec('"' + binaryPath + '"' + ' -version')
+ const versionFilePath = libraryPath + '.ver'
+ if (!(await fs.pathExists(versionFilePath))) return false
+ const version = (await fs.readFile(versionFilePath, 'utf8')).trim()
+ return this.validVersions.some((validVersion) => version.startsWith(validVersion))
+ } catch (err) {
+ Logger.error(`[Binary] Failed to check version of ${libraryPath}`, err)
+ return false
+ }
+ }
+
+ async isExecutableVersionValid(executablePath) {
+ try {
+ const { stdout } = await this.exec('"' + executablePath + '"' + ' -version')
const version = stdout.match(/version\s([\d\.]+)/)?.[1]
if (!version) return false
return this.validVersions.some((validVersion) => version.startsWith(validVersion))
} catch (err) {
- Logger.error(`[Binary] Failed to check version of ${binaryPath}`)
+ Logger.error(`[Binary] Failed to check version of ${executablePath}`, err)
+ return false
+ }
+ }
+
+ async isGood(binaryPath) {
+ try {
+ if (!binaryPath || !(await fs.pathExists(binaryPath))) return false
+ if (this.type === 'library') return await this.isLibraryVersionValid(binaryPath)
+ else if (this.type === 'executable') return await this.isExecutableVersionValid(binaryPath)
+ else return true
+ } catch (err) {
+ Logger.error(`[Binary] Failed to check ${this.type} ${this.name} at ${binaryPath}`, err)
return false
}
}
async download(destination) {
- await this.source.downloadBinary(this.name, this.validVersions[0], destination)
+ const version = this.validVersions[0]
+ try {
+ await this.source.downloadBinary(this.name, version, destination)
+ // if it's a library, write the version string to a file
+ if (this.type === 'library') {
+ const libraryPath = path.join(destination, this.fileName)
+ await fs.writeFile(libraryPath + '.ver', version)
+ }
+ } catch (err) {
+ Logger.error(`[Binary] Failed to download ${this.type} ${this.name} version ${version} to ${destination}`, err)
+ }
}
}
const ffbinaries = new FFBinariesDownloader()
-module.exports.ffbinaries = ffbinaries // for testing
-//const sqlean = new SQLeanDownloader()
-//module.exports.sqlean = sqlean // for testing
+const nunicode = new NunicodeDownloader()
class BinaryManager {
defaultRequiredBinaries = [
new Binary('ffmpeg', 'executable', 'FFMPEG_PATH', ['5.1'], ffbinaries), // ffmpeg executable
- new Binary('ffprobe', 'executable', 'FFPROBE_PATH', ['5.1'], ffbinaries) // ffprobe executable
- // TODO: Temporarily disabled due to db corruption issues
- // new Binary('unicode', 'library', 'SQLEAN_UNICODE_PATH', ['0.24.2'], sqlean) // sqlean unicode extension
+ new Binary('ffprobe', 'executable', 'FFPROBE_PATH', ['5.1'], ffbinaries), // ffprobe executable
+ new Binary('libnusqlite3', 'library', 'NUSQLITE3_PATH', ['1.2'], nunicode, false) // nunicode sqlite3 extension
]
constructor(requiredBinaries = this.defaultRequiredBinaries) {
@@ -249,7 +338,7 @@ class BinaryManager {
// Optional skip binaries check
if (process.env.SKIP_BINARIES_CHECK === '1') {
for (const binary of this.requiredBinaries) {
- if (!process.env[binary.envVariable]) {
+ if (!process.env[binary.envVariable] && binary.required) {
await Logger.fatal(`[BinaryManager] Environment variable ${binary.envVariable} must be set`)
process.exit(1)
}
@@ -265,21 +354,37 @@ class BinaryManager {
await this.removeOldBinaries(missingBinaries)
await this.install(missingBinaries)
const missingBinariesAfterInstall = await this.findRequiredBinaries()
- if (missingBinariesAfterInstall.length) {
- Logger.error(`[BinaryManager] Failed to find or install required binaries: ${missingBinariesAfterInstall.join(', ')}`)
+ const missingRequiredBinryNames = missingBinariesAfterInstall.filter((binary) => binary.required).map((binary) => binary.name)
+ if (missingRequiredBinryNames.length) {
+ Logger.error(`[BinaryManager] Failed to find or install required binaries: ${missingRequiredBinryNames.join(', ')}`)
process.exit(1)
}
this.initialized = true
}
+ /**
+ * Remove binary
+ *
+ * @param {string} destination
+ * @param {Binary} binary
+ */
async removeBinary(destination, binary) {
- const binaryPath = path.join(destination, binary.fileName)
- if (await fs.pathExists(binaryPath)) {
- Logger.debug(`[BinaryManager] Removing binary: ${binaryPath}`)
- await fs.remove(binaryPath)
+ try {
+ const binaryPath = path.join(destination, binary.fileName)
+ if (await fs.pathExists(binaryPath)) {
+ Logger.debug(`[BinaryManager] Removing binary: ${binaryPath}`)
+ await fs.remove(binaryPath)
+ }
+ } catch (err) {
+ Logger.error(`[BinaryManager] Error removing binary: ${binaryPath}`)
}
}
+ /**
+ * Remove old binaries
+ *
+ * @param {Binary[]} binaries
+ */
async removeOldBinaries(binaries) {
for (const binary of binaries) {
await this.removeBinary(this.mainInstallDir, binary)
@@ -290,26 +395,31 @@ class BinaryManager {
/**
* Find required binaries and return array of binary names that are missing
*
- * @returns {Promise}
+ * @returns {Promise} Array of missing binaries
*/
async findRequiredBinaries() {
const missingBinaries = []
for (const binary of this.requiredBinaries) {
const binaryPath = await binary.find(this.mainInstallDir, this.altInstallDir)
if (binaryPath) {
- Logger.info(`[BinaryManager] Found valid binary ${binary.name} at ${binaryPath}`)
+ Logger.info(`[BinaryManager] Found valid ${binary.type} ${binary.name} at ${binaryPath}`)
if (process.env[binary.envVariable] !== binaryPath) {
Logger.info(`[BinaryManager] Updating process.env.${binary.envVariable}`)
process.env[binary.envVariable] = binaryPath
}
} else {
- Logger.info(`[BinaryManager] ${binary.name} not found or version too old`)
+ Logger.info(`[BinaryManager] ${binary.name} not found or not a valid version`)
missingBinaries.push(binary)
}
}
return missingBinaries
}
+ /**
+ * Install missing binaries
+ *
+ * @param {Binary[]} binaries
+ */
async install(binaries) {
if (!binaries.length) return
Logger.info(`[BinaryManager] Installing binaries: ${binaries.map((binary) => binary.name).join(', ')}`)
@@ -323,3 +433,5 @@ class BinaryManager {
module.exports = BinaryManager
module.exports.Binary = Binary // for testing
+module.exports.ffbinaries = ffbinaries // for testing
+module.exports.nunicode = nunicode // for testing
diff --git a/server/managers/NotificationManager.js b/server/managers/NotificationManager.js
index a59c12815..c48e878c2 100644
--- a/server/managers/NotificationManager.js
+++ b/server/managers/NotificationManager.js
@@ -180,4 +180,4 @@ class NotificationManager {
})
}
}
-module.exports = NotificationManager
+module.exports = new NotificationManager()
diff --git a/server/managers/PodcastManager.js b/server/managers/PodcastManager.js
index adec59871..503f47c0c 100644
--- a/server/managers/PodcastManager.js
+++ b/server/managers/PodcastManager.js
@@ -14,6 +14,7 @@ const ffmpegHelpers = require('../utils/ffmpegHelpers')
const TaskManager = require('./TaskManager')
const CoverManager = require('../managers/CoverManager')
+const NotificationManager = require('../managers/NotificationManager')
const LibraryFile = require('../objects/files/LibraryFile')
const PodcastEpisodeDownload = require('../objects/PodcastEpisodeDownload')
@@ -22,9 +23,8 @@ const AudioFile = require('../objects/files/AudioFile')
const LibraryItem = require('../objects/LibraryItem')
class PodcastManager {
- constructor(watcher, notificationManager) {
+ constructor(watcher) {
this.watcher = watcher
- this.notificationManager = notificationManager
this.downloadQueue = []
this.currentDownload = null
@@ -71,12 +71,20 @@ class PodcastManager {
return
}
- const taskDescription = `Downloading episode "${podcastEpisodeDownload.podcastEpisode.title}".`
const taskData = {
libraryId: podcastEpisodeDownload.libraryId,
libraryItemId: podcastEpisodeDownload.libraryItemId
}
- const task = TaskManager.createAndAddTask('download-podcast-episode', 'Downloading Episode', taskDescription, false, taskData)
+ const taskTitleString = {
+ text: 'Downloading episode',
+ key: 'MessageDownloadingEpisode'
+ }
+ const taskDescriptionString = {
+ text: `Downloading episode "${podcastEpisodeDownload.podcastEpisode.title}".`,
+ key: 'MessageTaskDownloadingEpisodeDescription',
+ subs: [podcastEpisodeDownload.podcastEpisode.title]
+ }
+ const task = TaskManager.createAndAddTask('download-podcast-episode', taskTitleString, taskDescriptionString, false, taskData)
SocketAuthority.emitter('episode_download_started', podcastEpisodeDownload.toJSONForClient())
this.currentDownload = podcastEpisodeDownload
@@ -119,14 +127,22 @@ class PodcastManager {
if (!success) {
await fs.remove(this.currentDownload.targetPath)
this.currentDownload.setFinished(false)
- task.setFailed('Failed to download episode')
+ const taskFailedString = {
+ text: 'Failed',
+ key: 'MessageTaskFailed'
+ }
+ task.setFailed(taskFailedString)
} else {
Logger.info(`[PodcastManager] Successfully downloaded podcast episode "${this.currentDownload.podcastEpisode.title}"`)
this.currentDownload.setFinished(true)
task.setFinished()
}
} else {
- task.setFailed('Failed to download episode')
+ const taskFailedString = {
+ text: 'Failed',
+ key: 'MessageTaskFailed'
+ }
+ task.setFailed(taskFailedString)
this.currentDownload.setFinished(false)
}
@@ -187,7 +203,7 @@ class PodcastManager {
if (this.currentDownload.isAutoDownload) {
// Notifications only for auto downloaded episodes
- this.notificationManager.onPodcastEpisodeDownloaded(libraryItem, podcastEpisode)
+ NotificationManager.onPodcastEpisodeDownloaded(libraryItem, podcastEpisode)
}
return true
@@ -407,13 +423,35 @@ class PodcastManager {
* @param {import('../managers/CronManager')} cronManager
*/
async createPodcastsFromFeedUrls(rssFeedUrls, folder, autoDownloadEpisodes, cronManager) {
- const task = TaskManager.createAndAddTask('opml-import', 'OPML import', `Creating podcasts from ${rssFeedUrls.length} RSS feeds`, true, null)
+ const taskTitleString = {
+ text: 'OPML import',
+ key: 'MessageTaskOpmlImport'
+ }
+ const taskDescriptionString = {
+ text: `Creating podcasts from ${rssFeedUrls.length} RSS feeds`,
+ key: 'MessageTaskOpmlImportDescription',
+ subs: [rssFeedUrls.length]
+ }
+ const task = TaskManager.createAndAddTask('opml-import', taskTitleString, taskDescriptionString, true, null)
let numPodcastsAdded = 0
Logger.info(`[PodcastManager] createPodcastsFromFeedUrls: Importing ${rssFeedUrls.length} RSS feeds to folder "${folder.path}"`)
for (const feedUrl of rssFeedUrls) {
const feed = await getPodcastFeed(feedUrl).catch(() => null)
if (!feed?.episodes) {
- TaskManager.createAndEmitFailedTask('opml-import-feed', 'OPML import feed', `Importing RSS feed "${feedUrl}"`, 'Failed to get podcast feed')
+ const taskTitleStringFeed = {
+ text: 'OPML import feed',
+ key: 'MessageTaskOpmlImportFeed'
+ }
+ const taskDescriptionStringFeed = {
+ text: `Importing RSS feed "${feedUrl}"`,
+ key: 'MessageTaskOpmlImportFeedDescription',
+ subs: [feedUrl]
+ }
+ const taskErrorString = {
+ text: 'Failed to get podcast feed',
+ key: 'MessageTaskOpmlImportFeedFailed'
+ }
+ TaskManager.createAndEmitFailedTask('opml-import-feed', taskTitleStringFeed, taskDescriptionStringFeed, taskErrorString)
Logger.error(`[PodcastManager] createPodcastsFromFeedUrls: Failed to get podcast feed for "${feedUrl}"`)
continue
}
@@ -429,7 +467,20 @@ class PodcastManager {
})) > 0
if (existingLibraryItem) {
Logger.error(`[PodcastManager] createPodcastsFromFeedUrls: Podcast already exists at path "${podcastPath}"`)
- TaskManager.createAndEmitFailedTask('opml-import-feed', 'OPML import feed', `Creating podcast "${feed.metadata.title}"`, 'Podcast already exists at path')
+ const taskTitleStringFeed = {
+ text: 'OPML import feed',
+ key: 'MessageTaskOpmlImportFeed'
+ }
+ const taskDescriptionStringPodcast = {
+ text: `Creating podcast "${feed.metadata.title}"`,
+ key: 'MessageTaskOpmlImportFeedPodcastDescription',
+ subs: [feed.metadata.title]
+ }
+ const taskErrorString = {
+ text: 'Podcast already exists at path',
+ key: 'MessageTaskOpmlImportFeedPodcastExists'
+ }
+ TaskManager.createAndEmitFailedTask('opml-import-feed', taskTitleStringFeed, taskDescriptionStringPodcast, taskErrorString)
continue
}
@@ -442,7 +493,20 @@ class PodcastManager {
})
if (!successCreatingPath) {
Logger.error(`[PodcastManager] createPodcastsFromFeedUrls: Failed to create podcast folder at "${podcastPath}"`)
- TaskManager.createAndEmitFailedTask('opml-import-feed', 'OPML import feed', `Creating podcast "${feed.metadata.title}"`, 'Failed to create podcast folder')
+ const taskTitleStringFeed = {
+ text: 'OPML import feed',
+ key: 'MessageTaskOpmlImportFeed'
+ }
+ const taskDescriptionStringPodcast = {
+ text: `Creating podcast "${feed.metadata.title}"`,
+ key: 'MessageTaskOpmlImportFeedPodcastDescription',
+ subs: [feed.metadata.title]
+ }
+ const taskErrorString = {
+ text: 'Failed to create podcast folder',
+ key: 'MessageTaskOpmlImportFeedPodcastFailed'
+ }
+ TaskManager.createAndEmitFailedTask('opml-import-feed', taskTitleStringFeed, taskDescriptionStringPodcast, taskErrorString)
continue
}
@@ -504,7 +568,12 @@ class PodcastManager {
numPodcastsAdded++
}
- task.setFinished(`Added ${numPodcastsAdded} podcasts`)
+ const taskFinishedString = {
+ text: `Added ${numPodcastsAdded} podcasts`,
+ key: 'MessageTaskOpmlImportFinished',
+ subs: [numPodcastsAdded]
+ }
+ task.setFinished(taskFinishedString)
TaskManager.taskFinished(task)
Logger.info(`[PodcastManager] createPodcastsFromFeedUrls: Finished OPML import. Created ${numPodcastsAdded} podcasts out of ${rssFeedUrls.length} RSS feed URLs`)
}
diff --git a/server/managers/TaskManager.js b/server/managers/TaskManager.js
index 1a8b6c85b..5067f841a 100644
--- a/server/managers/TaskManager.js
+++ b/server/managers/TaskManager.js
@@ -1,6 +1,13 @@
const SocketAuthority = require('../SocketAuthority')
const Task = require('../objects/Task')
+/**
+ * @typedef TaskString
+ * @property {string} text
+ * @property {string} key
+ * @property {string[]} [subs]
+ */
+
class TaskManager {
constructor() {
/** @type {Task[]} */
@@ -33,14 +40,14 @@ class TaskManager {
* Create new task and add
*
* @param {string} action
- * @param {string} title
- * @param {string} description
+ * @param {TaskString} titleString
+ * @param {TaskString|null} descriptionString
* @param {boolean} showSuccess
* @param {Object} [data]
*/
- createAndAddTask(action, title, description, showSuccess, data = {}) {
+ createAndAddTask(action, titleString, descriptionString, showSuccess, data = {}) {
const task = new Task()
- task.setData(action, title, description, showSuccess, data)
+ task.setData(action, titleString, descriptionString, showSuccess, data)
this.addTask(task)
return task
}
@@ -49,14 +56,14 @@ class TaskManager {
* Create new failed task and add
*
* @param {string} action
- * @param {string} title
- * @param {string} description
- * @param {string} errorMessage
+ * @param {TaskString} titleString
+ * @param {TaskString|null} descriptionString
+ * @param {TaskString} errorMessageString
*/
- createAndEmitFailedTask(action, title, description, errorMessage) {
+ createAndEmitFailedTask(action, titleString, descriptionString, errorMessageString) {
const task = new Task()
- task.setData(action, title, description, false)
- task.setFailed(errorMessage)
+ task.setData(action, titleString, descriptionString, false)
+ task.setFailed(errorMessageString)
SocketAuthority.emitter('task_started', task.toJSON())
return task
}
diff --git a/server/models/Author.js b/server/models/Author.js
index 40e7f75a4..f3bbba574 100644
--- a/server/models/Author.js
+++ b/server/models/Author.js
@@ -1,6 +1,5 @@
const { DataTypes, Model, where, fn, col } = require('sequelize')
const parseNameString = require('../utils/parsers/parseNameString')
-const { asciiOnlyToLowerCase } = require('../utils/index')
class Author extends Model {
constructor(values, options) {
@@ -56,7 +55,7 @@ class Author extends Model {
static async getByNameAndLibrary(authorName, libraryId) {
return this.findOne({
where: [
- where(fn('lower', col('name')), asciiOnlyToLowerCase(authorName)),
+ where(fn('lower', col('name')), authorName.toLowerCase()),
{
libraryId
}
diff --git a/server/models/Series.js b/server/models/Series.js
index dc8d110fd..c57a1a116 100644
--- a/server/models/Series.js
+++ b/server/models/Series.js
@@ -1,7 +1,6 @@
const { DataTypes, Model, where, fn, col } = require('sequelize')
const { getTitlePrefixAtEnd } = require('../utils/index')
-const { asciiOnlyToLowerCase } = require('../utils/index')
class Series extends Model {
constructor(values, options) {
@@ -42,7 +41,7 @@ class Series extends Model {
static async getByNameAndLibrary(seriesName, libraryId) {
return this.findOne({
where: [
- where(fn('lower', col('name')), asciiOnlyToLowerCase(seriesName)),
+ where(fn('lower', col('name')), seriesName.toLowerCase()),
{
libraryId
}
diff --git a/server/objects/Task.js b/server/objects/Task.js
index db7e490e6..e6fb39636 100644
--- a/server/objects/Task.js
+++ b/server/objects/Task.js
@@ -1,4 +1,11 @@
-const uuidv4 = require("uuid").v4
+const uuidv4 = require('uuid').v4
+
+/**
+ * @typedef TaskString
+ * @property {string} text
+ * @property {string} key
+ * @property {string[]} [subs]
+ */
class Task {
constructor() {
@@ -11,10 +18,25 @@ class Task {
/** @type {string} */
this.title = null
+ /** @type {string} - Used for translation */
+ this.titleKey = null
+ /** @type {string[]} - Used for translation */
+ this.titleSubs = null
+
/** @type {string} */
this.description = null
+ /** @type {string} - Used for translation */
+ this.descriptionKey = null
+ /** @type {string[]} - Used for translation */
+ this.descriptionSubs = null
+
/** @type {string} */
this.error = null
+ /** @type {string} - Used for translation */
+ this.errorKey = null
+ /** @type {string[]} - Used for translation */
+ this.errorSubs = null
+
/** @type {boolean} client should keep the task visible after success */
this.showSuccess = false
@@ -35,8 +57,14 @@ class Task {
action: this.action,
data: this.data ? { ...this.data } : {},
title: this.title,
+ titleKey: this.titleKey,
+ titleSubs: this.titleSubs,
description: this.description,
+ descriptionKey: this.descriptionKey,
+ descriptionSubs: this.descriptionSubs,
error: this.error,
+ errorKey: this.errorKey,
+ errorSubs: this.errorSubs,
showSuccess: this.showSuccess,
isFailed: this.isFailed,
isFinished: this.isFinished,
@@ -47,30 +75,36 @@ class Task {
/**
* Set initial task data
- *
- * @param {string} action
- * @param {string} title
- * @param {string} description
- * @param {boolean} showSuccess
- * @param {Object} [data]
+ *
+ * @param {string} action
+ * @param {TaskString} titleString
+ * @param {TaskString|null} descriptionString
+ * @param {boolean} showSuccess
+ * @param {Object} [data]
*/
- setData(action, title, description, showSuccess, data = {}) {
+ setData(action, titleString, descriptionString, showSuccess, data = {}) {
this.id = uuidv4()
this.action = action
this.data = { ...data }
- this.title = title
- this.description = description
+ this.title = titleString.text
+ this.titleKey = titleString.key || null
+ this.titleSubs = titleString.subs || null
+ this.description = descriptionString?.text || null
+ this.descriptionKey = descriptionString?.key || null
+ this.descriptionSubs = descriptionString?.subs || null
this.showSuccess = showSuccess
this.startedAt = Date.now()
}
/**
* Set task as failed
- *
- * @param {string} message error message
+ *
+ * @param {TaskString} messageString
*/
- setFailed(message) {
- this.error = message
+ setFailed(messageString) {
+ this.error = messageString.text
+ this.errorKey = messageString.key || null
+ this.errorSubs = messageString.subs || null
this.isFailed = true
this.failedAt = Date.now()
this.setFinished()
@@ -78,15 +112,22 @@ class Task {
/**
* Set task as finished
- *
- * @param {string} [newDescription] update description
+ *
+ * @param {TaskString} [newDescriptionString] update description
+ * @param {boolean} [clearDescription] clear description
*/
- setFinished(newDescription = null) {
- if (newDescription) {
- this.description = newDescription
+ setFinished(newDescriptionString = null, clearDescription = false) {
+ if (newDescriptionString) {
+ this.description = newDescriptionString.text
+ this.descriptionKey = newDescriptionString.key || null
+ this.descriptionSubs = newDescriptionString.subs || null
+ } else if (clearDescription) {
+ this.description = null
+ this.descriptionKey = null
+ this.descriptionSubs = null
}
this.isFinished = true
this.finishedAt = Date.now()
}
}
-module.exports = Task
\ No newline at end of file
+module.exports = Task
diff --git a/server/providers/Audnexus.js b/server/providers/Audnexus.js
index 60762ede6..4f11a2a36 100644
--- a/server/providers/Audnexus.js
+++ b/server/providers/Audnexus.js
@@ -129,7 +129,7 @@ class Audnexus {
return null
}
- const author = await this.authorRequest(closestMatch.asin)
+ const author = await this.authorRequest(closestMatch.asin, region)
if (!author) {
return null
}
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index f44fedb47..1e9d27451 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -55,8 +55,6 @@ class ApiRouter {
this.rssFeedManager = Server.rssFeedManager
/** @type {import('../managers/CronManager')} */
this.cronManager = Server.cronManager
- /** @type {import('../managers/NotificationManager')} */
- this.notificationManager = Server.notificationManager
/** @type {import('../managers/EmailManager')} */
this.emailManager = Server.emailManager
this.apiCacheManager = Server.apiCacheManager
diff --git a/server/scanner/AudioFileScanner.js b/server/scanner/AudioFileScanner.js
index 1cd148f6f..2a70e6a07 100644
--- a/server/scanner/AudioFileScanner.js
+++ b/server/scanner/AudioFileScanner.js
@@ -475,16 +475,26 @@ class AudioFileScanner {
audioFiles.forEach((file) => {
if (file.duration) {
- const afChapters =
- file.chapters?.map((c) => ({
- ...c,
- id: c.id + currChapterId,
- start: c.start + currStartTime,
- end: c.end + currStartTime
- })) ?? []
+ // Multi-file audiobook may include the previous and next chapters embedded with close to 0 duration
+ // Filter these out and log a warning
+ // See https://github.com/advplyr/audiobookshelf/issues/3361
+ const afChaptersCleaned =
+ file.chapters?.filter((c) => {
+ if (c.end - c.start < 0.1) {
+ libraryScan.addLog(LogLevel.WARN, `Chapter "${c.title}" has invalid duration of ${c.end - c.start} seconds. Skipping this chapter.`)
+ return false
+ }
+ return true
+ }) || []
+ const afChapters = afChaptersCleaned.map((c) => ({
+ ...c,
+ id: c.id + currChapterId,
+ start: c.start + currStartTime,
+ end: c.end + currStartTime
+ }))
chapters = chapters.concat(afChapters)
- currChapterId += file.chapters?.length ?? 0
+ currChapterId += afChaptersCleaned.length ?? 0
currStartTime += file.duration
}
})
diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js
index 279fcf64d..f0737dac0 100644
--- a/server/scanner/BookScanner.js
+++ b/server/scanner/BookScanner.js
@@ -590,6 +590,10 @@ class BookScanner {
Database.addPublisherToFilterData(libraryItemData.libraryId, libraryItem.book.publisher)
Database.addLanguageToFilterData(libraryItemData.libraryId, libraryItem.book.language)
+ const publishedYear = libraryItem.book.publishedYear
+ const decade = publishedYear ? `${Math.floor(publishedYear / 10) * 10}` : null
+ Database.addPublishedDecadeToFilterData(libraryItemData.libraryId, decade)
+
// Load for emitting to client
libraryItem.media = await libraryItem.getMedia({
include: [
diff --git a/server/scanner/LibraryItemScanner.js b/server/scanner/LibraryItemScanner.js
index 1c3123df1..38608e479 100644
--- a/server/scanner/LibraryItemScanner.js
+++ b/server/scanner/LibraryItemScanner.js
@@ -15,12 +15,12 @@ const LibraryFile = require('../objects/files/LibraryFile')
const SocketAuthority = require('../SocketAuthority')
class LibraryItemScanner {
- constructor() { }
+ constructor() {}
/**
* Scan single library item
- *
- * @param {string} libraryItemId
+ *
+ * @param {string} libraryItemId
* @param {{relPath:string, path:string}} [updateLibraryItemDetails] used by watcher when item folder was renamed
* @returns {number} ScanResult
*/
@@ -76,8 +76,8 @@ class LibraryItemScanner {
/**
* Remove empty authors and series
- * @param {string} libraryId
- * @param {ScanLogger} scanLogger
+ * @param {string} libraryId
+ * @param {ScanLogger} scanLogger
* @returns {Promise}
*/
async checkAuthorsAndSeriesRemovedFromBooks(libraryId, scanLogger) {
@@ -90,11 +90,11 @@ class LibraryItemScanner {
}
/**
- *
- * @param {string} libraryItemPath
- * @param {import('../models/Library')} library
- * @param {import('../models/LibraryFolder')} folder
- * @param {boolean} isSingleMediaItem
+ *
+ * @param {string} libraryItemPath
+ * @param {import('../models/Library')} library
+ * @param {import('../models/LibraryFolder')} folder
+ * @param {boolean} isSingleMediaItem
* @returns {Promise}
*/
async getLibraryItemScanData(libraryItemPath, library, folder, isSingleMediaItem) {
@@ -105,7 +105,8 @@ class LibraryItemScanner {
let fileItems = []
- if (isSingleMediaItem) { // Single media item in root of folder
+ if (isSingleMediaItem) {
+ // Single media item in root of folder
fileItems = [
{
fullpath: libraryItemPath,
@@ -151,9 +152,9 @@ class LibraryItemScanner {
}
/**
- *
- * @param {import('../models/LibraryItem')} existingLibraryItem
- * @param {LibraryItemScanData} libraryItemData
+ *
+ * @param {import('../models/LibraryItem')} existingLibraryItem
+ * @param {LibraryItemScanData} libraryItemData
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
* @param {LibraryScan} libraryScan
* @returns {Promise<{libraryItem:LibraryItem, wasUpdated:boolean}>}
@@ -167,8 +168,8 @@ class LibraryItemScanner {
}
/**
- *
- * @param {LibraryItemScanData} libraryItemData
+ *
+ * @param {LibraryItemScanData} libraryItemData
* @param {import('../models/Library').LibrarySettingsObject} librarySettings
* @param {LibraryScan} libraryScan
* @returns {Promise}
@@ -181,17 +182,17 @@ class LibraryItemScanner {
newLibraryItem = await PodcastScanner.scanNewPodcastLibraryItem(libraryItemData, librarySettings, libraryScan)
}
if (newLibraryItem) {
- libraryScan.addLog(LogLevel.INFO, `Created new library item "${newLibraryItem.relPath}"`)
+ libraryScan.addLog(LogLevel.INFO, `Created new library item "${newLibraryItem.relPath}" with id "${newLibraryItem.id}"`)
}
return newLibraryItem
}
/**
* Scan library item folder coming from Watcher
- * @param {string} libraryItemPath
- * @param {import('../models/Library')} library
- * @param {import('../models/LibraryFolder')} folder
- * @param {boolean} isSingleMediaItem
+ * @param {string} libraryItemPath
+ * @param {import('../models/Library')} library
+ * @param {import('../models/LibraryFolder')} folder
+ * @param {boolean} isSingleMediaItem
* @returns {Promise} ScanResult
*/
async scanPotentialNewLibraryItem(libraryItemPath, library, folder, isSingleMediaItem) {
@@ -204,4 +205,4 @@ class LibraryItemScanner {
return this.scanNewLibraryItem(libraryItemScanData, library.settings, scanLogger)
}
}
-module.exports = new LibraryItemScanner()
\ No newline at end of file
+module.exports = new LibraryItemScanner()
diff --git a/server/scanner/LibraryScan.js b/server/scanner/LibraryScan.js
index 8994aa231..220c6eb4a 100644
--- a/server/scanner/LibraryScan.js
+++ b/server/scanner/LibraryScan.js
@@ -18,7 +18,6 @@ class LibraryScan {
this.startedAt = null
this.finishedAt = null
this.elapsed = null
- this.error = null
this.resultsMissing = 0
this.resultsAdded = 0
@@ -55,22 +54,6 @@ class LibraryScan {
get elapsedTimestamp() {
return secondsToTimestamp(this.elapsed / 1000)
}
- get getScanEmitData() {
- return {
- id: this.libraryId,
- type: this.type,
- name: this.libraryName,
- error: this.error,
- results: {
- added: this.resultsAdded,
- updated: this.resultsUpdated,
- missing: this.resultsMissing
- }
- }
- }
- get totalResults() {
- return this.resultsAdded + this.resultsUpdated + this.resultsMissing
- }
get logFilename() {
return date.format(new Date(), 'YYYY-MM-DD') + '_' + this.id + '.txt'
}
@@ -79,10 +62,19 @@ class LibraryScan {
if (this.resultsAdded) strs.push(`${this.resultsAdded} added`)
if (this.resultsUpdated) strs.push(`${this.resultsUpdated} updated`)
if (this.resultsMissing) strs.push(`${this.resultsMissing} missing`)
- const changesDetected = strs.length > 0 ? strs.join(', ') : 'No changes detected'
+ const changesDetected = strs.length > 0 ? strs.join(', ') : 'No changes needed'
const timeElapsed = `(${elapsedPretty(this.elapsed / 1000)})`
- const error = this.error ? `${this.error}. ` : ''
- return `${error}${changesDetected} ${timeElapsed}`
+ return `${changesDetected} ${timeElapsed}`
+ }
+
+ get scanResults() {
+ return {
+ added: this.resultsAdded,
+ updated: this.resultsUpdated,
+ missing: this.resultsMissing,
+ elapsed: this.elapsed,
+ text: this.scanResultsString
+ }
}
toJSON() {
@@ -93,7 +85,6 @@ class LibraryScan {
startedAt: this.startedAt,
finishedAt: this.finishedAt,
elapsed: this.elapsed,
- error: this.error,
resultsAdded: this.resultsAdded,
resultsUpdated: this.resultsUpdated,
resultsMissing: this.resultsMissing
@@ -113,14 +104,9 @@ class LibraryScan {
this.startedAt = Date.now()
}
- /**
- *
- * @param {string} error
- */
- setComplete(error = null) {
+ setComplete() {
this.finishedAt = Date.now()
this.elapsed = this.finishedAt - this.startedAt
- this.error = error
}
getLogLevelString(level) {
diff --git a/server/scanner/LibraryScanner.js b/server/scanner/LibraryScanner.js
index 5cd8b5c62..bd0bb310f 100644
--- a/server/scanner/LibraryScanner.js
+++ b/server/scanner/LibraryScanner.js
@@ -18,6 +18,7 @@ const Task = require('../objects/Task')
class LibraryScanner {
constructor() {
this.cancelLibraryScan = {}
+ /** @type {string[]} - library ids */
this.librariesScanning = []
this.scanningFilesChanged = false
@@ -30,7 +31,7 @@ class LibraryScanner {
* @returns {boolean}
*/
isLibraryScanning(libraryId) {
- return this.librariesScanning.some((ls) => ls.id === libraryId)
+ return this.librariesScanning.some((lid) => lid === libraryId)
}
/**
@@ -38,8 +39,7 @@ class LibraryScanner {
* @param {string} libraryId
*/
setCancelLibraryScan(libraryId) {
- const libraryScanning = this.librariesScanning.find((ls) => ls.id === libraryId)
- if (!libraryScanning) return
+ if (!this.isLibraryScanning(libraryId)) return
this.cancelLibraryScan[libraryId] = true
}
@@ -69,14 +69,19 @@ class LibraryScanner {
const libraryScan = new LibraryScan()
libraryScan.setData(library)
libraryScan.verbose = true
- this.librariesScanning.push(libraryScan.getScanEmitData)
+ this.librariesScanning.push(libraryScan.libraryId)
const taskData = {
libraryId: library.id,
libraryName: library.name,
libraryMediaType: library.mediaType
}
- const task = TaskManager.createAndAddTask('library-scan', `Scanning "${library.name}" library`, null, true, taskData)
+ const taskTitleString = {
+ text: `Scanning "${library.name}" library`,
+ key: 'MessageTaskScanningLibrary',
+ subs: [library.name]
+ }
+ const task = TaskManager.createAndAddTask('library-scan', taskTitleString, null, true, taskData)
Logger.info(`[LibraryScanner] Starting${forceRescan ? ' (forced)' : ''} library scan ${libraryScan.id} for ${libraryScan.libraryName}`)
@@ -98,17 +103,31 @@ class LibraryScanner {
await library.save()
}
- task.setFinished(`${canceled ? 'Canceled' : 'Completed'}. ${libraryScan.scanResultsString}`)
+ task.data.scanResults = libraryScan.scanResults
+ if (canceled) {
+ const taskFinishedString = {
+ text: 'Task canceled by user',
+ key: 'MessageTaskCanceledByUser'
+ }
+ task.setFinished(taskFinishedString)
+ } else {
+ task.setFinished(null, true)
+ }
} catch (err) {
- libraryScan.setComplete(err)
+ libraryScan.setComplete()
Logger.error(`[LibraryScanner] Library scan ${libraryScan.id} failed after ${libraryScan.elapsedTimestamp} | ${libraryScan.resultStats}.`, err)
- task.setFailed(`Failed. ${libraryScan.scanResultsString}`)
+ task.data.scanResults = libraryScan.scanResults
+ const taskFailedString = {
+ text: 'Failed',
+ key: 'MessageTaskFailed'
+ }
+ task.setFailed(taskFailedString)
}
if (this.cancelLibraryScan[libraryScan.libraryId]) delete this.cancelLibraryScan[libraryScan.libraryId]
- this.librariesScanning = this.librariesScanning.filter((ls) => ls.id !== library.id)
+ this.librariesScanning = this.librariesScanning.filter((lid) => lid !== library.id)
TaskManager.taskFinished(task)
@@ -441,9 +460,15 @@ class LibraryScanner {
if (results.added) resultStrs.push(`${results.added} added`)
if (results.updated) resultStrs.push(`${results.updated} updated`)
if (results.removed) resultStrs.push(`${results.removed} missing`)
- let scanResultStr = 'Scan finished with no changes'
+ let scanResultStr = 'No changes needed'
if (resultStrs.length) scanResultStr = resultStrs.join(', ')
- pendingTask.setFinished(scanResultStr)
+
+ pendingTask.data.scanResults = {
+ ...results,
+ text: scanResultStr,
+ elapsed: Date.now() - pendingTask.startedAt
+ }
+ pendingTask.setFinished(null, true)
TaskManager.taskFinished(pendingTask)
this.scanningFilesChanged = false
@@ -593,7 +618,7 @@ class LibraryScanner {
}
}
// Scan library item for updates
- Logger.debug(`[LibraryScanner] Folder update for relative path "${itemDir}" is in library item "${existingLibraryItem.media.metadata.title}" - scan for updates`)
+ Logger.debug(`[LibraryScanner] Folder update for relative path "${itemDir}" is in library item "${existingLibraryItem.media.metadata.title}" with id "${existingLibraryItem.id}" - scan for updates`)
itemGroupingResults[itemDir] = await LibraryItemScanner.scanLibraryItem(existingLibraryItem.id, updatedLibraryItemDetails)
continue
} else if (library.settings.audiobooksOnly && !hasAudioFiles(fileUpdateGroup, itemDir)) {
diff --git a/server/scanner/Scanner.js b/server/scanner/Scanner.js
index 06657de22..cfdeb1402 100644
--- a/server/scanner/Scanner.js
+++ b/server/scanner/Scanner.js
@@ -364,11 +364,16 @@ class Scanner {
const libraryScan = new LibraryScan()
libraryScan.setData(library, 'match')
- LibraryScanner.librariesScanning.push(libraryScan.getScanEmitData)
+ LibraryScanner.librariesScanning.push(libraryScan.libraryId)
const taskData = {
libraryId: library.id
}
- const task = TaskManager.createAndAddTask('library-match-all', `Matching books in "${library.name}"`, null, true, taskData)
+ const taskTitleString = {
+ text: `Matching books in "${library.name}"`,
+ key: 'MessageTaskMatchingBooksInLibrary',
+ subs: [library.name]
+ }
+ const task = TaskManager.createAndAddTask('library-match-all', taskTitleString, null, true, taskData)
Logger.info(`[Scanner] matchLibraryItems: Starting library match scan ${libraryScan.id} for ${libraryScan.libraryName}`)
let hasMoreChunks = true
@@ -392,15 +397,29 @@ class Scanner {
if (offset === 0) {
Logger.error(`[Scanner] matchLibraryItems: Library has no items ${library.id}`)
- libraryScan.setComplete('Library has no items')
- task.setFailed(libraryScan.error)
+ libraryScan.setComplete()
+ const taskFailedString = {
+ text: 'No items found',
+ key: 'MessageNoItemsFound'
+ }
+ task.setFailed(taskFailedString)
} else {
libraryScan.setComplete()
- task.setFinished(isCanceled ? 'Canceled' : libraryScan.scanResultsString)
+
+ task.data.scanResults = libraryScan.scanResults
+ if (isCanceled) {
+ const taskFinishedString = {
+ text: 'Task canceled by user',
+ key: 'MessageTaskCanceledByUser'
+ }
+ task.setFinished(taskFinishedString)
+ } else {
+ task.setFinished(null, true)
+ }
}
delete LibraryScanner.cancelLibraryScan[libraryScan.libraryId]
- LibraryScanner.librariesScanning = LibraryScanner.librariesScanning.filter((ls) => ls.id !== library.id)
+ LibraryScanner.librariesScanning = LibraryScanner.librariesScanning.filter((lid) => lid !== library.id)
TaskManager.taskFinished(task)
}
}
diff --git a/server/utils/index.js b/server/utils/index.js
index 2d52bcd08..fa7ae92ed 100644
--- a/server/utils/index.js
+++ b/server/utils/index.js
@@ -194,29 +194,6 @@ module.exports.getTitlePrefixAtEnd = (title) => {
return prefix ? `${sort}, ${prefix}` : title
}
-/**
- * to lower case for only ascii characters
- * used to handle sqlite that doesnt support unicode lower
- * @see https://github.com/advplyr/audiobookshelf/issues/2187
- *
- * @param {string} str
- * @returns {string}
- */
-module.exports.asciiOnlyToLowerCase = (str) => {
- if (!str) return ''
-
- let temp = ''
- for (let chars of str) {
- let value = chars.charCodeAt()
- if (value >= 65 && value <= 90) {
- temp += String.fromCharCode(value + 32)
- } else {
- temp += chars
- }
- }
- return temp
-}
-
/**
* Escape string used in RegExp
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
diff --git a/server/utils/queries/authorFilters.js b/server/utils/queries/authorFilters.js
index 675915350..3d6bc7bd6 100644
--- a/server/utils/queries/authorFilters.js
+++ b/server/utils/queries/authorFilters.js
@@ -54,13 +54,13 @@ module.exports = {
* Search authors
*
* @param {string} libraryId
- * @param {string} query
+ * @param {Database.TextQuery} query
* @param {number} limit
* @param {number} offset
* @returns {Promise