mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-01-04 13:39:38 -05:00
Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
964ef910b6 | ||
|
|
ba6a88a5bf | ||
|
|
1576164218 | ||
|
|
94400f7794 | ||
|
|
41e1b02f3a | ||
|
|
1337c60cde | ||
|
|
e9b4e07bd8 | ||
|
|
607fdffc18 | ||
|
|
216139119b | ||
|
|
19cbd1f8de | ||
|
|
bf893a56c9 | ||
|
|
3a2f680a51 | ||
|
|
ce7f891b9b | ||
|
|
8ec9da143f | ||
|
|
7f28fbb330 | ||
|
|
3111d1860a | ||
|
|
bd3dce26d9 | ||
|
|
db9ee301e3 | ||
|
|
7d8fb3bb10 | ||
|
|
6fa49e0aab | ||
|
|
30d3e41542 | ||
|
|
c58d613949 | ||
|
|
38ba7fbec2 | ||
|
|
6fad4521d4 | ||
|
|
2f72300636 | ||
|
|
b9cb54db71 | ||
|
|
aaaa314761 | ||
|
|
4e40dbc3a5 | ||
|
|
ba6a4f1224 | ||
|
|
524ed9b677 | ||
|
|
5bbcb9cac3 | ||
|
|
ff169f3fd0 | ||
|
|
cf7b08c993 | ||
|
|
d99a77837b | ||
|
|
23dcf684d9 | ||
|
|
9c2ed279df | ||
|
|
700d7fe68e | ||
|
|
69833db819 | ||
|
|
ab2026ecea | ||
|
|
811fd9018a | ||
|
|
6d89721371 | ||
|
|
ab3a137db9 | ||
|
|
a11cf7a90e | ||
|
|
c995816076 | ||
|
|
94e7fc6434 | ||
|
|
3916bfe833 | ||
|
|
3080ada35f | ||
|
|
4cddc597c1 | ||
|
|
ec07bfa940 | ||
|
|
d20d4bf8c1 | ||
|
|
09e26a9e56 | ||
|
|
ef74919f12 | ||
|
|
6462a50713 | ||
|
|
8c6c43657c | ||
|
|
b8ed56e91e | ||
|
|
dc0eaa32c9 | ||
|
|
60fc4e20e6 | ||
|
|
6f43b32214 | ||
|
|
5e8ae79d71 | ||
|
|
34718aa95d | ||
|
|
d731ad1bd7 | ||
|
|
e7fa698645 | ||
|
|
851d298916 | ||
|
|
1a27e2bef7 | ||
|
|
d64860001b | ||
|
|
b82ac3d536 | ||
|
|
91be9eb0fc | ||
|
|
d61bb0bea0 | ||
|
|
911d72971e | ||
|
|
b244cc8d41 | ||
|
|
8cc3bfa95e | ||
|
|
ba3d59c645 | ||
|
|
e416958b01 | ||
|
|
05c1ced65c | ||
|
|
057bc1a0c0 | ||
|
|
32fc224600 | ||
|
|
fcecd415c8 | ||
|
|
e384527b67 | ||
|
|
672672dd2a | ||
|
|
fd22a6f51d | ||
|
|
c674042319 | ||
|
|
a668921e29 | ||
|
|
04ed4810fd | ||
|
|
941c798d78 | ||
|
|
7f12c71eca | ||
|
|
f62d10746d | ||
|
|
13afa12456 | ||
|
|
4e1406f612 | ||
|
|
ce98bcc989 | ||
|
|
ff5cbae059 | ||
|
|
04a7f24bac | ||
|
|
68bfcb2e6e | ||
|
|
4bd7e21a51 | ||
|
|
37932f664a | ||
|
|
0081525ed3 | ||
|
|
7e13cb6ecf | ||
|
|
721dd14c1f | ||
|
|
047c8ec017 | ||
|
|
fa5d2b2020 | ||
|
|
dfe6505af0 | ||
|
|
b0e33970b8 | ||
|
|
d9f828c717 | ||
|
|
15ca3307bd | ||
|
|
fa3b7e2f60 | ||
|
|
a6de76a983 | ||
|
|
724e06e9d2 | ||
|
|
bf3db1dae0 | ||
|
|
410801347c | ||
|
|
5041f80cb0 | ||
|
|
7229cfce84 | ||
|
|
cb1ebd4a17 | ||
|
|
7929f3dc42 | ||
|
|
95cdb23efb | ||
|
|
182527bfa8 | ||
|
|
2eb19d46d5 | ||
|
|
10e7f142ec | ||
|
|
c55988102d | ||
|
|
d488b17869 | ||
|
|
ff27c0b58b | ||
|
|
2bd532eb9a | ||
|
|
e5fe31fe26 | ||
|
|
ec83eb0a27 | ||
|
|
6236f53b4f | ||
|
|
1b2cf50633 | ||
|
|
3ab638ed61 | ||
|
|
bd1309b680 | ||
|
|
00bc50c02d | ||
|
|
e8bb92826a | ||
|
|
a0cc42b385 | ||
|
|
7edc7ce861 | ||
|
|
0302ed986e | ||
|
|
babfb6978a | ||
|
|
2cb53fafd7 | ||
|
|
8dbe35e5aa | ||
|
|
bd06b6c716 | ||
|
|
0498d8cb83 | ||
|
|
129da51f76 | ||
|
|
e5bababeae | ||
|
|
9b332f0e66 | ||
|
|
a49c5afa46 | ||
|
|
9e1c907591 | ||
|
|
d638a328d8 | ||
|
|
f597798839 | ||
|
|
303ef6b7c5 |
@@ -6,5 +6,5 @@ module.exports.config = {
|
||||
MetadataPath: Path.resolve('metadata'),
|
||||
FFmpegPath: '/usr/bin/ffmpeg',
|
||||
FFProbePath: '/usr/bin/ffprobe',
|
||||
SkipBinariesCheck: false
|
||||
SkipBinariesCheck: true
|
||||
}
|
||||
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
73
.github/ISSUE_TEMPLATE/bug.yaml
vendored
73
.github/ISSUE_TEMPLATE/bug.yaml
vendored
@@ -1,40 +1,50 @@
|
||||
name: 🐞 Bug Report
|
||||
description: File a bug/issue
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
description: File a bug/issue and help us improve Audiobookshelf
|
||||
title: '[Bug]: '
|
||||
labels: ['bug', 'triage']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Please first search for your issue and check the [docs](https://audiobookshelf.org/docs)."
|
||||
value: 'Thank you for filing a bug report! 🐛'
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Mobile app issues report [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)."
|
||||
value: 'Please first search for your issue and check the [docs](https://audiobookshelf.org/docs).'
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Join the [discord server](https://discord.gg/HQgCbd6E75) for questions or if you are not sure about a bug."
|
||||
value: 'Report issues with the mobile app [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose).'
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "## Be as descriptive as you can. Include screenshots, error logs, browser, file types, everything you can think of that might be relevant."
|
||||
value: 'Join the [discord server](https://discord.gg/HQgCbd6E75) for questions or if you are not sure about a bug.'
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: Describe the issue
|
||||
description: What happened & what did you expect to happen
|
||||
label: What happened?
|
||||
placeholder: Tell us what you see!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: what-was-expected
|
||||
attributes:
|
||||
label: What did you expect to happen?
|
||||
placeholder: Tell us what you expected to see! Be as descriptive as you can and include screenshots if applicable.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce the issue
|
||||
value: "1. "
|
||||
value: '1. '
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: '## Install Environment'
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Audiobookshelf version
|
||||
description: Do not put 'Latest version', please put the actual version here
|
||||
placeholder: "e.g. v1.6.60"
|
||||
placeholder: 'e.g. v1.6.60'
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -46,6 +56,43 @@ body:
|
||||
- Debian/PPA
|
||||
- Windows Tray App
|
||||
- Built from source
|
||||
- Other
|
||||
- Other (list in "Additional Notes" box)
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: server-os
|
||||
attributes:
|
||||
label: What OS is your Audiobookshelf server hosted from?
|
||||
options:
|
||||
- Windows
|
||||
- macOS
|
||||
- Linux
|
||||
- Other (list in "Additional Notes" box)
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: desktop-browsers
|
||||
attributes:
|
||||
label: If the issue is being seen in the UI, what browsers are you seeing the problem on?
|
||||
options:
|
||||
- Chrome
|
||||
- Firefox
|
||||
- Safari
|
||||
- Edge
|
||||
- Firefox for Android
|
||||
- Chrome for Android
|
||||
- Safari on iOS
|
||||
- Other (list in "Additional Notes" box)
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: Please include any relevant logs here. This field is automatically formatted into code, so you do not need to include any backticks.
|
||||
placeholder: Paste logs here
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: additional-notes
|
||||
attributes:
|
||||
label: Additional Notes
|
||||
description: Anything else you want to add?
|
||||
placeholder: 'e.g. I have tried X, Y, and Z.'
|
||||
|
||||
56
.github/ISSUE_TEMPLATE/feature.yml
vendored
56
.github/ISSUE_TEMPLATE/feature.yml
vendored
@@ -1,17 +1,63 @@
|
||||
name: 🚀 Feature Request
|
||||
description: Request a feature/enhancement
|
||||
title: "[Enhancement]: "
|
||||
labels: ["enhancement"]
|
||||
title: '[Enhancement]: '
|
||||
labels: ['enhancement']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Please first search in both issues & discussions for your enhancement."
|
||||
value: '#### *Mobile app features should be [requested here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)*.'
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "### Mobile app features should be requested [here](https://github.com/advplyr/audiobookshelf-app/issues/new/choose)."
|
||||
value: '## Web/Server Feature Request Description'
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: 'Please first search in both issues & discussions for your enhancement.'
|
||||
- type: dropdown
|
||||
id: enhancment-type
|
||||
attributes:
|
||||
label: Type of Enhancement
|
||||
options:
|
||||
- Server Backend
|
||||
- Web Interface/Frontend
|
||||
- Documentation
|
||||
- type: textarea
|
||||
id: describe
|
||||
attributes:
|
||||
label: Describe the feature/enhancement
|
||||
label: Describe the Feature/Enhancement
|
||||
description: Please help us understand what you want.
|
||||
placeholder: What is your vision?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: the-why
|
||||
attributes:
|
||||
label: Why would this be helpful?
|
||||
description: Please help us understand why this would enhance your experience.
|
||||
placeholder: Explain the "why" or "use case".
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: image
|
||||
attributes:
|
||||
label: Future Implementation (Screenshot)
|
||||
description: Please help us visualize by including a doodle or screenshot.
|
||||
placeholder: How could this look?
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: '## Web/Server Current Implementation'
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Audiobookshelf Server Version
|
||||
description: Do not put 'Latest version', please put your current version number here
|
||||
placeholder: 'e.g. v1.6.60'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: current-image
|
||||
attributes:
|
||||
label: Current Implementation (Screenshot)
|
||||
description: What page were you looking at when you thought of this enhancement?
|
||||
placeholder: If an image is not applicable, please explain why.
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,3 +19,4 @@
|
||||
sw.*
|
||||
.DS_STORE
|
||||
.idea/*
|
||||
tailwind.compiled.css
|
||||
17
.prettierrc
Normal file
17
.prettierrc
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 400,
|
||||
"proseWrap": "never",
|
||||
"trailingComma": "none",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"options": {
|
||||
"singleQuote": false,
|
||||
"wrapAttributes": false,
|
||||
"sortAttributes": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
7
.vscode/extensions.json
vendored
Normal file
7
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"EditorConfig.EditorConfig",
|
||||
"esbenp.prettier-vscode",
|
||||
"octref.vetur"
|
||||
]
|
||||
}
|
||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -17,5 +17,11 @@
|
||||
"editor.formatOnSave": true,
|
||||
"editor.detectIndentation": true,
|
||||
"editor.tabSize": 2,
|
||||
"javascript.format.semicolons": "remove"
|
||||
"javascript.format.semicolons": "remove",
|
||||
"[javascript][json][jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "octref.vetur"
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
<widgets-cover-size-widget class="fixed right-4 z-50" :style="{ bottom: streamLibraryItem ? '181px' : '16px' }" />
|
||||
|
||||
<div v-if="loaded && !shelves.length && !search" class="w-full flex flex-col items-center justify-center py-12">
|
||||
<p class="text-center text-2xl mb-4 py-4">{{ libraryName }} Library is empty!</p>
|
||||
<p class="text-center text-2xl mb-4 py-4">{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}</p>
|
||||
<div v-if="userIsAdminOrUp" class="flex">
|
||||
<ui-btn to="/config" color="primary" class="w-52 mr-2">Configure Scanner</ui-btn>
|
||||
<ui-btn color="success" class="w-52" @click="scan">Scan Library</ui-btn>
|
||||
<ui-btn to="/config" color="primary" class="w-52 mr-2">{{ $strings.ButtonConfigureScanner }}</ui-btn>
|
||||
<ui-btn color="success" class="w-52" :loading="isScanningLibrary || tempIsScanning" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="loaded && !shelves.length && search" class="w-full h-40 flex items-center justify-center">
|
||||
@@ -58,7 +58,8 @@ export default {
|
||||
scannerParseSubtitle: false,
|
||||
wrapperClientWidth: 0,
|
||||
shelves: [],
|
||||
lastItemIndexSelected: -1
|
||||
lastItemIndexSelected: -1,
|
||||
tempIsScanning: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -97,6 +98,9 @@ export default {
|
||||
},
|
||||
streamLibraryItem() {
|
||||
return this.$store.state.streamLibraryItem
|
||||
},
|
||||
isScanningLibrary() {
|
||||
return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -273,14 +277,15 @@ export default {
|
||||
this.shelves = shelves
|
||||
},
|
||||
scan() {
|
||||
this.tempIsScanning = true
|
||||
this.$store
|
||||
.dispatch('libraries/requestLibraryScan', { libraryId: this.$store.state.libraries.currentLibraryId })
|
||||
.then(() => {
|
||||
this.$toast.success('Library scan started')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to start scan', error)
|
||||
this.$toast.error('Failed to start scan')
|
||||
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
|
||||
})
|
||||
.finally(() => {
|
||||
this.tempIsScanning = false
|
||||
})
|
||||
},
|
||||
userUpdated(user) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<p class="text-center text-2xl mb-4 py-4">{{ $getString('MessageXLibraryIsEmpty', [libraryName]) }}</p>
|
||||
<div v-if="userIsAdminOrUp" class="flex">
|
||||
<ui-btn to="/config" color="primary" class="w-52 mr-2">{{ $strings.ButtonConfigureScanner }}</ui-btn>
|
||||
<ui-btn color="success" class="w-52" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
|
||||
<ui-btn color="success" class="w-52" :loading="isScanningLibrary || tempIsScanning" @click="scan">{{ $strings.ButtonScanLibrary }}</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!totalShelves && initialized" class="w-full py-16">
|
||||
@@ -62,7 +62,8 @@ export default {
|
||||
currScrollTop: 0,
|
||||
resizeTimeout: null,
|
||||
mountWindowWidth: 0,
|
||||
lastItemIndexSelected: -1
|
||||
lastItemIndexSelected: -1,
|
||||
tempIsScanning: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -208,6 +209,9 @@ export default {
|
||||
},
|
||||
streamLibraryItem() {
|
||||
return this.$store.state.streamLibraryItem
|
||||
},
|
||||
isScanningLibrary() {
|
||||
return !!this.$store.getters['tasks/getRunningLibraryScanTask'](this.currentLibraryId)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -727,14 +731,15 @@ export default {
|
||||
}
|
||||
},
|
||||
scan() {
|
||||
this.tempIsScanning = true
|
||||
this.$store
|
||||
.dispatch('libraries/requestLibraryScan', { libraryId: this.currentLibraryId })
|
||||
.then(() => {
|
||||
this.$toast.success('Library scan started')
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to start scan', error)
|
||||
this.$toast.error('Failed to start scan')
|
||||
this.$toast.error(this.$strings.ToastLibraryScanFailedToStart)
|
||||
})
|
||||
.finally(() => {
|
||||
this.tempIsScanning = false
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -775,4 +780,4 @@ export default {
|
||||
background: var(--bookshelf-divider-bg);
|
||||
box-shadow: 2px 14px 8px #111111aa;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
</div>
|
||||
<div class="flex items-start mb-6 lg:mb-0" :class="playerHandler.isVideo ? 'ml-4 pl-96' : isSquareCover ? 'pl-18 sm:pl-24' : 'pl-12 sm:pl-16'">
|
||||
<div class="min-w-0">
|
||||
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
|
||||
{{ title }}
|
||||
</nuxt-link>
|
||||
<div class="flex items-center">
|
||||
<nuxt-link :to="`/item/${streamLibraryItem.id}`" class="hover:underline cursor-pointer text-sm sm:text-lg block truncate">
|
||||
{{ title }}
|
||||
</nuxt-link>
|
||||
<widgets-explicit-indicator v-if="isExplicit" />
|
||||
</div>
|
||||
<div v-if="!playerHandler.isVideo" class="text-gray-400 flex items-center">
|
||||
<span class="material-icons text-sm">person</span>
|
||||
<div class="flex items-center">
|
||||
@@ -18,7 +21,6 @@
|
||||
<nuxt-link v-for="(author, index) in authors" :key="index" :to="`/author/${author.id}`" class="hover:underline">{{ author.name }}<span v-if="index < authors.length - 1">, </span></nuxt-link>
|
||||
</div>
|
||||
<div v-else class="text-xs sm:text-base cursor-pointer pl-1 sm:pl-1.5">{{ $strings.LabelUnknown }}</div>
|
||||
<widgets-explicit-indicator :explicit="isExplicit"></widgets-explicit-indicator>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -82,13 +84,11 @@ export default {
|
||||
sleepTimer: null,
|
||||
displayTitle: null,
|
||||
currentPlaybackRate: 1,
|
||||
syncFailedToast: null
|
||||
syncFailedToast: null,
|
||||
coverAspectRatio: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
coverAspectRatio() {
|
||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||
},
|
||||
isSquareCover() {
|
||||
return this.coverAspectRatio === 1
|
||||
},
|
||||
@@ -138,7 +138,7 @@ export default {
|
||||
return this.streamLibraryItem?.mediaType === 'music'
|
||||
},
|
||||
isExplicit() {
|
||||
return this.mediaMetadata.explicit || false
|
||||
return !!this.mediaMetadata.explicit
|
||||
},
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
@@ -457,6 +457,9 @@ export default {
|
||||
episodeId,
|
||||
queueItems: payload.queueItems || []
|
||||
})
|
||||
// Set cover aspect ratio for this item's library since the library may change
|
||||
this.coverAspectRatio = this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.audioPlayer) this.$refs.audioPlayer.checkUpdateChapterTrack()
|
||||
})
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<template>
|
||||
<nuxt-link :to="`/author/${author.id}`">
|
||||
<div @mouseover="mouseover" @mouseleave="mouseleave">
|
||||
<div :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||
<nuxt-link :to="`/author/${author?.id}`">
|
||||
<div cy-id="card" :style="{ width: width + 'px'}" @mouseover="mouseover" @mouseleave="mouseleave">
|
||||
<div cy-id="imageArea" :style="{ height: height + 'px' }" class=" bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||
<!-- Image or placeholder -->
|
||||
<covers-author-image :author="author" />
|
||||
<covers-author-image :author="author"/>
|
||||
|
||||
<!-- Author name & num books overlay -->
|
||||
<div v-show="!searching && !nameBelow" class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2">
|
||||
<div cy-id="textInline" v-show="!searching && !nameBelow" class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2">
|
||||
<p class="text-center font-semibold truncate" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
||||
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} {{ $strings.LabelBooks }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Search icon btn -->
|
||||
<div v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor">
|
||||
<div cy-id="match" v-show="!searching && isHovering && userCanUpdate" class="absolute top-0 left-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="searchAuthor">
|
||||
<ui-tooltip :text="$strings.ButtonQuickMatch" direction="bottom">
|
||||
<span class="material-icons text-lg">search</span>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)">
|
||||
<div cy-id="edit" v-show="isHovering && !searching && userCanUpdate" class="absolute top-0 right-0 p-2 cursor-pointer hover:text-white text-gray-200 transform hover:scale-125 duration-150" @click.prevent.stop="$emit('edit', author)">
|
||||
<ui-tooltip :text="$strings.LabelEdit" direction="bottom">
|
||||
<span class="material-icons text-lg">edit</span>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
|
||||
<!-- Loading spinner -->
|
||||
<div v-show="searching" class="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
|
||||
<div cy-id="spinner" v-show="searching" class="absolute top-0 left-0 z-10 w-full h-full bg-black bg-opacity-50 flex items-center justify-center">
|
||||
<widgets-loading-spinner size="" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="nameBelow" class="w-full py-1 px-2">
|
||||
<p class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
||||
<div cy-id="nameBelow" v-show="nameBelow" class="w-full py-1 px-2">
|
||||
<p class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
<div class="flex-grow" />
|
||||
<p class="text-sm md:text-base">{{ book.publishedYear }}</p>
|
||||
</div>
|
||||
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">by {{ book.author }}</p>
|
||||
<p v-if="book.narrator" class="text-gray-400 text-xs">Narrated by {{ book.narrator }}</p>
|
||||
<p v-if="book.duration" class="text-gray-400 text-xs">Runtime: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
|
||||
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">{{ $getString('LabelByAuthor', [book.author]) }}</p>
|
||||
<p v-if="book.narrator" class="text-gray-400 text-xs">{{ $strings.LabelNarrators }}: {{ book.narrator }}</p>
|
||||
<p v-if="book.duration" class="text-gray-400 text-xs">{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
|
||||
<div v-if="book.series?.length" class="flex py-1 -mx-1">
|
||||
<div v-for="(series, index) in book.series" :key="index" class="bg-white bg-opacity-10 rounded-full px-1 py-0.5 mx-1">
|
||||
<p class="leading-3 text-xs text-gray-400">
|
||||
@@ -29,9 +29,9 @@
|
||||
</div>
|
||||
<div v-else class="px-4 flex-grow">
|
||||
<h1>
|
||||
<div class="flex items-center">{{ book.title }}<widgets-explicit-indicator :explicit="book.explicit" /></div>
|
||||
<div class="flex items-center">{{ book.title }}<widgets-explicit-indicator v-if="book.explicit" /></div>
|
||||
</h1>
|
||||
<p class="text-base text-gray-300 whitespace-nowrap truncate">by {{ book.author }}</p>
|
||||
<p class="text-base text-gray-300 whitespace-nowrap truncate">{{ $getString('LabelByAuthor', [book.author]) }}</p>
|
||||
<p v-if="book.genres" class="text-xs text-gray-400 leading-5">{{ book.genres.join(', ') }}</p>
|
||||
<p class="text-xs text-gray-400 leading-5">{{ book.trackCount }} Episodes</p>
|
||||
</div>
|
||||
@@ -75,11 +75,11 @@ export default {
|
||||
let differenceInMinutes = currentBookDurationMinutes - this.book.duration
|
||||
if (differenceInMinutes < 0) {
|
||||
differenceInMinutes = Math.abs(differenceInMinutes)
|
||||
return `(${this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)} shorter)`
|
||||
return this.$getString('LabelDurationComparisonLonger', [this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)])
|
||||
} else if (differenceInMinutes > 0) {
|
||||
return `(${this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)} longer)`
|
||||
return this.$getString('LabelDurationComparisonShorter', [this.$elapsedPrettyExtended(differenceInMinutes * 60, false, false)])
|
||||
}
|
||||
return '(exact match)'
|
||||
return this.$strings.LabelDurationComparisonExactMatch
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<p v-if="matchKey === 'subtitle'" class="truncate text-xs text-gray-300" v-html="matchHtml" />
|
||||
|
||||
<p v-if="matchKey !== 'authors'" class="text-xs text-gray-200 truncate">by {{ authorName }}</p>
|
||||
<p v-if="matchKey !== 'authors'" class="text-xs text-gray-200 truncate">{{ $getString('LabelByAuthor', [authorName]) }}</p>
|
||||
<p v-else class="truncate text-xs text-gray-200" v-html="matchHtml" />
|
||||
|
||||
<div v-if="matchKey === 'series' || matchKey === 'tags' || matchKey === 'isbn' || matchKey === 'asin' || matchKey === 'episode' || matchKey === 'narrators'" class="m-0 p-0 truncate text-xs" v-html="matchHtml" />
|
||||
@@ -69,7 +69,7 @@ export default {
|
||||
if (this.matchKey === 'episode') return `<p class="truncate">${this.$strings.LabelEpisode}: ${html}</p>`
|
||||
if (this.matchKey === 'tags') return `<p class="truncate">${this.$strings.LabelTags}: ${html}</p>`
|
||||
if (this.matchKey === 'subtitle') return `<p class="truncate">${html}</p>`
|
||||
if (this.matchKey === 'authors') return `by ${html}`
|
||||
if (this.matchKey === 'authors') this.$getString('LabelByAuthor', [html])
|
||||
if (this.matchKey === 'isbn') return `<p class="truncate">ISBN: ${html}</p>`
|
||||
if (this.matchKey === 'asin') return `<p class="truncate">ASIN: ${html}</p>`
|
||||
if (this.matchKey === 'series') return `<p class="truncate">${this.$strings.LabelSeries}: ${html}</p>`
|
||||
@@ -90,4 +90,4 @@ export default {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -21,15 +21,16 @@
|
||||
<div v-if="!isPodcast" class="flex items-end">
|
||||
<ui-text-input-with-label v-model.trim="itemData.author" :disabled="processing" :label="$strings.LabelAuthor" />
|
||||
<ui-tooltip :text="$strings.LabelUploaderItemFetchMetadataHelp">
|
||||
<div
|
||||
class="ml-2 mb-1 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-primary cursor-pointer"
|
||||
@click="fetchMetadata">
|
||||
<div class="ml-2 mb-1 w-8 h-8 bg-bg border border-white border-opacity-10 flex items-center justify-center rounded-full hover:bg-primary cursor-pointer" @click="fetchMetadata">
|
||||
<span class="text-base text-white text-opacity-80 font-mono material-icons">sync</span>
|
||||
</div>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<div v-else class="w-full">
|
||||
<p class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></p>
|
||||
<p class="px-1 text-sm font-semibold">
|
||||
{{ $strings.LabelDirectory }}
|
||||
<em class="font-normal text-xs pl-2">(auto)</em>
|
||||
</p>
|
||||
<ui-text-input :value="directory" disabled class="w-full font-mono text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,7 +41,10 @@
|
||||
</div>
|
||||
<div class="w-1/2 px-2">
|
||||
<div class="w-full">
|
||||
<label class="px-1 text-sm font-semibold">{{ $strings.LabelDirectory }} <em class="font-normal text-xs pl-2">(auto)</em></label>
|
||||
<label class="px-1 text-sm font-semibold">
|
||||
{{ $strings.LabelDirectory }}
|
||||
<em class="font-normal text-xs pl-2">(auto)</em>
|
||||
</label>
|
||||
<ui-text-input :value="directory" disabled class="w-full font-mono text-xs h-10" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,10 +55,10 @@
|
||||
<tables-uploaded-files-table v-if="item.ignoredFiles.length" :title="$strings.HeaderIgnoredFiles" :files="item.ignoredFiles" />
|
||||
</template>
|
||||
<widgets-alert v-if="uploadSuccess" type="success">
|
||||
<p class="text-base">{{ $strings.MessageUploaderItemSuccess }}</p>
|
||||
<p class="text-base">"{{ itemData.title }}" {{ $strings.MessageUploaderItemSuccess }}</p>
|
||||
</widgets-alert>
|
||||
<widgets-alert v-if="uploadFailed" type="error">
|
||||
<p class="text-base">{{ $strings.MessageUploaderItemFailed }}</p>
|
||||
<p class="text-base">"{{ itemData.title }}" {{ $strings.MessageUploaderItemFailed }}</p>
|
||||
</widgets-alert>
|
||||
|
||||
<div v-if="isNonInteractable" class="absolute top-0 left-0 w-full h-full bg-black bg-opacity-50 flex items-center justify-center z-20">
|
||||
@@ -70,7 +74,7 @@ export default {
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => { }
|
||||
default: () => {}
|
||||
},
|
||||
mediaType: String,
|
||||
processing: Boolean,
|
||||
@@ -99,7 +103,7 @@ export default {
|
||||
if (this.isPodcast) return this.itemData.title
|
||||
|
||||
const outputPathParts = [this.itemData.author, this.itemData.series, this.itemData.title]
|
||||
const cleanedOutputPathParts = outputPathParts.filter(Boolean).map(part => this.$sanitizeFilename(part))
|
||||
const cleanedOutputPathParts = outputPathParts.filter(Boolean).map((part) => this.$sanitizeFilename(part))
|
||||
|
||||
return Path.join(...cleanedOutputPathParts)
|
||||
},
|
||||
|
||||
@@ -1,128 +1,124 @@
|
||||
<template>
|
||||
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }" class="rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }" class="absolute rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||
<!-- When cover image does not fill -->
|
||||
<div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
|
||||
<div cy-id="coverBg" v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
|
||||
<div class="absolute cover-bg" ref="coverBg" />
|
||||
</div>
|
||||
|
||||
<!-- Alternative bookshelf title/author/sort -->
|
||||
<div v-if="isAlternativeBookshelfView || isAuthorBookshelfView" dir="auto" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||
<div cy-id="detailBottom" v-if="isAlternativeBookshelfView || isAuthorBookshelfView" dir="auto" class="absolute left-0 z-50 w-full" :style="{ bottom: `-${titleDisplayBottomOffset}rem` }">
|
||||
<div :style="{ fontSize: 0.9 * sizeMultiplier + 'rem' }">
|
||||
<ui-tooltip v-if="displayTitle" :text="displayTitle" :disabled="!displayTitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
|
||||
<p ref="displayTitle" class="truncate">{{ displayTitle }}</p>
|
||||
<widgets-explicit-indicator :explicit="isExplicit" />
|
||||
<p cy-id="title" ref="displayTitle" class="truncate">{{ displayTitle }}</p>
|
||||
<widgets-explicit-indicator cy-id="explicitIndicator" v-if="isExplicit" />
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || ' ' }}</p>
|
||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
<p cy-id="line2" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displayLineTwo || ' ' }}</p>
|
||||
<p cy-id="line3" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #78350f">
|
||||
<div cy-id="seriesSequenceList" v-if="seriesSequenceList" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20 text-right" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #78350f">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequenceList }}</p>
|
||||
</div>
|
||||
<div v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #cd9d49dd">
|
||||
<div cy-id="booksInSeries" v-else-if="booksInSeries" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }" style="background-color: #cd9d49dd">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ booksInSeries }}</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full h-full absolute top-0 left-0 rounded overflow-hidden z-10">
|
||||
<div v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
||||
<div cy-id="titleImageNotReady" v-show="libraryItem && !imageReady" class="absolute top-0 left-0 w-full h-full flex items-center justify-center" :style="{ padding: sizeMultiplier * 0.5 + 'rem' }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }" class="text-gray-300 text-center">{{ title }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Cover Image -->
|
||||
<img v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||
<img cy-id="coverImage" v-show="libraryItem" ref="cover" :src="bookCoverSrc" class="w-full h-full transition-opacity duration-300" :class="showCoverBg ? 'object-contain' : 'object-fill'" @load="imageLoaded" :style="{ opacity: imageReady ? 1 : 0 }" />
|
||||
|
||||
<!-- Placeholder Cover Title & Author -->
|
||||
<div v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
||||
<div cy-id="placeholderTitle" v-if="!hasCover" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem' }">
|
||||
<div>
|
||||
<p class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">
|
||||
{{ titleCleaned }}
|
||||
</p>
|
||||
<p cy-id="placeholderTitleText" class="text-center" style="color: rgb(247 223 187)" :style="{ fontSize: titleFontSize + 'rem' }">{{ titleCleaned }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
||||
<p class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
||||
<div cy-id="placeholderAuthor" v-if="!hasCover" class="absolute left-0 right-0 w-full flex items-center justify-center" :style="{ padding: placeholderCoverPadding + 'rem', bottom: authorBottom + 'rem' }">
|
||||
<p cy-id="placeholderAuthorText" class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'rem' }">{{ authorCleaned }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- No progress shown for collapsed series in library and podcasts (unless showing podcast episode) -->
|
||||
<div v-if="!booksInSeries && (!isPodcast || episodeProgress)" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||
<!-- Finished progress bar for collapsed series -->
|
||||
<div v-else-if="booksInSeries && seriesIsFinished" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b bg-success" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||
<!-- No progress shown for podcasts (unless showing podcast episode) -->
|
||||
<div cy-id="progressBar" v-if="!isPodcast || episodeProgress" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||
|
||||
<!-- Overlay is not shown if collapsing series in library -->
|
||||
<div v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block" :class="overlayWrapperClasslist">
|
||||
<div v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
|
||||
<div cy-id="overlay" v-show="!booksInSeries && libraryItem && (isHovering || isSelectionMode || isMoreMenuOpen) && !processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded md:block" :class="overlayWrapperClasslist">
|
||||
<div cy-id="playButton" v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
|
||||
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play">
|
||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
|
||||
<div cy-id="readButton" v-show="showReadButton" class="h-full flex items-center justify-center pointer-events-none">
|
||||
<div class="hover:text-white text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="clickReadEBook">
|
||||
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">auto_stories</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
|
||||
<div cy-id="editButton" v-if="userCanUpdate" v-show="!isSelectionMode" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-150 top-0 right-0" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="editClick">
|
||||
<span class="material-icons" :style="{ fontSize: sizeMultiplier + 'rem' }">edit</span>
|
||||
</div>
|
||||
|
||||
<!-- Radio button -->
|
||||
<div v-if="!isAuthorBookshelfView" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
|
||||
<div cy-id="selectedRadioButton" v-if="!isAuthorBookshelfView" class="absolute cursor-pointer hover:text-yellow-300 hover:scale-125 transform duration-100" :style="{ top: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="selectBtnClick">
|
||||
<span class="material-icons" :class="selected ? 'text-yellow-400' : ''" :style="{ fontSize: 1.25 * sizeMultiplier + 'rem' }">{{ selected ? 'radio_button_checked' : 'radio_button_unchecked' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- More Menu Icon -->
|
||||
<div ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="hidden md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore">
|
||||
<div cy-id="moreButton" ref="moreIcon" v-show="!isSelectionMode && moreMenuItems.length" class="md:block absolute cursor-pointer hover:text-yellow-300 300 hover:scale-125 transform duration-150" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickShowMore">
|
||||
<span class="material-icons" :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">more_vert</span>
|
||||
</div>
|
||||
|
||||
<div v-if="ebookFormat" class="absolute" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }">
|
||||
<div cy-id="ebookFormat" v-if="ebookFormat" class="absolute" :style="{ bottom: 0.375 * sizeMultiplier + 'rem', left: 0.375 * sizeMultiplier + 'rem' }">
|
||||
<span class="text-white/80" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ ebookFormat }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Processing/loading spinner overlay -->
|
||||
<div v-if="processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
|
||||
<div cy-id="loadingSpinner" v-if="processing" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 rounded flex items-center justify-center">
|
||||
<widgets-loading-spinner size="la-lg" />
|
||||
</div>
|
||||
|
||||
<!-- Series name overlay -->
|
||||
<div v-if="booksInSeries && libraryItem && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: sizeMultiplier + 'rem' }">
|
||||
<div cy-id="seriesNameOverlay" v-if="booksInSeries && libraryItem && isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-60 rounded flex items-center justify-center" :style="{ padding: sizeMultiplier + 'rem' }">
|
||||
<p v-if="seriesName" class="text-gray-200 text-center" :style="{ fontSize: 1.1 * sizeMultiplier + 'rem' }">{{ seriesName }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Error widget -->
|
||||
<ui-tooltip v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
|
||||
<ui-tooltip cy-id="ErrorTooltip" v-if="showError" :text="errorText" class="absolute bottom-4 left-0 z-10">
|
||||
<div :style="{ height: 1.5 * sizeMultiplier + 'rem', width: 2.5 * sizeMultiplier + 'rem' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
|
||||
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
||||
</div>
|
||||
</ui-tooltip>
|
||||
|
||||
<div v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }">
|
||||
<div cy-id="rssFeed" v-if="rssFeed && !isSelectionMode && !isHovering" class="absolute text-success top-0 left-0 z-10" :style="{ padding: 0.375 * sizeMultiplier + 'rem' }">
|
||||
<span class="material-icons" :style="{ fontSize: sizeMultiplier * 1.5 + 'rem' }">rss_feed</span>
|
||||
</div>
|
||||
|
||||
<!-- Series sequence -->
|
||||
<div v-if="seriesSequence && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
<div cy-id="seriesSequence" v-if="seriesSequence && !isHovering && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Podcast Episode # -->
|
||||
<div v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
<div cy-id="podcastEpisodeNumber" v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">
|
||||
Episode<span v-if="recentEpisodeNumber"> #{{ recentEpisodeNumber }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Podcast Num Episodes -->
|
||||
<div v-else-if="!numEpisodesIncomplete && numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||
<div cy-id="numEpisodes" v-else-if="!numEpisodesIncomplete && numEpisodes && !isHovering && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Podcast Num Episodes -->
|
||||
<div v-else-if="numEpisodesIncomplete && !isHovering && !isSelectionMode" class="absolute rounded-full bg-yellow-400 text-black font-semibold box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||
<div cy-id="numEpisodesIncomplete" v-else-if="numEpisodesIncomplete && !isHovering && !isSelectionMode" class="absolute rounded-full bg-yellow-400 text-black font-semibold box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodesIncomplete }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -343,11 +339,22 @@ export default {
|
||||
if (!this.userProgress || this.userProgress.progress) return false
|
||||
return this.userProgress.ebookProgress > 0
|
||||
},
|
||||
seriesProgressPercent() {
|
||||
if (!this.libraryItemIdsInSeries.length) return 0
|
||||
let progressPercent = 0
|
||||
const useEBookProgress = this.useEBookProgress
|
||||
this.libraryItemIdsInSeries.forEach((lid) => {
|
||||
const progress = this.store.getters['user/getUserMediaProgress'](lid)
|
||||
if (progress) progressPercent += progress.isFinished ? 1 : useEBookProgress ? progress.ebookProgress || 0 : progress.progress || 0
|
||||
})
|
||||
return progressPercent / this.libraryItemIdsInSeries.length
|
||||
},
|
||||
userProgressPercent() {
|
||||
if (this.useEBookProgress) return Math.max(Math.min(1, this.userProgress.ebookProgress), 0)
|
||||
return this.userProgress ? Math.max(Math.min(1, this.userProgress.progress), 0) || 0 : 0
|
||||
let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0
|
||||
return Math.max(Math.min(1, progressPercent), 0)
|
||||
},
|
||||
itemIsFinished() {
|
||||
if (this.booksInSeries) return this.seriesIsFinished
|
||||
return this.userProgress ? !!this.userProgress.isFinished : false
|
||||
},
|
||||
seriesIsFinished() {
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<template>
|
||||
<div ref="card" :id="`series-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||
<div cy-id="card" ref="card" :id="`series-card-${index}`" :style="{ width: width + 'px', height: height + 'px' }" class="absolute rounded-sm z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
|
||||
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
|
||||
<div class="w-full h-full bg-primary relative rounded overflow-hidden z-0">
|
||||
<covers-group-cover v-if="series" ref="cover" :id="seriesId" :name="displayTitle" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</div>
|
||||
|
||||
<div class="absolute z-10 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ books.length }}</div>
|
||||
<div cy-id="seriesLengthMarker" class="absolute z-10 top-1.5 right-1.5 rounded-md leading-3 text-sm p-1 font-semibold text-white flex items-center justify-center" style="background-color: #cd9d49dd">{{ books.length }}</div>
|
||||
|
||||
<div v-if="seriesPercentInProgress > 0" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b w-full" :class="isSeriesFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: seriesPercentInProgress * 100 + '%' }" />
|
||||
<div cy-id="seriesProgressBar" v-if="seriesPercentInProgress > 0" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b w-full" :class="isSeriesFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: seriesPercentInProgress * 100 + '%' }" />
|
||||
|
||||
<div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
|
||||
<div cy-id="hoveringDisplayTitle" v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
|
||||
<p :style="{ fontSize: 1.2 * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
|
||||
</div>
|
||||
|
||||
<span v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 * sizeMultiplier + 'rem', left: 0.5 * sizeMultiplier + 'rem', fontSize: 1.5 * sizeMultiplier + 'rem' }">rss_feed</span>
|
||||
<span cy-id="rssFeedMarker" v-if="!isHovering && rssFeed" class="absolute z-10 material-icons text-success" :style="{ top: 0.5 * sizeMultiplier + 'rem', left: 0.5 * sizeMultiplier + 'rem', fontSize: 1.5 * sizeMultiplier + 'rem' }">rss_feed</span>
|
||||
|
||||
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div cy-id="standardBottomText" v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-10 left-0 right-0 mx-auto -bottom-6 h-6 rounded-md text-center" :style="{ width: Math.min(200, width) + 'px' }">
|
||||
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-sm border" :style="{ padding: `0rem ${0.5 * sizeMultiplier}rem` }">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ displayTitle }}</p>
|
||||
<p cy-id="standardBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize + 'rem' }">{{ displayTitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
|
||||
<p class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
|
||||
<p v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
<div cy-id="detailBottomText" v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8 h-8 py-1 rounded-md text-center">
|
||||
<p cy-id="detailBottomDisplayTitle" class="truncate" :style="{ fontSize: labelFontSize * sizeMultiplier + 'rem' }">{{ displayTitle }}</p>
|
||||
<p cy-id="detailBottomSortLine" v-if="displaySortLine" class="truncate text-gray-400" :style="{ fontSize: 0.8 * sizeMultiplier + 'rem' }">{{ displaySortLine }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -119,9 +119,13 @@ export default {
|
||||
return this.seriesBookProgress.some((p) => !p.isFinished && p.progress > 0)
|
||||
},
|
||||
seriesPercentInProgress() {
|
||||
let totalFinishedAndInProgress = this.seriesBooksFinished.length
|
||||
if (this.hasSeriesBookInProgress) totalFinishedAndInProgress += 1
|
||||
return Math.min(1, Math.max(0, totalFinishedAndInProgress / this.books.length))
|
||||
if (!this.books.length) return 0
|
||||
let progressPercent = 0
|
||||
this.seriesBookProgress.forEach((progress) => {
|
||||
progressPercent += progress.isFinished ? 1 : progress.progress || 0
|
||||
})
|
||||
progressPercent /= this.books.length
|
||||
return Math.min(1, Math.max(0, progressPercent))
|
||||
},
|
||||
isSeriesFinished() {
|
||||
return this.books.length === this.seriesBooksFinished.length
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(narrator.name)}`">
|
||||
<div :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${$encode(name)}`">
|
||||
<div cy-id="card" :style="{ width: width + 'px', height: height + 'px' }" class="bg-primary box-shadow-book rounded-md relative overflow-hidden">
|
||||
<div class="absolute inset-0 w-full h-full flex items-center justify-center pointer-events-none opacity-40">
|
||||
<span class="material-icons-outlined text-[10rem]">record_voice_over</span>
|
||||
</div>
|
||||
|
||||
<!-- Narrator name & num books overlay -->
|
||||
<div class="absolute bottom-0 left-0 w-full py-1 bg-black bg-opacity-60 px-2">
|
||||
<p class="text-center font-semibold truncate" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
||||
<p class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
|
||||
<p cy-id="name" class="text-center font-semibold truncate text-gray-200" :style="{ fontSize: sizeMultiplier * 0.75 + 'rem' }">{{ name }}</p>
|
||||
<p cy-id="numBooks" class="text-center text-gray-200" :style="{ fontSize: sizeMultiplier * 0.65 + 'rem' }">{{ numBooks }} Book{{ numBooks === 1 ? '' : 's' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</nuxt-link>
|
||||
@@ -21,8 +21,14 @@ export default {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
width: Number,
|
||||
height: Number,
|
||||
width: {
|
||||
type: Number,
|
||||
default: 150
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
sizeMultiplier: {
|
||||
type: Number,
|
||||
default: 1
|
||||
|
||||
@@ -84,4 +84,4 @@ export default {
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -101,9 +101,14 @@ export default {
|
||||
},
|
||||
fullCoverUrl() {
|
||||
if (!this.libraryItem) return null
|
||||
var store = this.$store || this.$nuxt.$store
|
||||
const store = this.$store || this.$nuxt.$store
|
||||
return store.getters['globals/getLibraryItemCoverSrc'](this.libraryItem, this.placeholderUrl)
|
||||
},
|
||||
rawCoverUrl() {
|
||||
if (!this.libraryItem) return null
|
||||
const store = this.$store || this.$nuxt.$store
|
||||
return store.getters['globals/getLibraryItemCoverSrc'](this.libraryItem, this.placeholderUrl, true)
|
||||
},
|
||||
cover() {
|
||||
return this.media.coverPath || this.placeholderUrl
|
||||
},
|
||||
@@ -126,9 +131,6 @@ export default {
|
||||
authorBottom() {
|
||||
return 0.75 * this.sizeMultiplier
|
||||
},
|
||||
userToken() {
|
||||
return this.$store.getters['user/getToken']
|
||||
},
|
||||
resolution() {
|
||||
return `${this.naturalWidth}x${this.naturalHeight}px`
|
||||
}
|
||||
@@ -136,7 +138,7 @@ export default {
|
||||
methods: {
|
||||
clickCover() {
|
||||
if (this.expandOnClick && this.libraryItem) {
|
||||
this.$store.commit('globals/setRawCoverPreviewModal', this.libraryItem.id)
|
||||
this.$store.commit('globals/setRawCoverPreviewModal', this.rawCoverUrl)
|
||||
}
|
||||
},
|
||||
setCoverBg() {
|
||||
|
||||
@@ -65,7 +65,7 @@ export default {
|
||||
return 0.8 * this.sizeMultiplier
|
||||
},
|
||||
resolution() {
|
||||
return `${this.naturalWidth}x${this.naturalHeight}px`
|
||||
return `${this.naturalWidth}×${this.naturalHeight}px`
|
||||
},
|
||||
placeholderUrl() {
|
||||
const config = this.$config || this.$nuxt.$config
|
||||
|
||||
@@ -10,21 +10,21 @@
|
||||
<div class="w-full p-8">
|
||||
<div class="flex py-2">
|
||||
<div class="w-1/2 px-2">
|
||||
<ui-text-input-with-label v-model="newUser.username" :label="$strings.LabelUsername" />
|
||||
<ui-text-input-with-label v-model.trim="newUser.username" :label="$strings.LabelUsername" />
|
||||
</div>
|
||||
<div class="w-1/2 px-2">
|
||||
<ui-text-input-with-label v-if="!isEditingRoot" v-model="newUser.password" :label="isNew ? $strings.LabelPassword : $strings.LabelChangePassword" type="password" />
|
||||
<ui-text-input-with-label v-else v-model="newUser.email" :label="$strings.LabelEmail" />
|
||||
<ui-text-input-with-label v-else v-model.trim="newUser.email" :label="$strings.LabelEmail" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="!isEditingRoot" class="flex py-2">
|
||||
<div class="w-1/2 px-2">
|
||||
<ui-text-input-with-label v-model="newUser.email" :label="$strings.LabelEmail" />
|
||||
<ui-text-input-with-label v-model.trim="newUser.email" :label="$strings.LabelEmail" />
|
||||
</div>
|
||||
<div class="px-2 w-52">
|
||||
<ui-dropdown v-model="newUser.type" :label="$strings.LabelAccountType" :disabled="isEditingRoot" :items="accountTypes" small @input="userTypeUpdated" />
|
||||
</div>
|
||||
<!-- <div class="flex-grow" /> -->
|
||||
|
||||
<div class="flex items-center pt-4 px-2">
|
||||
<p class="px-3 font-semibold" id="user-enabled-toggle" :class="isEditingRoot ? 'text-gray-300' : ''">{{ $strings.LabelEnable }}</p>
|
||||
<ui-toggle-switch labeledBy="user-enabled-toggle" v-model="newUser.isActive" :disabled="isEditingRoot" />
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden p-6" style="max-height: 80vh">
|
||||
<div class="flex items-center">
|
||||
<p class="text-base text-gray-200">{{ _session.displayTitle }}</p>
|
||||
<p v-if="_session.displayAuthor" class="text-xs text-gray-400 px-4">by {{ _session.displayAuthor }}</p>
|
||||
<p v-if="_session.displayAuthor" class="text-xs text-gray-400 px-4">{{ $getString('LabelByAuthor', [_session.displayAuthor]) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full h-px bg-white bg-opacity-10 my-4" />
|
||||
|
||||
@@ -20,14 +20,11 @@ export default {
|
||||
this.$store.commit('globals/setShowRawCoverPreviewModal', val)
|
||||
}
|
||||
},
|
||||
selectedLibraryItemId() {
|
||||
return this.$store.state.globals.selectedLibraryItemId
|
||||
},
|
||||
rawCoverUrl() {
|
||||
return this.$store.getters['globals/getLibraryItemCoverSrcById'](this.selectedLibraryItemId, null, true)
|
||||
return this.$store.state.globals.selectedRawCoverUrl
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="flex">
|
||||
<div class="w-40 p-2">
|
||||
<div class="w-full h-45 relative">
|
||||
<covers-author-image :author="author" />
|
||||
<covers-author-image :author="authorCopy" />
|
||||
<div v-if="userCanDelete && !processing && author.imagePath" class="absolute top-0 left-0 w-full h-full opacity-0 hover:opacity-100">
|
||||
<span class="absolute top-2 right-2 material-icons text-error transform hover:scale-125 transition-transform cursor-pointer text-lg" @click="removeCover">delete</span>
|
||||
</div>
|
||||
@@ -30,9 +30,6 @@
|
||||
<ui-text-input-with-label v-model="authorCopy.asin" :disabled="processing" label="ASIN" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="p-2">
|
||||
<ui-text-input-with-label v-model="authorCopy.imagePath" :disabled="processing" :label="$strings.LabelPhotoPathURL" />
|
||||
</div> -->
|
||||
<div class="p-2">
|
||||
<ui-textarea-with-label v-model="authorCopy.description" :disabled="processing" :label="$strings.LabelDescription" :rows="8" />
|
||||
</div>
|
||||
@@ -106,9 +103,9 @@ export default {
|
||||
methods: {
|
||||
init() {
|
||||
this.imageUrl = ''
|
||||
this.authorCopy.name = this.author.name
|
||||
this.authorCopy.asin = this.author.asin
|
||||
this.authorCopy.description = this.author.description
|
||||
this.authorCopy = {
|
||||
...this.author
|
||||
}
|
||||
},
|
||||
removeClick() {
|
||||
const payload = {
|
||||
@@ -171,7 +168,9 @@ export default {
|
||||
.$delete(`/api/authors/${this.authorId}/image`)
|
||||
.then((data) => {
|
||||
this.$toast.success(this.$strings.ToastAuthorImageRemoveSuccess)
|
||||
this.$store.commit('globals/showEditAuthorModal', data.author)
|
||||
|
||||
this.authorCopy.updatedAt = data.author.updatedAt
|
||||
this.authorCopy.imagePath = data.author.imagePath
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed', error)
|
||||
@@ -196,7 +195,9 @@ export default {
|
||||
.then((data) => {
|
||||
this.imageUrl = ''
|
||||
this.$toast.success('Author image updated')
|
||||
this.$store.commit('globals/showEditAuthorModal', data.author)
|
||||
|
||||
this.authorCopy.updatedAt = data.author.updatedAt
|
||||
this.authorCopy.imagePath = data.author.imagePath
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed', error)
|
||||
@@ -231,8 +232,11 @@ export default {
|
||||
} else if (response.updated) {
|
||||
if (response.author.imagePath) {
|
||||
this.$toast.success(this.$strings.ToastAuthorUpdateSuccess)
|
||||
this.$store.commit('globals/showEditAuthorModal', response.author)
|
||||
} else this.$toast.success(this.$strings.ToastAuthorUpdateSuccessNoImageFound)
|
||||
|
||||
this.authorCopy = {
|
||||
...response.author
|
||||
}
|
||||
} else {
|
||||
this.$toast.info('No updates were made for Author')
|
||||
}
|
||||
@@ -242,4 +246,4 @@ export default {
|
||||
mounted() {},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -122,7 +122,7 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to get collections', error)
|
||||
this.$toast.error('Failed to load collections')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
|
||||
@@ -46,7 +46,12 @@ export default {
|
||||
ereaderDevice: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
}
|
||||
},
|
||||
users: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
loadUsers: Function
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -56,8 +61,7 @@ export default {
|
||||
email: '',
|
||||
availabilityOption: 'adminAndUp',
|
||||
users: []
|
||||
},
|
||||
users: []
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -108,25 +112,13 @@ export default {
|
||||
methods: {
|
||||
availabilityOptionChanged(option) {
|
||||
if (option === 'specificUsers' && !this.users.length) {
|
||||
this.loadUsers()
|
||||
this.callLoadUsers()
|
||||
}
|
||||
},
|
||||
async loadUsers() {
|
||||
async callLoadUsers() {
|
||||
this.processing = true
|
||||
this.users = await this.$axios
|
||||
.$get('/api/users')
|
||||
.then((res) => {
|
||||
return res.users.sort((a, b) => {
|
||||
return a.createdAt - b.createdAt
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed', error)
|
||||
return []
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
})
|
||||
await this.loadUsers()
|
||||
this.processing = false
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.ereaderNameInput.blur()
|
||||
@@ -226,10 +218,6 @@ export default {
|
||||
this.newDevice.email = this.ereaderDevice.email
|
||||
this.newDevice.availabilityOption = this.ereaderDevice.availabilityOption || 'adminOrUp'
|
||||
this.newDevice.users = this.ereaderDevice.users || []
|
||||
|
||||
if (this.newDevice.availabilityOption === 'specificUsers' && !this.users.length) {
|
||||
this.loadUsers()
|
||||
}
|
||||
} else {
|
||||
this.newDevice.name = ''
|
||||
this.newDevice.email = ''
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="w-full h-full overflow-y-auto overflow-x-hidden px-4 py-6">
|
||||
<div class="w-full mb-4">
|
||||
<tables-chapters-table v-if="chapters.length" :library-item="libraryItem" keep-open />
|
||||
<tables-chapters-table v-if="chapters.length" :library-item="libraryItem" keep-open @close="closeModal" />
|
||||
<div v-if="!chapters.length" class="py-4 text-center">
|
||||
<p class="mb-8 text-xl">{{ $strings.MessageNoChapters }}</p>
|
||||
<ui-btn v-if="userCanUpdate" :to="`/audiobook/${libraryItem.id}/chapters`">{{ $strings.ButtonAddChapters }}</ui-btn>
|
||||
<ui-btn v-if="userCanUpdate" :to="`/audiobook/${libraryItem.id}/chapters`" @click="clickAddChapters">{{ $strings.ButtonAddChapters }}</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
return this.libraryItem?.media || {}
|
||||
},
|
||||
chapters() {
|
||||
return this.media.chapters || []
|
||||
@@ -32,6 +32,15 @@ export default {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.$emit('close')
|
||||
},
|
||||
clickAddChapters() {
|
||||
if (this.$route.name === 'audiobook-id-chapters' && this.$route.params?.id === this.libraryItem?.id) {
|
||||
this.closeModal()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
<p class="text-xl pl-3">{{ $strings.HeaderUpdateDetails }}</p>
|
||||
</div>
|
||||
<ui-checkbox v-model="selectAll" checkbox-bg="bg" @input="selectAllToggled" />
|
||||
<ui-checkbox v-model="selectAll" :label="$strings.LabelSelectAll" checkbox-bg="bg" @input="selectAllToggled" />
|
||||
<form @submit.prevent="submitMatchUpdate">
|
||||
<div v-if="selectedMatchOrig.cover" class="flex flex-wrap md:flex-nowrap items-center justify-center">
|
||||
<div class="flex flex-grow items-center py-2">
|
||||
@@ -42,13 +42,13 @@
|
||||
|
||||
<div class="flex py-2">
|
||||
<div>
|
||||
<p class="text-center text-gray-200">New</p>
|
||||
<p class="text-center text-gray-200">{{ $strings.LabelNew }}</p>
|
||||
<a :href="selectedMatch.cover" target="_blank" class="bg-primary">
|
||||
<covers-preview-cover :src="selectedMatch.cover" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="media.coverPath">
|
||||
<p class="text-center text-gray-200">Current</p>
|
||||
<div v-if="media.coverPath" class="ml-0.5">
|
||||
<p class="text-center text-gray-200">{{ $strings.LabelCurrent }}</p>
|
||||
<a :href="$store.getters['globals/getLibraryItemCoverSrc'](libraryItem, null, true)" target="_blank" class="bg-primary">
|
||||
<covers-preview-cover :src="$store.getters['globals/getLibraryItemCoverSrc'](libraryItem, null, true)" :width="100" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
</a>
|
||||
@@ -79,7 +79,7 @@
|
||||
<div v-if="selectedMatchOrig.narrator" class="flex items-center py-2">
|
||||
<ui-checkbox v-model="selectedMatchUsage.narrator" checkbox-bg="bg" @input="checkboxToggled" />
|
||||
<div class="flex-grow ml-4">
|
||||
<ui-text-input-with-label v-model="selectedMatch.narrator" :disabled="!selectedMatchUsage.narrator" :label="$strings.LabelNarrators" />
|
||||
<ui-multi-select v-model="selectedMatch.narrator" :items="narrators" :disabled="!selectedMatchUsage.narrator" :label="$strings.LabelNarrators" />
|
||||
<p v-if="mediaMetadata.narratorName" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.narratorName || '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,7 +122,7 @@
|
||||
<div v-if="selectedMatchOrig.tags" class="flex items-center py-2">
|
||||
<ui-checkbox v-model="selectedMatchUsage.tags" checkbox-bg="bg" @input="checkboxToggled" />
|
||||
<div class="flex-grow ml-4">
|
||||
<ui-text-input-with-label v-model="selectedMatch.tags" :disabled="!selectedMatchUsage.tags" :label="$strings.LabelTags" />
|
||||
<ui-multi-select v-model="selectedMatch.tags" :items="tags" :disabled="!selectedMatchUsage.tags" :label="$strings.LabelTags" />
|
||||
<p v-if="media.tags" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ media.tags.join(', ') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,14 +180,14 @@
|
||||
<ui-checkbox v-model="selectedMatchUsage.explicit" checkbox-bg="bg" @input="checkboxToggled" />
|
||||
<div class="flex-grow ml-4" :class="{ 'pt-4': mediaMetadata.explicit != null }">
|
||||
<ui-checkbox v-model="selectedMatch.explicit" :label="$strings.LabelExplicit" :disabled="!selectedMatchUsage.explicit" :checkbox-bg="!selectedMatchUsage.explicit ? 'bg' : 'primary'" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
||||
<p v-if="mediaMetadata.explicit != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.explicit ? 'Explicit (checked)' : 'Not Explicit (unchecked)' }}</p>
|
||||
<p v-if="mediaMetadata.explicit != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.explicit ? $strings.LabelExplicitChecked : $strings.LabelExplicitUnchecked }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedMatchOrig.abridged != null" class="flex items-center pb-2" :class="{ 'pt-2': mediaMetadata.abridged == null }">
|
||||
<ui-checkbox v-model="selectedMatchUsage.abridged" checkbox-bg="bg" @input="checkboxToggled" />
|
||||
<div class="flex-grow ml-4" :class="{ 'pt-4': mediaMetadata.abridged != null }">
|
||||
<ui-checkbox v-model="selectedMatch.abridged" :label="$strings.LabelAbridged" :disabled="!selectedMatchUsage.abridged" :checkbox-bg="!selectedMatchUsage.abridged ? 'bg' : 'primary'" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
|
||||
<p v-if="mediaMetadata.abridged != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.abridged ? 'Abridged (checked)' : 'Unabridged (unchecked)' }}</p>
|
||||
<p v-if="mediaMetadata.abridged != null" class="text-xs ml-1 text-white text-opacity-60">{{ $strings.LabelCurrently }} {{ mediaMetadata.abridged ? $strings.LabelAbridgedChecked : $strings.LabelAbridgedUnchecked }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -280,6 +280,9 @@ export default {
|
||||
bookCoverAspectRatio() {
|
||||
return this.$store.getters['libraries/getBookCoverAspectRatio']
|
||||
},
|
||||
filterData() {
|
||||
return this.$store.state.libraries.filterData
|
||||
},
|
||||
providers() {
|
||||
if (this.isPodcast) return this.$store.state.scanners.podcastProviders
|
||||
return this.$store.state.scanners.providers
|
||||
@@ -305,11 +308,16 @@ export default {
|
||||
isPodcast() {
|
||||
return this.mediaType == 'podcast'
|
||||
},
|
||||
narrators() {
|
||||
return this.filterData.narrators || []
|
||||
},
|
||||
genres() {
|
||||
const filterData = this.$store.state.libraries.filterData || {}
|
||||
const currentGenres = filterData.genres || []
|
||||
const currentGenres = this.filterData.genres || []
|
||||
const selectedMatchGenres = this.selectedMatch.genres || []
|
||||
return [...new Set([...currentGenres, ...selectedMatchGenres])]
|
||||
},
|
||||
tags() {
|
||||
return this.filterData.tags || []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -479,6 +487,12 @@ export default {
|
||||
// match.genres = match.genres.join(',')
|
||||
match.genres = match.genres.split(',').map((g) => g.trim())
|
||||
}
|
||||
if (match.tags && !Array.isArray(match.tags)) {
|
||||
match.tags = match.tags.split(',').map((g) => g.trim())
|
||||
}
|
||||
if (match.narrator && !Array.isArray(match.narrator)) {
|
||||
match.narrator = match.narrator.split(',').map((g) => g.trim())
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Select Match', match)
|
||||
@@ -522,11 +536,11 @@ export default {
|
||||
)
|
||||
updatePayload.metadata.authors = authorPayload
|
||||
} else if (key === 'narrator') {
|
||||
updatePayload.metadata.narrators = this.selectedMatch[key].split(',').map((v) => v.trim())
|
||||
updatePayload.metadata.narrators = this.selectedMatch[key]
|
||||
} else if (key === 'genres') {
|
||||
updatePayload.metadata.genres = [...this.selectedMatch[key]]
|
||||
} else if (key === 'tags') {
|
||||
updatePayload.tags = this.selectedMatch[key].split(',').map((v) => v.trim())
|
||||
updatePayload.tags = this.selectedMatch[key]
|
||||
} else if (key === 'itunesId') {
|
||||
updatePayload.metadata.itunesId = Number(this.selectedMatch[key])
|
||||
} else {
|
||||
|
||||
@@ -60,8 +60,19 @@
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isBookLibrary" class="py-3">
|
||||
<div class="flex items-center">
|
||||
<ui-toggle-switch v-model="epubsAllowScriptedContent" @input="formUpdated" />
|
||||
<ui-tooltip :text="$strings.LabelSettingsEpubsAllowScriptedContentHelp">
|
||||
<p class="pl-4 text-base">
|
||||
{{ $strings.LabelSettingsEpubsAllowScriptedContent }}
|
||||
<span class="material-icons icon-text text-sm">info_outlined</span>
|
||||
</p>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isPodcastLibrary" class="py-3">
|
||||
<ui-dropdown :label="$strings.LabelPodcastSearchRegion" v-model="podcastSearchRegion" :items="$podcastSearchRegionOptions" small class="max-w-52" @input="formUpdated" />
|
||||
<ui-dropdown :label="$strings.LabelPodcastSearchRegion" v-model="podcastSearchRegion" :items="$podcastSearchRegionOptions" small class="max-w-72" menu-max-height="200px" @input="formUpdated" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -83,6 +94,7 @@ export default {
|
||||
skipMatchingMediaWithAsin: false,
|
||||
skipMatchingMediaWithIsbn: false,
|
||||
audiobooksOnly: false,
|
||||
epubsAllowScriptedContent: false,
|
||||
hideSingleBookSeries: false,
|
||||
onlyShowLaterBooksInContinueSeries: false,
|
||||
podcastSearchRegion: 'us'
|
||||
@@ -118,6 +130,7 @@ export default {
|
||||
skipMatchingMediaWithAsin: !!this.skipMatchingMediaWithAsin,
|
||||
skipMatchingMediaWithIsbn: !!this.skipMatchingMediaWithIsbn,
|
||||
audiobooksOnly: !!this.audiobooksOnly,
|
||||
epubsAllowScriptedContent: !!this.epubsAllowScriptedContent,
|
||||
hideSingleBookSeries: !!this.hideSingleBookSeries,
|
||||
onlyShowLaterBooksInContinueSeries: !!this.onlyShowLaterBooksInContinueSeries,
|
||||
podcastSearchRegion: this.podcastSearchRegion
|
||||
@@ -133,6 +146,7 @@ export default {
|
||||
this.skipMatchingMediaWithAsin = !!this.librarySettings.skipMatchingMediaWithAsin
|
||||
this.skipMatchingMediaWithIsbn = !!this.librarySettings.skipMatchingMediaWithIsbn
|
||||
this.audiobooksOnly = !!this.librarySettings.audiobooksOnly
|
||||
this.epubsAllowScriptedContent = !!this.librarySettings.epubsAllowScriptedContent
|
||||
this.hideSingleBookSeries = !!this.librarySettings.hideSingleBookSeries
|
||||
this.onlyShowLaterBooksInContinueSeries = !!this.librarySettings.onlyShowLaterBooksInContinueSeries
|
||||
this.podcastSearchRegion = this.librarySettings.podcastSearchRegion || 'us'
|
||||
@@ -142,4 +156,4 @@ export default {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -115,7 +115,7 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to get playlists', error)
|
||||
this.$toast.error('Failed to load user playlists')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<!-- <span class="material-icons text-2xl cursor-pointer" @click="toggleFullscreen(true)">expand_less</span> -->
|
||||
|
||||
<ui-tooltip direction="top" :text="$strings.LabelVolume">
|
||||
<controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden md:block" />
|
||||
<controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden sm:block" />
|
||||
</ui-tooltip>
|
||||
|
||||
<ui-tooltip direction="top" :text="$strings.LabelSleepTimer">
|
||||
|
||||
@@ -63,6 +63,9 @@ export default {
|
||||
libraryItemId() {
|
||||
return this.libraryItem?.id
|
||||
},
|
||||
allowScriptedContent() {
|
||||
return this.$store.getters['libraries/getLibraryEpubsAllowScriptedContent']
|
||||
},
|
||||
hasPrev() {
|
||||
return !this.rendition?.location?.atStart
|
||||
},
|
||||
@@ -316,7 +319,7 @@ export default {
|
||||
reader.rendition = reader.book.renderTo('viewer', {
|
||||
width: this.readerWidth,
|
||||
height: this.readerHeight * 0.8,
|
||||
allowScriptedContent: true,
|
||||
allowScriptedContent: this.allowScriptedContent,
|
||||
spread: 'auto',
|
||||
snap: true,
|
||||
manager: 'continuous',
|
||||
|
||||
@@ -271,7 +271,7 @@ export default {
|
||||
this.$emit('update:processing', true)
|
||||
this.yearStats = await this.$axios.$get(`/api/me/stats/year/${this.year}`).catch((err) => {
|
||||
console.error('Failed to load stats for year', err)
|
||||
this.$toast.error('Failed to load year stats')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return null
|
||||
})
|
||||
await this.initCanvas()
|
||||
@@ -282,4 +282,4 @@ export default {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -250,7 +250,7 @@ export default {
|
||||
this.$emit('update:processing', true)
|
||||
this.yearStats = await this.$axios.$get(`/api/stats/year/${this.year}`).catch((err) => {
|
||||
console.error('Failed to load stats for year', err)
|
||||
this.$toast.error('Failed to load year stats')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return null
|
||||
})
|
||||
await this.initCanvas()
|
||||
@@ -261,4 +261,4 @@ export default {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -180,7 +180,7 @@ export default {
|
||||
this.$emit('update:processing', true)
|
||||
this.yearStats = await this.$axios.$get(`/api/me/stats/year/${this.year}`).catch((err) => {
|
||||
console.error('Failed to load stats for year', err)
|
||||
this.$toast.error('Failed to load year stats')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return null
|
||||
})
|
||||
await this.initCanvas()
|
||||
@@ -191,4 +191,4 @@ export default {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -94,11 +94,11 @@ export default {
|
||||
this.$axios
|
||||
.$delete(`/api/items/${this.libraryItemId}/file/${this.track.audioFile.ino}`)
|
||||
.then(() => {
|
||||
this.$toast.success('File deleted')
|
||||
this.$toast.success(this.$strings.ToastDeleteFileSuccess)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to delete file', error)
|
||||
this.$toast.error('Failed to delete file')
|
||||
this.$toast.error(this.$strings.ToastDeleteFileFailed)
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -112,4 +112,4 @@ export default {
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -176,7 +176,7 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to load backups', error)
|
||||
this.$toast.error('Failed to load backups')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<p class="pr-4">{{ $strings.HeaderChapters }}</p>
|
||||
<span class="bg-black-400 rounded-xl py-1 px-2 text-sm font-mono">{{ chapters.length }}</span>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn v-if="userCanUpdate" small :to="`/audiobook/${libraryItemId}/chapters`" color="primary" class="mr-2">{{ $strings.ButtonEditChapters }}</ui-btn>
|
||||
<ui-btn v-if="userCanUpdate" small :to="`/audiobook/${libraryItemId}/chapters`" color="primary" class="mr-2" @click="clickEditChapters">{{ $strings.ButtonEditChapters }}</ui-btn>
|
||||
<div v-if="!keepOpen" class="cursor-pointer h-10 w-10 rounded-full hover:bg-black-400 flex justify-center items-center duration-500" :class="expanded ? 'transform rotate-180' : ''">
|
||||
<span class="material-icons text-4xl">expand_more</span>
|
||||
</div>
|
||||
@@ -107,8 +107,14 @@ export default {
|
||||
}
|
||||
this.$store.commit('globals/setConfirmPrompt', payload)
|
||||
}
|
||||
},
|
||||
clickEditChapters() {
|
||||
// Used for Chapters tab in modal
|
||||
if (this.$route.name === 'audiobook-id-chapters' && this.$route.params?.id === this.libraryItem?.id) {
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -115,11 +115,11 @@ export default {
|
||||
this.$axios
|
||||
.$delete(`/api/items/${this.libraryItemId}/file/${this.file.ino}`)
|
||||
.then(() => {
|
||||
this.$toast.success('File deleted')
|
||||
this.$toast.success(this.$strings.ToastDeleteFileSuccess)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to delete file', error)
|
||||
this.$toast.error('Failed to delete file')
|
||||
this.$toast.error(this.$strings.ToastDeleteFileFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
@@ -136,4 +136,4 @@ export default {
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -89,11 +89,11 @@ export default {
|
||||
this.$axios
|
||||
.$delete(`/api/items/${this.libraryItemId}/file/${this.file.ino}`)
|
||||
.then(() => {
|
||||
this.$toast.success('File deleted')
|
||||
this.$toast.success(this.$strings.ToastDeleteFileSuccess)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to delete file', error)
|
||||
this.$toast.error('Failed to delete file')
|
||||
this.$toast.error(this.$strings.ToastDeleteFileFailed)
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -107,4 +107,4 @@ export default {
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<th class="w-32 hidden sm:table-cell">{{ $strings.LabelCreatedAt }}</th>
|
||||
<th class="w-32"></th>
|
||||
</tr>
|
||||
<tr v-for="user in users" :key="user.id" class="cursor-pointer" :class="user.isActive ? '' : 'bg-error bg-opacity-20'" @click="$router.push(`/config/users/${user.id}`)">
|
||||
<tr v-for="user in users" :key="user.id" class="cursor-pointer" :class="user.isActive ? '' : '!bg-error/10'" @click="$router.push(`/config/users/${user.id}`)">
|
||||
<td>
|
||||
<div class="flex items-center">
|
||||
<widgets-online-indicator :value="!!usersOnline[user.id]" />
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<div v-if="userCanUpdate" class="mx-1" :class="isHovering ? '' : 'ml-6'">
|
||||
<ui-icon-btn icon="edit" borderless @click="clickEdit" />
|
||||
</div>
|
||||
<div v-if="userCanDelete" class="mx-1">
|
||||
<div class="mx-1" :class="isHovering ? '' : 'ml-6'">
|
||||
<ui-icon-btn icon="close" borderless @click="removeClick" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,8 +75,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
translateDistance() {
|
||||
if (!this.userCanUpdate && !this.userCanDelete) return 'translate-x-0'
|
||||
else if (!this.userCanUpdate || !this.userCanDelete) return '-translate-x-12'
|
||||
if (!this.userCanUpdate) return '-translate-x-12'
|
||||
return '-translate-x-24'
|
||||
},
|
||||
libraryItem() {
|
||||
@@ -233,4 +232,4 @@ export default {
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<td class="px-4">
|
||||
<div class="flex items-center">
|
||||
<nuxt-link :to="`/item/${downloadQueued.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ downloadQueued.podcastTitle }}</nuxt-link>
|
||||
<widgets-explicit-indicator :explicit="downloadQueued.podcastExplicit" />
|
||||
<widgets-explicit-indicator v-if="downloadQueued.podcastExplicit" />
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<nuxt-link v-if="to" :to="to" class="abs-btn outline-none rounded-md shadow-md relative border border-gray-600 text-center" :disabled="disabled || loading" :class="classList">
|
||||
<nuxt-link v-if="to" :to="to" class="abs-btn outline-none rounded-md shadow-md relative border border-gray-600 text-center" :disabled="disabled || loading" :class="classList" @click.native="click">
|
||||
<slot />
|
||||
<div v-if="loading" class="text-white absolute top-0 left-0 w-full h-full flex items-center justify-center text-opacity-100">
|
||||
<svg class="animate-spin" style="width: 24px; height: 24px" viewBox="0 0 24 24">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</button>
|
||||
|
||||
<transition name="menu">
|
||||
<ul v-show="showMenu" class="absolute z-10 -mt-px w-full bg-primary border border-black-200 shadow-lg max-h-56 rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto sm:text-sm" tabindex="-1" role="listbox">
|
||||
<ul v-show="showMenu" class="absolute z-10 -mt-px w-full bg-primary border border-black-200 shadow-lg rounded-md py-1 ring-1 ring-black ring-opacity-5 overflow-auto sm:text-sm" tabindex="-1" role="listbox" :style="{ maxHeight: menuMaxHeight }">
|
||||
<template v-for="item in itemsToShow">
|
||||
<li :key="item.value" class="text-gray-100 relative py-2 cursor-pointer hover:bg-black-400" :id="'listbox-option-' + item.value" role="option" tabindex="0" @keyup.enter="clickedOption(item.value)" @click="clickedOption(item.value)">
|
||||
<div class="flex items-center">
|
||||
@@ -41,7 +41,11 @@ export default {
|
||||
default: () => []
|
||||
},
|
||||
disabled: Boolean,
|
||||
small: Boolean
|
||||
small: Boolean,
|
||||
menuMaxHeight: {
|
||||
type: String,
|
||||
default: '224px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -83,15 +83,21 @@ export default {
|
||||
},
|
||||
async updateLibrary(library) {
|
||||
var currLibraryId = this.currentLibraryId
|
||||
if (currLibraryId === library.id) {
|
||||
return
|
||||
}
|
||||
|
||||
this.disabled = true
|
||||
await this.$store.dispatch('libraries/fetch', library.id)
|
||||
|
||||
if (this.$route.name.startsWith('config')) {
|
||||
// No need to refresh
|
||||
} else if (this.$route.name.startsWith('library')) {
|
||||
var newRoute = this.$route.path.replace(currLibraryId, library.id)
|
||||
} else if (this.$route.name.startsWith('library') && this.$route.name !== 'library-library-series-id') {
|
||||
const newRoute = this.$route.path.replace(currLibraryId, library.id)
|
||||
this.$router.push(newRoute)
|
||||
} else if (this.$route.name === 'library-library-series-id' && library.mediaType === 'book') {
|
||||
// For series item page redirect to root series page
|
||||
this.$router.push(`/library/${library.id}/bookshelf/series`)
|
||||
} else {
|
||||
this.$router.push(`/library/${library.id}`)
|
||||
}
|
||||
@@ -107,4 +113,4 @@ export default {
|
||||
.librariesDropdownMenu {
|
||||
max-height: calc(100vh - 75px);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -302,6 +302,14 @@ export default {
|
||||
this.recalcMenuPos()
|
||||
})
|
||||
},
|
||||
resetInput() {
|
||||
this.textInput = null
|
||||
this.currentSearch = null
|
||||
this.selectedMenuItemIndex = null
|
||||
this.$nextTick(() => {
|
||||
this.blur()
|
||||
})
|
||||
},
|
||||
insertNewItem(item) {
|
||||
this.selected.push(item)
|
||||
this.$emit('input', this.selected)
|
||||
@@ -316,15 +324,18 @@ export default {
|
||||
submitForm() {
|
||||
if (!this.textInput) return
|
||||
|
||||
var cleaned = this.textInput.trim()
|
||||
var matchesItem = this.items.find((i) => {
|
||||
return i === cleaned
|
||||
})
|
||||
if (matchesItem) {
|
||||
this.clickedOption(null, matchesItem)
|
||||
const cleaned = this.textInput.trim()
|
||||
if (!cleaned) {
|
||||
this.resetInput()
|
||||
} else {
|
||||
this.insertNewItem(this.textInput)
|
||||
const matchesItem = this.items.find((i) => i === cleaned)
|
||||
if (matchesItem) {
|
||||
this.clickedOption(null, matchesItem)
|
||||
} else {
|
||||
this.insertNewItem(cleaned)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.$refs.input) this.$refs.input.style.width = '24px'
|
||||
},
|
||||
scroll() {
|
||||
@@ -352,4 +363,4 @@ input:read-only {
|
||||
color: #aaa;
|
||||
background-color: #444;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div tabindex="0" @focus="focusDigit('second0')" class="relative">
|
||||
<div class="rounded text-gray-200 border w-full px-3 py-2" :class="focusedDigit ? 'bg-primary bg-opacity-50 border-gray-300' : 'bg-primary border-gray-600'" @click="clickInput" v-click-outside="clickOutsideObj">
|
||||
<div class="flex items-center">
|
||||
<template v-for="(digit, index) in digitDisplay">
|
||||
@@ -174,7 +174,7 @@ export default {
|
||||
return this.increaseFocused()
|
||||
} else if (evt.key === 'ArrowDown') {
|
||||
return this.decreaseFocused()
|
||||
} else if (evt.key === 'Enter' || evt.key === 'Escape') {
|
||||
} else if (evt.key === 'Enter' || evt.key === 'Escape' || evt.key === 'Tab') {
|
||||
return this.removeFocus()
|
||||
}
|
||||
|
||||
@@ -209,4 +209,4 @@ export default {
|
||||
.digit-focused {
|
||||
background-color: #555;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ui-tooltip v-if="explicit" :text="$strings.LabelExplicit" direction="top">
|
||||
<ui-tooltip :text="$strings.LabelExplicit" direction="top">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12px" height="12px" viewBox="0 0 512 512" class="ml-1">
|
||||
<path
|
||||
fill="white"
|
||||
@@ -40,9 +40,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
explicit: Boolean
|
||||
},
|
||||
props: {},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="wrapper" class="absolute bg-bg rounded-md py-1 border border-black-200 shadow-lg z-50" v-click-outside="clickOutsideObj" :style="{ width: menuWidth + 'px' }" style="top: 0; left: 0">
|
||||
<div ref="wrapper" class="absolute bg-bg rounded-md py-1 border border-black-200 shadow-lg z-50" v-click-outside="clickOutsideObj" :style="{ width: menuWidth + 'px' }">
|
||||
<template v-for="(item, index) in items">
|
||||
<template v-if="item.subitems">
|
||||
<div :key="index" class="flex items-center px-2 py-1.5 hover:bg-white/5 text-white text-xs cursor-default" :class="{ 'bg-white/5': mouseoverItemIndex == index }" @mouseover="mouseoverItem(index)" @mouseleave="mouseleaveItem(index)" @click.stop>
|
||||
@@ -94,4 +94,4 @@ export default {
|
||||
},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
11
client/cypress.config.js
Normal file
11
client/cypress.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { defineConfig } = require("cypress")
|
||||
|
||||
module.exports = defineConfig({
|
||||
component: {
|
||||
devServer: {
|
||||
framework: "nuxt",
|
||||
bundler: "webpack"
|
||||
},
|
||||
specPattern: "cypress/tests/**/*.cy.js"
|
||||
}
|
||||
})
|
||||
BIN
client/cypress/fixtures/images/book_placeholder.jpg
Normal file
BIN
client/cypress/fixtures/images/book_placeholder.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
BIN
client/cypress/fixtures/images/cover1.jpg
Normal file
BIN
client/cypress/fixtures/images/cover1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
BIN
client/cypress/fixtures/images/cover2.jpg
Normal file
BIN
client/cypress/fixtures/images/cover2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 315 KiB |
31
client/cypress/support/commands.js
Normal file
31
client/cypress/support/commands.js
Normal file
@@ -0,0 +1,31 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
Cypress.Commands.overwriteQuery('get', function (originalFn, ...args) {
|
||||
if (args.length > 0 && typeof args[0] === 'string' && args[0].startsWith('&')) {
|
||||
args[0] = `[cy-id="${args[0].substring(1)}"]`
|
||||
}
|
||||
return originalFn.apply(this, args)
|
||||
})
|
||||
12
client/cypress/support/component-index.html
Normal file
12
client/cypress/support/component-index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Components App</title>
|
||||
</head>
|
||||
<body class="text-white bg-bg">
|
||||
<div data-cy-root></div>
|
||||
</body>
|
||||
</html>
|
||||
38
client/cypress/support/component.js
Normal file
38
client/cypress/support/component.js
Normal file
@@ -0,0 +1,38 @@
|
||||
// ***********************************************************
|
||||
// This example support/component.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
import '../../assets/app.css'
|
||||
import './tailwind.compiled.css'
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
import Vue from 'vue'
|
||||
|
||||
import { Constants } from '../../plugins/constants'
|
||||
import Strings from '../../strings/en-us.json'
|
||||
import '../../plugins/utils'
|
||||
import '../../plugins/init.client'
|
||||
|
||||
import { mount } from 'cypress/vue2'
|
||||
|
||||
//Cypress.Commands.add('mount', mount)
|
||||
Cypress.Commands.add('mount', (component, options = {}) => {
|
||||
|
||||
Vue.prototype.$constants = Constants
|
||||
Vue.prototype.$strings = Strings
|
||||
|
||||
return mount(component, options)
|
||||
})
|
||||
|
||||
// Example use:
|
||||
// cy.mount(MyComponent)
|
||||
191
client/cypress/tests/components/cards/AuthorCard.cy.js
Normal file
191
client/cypress/tests/components/cards/AuthorCard.cy.js
Normal file
@@ -0,0 +1,191 @@
|
||||
// Import the necessary dependencies
|
||||
import AuthorCard from '@/components/cards/AuthorCard.vue'
|
||||
import AuthorImage from '@/components/covers/AuthorImage.vue'
|
||||
import Tooltip from '@/components/ui/Tooltip.vue'
|
||||
import LoadingSpinner from '@/components/widgets/LoadingSpinner.vue'
|
||||
|
||||
describe('AuthorCard', () => {
|
||||
const author = {
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
numBooks: 5
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
author,
|
||||
width: 192 * 0.8,
|
||||
height: 192,
|
||||
sizeMultiplier: 1,
|
||||
nameBelow: false
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$strings: {
|
||||
LabelBooks: 'Books',
|
||||
ButtonQuickMatch: 'Quick Match'
|
||||
},
|
||||
$store: {
|
||||
getters: {
|
||||
'user/getUserCanUpdate': true,
|
||||
'libraries/getLibraryProvider': () => 'audible.us'
|
||||
},
|
||||
state: {
|
||||
libraries: {
|
||||
currentLibraryId: 'library-123'
|
||||
}
|
||||
}
|
||||
},
|
||||
$eventBus: {
|
||||
$on: () => { },
|
||||
$off: () => { },
|
||||
},
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
'covers-author-image': AuthorImage,
|
||||
'ui-tooltip': Tooltip,
|
||||
'widgets-loading-spinner': LoadingSpinner
|
||||
}
|
||||
|
||||
const mountOptions = { propsData, mocks, stubs }
|
||||
|
||||
it('renders the component', () => {
|
||||
cy.mount(AuthorCard, mountOptions)
|
||||
|
||||
cy.get('&textInline').should('be.visible')
|
||||
cy.get('&match').should('be.hidden')
|
||||
cy.get('&edit').should('be.hidden')
|
||||
cy.get('&nameBelow').should('be.hidden')
|
||||
cy.get('&card').should(($el) => {
|
||||
const width = $el.width()
|
||||
const height = $el.height()
|
||||
expect(width).to.be.closeTo(propsData.width, 0.01)
|
||||
expect(height).to.be.closeTo(propsData.height, 0.01)
|
||||
})
|
||||
})
|
||||
|
||||
it('renders the component with the author name below', () => {
|
||||
const updatedPropsData = { ...propsData, nameBelow: true }
|
||||
cy.mount(AuthorCard, { ...mountOptions, propsData: updatedPropsData })
|
||||
|
||||
cy.get('&textInline').should('be.hidden')
|
||||
cy.get('&match').should('be.hidden')
|
||||
cy.get('&edit').should('be.hidden')
|
||||
let nameBelowHeight
|
||||
cy.get('&nameBelow')
|
||||
.should('be.visible')
|
||||
.and('have.text', 'John Doe')
|
||||
.and(($el) => {
|
||||
const height = $el.height()
|
||||
const width = $el.width()
|
||||
const sizeMultiplier = propsData.sizeMultiplier
|
||||
const defaultFontSize = 16
|
||||
const defaultLineHeight = 1.5
|
||||
const fontSizeMultiplier = 0.75
|
||||
const px2 = 16
|
||||
expect(height).to.be.closeTo(defaultFontSize * fontSizeMultiplier * sizeMultiplier * defaultLineHeight, 0.01)
|
||||
nameBelowHeight = height
|
||||
expect(width).to.be.closeTo(propsData.width - px2, 0.01)
|
||||
})
|
||||
cy.get('&card').should(($el) => {
|
||||
const width = $el.width()
|
||||
const height = $el.height()
|
||||
const py1 = 8
|
||||
expect(width).to.be.closeTo(propsData.width, 0.01)
|
||||
expect(height).to.be.closeTo(propsData.height + nameBelowHeight + py1, 0.01)
|
||||
})
|
||||
})
|
||||
|
||||
it('renders quick-match and edit buttons on mouse hover', () => {
|
||||
cy.mount(AuthorCard, mountOptions)
|
||||
|
||||
// before mouseover
|
||||
cy.get('&match').should('be.hidden')
|
||||
cy.get('&edit').should('be.hidden')
|
||||
// after mouseover
|
||||
cy.get('&card').trigger('mouseover')
|
||||
cy.get('&match').should('be.visible')
|
||||
cy.get('&edit').should('be.visible')
|
||||
// after mouseleave
|
||||
cy.get('&card').trigger('mouseleave')
|
||||
cy.get('&match').should('be.hidden')
|
||||
cy.get('&edit').should('be.hidden')
|
||||
|
||||
})
|
||||
|
||||
it('renders the component with spinner while searching', () => {
|
||||
const data = () => { return { searching: true, isHovering: false } }
|
||||
cy.mount(AuthorCard, { ...mountOptions, data })
|
||||
|
||||
cy.get('&textInline').should('be.hidden')
|
||||
cy.get('&match').should('be.hidden')
|
||||
cy.get('&edit').should('be.hidden')
|
||||
cy.get('&spinner').should('be.visible')
|
||||
})
|
||||
|
||||
it('toasts after quick match with no updates', () => {
|
||||
const updatedMocks = {
|
||||
...mocks,
|
||||
$axios: {
|
||||
$post: cy.stub().resolves({ updated: false, author: { name: 'John Doe' } })
|
||||
},
|
||||
$toast: {
|
||||
success: cy.spy().as('success'),
|
||||
error: cy.spy().as('error'),
|
||||
info: cy.spy().as('info')
|
||||
}
|
||||
}
|
||||
cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks })
|
||||
cy.get('&card').trigger('mouseover')
|
||||
cy.get('&match').click()
|
||||
|
||||
cy.get('&spinner').should('be.hidden')
|
||||
cy.get('@success').should('not.have.been.called')
|
||||
cy.get('@error').should('not.have.been.called')
|
||||
cy.get('@info').should('have.been.called')
|
||||
})
|
||||
|
||||
it('toasts after quick match with updates and no image', () => {
|
||||
const updatedMocks = {
|
||||
...mocks,
|
||||
$axios: {
|
||||
$post: cy.stub().resolves({ updated: true, author: { name: 'John Doe' } })
|
||||
},
|
||||
$toast: {
|
||||
success: cy.stub().as('success'),
|
||||
error: cy.spy().as('error'),
|
||||
info: cy.spy().as('info')
|
||||
}
|
||||
}
|
||||
cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks })
|
||||
cy.get('&card').trigger('mouseover')
|
||||
cy.get('&match').click()
|
||||
|
||||
cy.get('&spinner').should('be.hidden')
|
||||
cy.get('@success').should('have.been.calledOnceWithExactly', 'Author John Doe was updated (no image found)')
|
||||
cy.get('@error').should('not.have.been.called')
|
||||
cy.get('@info').should('not.have.been.called')
|
||||
})
|
||||
|
||||
it('toasts after quick match with updates including image', () => {
|
||||
const updatedMocks = {
|
||||
...mocks,
|
||||
$axios: {
|
||||
$post: cy.stub().resolves({ updated: true, author: { name: 'John Doe', imagePath: "path/to/image" } })
|
||||
},
|
||||
$toast: {
|
||||
success: cy.stub().as('success'),
|
||||
error: cy.spy().as('error'),
|
||||
info: cy.spy().as('info')
|
||||
}
|
||||
}
|
||||
cy.mount(AuthorCard, { ...mountOptions, mocks: updatedMocks })
|
||||
cy.get('&card').trigger('mouseover')
|
||||
cy.get('&match').click()
|
||||
|
||||
cy.get('&spinner').should('be.hidden')
|
||||
cy.get('@success').should('have.been.calledOnceWithExactly', 'Author John Doe was updated')
|
||||
cy.get('@error').should('not.have.been.called')
|
||||
cy.get('@info').should('not.have.been.called')
|
||||
})
|
||||
})
|
||||
342
client/cypress/tests/components/cards/LazyBookCard.cy.js
Normal file
342
client/cypress/tests/components/cards/LazyBookCard.cy.js
Normal file
@@ -0,0 +1,342 @@
|
||||
import LazyBookCard from '@/components/cards/LazyBookCard'
|
||||
import Tooltip from '@/components/ui/Tooltip.vue'
|
||||
import ExplicitIndicator from '@/components/widgets/ExplicitIndicator.vue'
|
||||
import LoadingSpinner from '@/components/widgets/LoadingSpinner.vue'
|
||||
import { Constants } from '@/plugins/constants'
|
||||
|
||||
function createMountOptions() {
|
||||
const book = {
|
||||
id: '1',
|
||||
ino: '281474976785140',
|
||||
libraryId: 'library-123',
|
||||
folderId: 'folder-123',
|
||||
path: '/path/to/book',
|
||||
relPath: 'book',
|
||||
isFile: false,
|
||||
mtimeMs: 1689017292016,
|
||||
ctimeMs: 1689017292016,
|
||||
birthtimeMs: 1689017281555,
|
||||
addedAt: 1700154928492,
|
||||
updatedAt: 1713300533345,
|
||||
isMissing: false,
|
||||
isInvalid: false,
|
||||
mediaType: 'book',
|
||||
media: {
|
||||
id: 'book1',
|
||||
metadata: {
|
||||
title: 'The Fellowship of the Ring',
|
||||
titleIgnorePrefix: 'Fellowship of the Ring',
|
||||
subtitle: 'LOTR, Book 1',
|
||||
authorName: 'J. R. R. Tolkien',
|
||||
authorNameLF: 'Tolkien, J. R. R.',
|
||||
narratorName: 'Andy Sirkis',
|
||||
genres: ['Science Fiction & Fantasy'],
|
||||
publishedYear: '2017',
|
||||
publishedDate: null,
|
||||
publisher: 'Book Publisher',
|
||||
description: 'Book Description',
|
||||
isbn: null,
|
||||
asin: 'B075LXMLNV',
|
||||
language: 'English',
|
||||
explicit: false,
|
||||
abridged: false
|
||||
},
|
||||
coverPath: null,
|
||||
tags: ['Fantasy', 'Adventure'],
|
||||
numTracks: 1,
|
||||
numAudioFiles: 1,
|
||||
numChapters: 31,
|
||||
duration: 64410,
|
||||
size: 511206878
|
||||
},
|
||||
numFiles: 4,
|
||||
size: 511279587
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
index: 0,
|
||||
bookMount: book,
|
||||
bookCoverAspectRatio: 1,
|
||||
bookshelfView: Constants.BookshelfView.DETAIL,
|
||||
continueListeningShelf: false,
|
||||
filterBy: null,
|
||||
width: 192,
|
||||
height: 192,
|
||||
sortingIgnorePrefix: false,
|
||||
orderBy: null
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
'ui-tooltip': Tooltip,
|
||||
'widgets-explicit-indicator': ExplicitIndicator,
|
||||
'widgets-loading-spinner': LoadingSpinner
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$config: {
|
||||
routerBasePath: 'https://my.server.com'
|
||||
},
|
||||
$store: {
|
||||
commit: () => {},
|
||||
getters: {
|
||||
'user/getUserCanUpdate': true,
|
||||
'user/getUserCanDelete': true,
|
||||
'user/getUserCanDownload': true,
|
||||
'user/getIsAdminOrUp': true,
|
||||
'user/getUserMediaProgress': (id) => null,
|
||||
'libraries/getLibraryProvider': () => 'audible.us',
|
||||
'globals/getLibraryItemCoverSrc': () => 'https://my.server.com/book_placeholder.jpg',
|
||||
getLibraryItemsStreaming: () => null,
|
||||
getIsMediaQueued: () => false,
|
||||
getIsStreamingFromDifferentLibrary: () => false
|
||||
},
|
||||
state: {
|
||||
libraries: {
|
||||
currentLibraryId: 'library-123'
|
||||
},
|
||||
processingBatch: false,
|
||||
serverSettings: {
|
||||
dateFormat: 'MM/dd/yyyy'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { propsData, stubs, mocks }
|
||||
}
|
||||
|
||||
describe('LazyBookCard', () => {
|
||||
let mountOptions = null
|
||||
beforeEach(() => {
|
||||
mountOptions = createMountOptions()
|
||||
// cy.intercept(
|
||||
// 'https://my.server.com/**/*',
|
||||
// { middleware: true },
|
||||
// (req) => {
|
||||
// req.on('before:response', (res) => {
|
||||
// // force all API responses to not be cached
|
||||
// res.headers['cache-control'] = 'no-store'
|
||||
// })
|
||||
// }
|
||||
// )
|
||||
})
|
||||
|
||||
before(() => {
|
||||
// Put placeholder image is in the browser cache
|
||||
mountOptions = createMountOptions()
|
||||
cy.intercept('https://my.server.com/book_placeholder.jpg', { fixture: 'images/book_placeholder.jpg' }).as('bookCover')
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
cy.wait('@bookCover')
|
||||
|
||||
// Put cover1 (aspect ratio 1.6) image in the browser cache
|
||||
mountOptions = createMountOptions()
|
||||
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover1.jpg'
|
||||
cy.intercept('https://my.server.com/cover1.jpg', { fixture: 'images/cover1.jpg' }).as('bookCover1')
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
cy.wait('@bookCover1')
|
||||
|
||||
// Put cover2 (aspect ratio 1) image in the browser cache
|
||||
mountOptions = createMountOptions()
|
||||
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover2.jpg'
|
||||
cy.intercept('https://my.server.com/cover2.jpg', { fixture: 'images/cover2.jpg' }).as('bookCover2')
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
cy.wait('@bookCover2')
|
||||
})
|
||||
|
||||
it('renders the component correctly', () => {
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&titleImageNotReady').should('be.hidden')
|
||||
cy.get('&coverImage').should('have.css', 'opacity', '1')
|
||||
cy.get('&coverBg').should('be.hidden')
|
||||
cy.get('&overlay').should('be.hidden')
|
||||
cy.get('&detailBottom').should('be.visible')
|
||||
cy.get('&title').should('have.text', 'The Fellowship of the Ring')
|
||||
cy.get('&explicitIndicator').should('not.exist')
|
||||
cy.get('&line2').should('have.text', 'J. R. R. Tolkien')
|
||||
cy.get('&line3').should('not.exist')
|
||||
cy.get('seriesSequenceList').should('not.exist')
|
||||
cy.get('&booksInSeries').should('not.exist')
|
||||
cy.get('&placeholderTitle').should('be.visible')
|
||||
cy.get('&placeholderTitleText').should('have.text', 'The Fellowship of the Ring')
|
||||
cy.get('&placeholderAuthor').should('be.visible')
|
||||
cy.get('&placeholderAuthorText').should('have.text', 'J. R. R. Tolkien')
|
||||
cy.get('&progressBar').should('be.hidden')
|
||||
cy.get('&finishedProgressBar').should('not.exist')
|
||||
cy.get('&loadingSpinner').should('not.exist')
|
||||
cy.get('&seriesNameOverlay').should('not.exist')
|
||||
cy.get('&errorTooltip').should('not.exist')
|
||||
cy.get('&rssFeed').should('not.exist')
|
||||
cy.get('&seriesSequence').should('not.exist')
|
||||
cy.get('&podcastEpisdeNumber').should('not.exist')
|
||||
|
||||
// this should actually fail, since the height does not cover
|
||||
// the detailBottom element, currently rendered outside the card's area,
|
||||
// and requires complex layout calculations outside of the component.
|
||||
// todo: fix the component to render the detailBottom element inside the card's area
|
||||
cy.get('#book-card-0').should(($el) => {
|
||||
const width = $el.width()
|
||||
const height = $el.height()
|
||||
expect(width).to.be.closeTo(mountOptions.propsData.width, 0.01)
|
||||
expect(height).to.be.closeTo(mountOptions.propsData.height, 0.01)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows overlay on mouseover', () => {
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
cy.get('#book-card-0').trigger('mouseover')
|
||||
|
||||
cy.get('&titleImageNotReady').should('be.hidden')
|
||||
cy.get('&overlay').should('be.visible')
|
||||
cy.get('&playButton').should('be.visible')
|
||||
cy.get('&readButton').should('be.hidden')
|
||||
cy.get('&editButton').should('be.visible')
|
||||
cy.get('&selectedRadioButton').should('be.visible').and('have.text', 'radio_button_unchecked')
|
||||
cy.get('&moreButton').should('be.visible')
|
||||
cy.get('&ebookFormat').should('not.exist')
|
||||
})
|
||||
|
||||
it('routes to item page when clicked', () => {
|
||||
mountOptions.mocks.$router = { push: cy.stub().as('routerPush') }
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
cy.get('#book-card-0').click()
|
||||
|
||||
cy.get('@routerPush').should('have.been.calledOnceWithExactly', '/item/1')
|
||||
})
|
||||
|
||||
it('shows titleImageNotReady and sets opacity 0 on coverImage when image not ready', () => {
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&titleImageNotReady').should('be.visible')
|
||||
cy.get('&coverImage').should('have.css', 'opacity', '0')
|
||||
})
|
||||
|
||||
it('shows coverBg when coverImage has different aspect ratio', () => {
|
||||
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover1.jpg'
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&coverBg').should('be.visible')
|
||||
cy.get('&coverImage').should('have.class', 'object-contain')
|
||||
})
|
||||
|
||||
it('hides coverBg when coverImage has same aspect ratio', () => {
|
||||
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover2.jpg'
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&coverBg').should('be.hidden')
|
||||
cy.get('&coverImage').should('have.class', 'object-fill')
|
||||
})
|
||||
|
||||
// The logic for displaying placeholder title and author seems incorrect.
|
||||
// It is currently based on existence of coverPath, but should be based weater the actual cover image is placeholder or not.
|
||||
// todo: fix the logic to display placeholder title and author based on the actual cover image.
|
||||
it('hides placeholderTitle and placeholderAuthor when book has cover', () => {
|
||||
mountOptions.mocks.$store.getters['globals/getLibraryItemCoverSrc'] = () => 'https://my.server.com/cover1.jpg'
|
||||
mountOptions.propsData.bookMount.media.coverPath = 'cover1.jpg'
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&placeholderTitle').should('not.exist')
|
||||
cy.get('&placeholderAuthor').should('not.exist')
|
||||
})
|
||||
|
||||
it('hides detailBottom when bookShelfView is STANDARD', () => {
|
||||
mountOptions.propsData.bookshelfView = Constants.BookshelfView.STANDARD
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&detailBottom').should('not.exist')
|
||||
})
|
||||
|
||||
it('shows explicit indicator when book is explicit', () => {
|
||||
mountOptions.propsData.bookMount.media.metadata.explicit = true
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&explicitIndicator').should('be.visible')
|
||||
})
|
||||
|
||||
describe('when collapsedSeries is present', () => {
|
||||
beforeEach(() => {
|
||||
mountOptions.propsData.bookMount.collapsedSeries = {
|
||||
id: 'series-123',
|
||||
name: 'The Lord of the Rings',
|
||||
nameIgnorePrefix: 'Lord of the Rings',
|
||||
numBooks: 3,
|
||||
libraryItemIds: ['1', '2', '3']
|
||||
}
|
||||
})
|
||||
|
||||
it('shows the collpased series', () => {
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&seriesSequenceList').should('not.exist')
|
||||
cy.get('&booksInSeries').should('be.visible').and('have.text', '3')
|
||||
cy.get('&title').should('be.visible').and('have.text', 'The Lord of the Rings')
|
||||
cy.get('&line2').should('be.visible').and('have.text', '\u00a0')
|
||||
cy.get('&progressBar').should('be.hidden')
|
||||
})
|
||||
|
||||
it('shows the seriesNameOverlay on mouseover', () => {
|
||||
mountOptions.propsData.bookMount.media.metadata.series = {
|
||||
id: 'series-456',
|
||||
name: 'Middle Earth Chronicles',
|
||||
sequence: 1
|
||||
}
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
cy.get('#book-card-0').trigger('mouseover')
|
||||
|
||||
cy.get('&seriesNameOverlay').should('be.visible').and('have.text', 'Middle Earth Chronicles')
|
||||
})
|
||||
|
||||
it('shows the seriesSequenceList when collapsed series has a sequence list', () => {
|
||||
mountOptions.propsData.bookMount.collapsedSeries.seriesSequenceList = '1-3'
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&seriesSequenceList').should('be.visible').and('have.text', '#1-3')
|
||||
cy.get('&booksInSeries').should('not.exist')
|
||||
})
|
||||
|
||||
it('routes to the series page when clicked', () => {
|
||||
mountOptions.mocks.$router = { push: cy.stub().as('routerPush') }
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
cy.get('#book-card-0').click()
|
||||
|
||||
cy.get('@routerPush').should('have.been.calledOnceWithExactly', '/library/library-123/series/series-123')
|
||||
})
|
||||
|
||||
it('shows the series progress bar when series has progress', () => {
|
||||
mountOptions.mocks.$store.getters['user/getUserMediaProgress'] = (id) => {
|
||||
switch (id) {
|
||||
case '1':
|
||||
return { isFinished: true }
|
||||
case '2':
|
||||
return { progress: 0.5 }
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&progressBar')
|
||||
.should('be.visible')
|
||||
.and('have.class', 'bg-yellow-400')
|
||||
.and(($el) => {
|
||||
const width = $el.width()
|
||||
expect(width).to.be.closeTo(((1 + 0.5) / 3) * mountOptions.propsData.width, 0.01)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows full green progress bar when all books are finished', () => {
|
||||
mountOptions.mocks.$store.getters['user/getUserMediaProgress'] = (id) => {
|
||||
return { isFinished: true }
|
||||
}
|
||||
cy.mount(LazyBookCard, mountOptions)
|
||||
|
||||
cy.get('&progressBar')
|
||||
.should('be.visible')
|
||||
.and('have.class', 'bg-success')
|
||||
.and(($el) => {
|
||||
const width = $el.width()
|
||||
expect(width).to.be.equal(mountOptions.propsData.width)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
217
client/cypress/tests/components/cards/LazySeriesCard.cy.js
Normal file
217
client/cypress/tests/components/cards/LazySeriesCard.cy.js
Normal file
@@ -0,0 +1,217 @@
|
||||
import LazySeriesCard from '@/components/cards/LazySeriesCard.vue'
|
||||
import GroupCover from '@/components/covers/GroupCover.vue'
|
||||
|
||||
describe('LazySeriesCard', () => {
|
||||
const series = {
|
||||
id: 1,
|
||||
name: 'The Lord of the Rings',
|
||||
nameIgnorePrefix: 'Lord of the Rings',
|
||||
books: [
|
||||
{ id: 1, updatedAt: /* 04/14/2024 */ 1713099600000, addedAt: 1713099600000, media: { coverPath: 'cover1.jpg' }, title: 'The Fellowship of the Ring' },
|
||||
{ id: 2, updatedAt: /* 04/15/2024 */ 1713186000000, addedAt: 1713186000000, media: { coverPath: 'cover2.jpg' }, title: 'The Two Towers' },
|
||||
{ id: 3, updatedAt: /* 04/16/2024 */ 1713272400000, addedAt: 1713272400000, media: { coverPath: 'cover3.jpg' }, title: 'The Return of the King' }
|
||||
],
|
||||
addedAt: /* 04/17/2024 */ 1713358800000,
|
||||
totalDuration: /* 7h 30m */ 3600 * 7 + 60 * 30,
|
||||
rssFeed: 'https://example.com/feed.rss'
|
||||
}
|
||||
|
||||
const propsData = {
|
||||
index: 0,
|
||||
width: 192 * 2,
|
||||
height: 192,
|
||||
sizeMultiplier: 1,
|
||||
bookCoverAspectRatio: 1,
|
||||
bookshelfView: 1,
|
||||
isCategorized: false,
|
||||
seriesMount: series,
|
||||
sortingIgnorePrefix: false,
|
||||
orderBy: 'addedAt'
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
'covers-group-cover': GroupCover
|
||||
}
|
||||
|
||||
const mocks = {
|
||||
$store: {
|
||||
getters: {
|
||||
'user/getUserCanUpdate': true,
|
||||
'user/getUserMediaProgress': (id) => null,
|
||||
'libraries/getLibraryProvider': () => 'audible.us',
|
||||
'globals/getLibraryItemCoverSrc': () => 'https://my.server.com/book_placeholder.jpg'
|
||||
},
|
||||
state: {
|
||||
libraries: {
|
||||
currentLibraryId: 'library-123'
|
||||
},
|
||||
serverSettings: {
|
||||
dateFormat: 'MM/dd/yyyy'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
before(() => {
|
||||
cy.intercept('GET', 'https://my.server.com/book_placeholder.jpg', { fixture: 'images/book_placeholder.jpg' }).as('bookCover')
|
||||
cy.mount(LazySeriesCard, { propsData, stubs, mocks })
|
||||
cy.wait('@bookCover')
|
||||
// Now the placeholder image is in the browser cache
|
||||
})
|
||||
|
||||
it('renders the component', () => {
|
||||
cy.mount(LazySeriesCard, { propsData, stubs, mocks })
|
||||
|
||||
cy.get('&card').should(($el) => {
|
||||
const width = $el.width()
|
||||
const height = $el.height()
|
||||
expect(width).to.be.closeTo(propsData.width, 0.01)
|
||||
expect(height).to.be.closeTo(propsData.height, 0.01)
|
||||
})
|
||||
cy.get('&seriesLengthMarker').should('be.visible').and('have.text', propsData.seriesMount.books.length)
|
||||
cy.get('&seriesProgressBar').should('not.exist')
|
||||
cy.get('&hoveringDisplayTitle').should('be.hidden')
|
||||
cy.get('&rssFeedMarker').should('be.visible')
|
||||
cy.get('&standardBottomDisplayTitle').should('not.exist')
|
||||
cy.get('&detailBottomDisplayTitle').should('be.visible')
|
||||
cy.get('&detailBottomDisplayTitle').should('have.text', 'The Lord of the Rings')
|
||||
cy.get('&detailBottomSortLine').should('have.text', 'Added 04/17/2024')
|
||||
})
|
||||
|
||||
it('shows series name and hides rss feed marker on mouseover', () => {
|
||||
cy.mount(LazySeriesCard, { propsData, stubs, mocks })
|
||||
cy.get('&card').trigger('mouseover')
|
||||
|
||||
cy.get('&hoveringDisplayTitle').should('be.visible').should('have.text', 'The Lord of the Rings')
|
||||
cy.get('&rssFeedMarker').should('not.exist')
|
||||
})
|
||||
|
||||
it('routes properly when clicked', () => {
|
||||
const updatedMocks = {
|
||||
...mocks,
|
||||
$router: {
|
||||
push: cy.stub().as('routerPush')
|
||||
}
|
||||
}
|
||||
cy.mount(LazySeriesCard, { propsData, stubs, mocks: updatedMocks })
|
||||
cy.get('&card').click()
|
||||
|
||||
cy.get('@routerPush').should('have.been.calledOnceWithExactly', '/library/library-123/series/1')
|
||||
})
|
||||
|
||||
it('shows progress bar when progress is available', () => {
|
||||
const updatedMocks = {
|
||||
...mocks,
|
||||
$store: {
|
||||
...mocks.$store,
|
||||
getters: {
|
||||
...mocks.$store.getters,
|
||||
'user/getUserMediaProgress': (id) => {
|
||||
switch (id) {
|
||||
case 1:
|
||||
return { isFinished: true }
|
||||
case 2:
|
||||
return { progress: 0.5 }
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cy.mount(LazySeriesCard, { propsData, stubs, mocks: updatedMocks })
|
||||
|
||||
cy.get('&seriesProgressBar')
|
||||
.should('be.visible')
|
||||
.and('have.class', 'bg-yellow-400')
|
||||
.and(($el) => {
|
||||
const width = $el.width()
|
||||
expect(width).to.be.closeTo(((1 + 0.5) / 3) * propsData.width, 0.01)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows full green progress bar when all books are finished', () => {
|
||||
const updatedMocks = {
|
||||
...mocks,
|
||||
$store: {
|
||||
...mocks.$store,
|
||||
getters: {
|
||||
...mocks.$store.getters,
|
||||
'user/getUserMediaProgress': (id) => {
|
||||
return { isFinished: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cy.mount(LazySeriesCard, { propsData, stubs, mocks: updatedMocks })
|
||||
|
||||
cy.get('&seriesProgressBar')
|
||||
.should('be.visible')
|
||||
.and('have.class', 'bg-success')
|
||||
.and(($el) => {
|
||||
const width = $el.width()
|
||||
expect(width).to.equal(propsData.width)
|
||||
})
|
||||
})
|
||||
|
||||
it('hides the rss feed marker when there is no rss feed', () => {
|
||||
const updatedPropsData = {
|
||||
...propsData,
|
||||
seriesMount: { ...series, rssFeed: null }
|
||||
}
|
||||
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
|
||||
|
||||
cy.get('&rssFeedMarker').should('not.exist')
|
||||
})
|
||||
|
||||
it('shows the standard bottom display when bookshelf view is 0', () => {
|
||||
const updatedPropsData = {
|
||||
...propsData,
|
||||
bookshelfView: 0
|
||||
}
|
||||
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
|
||||
|
||||
cy.get('&standardBottomDisplayTitle').should('be.visible')
|
||||
cy.get('&detailBottomDisplayTitle').should('not.exist')
|
||||
})
|
||||
|
||||
it('shows total duration in sort line when orderBy is totalDuration', () => {
|
||||
const updatedPropsData = {
|
||||
...propsData,
|
||||
orderBy: 'totalDuration'
|
||||
}
|
||||
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
|
||||
|
||||
cy.get('&detailBottomSortLine').should('have.text', 'Duration 7h 30m')
|
||||
})
|
||||
|
||||
it('shows last book updated date in sort line when orderBy is lastBookUpdated', () => {
|
||||
const updatedPropsData = {
|
||||
...propsData,
|
||||
orderBy: 'lastBookUpdated'
|
||||
}
|
||||
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
|
||||
|
||||
cy.get('&detailBottomSortLine').should('have.text', 'Last Book Updated 04/16/2024')
|
||||
})
|
||||
|
||||
it('shows last book added date in sort line when orderBy is lastBookAdded', () => {
|
||||
const updatedPropsData = {
|
||||
...propsData,
|
||||
orderBy: 'lastBookAdded'
|
||||
}
|
||||
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
|
||||
|
||||
cy.get('&detailBottomSortLine').should('have.text', 'Last Book Added 04/16/2024')
|
||||
})
|
||||
|
||||
it('shows nameIgnorePrefix when sortingIgnorePrefix is true', () => {
|
||||
const updatedPropsData = {
|
||||
...propsData,
|
||||
sortingIgnorePrefix: true
|
||||
}
|
||||
cy.mount(LazySeriesCard, { propsData: updatedPropsData, stubs, mocks })
|
||||
|
||||
cy.get('&detailBottomDisplayTitle').should('have.text', 'Lord of the Rings')
|
||||
})
|
||||
})
|
||||
85
client/cypress/tests/components/cards/NarratorCard.cy.js
Normal file
85
client/cypress/tests/components/cards/NarratorCard.cy.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import NarratorCard from '@/components/cards/NarratorCard.vue'
|
||||
|
||||
describe('<NarratorCard />', () => {
|
||||
const narrator = {
|
||||
name: 'John Doe',
|
||||
numBooks: 5
|
||||
}
|
||||
const propsData = {
|
||||
narrator,
|
||||
width: 200,
|
||||
height: 150,
|
||||
sizeMultiplier: 1.2
|
||||
}
|
||||
const mocks = {
|
||||
$store: {
|
||||
getters: {
|
||||
'user/getUserCanUpdate': true
|
||||
},
|
||||
state: {
|
||||
libraries: {
|
||||
currentLibraryId: 'library-123'
|
||||
}
|
||||
}
|
||||
},
|
||||
$encode: (value) => value
|
||||
}
|
||||
|
||||
it('renders the component', () => {
|
||||
let mountOptions = { propsData, mocks }
|
||||
// see: https://on.cypress.io/mounting-vue
|
||||
cy.mount(NarratorCard, mountOptions)
|
||||
})
|
||||
|
||||
it('renders the narrator name correctly', () => {
|
||||
let mountOptions = { propsData, mocks }
|
||||
cy.mount(NarratorCard, mountOptions)
|
||||
|
||||
cy.get('&name').should('have.text', 'John Doe')
|
||||
})
|
||||
|
||||
it('renders the number of books correctly', () => {
|
||||
let mountOptions = { propsData, mocks }
|
||||
cy.mount(NarratorCard, mountOptions)
|
||||
|
||||
cy.get('&numBooks').should('have.text', '5 Books')
|
||||
})
|
||||
|
||||
it('renders 1 book correctly', () => {
|
||||
let propsData = { narrator: { name: 'John Doe', numBooks: 1 }, width: 200, height: 150, sizeMultiplier: 1.2 }
|
||||
let mountOptions = { propsData, mocks }
|
||||
cy.mount(NarratorCard, mountOptions)
|
||||
|
||||
cy.get('&numBooks').should('have.text', '1 Book')
|
||||
})
|
||||
|
||||
it('renders the default name and num-books when narrator is not provided', () => {
|
||||
let propsData = { width: 200, height: 150, sizeMultiplier: 1.2 }
|
||||
let mountOptions = { propsData, mocks }
|
||||
cy.mount(NarratorCard, mountOptions)
|
||||
cy.get('&name').should('have.text', '')
|
||||
cy.get('&numBooks').should('have.text', '0 Books')
|
||||
})
|
||||
|
||||
it('has the correct width and height', () => {
|
||||
let mountOptions = { propsData, mocks }
|
||||
cy.mount(NarratorCard, mountOptions)
|
||||
cy.get('&card').should('have.css', 'width', '200px')
|
||||
cy.get('&card').should('have.css', 'height', '150px')
|
||||
})
|
||||
|
||||
it('has the correct width and height when not provided', () => {
|
||||
let propsData = { narrator, sizeMultiplier: 1.2 }
|
||||
let mountOptions = { propsData, mocks }
|
||||
cy.mount(NarratorCard, mountOptions)
|
||||
cy.get('&card').should('have.css', 'width', '150px')
|
||||
cy.get('&card').should('have.css', 'height', '100px')
|
||||
})
|
||||
|
||||
it('has the correct font sizes', () => {
|
||||
let mountOptions = { propsData, mocks }
|
||||
cy.mount(NarratorCard, mountOptions)
|
||||
cy.get('&name').should('have.css', 'font-size', '14.4px') // 0.75 * 1.2 * 16
|
||||
cy.get('&numBooks').should('have.css', 'font-size', '12.48px') // 0.65 * 1.2 * 16
|
||||
})
|
||||
})
|
||||
@@ -153,4 +153,6 @@ module.exports = {
|
||||
* See: [Issue tracker](https://github.com/nuxt-community/tailwindcss-module/issues/480)
|
||||
*/
|
||||
devServerHandlers: [],
|
||||
|
||||
ignore: ["**/*.test.*", "**/*.cy.*"]
|
||||
}
|
||||
|
||||
1076
client/package-lock.json
generated
1076
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-client",
|
||||
"version": "2.9.0",
|
||||
"version": "2.10.1",
|
||||
"buildNumber": 1,
|
||||
"description": "Self-hosted audiobook and podcast client",
|
||||
"main": "index.js",
|
||||
@@ -9,7 +9,10 @@
|
||||
"dev2": "nuxt --hostname localhost --port 1337",
|
||||
"build": "nuxt build",
|
||||
"start": "nuxt start",
|
||||
"generate": "nuxt generate"
|
||||
"generate": "nuxt generate",
|
||||
"test": "npm run compile-tailwind && cypress run --component --browser chrome",
|
||||
"test-visually": "npm run compile-tailwind && cypress open --component --browser chrome",
|
||||
"compile-tailwind": "tailwindcss -i ./assets/tailwind.css -o ./cypress/support/tailwind.compiled.css"
|
||||
},
|
||||
"author": "advplyr",
|
||||
"license": "ISC",
|
||||
@@ -33,7 +36,8 @@
|
||||
"devDependencies": {
|
||||
"@nuxtjs/pwa": "^3.3.5",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"cypress": "^13.7.3",
|
||||
"postcss": "^8.3.6",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="w-12 hidden lg:block" />
|
||||
<p class="text-lg mb-4 font-semibold">{{ $strings.HeaderChapters }}</p>
|
||||
<div class="flex-grow" />
|
||||
<ui-checkbox v-model="showSecondInputs" checkbox-bg="primary" small label-class="text-sm text-gray-200 pl-1" label="Show seconds" class="mx-2" />
|
||||
<ui-checkbox v-model="showSecondInputs" checkbox-bg="primary" small label-class="text-sm text-gray-200 pl-1" :label="$strings.LabelShowSeconds" class="mx-2" />
|
||||
<div class="w-32 hidden lg:block" />
|
||||
</div>
|
||||
<div class="flex items-center mb-3 py-1 -mx-1">
|
||||
@@ -639,4 +639,4 @@ export default {
|
||||
this.destroyAudioEl()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<p v-if="author.description" class="text-white text-opacity-60 uppercase text-xs mb-2">{{ $strings.LabelDescription }}</p>
|
||||
<p ref="description" id="author-description" class="text-white max-w-3xl text-base whitespace-pre-wrap" :class="{ 'show-full': showFullDescription }">{{ author.description }}</p>
|
||||
<button v-if="isDescriptionClamped" class="py-0.5 flex items-center text-slate-300 hover:text-white" @click="showFullDescription = !showFullDescription">
|
||||
{{ showFullDescription ? 'Read less' : 'Read more' }} <span class="material-icons text-xl pl-1">{{ showFullDescription ? 'expand_less' : 'expand_more' }}</span>
|
||||
{{ showFullDescription ? $strings.ButtonReadLess : $strings.ButtonReadMore }} <span class="material-icons text-xl pl-1">{{ showFullDescription ? 'expand_less' : 'expand_more' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -140,4 +140,4 @@ export default {
|
||||
-webkit-line-clamp: unset;
|
||||
max-height: 999rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -310,14 +310,14 @@ export default {
|
||||
.then((data) => {
|
||||
this.$store.commit('setServerSettings', data.serverSettings)
|
||||
if (data.updated) {
|
||||
this.$toast.success('Server settings updated')
|
||||
this.$toast.success(this.$strings.ToastServerSettingsUpdateSuccess)
|
||||
} else {
|
||||
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to update server settings', error)
|
||||
this.$toast.error('Failed to update server settings')
|
||||
this.$toast.error(this.$strings.ToastServerSettingsUpdateFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.savingSettings = false
|
||||
@@ -347,4 +347,4 @@ export default {
|
||||
padding: 2px 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<app-settings-content :header-text="$strings.HeaderEmailSettings" :description="''">
|
||||
<template #header-items>
|
||||
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
||||
<a href="https://www.audiobookshelf.org/guides/send_to_ereader" target="_blank" class="inline-flex">
|
||||
<span class="material-icons text-xl w-5 text-gray-200">help_outline</span>
|
||||
</a>
|
||||
</ui-tooltip>
|
||||
</template>
|
||||
|
||||
<form @submit.prevent="submitForm">
|
||||
<div class="flex items-center -mx-1 mb-2">
|
||||
<div class="w-full md:w-3/4 px-1">
|
||||
@@ -51,7 +59,7 @@
|
||||
</div>
|
||||
</app-settings-content>
|
||||
|
||||
<app-settings-content :header-text="$strings.HeaderEreaderDevices" :description="''">
|
||||
<app-settings-content :header-text="$strings.HeaderEreaderDevices" :description="$strings.MessageEreaderDevices">
|
||||
<template #header-items>
|
||||
<div class="flex-grow" />
|
||||
|
||||
@@ -62,6 +70,7 @@
|
||||
<tr>
|
||||
<th class="text-left">{{ $strings.LabelName }}</th>
|
||||
<th class="text-left">{{ $strings.LabelEmail }}</th>
|
||||
<th class="text-left">{{ $strings.LabelAccessibleBy }}</th>
|
||||
<th class="w-40"></th>
|
||||
</tr>
|
||||
<tr v-for="device in existingEReaderDevices" :key="device.name">
|
||||
@@ -71,6 +80,9 @@
|
||||
<td class="text-left">
|
||||
<p class="text-sm md:text-base text-gray-100">{{ device.email }}</p>
|
||||
</td>
|
||||
<td class="text-left">
|
||||
<p class="text-sm md:text-base text-gray-100">{{ getAccessibleBy(device) }}</p>
|
||||
</td>
|
||||
<td class="w-40">
|
||||
<div class="flex justify-end items-center h-10">
|
||||
<ui-icon-btn icon="edit" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingDeviceName === device.name" class="mx-1" @click="editDeviceClick(device)" />
|
||||
@@ -79,12 +91,12 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div v-else class="text-center py-4">
|
||||
<div v-else-if="!loading" class="text-center py-4">
|
||||
<p class="text-lg text-gray-100">No Devices</p>
|
||||
</div>
|
||||
</app-settings-content>
|
||||
|
||||
<modals-emails-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="existingEReaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
|
||||
<modals-emails-e-reader-device-modal v-model="showEReaderDeviceModal" :users="users" :existing-devices="existingEReaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" :loadUsers="loadUsers" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -97,6 +109,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
users: [],
|
||||
loading: false,
|
||||
savingSettings: false,
|
||||
sendingTest: false,
|
||||
@@ -138,6 +151,30 @@ export default {
|
||||
...this.settings
|
||||
}
|
||||
},
|
||||
async loadUsers() {
|
||||
if (this.users.length) return
|
||||
this.users = await this.$axios
|
||||
.$get('/api/users')
|
||||
.then((res) => {
|
||||
return res.users.sort((a, b) => {
|
||||
return a.createdAt - b.createdAt
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed', error)
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return []
|
||||
})
|
||||
},
|
||||
getAccessibleBy(device) {
|
||||
const user = device.availabilityOption
|
||||
if (user === 'userOrUp') return 'Users (excluding Guests)'
|
||||
if (user === 'guestOrUp') return 'Users (including Guests)'
|
||||
if (user === 'specificUsers') {
|
||||
return device.users.map((id) => this.users.find((u) => u.id === id)?.username).join(', ')
|
||||
}
|
||||
return 'Admins Only'
|
||||
},
|
||||
editDeviceClick(device) {
|
||||
this.selectedEReaderDevice = device
|
||||
this.showEReaderDeviceModal = true
|
||||
@@ -176,6 +213,11 @@ export default {
|
||||
ereaderDevicesUpdated(ereaderDevices) {
|
||||
this.settings.ereaderDevices = ereaderDevices
|
||||
this.newSettings.ereaderDevices = ereaderDevices.map((d) => ({ ...d }))
|
||||
|
||||
// Load users if a device has availability set to specific users
|
||||
if (ereaderDevices.some((device) => device.availabilityOption === 'specificUsers')) {
|
||||
this.loadUsers()
|
||||
}
|
||||
},
|
||||
addNewDeviceClick() {
|
||||
this.selectedEReaderDevice = null
|
||||
@@ -243,7 +285,12 @@ export default {
|
||||
|
||||
this.$axios
|
||||
.$get(`/api/emails/settings`)
|
||||
.then((data) => {
|
||||
.then(async (data) => {
|
||||
// Load users if a device has availability set to specific users
|
||||
if (data.settings.ereaderDevices.some((device) => device.availabilityOption === 'specificUsers')) {
|
||||
await this.loadUsers()
|
||||
}
|
||||
|
||||
this.settings = data.settings
|
||||
this.newSettings = {
|
||||
...this.settings
|
||||
@@ -251,7 +298,7 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to get email settings', error)
|
||||
this.$toast.error('Failed to load email settings')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
@@ -263,4 +310,4 @@ export default {
|
||||
},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -199,16 +199,15 @@
|
||||
|
||||
<div class="h-0.5 bg-primary bg-opacity-30 w-full" />
|
||||
|
||||
<!-- confirm cache purge dialog -->
|
||||
<prompt-dialog v-model="showConfirmPurgeCache" :width="675">
|
||||
<div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300">
|
||||
<p class="text-error font-semibold">Important Notice!</p>
|
||||
<p class="my-2 text-center">Purge cache will delete the entire directory at <span class="font-mono">/metadata/cache</span>.</p>
|
||||
|
||||
<p class="text-center mb-8">Are you sure you want to remove the cache directory?</p>
|
||||
<p class="text-error font-semibold">{{ $strings.MessageImportantNotice }}</p>
|
||||
<p class="my-8 text-center" v-html="$strings.MessageConfirmPurgeCache" />
|
||||
<div class="flex px-1 items-center">
|
||||
<ui-btn color="primary" @click="showConfirmPurgeCache = false">Nevermind</ui-btn>
|
||||
<ui-btn color="primary" @click="showConfirmPurgeCache = false">{{ $strings.ButtonNevermind }}</ui-btn>
|
||||
<div class="flex-grow" />
|
||||
<ui-btn color="success" @click="confirmPurge">Yes, Purge!</ui-btn>
|
||||
<ui-btn color="success" @click="confirmPurge">{{ $strings.ButtonYes }}</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</prompt-dialog>
|
||||
@@ -275,7 +274,7 @@ export default {
|
||||
updateSortingPrefixes() {
|
||||
const prefixes = [...new Set(this.newServerSettings.sortingPrefixes.map((prefix) => prefix.trim().toLowerCase()) || [])]
|
||||
if (!prefixes.length) {
|
||||
this.$toast.error('Must have at least 1 prefix')
|
||||
this.$toast.error(this.$strings.ToastSortingPrefixesEmptyError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -283,7 +282,7 @@ export default {
|
||||
this.$axios
|
||||
.$patch(`/api/sorting-prefixes`, { sortingPrefixes: prefixes })
|
||||
.then((data) => {
|
||||
this.$toast.success(`Sorting prefixes updated. ${data.rowsUpdated} rows`)
|
||||
this.$toast.success(this.$getString('ToastSortingPrefixesUpdateSuccess', [data.rowsUpdated]))
|
||||
if (data.serverSettings) {
|
||||
this.$store.commit('setServerSettings', data.serverSettings)
|
||||
}
|
||||
@@ -291,7 +290,7 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to update prefixes', error)
|
||||
this.$toast.error('Failed to update sorting prefixes')
|
||||
this.$toast.error(this.$strings.ToastSortingPrefixesUpdateFailed)
|
||||
})
|
||||
.finally(() => {
|
||||
this.savingPrefixes = false
|
||||
@@ -329,7 +328,7 @@ export default {
|
||||
.dispatch('updateServerSettings', payload)
|
||||
.then(() => {
|
||||
this.updatingServerSettings = false
|
||||
this.$toast.success('Server settings updated')
|
||||
this.$toast.success(this.$strings.ToastServerSettingsUpdateSuccess)
|
||||
|
||||
if (payload.language) {
|
||||
// Updating language after save allows for re-rendering
|
||||
@@ -339,7 +338,7 @@ export default {
|
||||
.catch((error) => {
|
||||
console.error('Failed to update server settings', error)
|
||||
this.updatingServerSettings = false
|
||||
this.$toast.error('Failed to update server settings')
|
||||
this.$toast.error(this.$strings.ToastServerSettingsUpdateFailed)
|
||||
})
|
||||
},
|
||||
initServerSettings() {
|
||||
@@ -359,11 +358,11 @@ export default {
|
||||
await this.$axios
|
||||
.$post('/api/cache/purge')
|
||||
.then(() => {
|
||||
this.$toast.success('Cache Purged!')
|
||||
this.$toast.success(this.$strings.ToastCachePurgeSuccess)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to purge cache', error)
|
||||
this.$toast.error('Failed to purge cache')
|
||||
this.$toast.error(this.$strings.ToastCachePurgeFailed)
|
||||
})
|
||||
this.isPurgingCache = false
|
||||
},
|
||||
@@ -384,11 +383,11 @@ export default {
|
||||
await this.$axios
|
||||
.$post('/api/cache/items/purge')
|
||||
.then(() => {
|
||||
this.$toast.success('Items Cache Purged!')
|
||||
this.$toast.success(this.$strings.ToastCachePurgeSuccess)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to purge items cache', error)
|
||||
this.$toast.error('Failed to purge items cache')
|
||||
this.$toast.error(this.$strings.ToastCachePurgeFailed)
|
||||
})
|
||||
this.isPurgingCache = false
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed', error)
|
||||
this.$toast.error('Failed to load custom metadata providers')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
})
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<app-settings-content :header-text="$strings.HeaderLogs">
|
||||
<app-settings-content :header-text="$strings.HeaderLogs" :description="$strings.MessageLogsDescription">
|
||||
<template #header-items>
|
||||
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
|
||||
<a href="https://www.audiobookshelf.org/guides/server_logs" target="_blank" class="inline-flex">
|
||||
<span class="material-icons text-xl w-5 text-gray-200">help_outline</span>
|
||||
</a>
|
||||
</ui-tooltip>
|
||||
</template>
|
||||
|
||||
<div class="flex justify-between mb-2 place-items-end">
|
||||
<ui-text-input ref="input" v-model="search" placeholder="Search filter.." @input="inputUpdate" clearable class="w-full sm:w-40 h-8 text-sm sm:mb-0" />
|
||||
|
||||
@@ -139,7 +147,7 @@ export default {
|
||||
async loadLoggerData() {
|
||||
const loggerData = await this.$axios.$get('/api/logger-data').catch((error) => {
|
||||
console.error('Failed to load logger data', error)
|
||||
this.$toast.error('Failed to load logger data')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
})
|
||||
|
||||
this.loadedLogs = loggerData?.currentDailyLogs || []
|
||||
@@ -183,4 +191,4 @@ export default {
|
||||
.logmessage {
|
||||
width: calc(100% - 208px);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -142,7 +142,7 @@ export default {
|
||||
this.loading = true
|
||||
const notificationResponse = await this.$axios.$get('/api/notifications').catch((error) => {
|
||||
console.error('Failed to get notification settings', error)
|
||||
this.$toast.error('Failed to load notification settings')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return null
|
||||
})
|
||||
this.loading = false
|
||||
@@ -172,4 +172,4 @@ export default {
|
||||
this.$root.socket.off('notifications_updated', this.notificationsUpdated)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -134,7 +134,7 @@ export default {
|
||||
return null
|
||||
})
|
||||
if (!data) {
|
||||
this.$toast.error('Failed to load RSS feeds')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return
|
||||
}
|
||||
this.feeds = data.feeds
|
||||
|
||||
@@ -426,7 +426,7 @@ export default {
|
||||
})
|
||||
this.loading = false
|
||||
if (!data) {
|
||||
this.$toast.error('Failed to load listening sessions')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ export default {
|
||||
return null
|
||||
})
|
||||
if (!data) {
|
||||
this.$toast.error('Failed to load open sessions')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,7 @@ export default {
|
||||
return null
|
||||
})
|
||||
if (!data) {
|
||||
this.$toast.error('Failed to load listening sessions')
|
||||
this.$toast.error(this.$strings.ToastFailedToLoadData)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<h1 class="text-2xl md:text-3xl font-semibold">
|
||||
<div class="flex items-center">
|
||||
{{ title }}
|
||||
<widgets-explicit-indicator :explicit="isExplicit" />
|
||||
<widgets-explicit-indicator v-if="isExplicit" />
|
||||
<widgets-abridged-indicator v-if="isAbridged" />
|
||||
</div>
|
||||
</h1>
|
||||
@@ -40,7 +40,7 @@
|
||||
</template>
|
||||
|
||||
<template v-if="!isVideo">
|
||||
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">by {{ podcastAuthor || 'Unknown' }}</p>
|
||||
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">{{ $getString('LabelByAuthor', [podcastAuthor]) }}</p>
|
||||
<p v-else-if="musicArtists.length" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl max-w-[calc(100vw-2rem)] overflow-hidden overflow-ellipsis">
|
||||
<nuxt-link v-for="(artist, index) in musicArtists" :key="index" :to="`/artist/${$encode(artist)}`" class="hover:underline">{{ artist }}<span v-if="index < musicArtists.length - 1">, </span></nuxt-link>
|
||||
</p>
|
||||
@@ -130,7 +130,7 @@
|
||||
<div class="my-4 w-full">
|
||||
<p ref="description" id="item-description" dir="auto" class="text-base text-gray-100 whitespace-pre-line mb-1" :class="{ 'show-full': showFullDescription }">{{ description }}</p>
|
||||
<button v-if="isDescriptionClamped" class="py-0.5 flex items-center text-slate-300 hover:text-white" @click="showFullDescription = !showFullDescription">
|
||||
{{ showFullDescription ? 'Read less' : 'Read more' }} <span class="material-icons text-xl pl-1">{{ showFullDescription ? 'expand_less' : 'expand_more' }}</span>
|
||||
{{ showFullDescription ? $strings.ButtonReadLess : $strings.ButtonReadMore }} <span class="material-icons text-xl pl-1">{{ showFullDescription ? 'expand_less' : 'expand_more' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -285,7 +285,7 @@ export default {
|
||||
return this.mediaMetadata.subtitle
|
||||
},
|
||||
podcastAuthor() {
|
||||
return this.mediaMetadata.author || ''
|
||||
return this.mediaMetadata.author || 'Unknown'
|
||||
},
|
||||
authors() {
|
||||
return this.mediaMetadata.authors || []
|
||||
@@ -807,4 +807,4 @@ export default {
|
||||
-webkit-line-clamp: unset;
|
||||
max-height: 999rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -79,9 +79,6 @@ export default {
|
||||
}
|
||||
},
|
||||
authorUpdated(author) {
|
||||
if (this.selectedAuthor && this.selectedAuthor.id === author.id) {
|
||||
this.$store.commit('globals/setSelectedAuthor', author)
|
||||
}
|
||||
this.authors = this.authors.map((au) => {
|
||||
if (au.id === author.id) {
|
||||
return author
|
||||
@@ -108,4 +105,4 @@ export default {
|
||||
this.$root.socket.off('author_removed', this.authorRemoved)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="flex-grow px-2">
|
||||
<div class="flex items-center">
|
||||
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcastTitle }}</nuxt-link>
|
||||
<widgets-explicit-indicator :explicit="episode.podcastExplicit" />
|
||||
<widgets-explicit-indicator v-if="episode.podcastExplicit" />
|
||||
</div>
|
||||
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
|
||||
</div>
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="hidden md:block">
|
||||
<div class="flex items-center">
|
||||
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcastTitle }}</nuxt-link>
|
||||
<widgets-explicit-indicator :explicit="episode.podcastExplicit" />
|
||||
<widgets-explicit-indicator v-if="episode.podcastExplicit" />
|
||||
</div>
|
||||
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="flex" @click.stop>
|
||||
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcast.metadata.title }}</nuxt-link>
|
||||
</div>
|
||||
<widgets-explicit-indicator :explicit="episode.podcast.metadata.explicit" />
|
||||
<widgets-explicit-indicator v-if="episode.podcast.metadata.explicit" />
|
||||
</div>
|
||||
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
|
||||
</div>
|
||||
@@ -29,7 +29,7 @@
|
||||
<div class="flex" @click.stop>
|
||||
<nuxt-link :to="`/item/${episode.libraryItemId}`" class="text-sm text-gray-200 hover:underline">{{ episode.podcast.metadata.title }}</nuxt-link>
|
||||
</div>
|
||||
<widgets-explicit-indicator :explicit="episode.podcast.metadata.explicit" />
|
||||
<widgets-explicit-indicator v-if="episode.podcast.metadata.explicit" />
|
||||
</div>
|
||||
<p class="text-xs text-gray-300 mb-1">{{ $dateDistanceFromNow(episode.publishedAt) }}</p>
|
||||
</div>
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
<div class="flex-grow pl-4 max-w-2xl">
|
||||
<div class="flex items-center">
|
||||
<a :href="podcast.pageUrl" class="text-base md:text-lg text-gray-200 hover:underline" target="_blank" @click.stop>{{ podcast.title }}</a>
|
||||
<widgets-explicit-indicator :explicit="podcast.explicit" />
|
||||
<widgets-explicit-indicator v-if="podcast.explicit" />
|
||||
<widgets-already-in-library-indicator :already-in-library="podcast.alreadyInLibrary" />
|
||||
</div>
|
||||
<p class="text-sm md:text-base text-gray-300 whitespace-nowrap truncate">by {{ podcast.artistName }}</p>
|
||||
<p class="text-sm md:text-base text-gray-300 whitespace-nowrap truncate">{{ $getString('LabelByAuthor', [podcast.artistName]) }}</p>
|
||||
<p class="text-xs text-gray-400 leading-5">{{ podcast.genres.join(', ') }}</p>
|
||||
<p class="text-xs text-gray-400 leading-5">{{ podcast.trackCount }} {{ $strings.HeaderEpisodes }}</p>
|
||||
</div>
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
<form @submit.prevent="submitServerSetup">
|
||||
<p class="text-lg font-semibold mb-2 pl-1 text-center">Create Root User</p>
|
||||
<ui-text-input-with-label v-model.trim="newRoot.username" label="Username" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||
<ui-text-input-with-label v-model.trim="newRoot.password" label="Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||
<ui-text-input-with-label v-model.trim="confirmPassword" label="Confirm Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||
<ui-text-input-with-label v-model="newRoot.password" label="Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||
<ui-text-input-with-label v-model="confirmPassword" label="Confirm Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
|
||||
|
||||
<p class="text-lg font-semibold mt-6 mb-2 pl-1 text-center">Directory Paths</p>
|
||||
<ui-text-input-with-label v-model="ConfigPath" label="Config Path" disabled class="w-full mb-3 text-sm" />
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
{{ streaming ? $strings.ButtonPlaying : $strings.ButtonPlay }}
|
||||
</ui-btn>
|
||||
|
||||
<ui-icon-btn v-if="userCanUpdate" icon="edit" class="mx-0.5" @click="editClick" />
|
||||
<ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
|
||||
|
||||
<ui-icon-btn v-if="userCanDelete" icon="delete" class="mx-0.5" @click="removeClick" />
|
||||
<ui-icon-btn icon="delete" class="mx-0.5" @click="removeClick" />
|
||||
</div>
|
||||
|
||||
<div class="my-8 max-w-2xl">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { supplant } from './utils'
|
||||
const defaultCode = 'en-us'
|
||||
|
||||
const languageCodeMap = {
|
||||
'bg': { label: 'Български', dateFnsLocale: 'bg' },
|
||||
'bn': { label: 'বাংলা', dateFnsLocale: 'bn' },
|
||||
'cs': { label: 'Čeština', dateFnsLocale: 'cs' },
|
||||
'da': { label: 'Dansk', dateFnsLocale: 'da' },
|
||||
@@ -38,7 +39,30 @@ Vue.prototype.$languageCodeOptions = Object.keys(languageCodeMap).map(code => {
|
||||
|
||||
// iTunes search API uses ISO 3166 country codes: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
||||
const podcastSearchRegionMap = {
|
||||
'br': { label: 'Brasil' },
|
||||
'be': { label: 'België / Belgique / Belgien' },
|
||||
'cz': { label: 'Česko' },
|
||||
'dk': { label: 'Danmark' },
|
||||
'de': { label: 'Deutschland' },
|
||||
'ee': { label: 'Eesti' },
|
||||
'es': { label: 'España / Espanya / Espainia' },
|
||||
'fr': { label: 'France' },
|
||||
'hr': { label: 'Hrvatska' },
|
||||
'il': { label: 'ישראל / إسرائيل' },
|
||||
'it': { label: 'Italia' },
|
||||
'lu': { label: 'Luxembourg / Luxemburg / Lëtezebuerg' },
|
||||
'hu': { label: 'Magyarország' },
|
||||
'nl': { label: 'Nederland' },
|
||||
'no': { label: 'Norge' },
|
||||
'at': { label: 'Österreich' },
|
||||
'pl': { label: 'Polska' },
|
||||
'pt': { label: 'Portugal' },
|
||||
'ru': { label: 'Россия' },
|
||||
'ch': { label: 'Schweiz / Suisse / Svizzera' },
|
||||
'se': { label: 'Sverige' },
|
||||
'vn': { label: 'Việt Nam' },
|
||||
'ua': { label: 'Україна' },
|
||||
'gb': { label: 'United Kingdom' },
|
||||
'us': { label: 'United States' },
|
||||
'cn': { label: '中国' }
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export const state = () => ({
|
||||
selectedCollection: null,
|
||||
selectedAuthor: null,
|
||||
selectedMediaItems: [],
|
||||
selectedLibraryItemId: null,
|
||||
selectedRawCoverUrl: null,
|
||||
isCasting: false, // Actively casting
|
||||
isChromecastInitialized: false, // Script loadeds
|
||||
showBatchQuickMatchModal: false,
|
||||
@@ -82,34 +82,40 @@ export const state = () => ({
|
||||
})
|
||||
|
||||
export const getters = {
|
||||
getLibraryItemCoverSrc: (state, getters, rootState, rootGetters) => (libraryItem, placeholder = null, raw = false) => {
|
||||
if (!placeholder) placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
||||
if (!libraryItem) return placeholder
|
||||
const media = libraryItem.media
|
||||
if (!media?.coverPath || media.coverPath === placeholder) return placeholder
|
||||
getLibraryItemCoverSrc:
|
||||
(state, getters, rootState, rootGetters) =>
|
||||
(libraryItem, placeholder = null, raw = false) => {
|
||||
if (!placeholder) placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
||||
if (!libraryItem) return placeholder
|
||||
const media = libraryItem.media
|
||||
if (!media?.coverPath || media.coverPath === placeholder) return placeholder
|
||||
|
||||
// Absolute URL covers (should no longer be used)
|
||||
if (media.coverPath.startsWith('http:') || media.coverPath.startsWith('https:')) return media.coverPath
|
||||
// Absolute URL covers (should no longer be used)
|
||||
if (media.coverPath.startsWith('http:') || media.coverPath.startsWith('https:')) return media.coverPath
|
||||
|
||||
const userToken = rootGetters['user/getToken']
|
||||
const lastUpdate = libraryItem.updatedAt || Date.now()
|
||||
const libraryItemId = libraryItem.libraryItemId || libraryItem.id // Workaround for /users/:id page showing media progress covers
|
||||
const userToken = rootGetters['user/getToken']
|
||||
const lastUpdate = libraryItem.updatedAt || Date.now()
|
||||
const libraryItemId = libraryItem.libraryItemId || libraryItem.id // Workaround for /users/:id page showing media progress covers
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') { // Testing
|
||||
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}&ts=${lastUpdate}${raw ? '&raw=1' : ''}`
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// Testing
|
||||
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}&ts=${lastUpdate}${raw ? '&raw=1' : ''}`
|
||||
}
|
||||
|
||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}&ts=${lastUpdate}${raw ? '&raw=1' : ''}`
|
||||
},
|
||||
getLibraryItemCoverSrcById: (state, getters, rootState, rootGetters) => (libraryItemId, timestamp = null, raw = false) => {
|
||||
const placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
||||
if (!libraryItemId) return placeholder
|
||||
const userToken = rootGetters['user/getToken']
|
||||
if (process.env.NODE_ENV !== 'production') { // Testing
|
||||
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
||||
}
|
||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
||||
},
|
||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}&ts=${lastUpdate}${raw ? '&raw=1' : ''}`
|
||||
},
|
||||
getLibraryItemCoverSrcById:
|
||||
(state, getters, rootState, rootGetters) =>
|
||||
(libraryItemId, timestamp = null, raw = false) => {
|
||||
const placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
||||
if (!libraryItemId) return placeholder
|
||||
const userToken = rootGetters['user/getToken']
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// Testing
|
||||
return `http://localhost:3333${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
||||
}
|
||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
||||
},
|
||||
getIsBatchSelectingMediaItems: (state) => {
|
||||
return state.selectedMediaItems.length
|
||||
}
|
||||
@@ -161,8 +167,8 @@ export const mutations = {
|
||||
setShowRawCoverPreviewModal(state, val) {
|
||||
state.showRawCoverPreviewModal = val
|
||||
},
|
||||
setRawCoverPreviewModal(state, libraryItemId) {
|
||||
state.selectedLibraryItemId = libraryItemId
|
||||
setRawCoverPreviewModal(state, rawCoverUrl) {
|
||||
state.selectedRawCoverUrl = rawCoverUrl
|
||||
state.showRawCoverPreviewModal = true
|
||||
},
|
||||
setEditCollection(state, collection) {
|
||||
@@ -202,17 +208,16 @@ export const mutations = {
|
||||
state.selectedMediaItems = []
|
||||
},
|
||||
toggleMediaItemSelected(state, item) {
|
||||
if (state.selectedMediaItems.some(i => i.id === item.id)) {
|
||||
state.selectedMediaItems = state.selectedMediaItems.filter(i => i.id !== item.id)
|
||||
if (state.selectedMediaItems.some((i) => i.id === item.id)) {
|
||||
state.selectedMediaItems = state.selectedMediaItems.filter((i) => i.id !== item.id)
|
||||
} else {
|
||||
state.selectedMediaItems.push(item)
|
||||
}
|
||||
},
|
||||
setMediaItemSelected(state, { item, selected }) {
|
||||
const isAlreadySelected = state.selectedMediaItems.some(i => i.id === item.id)
|
||||
const isAlreadySelected = state.selectedMediaItems.some((i) => i.id === item.id)
|
||||
if (isAlreadySelected && !selected) {
|
||||
state.selectedMediaItems = state.selectedMediaItems.filter(i => i.id !== item.id)
|
||||
|
||||
state.selectedMediaItems = state.selectedMediaItems.filter((i) => i.id !== item.id)
|
||||
} else if (selected && !isAlreadySelected) {
|
||||
state.selectedMediaItems.push(item)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ export const state = () => ({
|
||||
})
|
||||
|
||||
export const getters = {
|
||||
getCurrentLibrary: state => {
|
||||
return state.libraries.find(lib => lib.id === state.currentLibraryId)
|
||||
getCurrentLibrary: (state) => {
|
||||
return state.libraries.find((lib) => lib.id === state.currentLibraryId)
|
||||
},
|
||||
getCurrentLibraryName: (state, getters) => {
|
||||
var currentLibrary = getters.getCurrentLibrary
|
||||
@@ -28,11 +28,11 @@ export const getters = {
|
||||
if (!getters.getCurrentLibrary) return null
|
||||
return getters.getCurrentLibrary.mediaType
|
||||
},
|
||||
getSortedLibraries: state => () => {
|
||||
return state.libraries.map(lib => ({ ...lib })).sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
getSortedLibraries: (state) => () => {
|
||||
return state.libraries.map((lib) => ({ ...lib })).sort((a, b) => a.displayOrder - b.displayOrder)
|
||||
},
|
||||
getLibraryProvider: state => libraryId => {
|
||||
var library = state.libraries.find(l => l.id === libraryId)
|
||||
getLibraryProvider: (state) => (libraryId) => {
|
||||
var library = state.libraries.find((l) => l.id === libraryId)
|
||||
if (!library) return null
|
||||
return library.provider
|
||||
},
|
||||
@@ -60,11 +60,14 @@ export const getters = {
|
||||
getLibraryIsAudiobooksOnly: (state, getters) => {
|
||||
return !!getters.getCurrentLibrarySettings?.audiobooksOnly
|
||||
},
|
||||
getCollection: state => id => {
|
||||
return state.collections.find(c => c.id === id)
|
||||
getLibraryEpubsAllowScriptedContent: (state, getters) => {
|
||||
return !!getters.getCurrentLibrarySettings?.epubsAllowScriptedContent
|
||||
},
|
||||
getPlaylist: state => id => {
|
||||
return state.userPlaylists.find(p => p.id === id)
|
||||
getCollection: (state) => (id) => {
|
||||
return state.collections.find((c) => c.id === id)
|
||||
},
|
||||
getPlaylist: (state) => (id) => {
|
||||
return state.userPlaylists.find((p) => p.id === id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +78,8 @@ export const actions = {
|
||||
loadFolders({ state, commit }) {
|
||||
if (state.folders.length) {
|
||||
const lastCheck = Date.now() - state.folderLastUpdate
|
||||
if (lastCheck < 1000 * 5) { // 5 seconds
|
||||
if (lastCheck < 1000 * 5) {
|
||||
// 5 seconds
|
||||
// Folders up to date
|
||||
return state.folders
|
||||
}
|
||||
@@ -204,7 +208,7 @@ export const mutations = {
|
||||
})
|
||||
},
|
||||
addUpdate(state, library) {
|
||||
var index = state.libraries.findIndex(a => a.id === library.id)
|
||||
var index = state.libraries.findIndex((a) => a.id === library.id)
|
||||
if (index >= 0) {
|
||||
state.libraries.splice(index, 1, library)
|
||||
} else {
|
||||
@@ -216,19 +220,19 @@ export const mutations = {
|
||||
})
|
||||
},
|
||||
remove(state, library) {
|
||||
state.libraries = state.libraries.filter(a => a.id !== library.id)
|
||||
state.libraries = state.libraries.filter((a) => a.id !== library.id)
|
||||
|
||||
state.listeners.forEach((listener) => {
|
||||
listener.meth()
|
||||
})
|
||||
},
|
||||
addListener(state, listener) {
|
||||
var index = state.listeners.findIndex(l => l.id === listener.id)
|
||||
var index = state.listeners.findIndex((l) => l.id === listener.id)
|
||||
if (index >= 0) state.listeners.splice(index, 1, listener)
|
||||
else state.listeners.push(listener)
|
||||
},
|
||||
removeListener(state, listenerId) {
|
||||
state.listeners = state.listeners.filter(l => l.id !== listenerId)
|
||||
state.listeners = state.listeners.filter((l) => l.id !== listenerId)
|
||||
},
|
||||
setLibraryFilterData(state, filterData) {
|
||||
state.filterData = filterData
|
||||
@@ -238,7 +242,7 @@ export const mutations = {
|
||||
},
|
||||
removeSeriesFromFilterData(state, seriesId) {
|
||||
if (!seriesId || !state.filterData) return
|
||||
state.filterData.series = state.filterData.series.filter(se => se.id !== seriesId)
|
||||
state.filterData.series = state.filterData.series.filter((se) => se.id !== seriesId)
|
||||
},
|
||||
updateFilterDataWithItem(state, libraryItem) {
|
||||
if (!libraryItem || !state.filterData) return
|
||||
@@ -260,12 +264,12 @@ export const mutations = {
|
||||
// Add/update book authors
|
||||
if (mediaMetadata.authors?.length) {
|
||||
mediaMetadata.authors.forEach((author) => {
|
||||
const indexOf = state.filterData.authors.findIndex(au => au.id === author.id)
|
||||
const indexOf = state.filterData.authors.findIndex((au) => au.id === author.id)
|
||||
if (indexOf >= 0) {
|
||||
state.filterData.authors.splice(indexOf, 1, author)
|
||||
} else {
|
||||
state.filterData.authors.push(author)
|
||||
state.filterData.authors.sort((a, b) => (a.name || '').localeCompare((b.name || '')))
|
||||
state.filterData.authors.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -273,12 +277,12 @@ export const mutations = {
|
||||
// Add/update series
|
||||
if (mediaMetadata.series?.length) {
|
||||
mediaMetadata.series.forEach((series) => {
|
||||
const indexOf = state.filterData.series.findIndex(se => se.id === series.id)
|
||||
const indexOf = state.filterData.series.findIndex((se) => se.id === series.id)
|
||||
if (indexOf >= 0) {
|
||||
state.filterData.series.splice(indexOf, 1, { id: series.id, name: series.name })
|
||||
} else {
|
||||
state.filterData.series.push({ id: series.id, name: series.name })
|
||||
state.filterData.series.sort((a, b) => (a.name || '').localeCompare((b.name || '')))
|
||||
state.filterData.series.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -329,7 +333,7 @@ export const mutations = {
|
||||
state.collections = collections
|
||||
},
|
||||
addUpdateCollection(state, collection) {
|
||||
var index = state.collections.findIndex(c => c.id === collection.id)
|
||||
var index = state.collections.findIndex((c) => c.id === collection.id)
|
||||
if (index >= 0) {
|
||||
state.collections.splice(index, 1, collection)
|
||||
} else {
|
||||
@@ -337,14 +341,14 @@ export const mutations = {
|
||||
}
|
||||
},
|
||||
removeCollection(state, collection) {
|
||||
state.collections = state.collections.filter(c => c.id !== collection.id)
|
||||
state.collections = state.collections.filter((c) => c.id !== collection.id)
|
||||
},
|
||||
setUserPlaylists(state, playlists) {
|
||||
state.userPlaylists = playlists
|
||||
state.numUserPlaylists = playlists.length
|
||||
},
|
||||
addUpdateUserPlaylist(state, playlist) {
|
||||
const index = state.userPlaylists.findIndex(p => p.id === playlist.id)
|
||||
const index = state.userPlaylists.findIndex((p) => p.id === playlist.id)
|
||||
if (index >= 0) {
|
||||
state.userPlaylists.splice(index, 1, playlist)
|
||||
} else {
|
||||
@@ -353,10 +357,10 @@ export const mutations = {
|
||||
}
|
||||
},
|
||||
removeUserPlaylist(state, playlist) {
|
||||
state.userPlaylists = state.userPlaylists.filter(p => p.id !== playlist.id)
|
||||
state.userPlaylists = state.userPlaylists.filter((p) => p.id !== playlist.id)
|
||||
state.numUserPlaylists = state.userPlaylists.length
|
||||
},
|
||||
setEReaderDevices(state, ereaderDevices) {
|
||||
state.ereaderDevices = ereaderDevices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
810
client/strings/bg.json
Normal file
810
client/strings/bg.json
Normal file
@@ -0,0 +1,810 @@
|
||||
{
|
||||
"ButtonAdd": "Добави",
|
||||
"ButtonAddChapters": "Добави Глави",
|
||||
"ButtonAddDevice": "Добави Устройство",
|
||||
"ButtonAddLibrary": "Добави Библиотека",
|
||||
"ButtonAddPodcasts": "Добави Подкаст",
|
||||
"ButtonAddUser": "Добави Потребител",
|
||||
"ButtonAddYourFirstLibrary": "Добави първата ти библиотека",
|
||||
"ButtonApply": "Приложи",
|
||||
"ButtonApplyChapters": "Приложи Глави",
|
||||
"ButtonAuthors": "Автори",
|
||||
"ButtonBrowseForFolder": "Прегледай за папка",
|
||||
"ButtonCancel": "Откажи",
|
||||
"ButtonCancelEncode": "Откажи закодирането",
|
||||
"ButtonChangeRootPassword": "Промени паролата за Root",
|
||||
"ButtonCheckAndDownloadNewEpisodes": "Провери и Свали Нови Епизоди",
|
||||
"ButtonChooseAFolder": "Избери Папка",
|
||||
"ButtonChooseFiles": "Избери Файлове",
|
||||
"ButtonClearFilter": "Изчисти Филтър",
|
||||
"ButtonCloseFeed": "Затвори Feed",
|
||||
"ButtonCollections": "Колекции",
|
||||
"ButtonConfigureScanner": "Конфигурирай Скенера",
|
||||
"ButtonCreate": "Създай",
|
||||
"ButtonCreateBackup": "Създай Backup",
|
||||
"ButtonDelete": "Изтрий",
|
||||
"ButtonDownloadQueue": "Опашка за Сваляне",
|
||||
"ButtonEdit": "Редактирай",
|
||||
"ButtonEditChapters": "Редактирай Глави",
|
||||
"ButtonEditPodcast": "Редактирай Подкаст",
|
||||
"ButtonForceReScan": "Принудително Пресканиране",
|
||||
"ButtonFullPath": "Пълен Път",
|
||||
"ButtonHide": "Скрий",
|
||||
"ButtonHome": "Начало",
|
||||
"ButtonIssues": "Проблеми",
|
||||
"ButtonJumpBackward": "Прескочи назад",
|
||||
"ButtonJumpForward": "Прескоци напред",
|
||||
"ButtonLatest": "Последни",
|
||||
"ButtonLibrary": "Библиотека",
|
||||
"ButtonLogout": "Изход",
|
||||
"ButtonLookup": "Търси",
|
||||
"ButtonManageTracks": "Управление на Канали",
|
||||
"ButtonMapChapterTitles": "Асоцийрай Заглавия на Глави",
|
||||
"ButtonMatchAllAuthors": "Съвпадение на Всички Автори",
|
||||
"ButtonMatchBooks": "Съвпадение на Книги",
|
||||
"ButtonNevermind": "Няма значение",
|
||||
"ButtonNext": "Next",
|
||||
"ButtonNextChapter": "Следваща Глава",
|
||||
"ButtonOk": "Добре",
|
||||
"ButtonOpenFeed": "Отвори Feed",
|
||||
"ButtonOpenManager": "Отвори Мениджър",
|
||||
"ButtonPause": "Пауза",
|
||||
"ButtonPlay": "Пусни",
|
||||
"ButtonPlaying": "Пуска се",
|
||||
"ButtonPlaylists": "Плейлисти",
|
||||
"ButtonPrevious": "Previous",
|
||||
"ButtonPreviousChapter": "Предишна Глава",
|
||||
"ButtonPurgeAllCache": "Изчисти Всички Кешове",
|
||||
"ButtonPurgeItemsCache": "Изчисти Кеша на Елементи",
|
||||
"ButtonPurgeMediaProgress": "Изчисти Прогреса на Медията",
|
||||
"ButtonQueueAddItem": "Добави към опашката",
|
||||
"ButtonQueueRemoveItem": "Премахни от опашката",
|
||||
"ButtonQuickMatch": "Бързо Съпоставяне",
|
||||
"ButtonRead": "Прочети",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonRemove": "Премахни",
|
||||
"ButtonRemoveAll": "Премахни Всички",
|
||||
"ButtonRemoveAllLibraryItems": "Премахни Всички Елементи от Библиотеката",
|
||||
"ButtonRemoveFromContinueListening": "Премахни от Продължаване на Слушане",
|
||||
"ButtonRemoveFromContinueReading": "Премахни от Продължаване на Четене",
|
||||
"ButtonRemoveSeriesFromContinueSeries": "Премахни Серия от Продължаване на Серии",
|
||||
"ButtonReScan": "Пресканирай",
|
||||
"ButtonReset": "Нулиране",
|
||||
"ButtonResetToDefault": "Нулиране до стойност по подразбиране",
|
||||
"ButtonRestore": "Възстанови",
|
||||
"ButtonSave": "Запази",
|
||||
"ButtonSaveAndClose": "Запази и Затвори",
|
||||
"ButtonSaveTracklist": "Запази Списък с Канали",
|
||||
"ButtonScan": "Сканирай",
|
||||
"ButtonScanLibrary": "Сканирай Библиотека",
|
||||
"ButtonSearch": "Търси",
|
||||
"ButtonSelectFolderPath": "Избери Път на Папка",
|
||||
"ButtonSeries": "Серии",
|
||||
"ButtonSetChaptersFromTracks": "Задай Глави от Песни",
|
||||
"ButtonShare": "Share",
|
||||
"ButtonShiftTimes": "Измести Времената",
|
||||
"ButtonShow": "Покажи",
|
||||
"ButtonStartM4BEncode": "Започни M4B Кодиране",
|
||||
"ButtonStartMetadataEmbed": "Започни Вграждане на Метаданни",
|
||||
"ButtonSubmit": "Изпрати",
|
||||
"ButtonTest": "Тест",
|
||||
"ButtonUpload": "Качи",
|
||||
"ButtonUploadBackup": "Качи Backup",
|
||||
"ButtonUploadCover": "Качи Корица",
|
||||
"ButtonUploadOPMLFile": "Качи OPML Файл",
|
||||
"ButtonUserDelete": "Изтрий Потребител {0}",
|
||||
"ButtonUserEdit": "Редактирай Потребител {0}",
|
||||
"ButtonViewAll": "Виж Всички",
|
||||
"ButtonYes": "Да",
|
||||
"ErrorUploadFetchMetadataAPI": "Грешка при Взимане на Метаданни ",
|
||||
"ErrorUploadFetchMetadataNoResults": "Метаданните не могат да бъдат взети - опитайте да обновите заглавието и/или автора",
|
||||
"ErrorUploadLacksTitle": "Трябва да има Заглавие",
|
||||
"HeaderAccount": "Профил",
|
||||
"HeaderAdvanced": "Разширени",
|
||||
"HeaderAppriseNotificationSettings": "Apprise Notification Опции",
|
||||
"HeaderAudiobookTools": "Инструмент за Менижиране на Аудиокниги ",
|
||||
"HeaderAudioTracks": "Звуков Канал",
|
||||
"HeaderAuthentication": "Аутентикация",
|
||||
"HeaderBackups": "Backups",
|
||||
"HeaderChangePassword": "Промяна на Парола",
|
||||
"HeaderChapters": "Глави",
|
||||
"HeaderChooseAFolder": "Избети Папка",
|
||||
"HeaderCollection": "Колекция",
|
||||
"HeaderCollectionItems": "Елементи на Колекция",
|
||||
"HeaderCover": "Корица",
|
||||
"HeaderCurrentDownloads": "Текущи Сваляния",
|
||||
"HeaderCustomMetadataProviders": "Потребителски Доставчици на Метаданни",
|
||||
"HeaderDetails": "Детайли",
|
||||
"HeaderDownloadQueue": "Опашка за Сваляне",
|
||||
"HeaderEbookFiles": "Файлове на Електронни книги",
|
||||
"HeaderEmail": "Емейл",
|
||||
"HeaderEmailSettings": "Настройки Емайл",
|
||||
"HeaderEpisodes": "Епизоди",
|
||||
"HeaderEreaderDevices": "Елктронни Четци",
|
||||
"HeaderEreaderSettings": "Настройки на Електронни Четци",
|
||||
"HeaderFiles": "Файлове",
|
||||
"HeaderFindChapters": "Намери Глави",
|
||||
"HeaderIgnoredFiles": "Игнорирани Файлове",
|
||||
"HeaderItemFiles": "Файлове на Елемент",
|
||||
"HeaderItemMetadataUtils": "Инструменти за Метаданни на Елемент",
|
||||
"HeaderLastListeningSession": "Последна Сесия на Слушане",
|
||||
"HeaderLatestEpisodes": "Последни Епизоди",
|
||||
"HeaderLibraries": "Библиотеки",
|
||||
"HeaderLibraryFiles": "Файлове на Библиотека",
|
||||
"HeaderLibraryStats": "Статистика на Библиотека",
|
||||
"HeaderListeningSessions": "Сесии на Слушане",
|
||||
"HeaderListeningStats": "Статистика на Слушане",
|
||||
"HeaderLogin": "Вход",
|
||||
"HeaderLogs": "Логове",
|
||||
"HeaderManageGenres": "Управление на Жанрове",
|
||||
"HeaderManageTags": "Управление на Тагове",
|
||||
"HeaderMapDetails": "Асоцирай Детайли",
|
||||
"HeaderMatch": "Съпостави",
|
||||
"HeaderMetadataOrderOfPrecedence": "Предимство на Метаданни",
|
||||
"HeaderMetadataToEmbed": "Метаданни за Вграждане",
|
||||
"HeaderNewAccount": "Нов Профил",
|
||||
"HeaderNewLibrary": "Нова Библиотека",
|
||||
"HeaderNotifications": "Известия",
|
||||
"HeaderOpenIDConnectAuthentication": "OpenID Connect Аутентикация",
|
||||
"HeaderOpenRSSFeed": "Отвори RSS Feed",
|
||||
"HeaderOtherFiles": "Други Файлове",
|
||||
"HeaderPasswordAuthentication": "Паролна Аутентикация",
|
||||
"HeaderPermissions": "Права",
|
||||
"HeaderPlayerQueue": "Опашка на Плейъра",
|
||||
"HeaderPlaylist": "Плейлист",
|
||||
"HeaderPlaylistItems": "Елементи на Плейлист",
|
||||
"HeaderPodcastsToAdd": "Подкасти за Добавяне",
|
||||
"HeaderPreviewCover": "Преглед на Корица",
|
||||
"HeaderRemoveEpisode": "Премахни Епизод",
|
||||
"HeaderRemoveEpisodes": "Премахни {0} Епизоди",
|
||||
"HeaderRSSFeedGeneral": "RSS Детайли",
|
||||
"HeaderRSSFeedIsOpen": "RSS Feed е Отворен",
|
||||
"HeaderRSSFeeds": "RSS Feed-ове",
|
||||
"HeaderSavedMediaProgress": "Запазен Прогрес на Медията",
|
||||
"HeaderSchedule": "График",
|
||||
"HeaderScheduleLibraryScans": "График за Автоматично Сканиране на Библиотека",
|
||||
"HeaderSession": "Сесия",
|
||||
"HeaderSetBackupSchedule": "Задай График за Backup",
|
||||
"HeaderSettings": "Настройки",
|
||||
"HeaderSettingsDisplay": "Визуализация",
|
||||
"HeaderSettingsExperimental": "Експериментални Функции",
|
||||
"HeaderSettingsGeneral": "Общи",
|
||||
"HeaderSettingsScanner": "Скенер",
|
||||
"HeaderSleepTimer": "Таймер за Сън",
|
||||
"HeaderStatsLargestItems": "Най-Големите Елементи",
|
||||
"HeaderStatsLongestItems": "Най-Дългите Елементи (часове)",
|
||||
"HeaderStatsMinutesListeningChart": "Минути на Слушане (последни 7 дни)",
|
||||
"HeaderStatsRecentSessions": "Скорошни Сесии",
|
||||
"HeaderStatsTop10Authors": "Топ 10 Автори",
|
||||
"HeaderStatsTop5Genres": "Топ 5 Жанрове",
|
||||
"HeaderTableOfContents": "Съдържание",
|
||||
"HeaderTools": "Инструменти",
|
||||
"HeaderUpdateAccount": "Обнови Профил",
|
||||
"HeaderUpdateAuthor": "Обнови Автор",
|
||||
"HeaderUpdateDetails": "Обнови Детайли",
|
||||
"HeaderUpdateLibrary": "Обнови Библиотека",
|
||||
"HeaderUsers": "Потребители",
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Твоята Статистика",
|
||||
"LabelAbridged": "Съкратен",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Тип на Профила",
|
||||
"LabelAccountTypeAdmin": "Администратор",
|
||||
"LabelAccountTypeGuest": "Гост",
|
||||
"LabelAccountTypeUser": "Потребител",
|
||||
"LabelActivity": "Дейност",
|
||||
"LabelAdded": "Добавени",
|
||||
"LabelAddedAt": "Добавени На",
|
||||
"LabelAddToCollection": "Добави в Колекция",
|
||||
"LabelAddToCollectionBatch": "Добави {0} Книги в Колекция",
|
||||
"LabelAddToPlaylist": "Добави в Плейлист",
|
||||
"LabelAddToPlaylistBatch": "Добави {0} Елемент в Плейлист",
|
||||
"LabelAdminUsersOnly": "Само за Администратори",
|
||||
"LabelAll": "Всички",
|
||||
"LabelAllUsers": "Всички Потребители",
|
||||
"LabelAllUsersExcludingGuests": "Всички потребители без гости",
|
||||
"LabelAllUsersIncludingGuests": "Всички потребители включително гости",
|
||||
"LabelAlreadyInYourLibrary": "Вече е в твоята библиотека",
|
||||
"LabelAppend": "Добави",
|
||||
"LabelAuthor": "Автор",
|
||||
"LabelAuthorFirstLast": "Автор (Първо Име, Фамилия)",
|
||||
"LabelAuthorLastFirst": "Автор (Фамилия, Първо Име)",
|
||||
"LabelAuthors": "Автори",
|
||||
"LabelAutoDownloadEpisodes": "Автоматично Сваляне на Епизоди",
|
||||
"LabelAutoFetchMetadata": "Автоматично Взимане на Метаданни",
|
||||
"LabelAutoFetchMetadataHelp": "Взима метаданни за заглвие, автор и серии за да опрости качването. Допълнителни метаданни може да трябва да бъде взера след качване.",
|
||||
"LabelAutoLaunch": "Автоматично Стартиране",
|
||||
"LabelAutoLaunchDescription": "Пренасочване към доставчик за аутентикация автоматично когато се навигира до страницата за вход (ръчно заменяне на пътя <code>/login?autoLaunch=0</code>)",
|
||||
"LabelAutoRegister": "Автоматична Регистрация",
|
||||
"LabelAutoRegisterDescription": "Автоматично създаване на нови потребители след вход",
|
||||
"LabelBackToUser": "Обратно към Потребител",
|
||||
"LabelBackupLocation": "Местоположение на Архив",
|
||||
"LabelBackupsEnableAutomaticBackups": "Включи автоматично архивиране",
|
||||
"LabelBackupsEnableAutomaticBackupsHelp": "Архиви запазени в /metadata/backups",
|
||||
"LabelBackupsMaxBackupSize": "Максимален размер на архива (в GB)",
|
||||
"LabelBackupsMaxBackupSizeHelp": "За защита срещу грешки в конфигурацията, архивите ще се провалят ако надхвърлят конфигурирания размер.",
|
||||
"LabelBackupsNumberToKeep": "Брой архиви за запазване",
|
||||
"LabelBackupsNumberToKeepHelp": "Само 1 архив ще бъде премахнат на веднъж, така че ако вече имате повече архиви от това трябва да ги премахнете ръчно.",
|
||||
"LabelBitrate": "Битрейт",
|
||||
"LabelBooks": "Книги",
|
||||
"LabelButtonText": "Текст на Бутон",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Промени Парола",
|
||||
"LabelChannels": "Канали",
|
||||
"LabelChapters": "Глави",
|
||||
"LabelChaptersFound": "намерени глави",
|
||||
"LabelChapterTitle": "Заглавие на Глава",
|
||||
"LabelClickForMoreInfo": "Кликни за повече информация",
|
||||
"LabelClosePlayer": "Затвори Плейъра",
|
||||
"LabelCodec": "Кодек",
|
||||
"LabelCollapseSeries": "Свий Серия",
|
||||
"LabelCollection": "Колекция",
|
||||
"LabelCollections": "Колекции",
|
||||
"LabelComplete": "Завършено",
|
||||
"LabelConfirmPassword": "Потвърди Парола",
|
||||
"LabelContinueListening": "Продължи Слушане",
|
||||
"LabelContinueReading": "Продължи Четене",
|
||||
"LabelContinueSeries": "Продължи Серия",
|
||||
"LabelCover": "Корица",
|
||||
"LabelCoverImageURL": "URL на Корица",
|
||||
"LabelCreatedAt": "Създадено на",
|
||||
"LabelCronExpression": "Cron Expression",
|
||||
"LabelCurrent": "Текущо",
|
||||
"LabelCurrently": "Текущо:",
|
||||
"LabelCustomCronExpression": "Потребителски Cron Expression:",
|
||||
"LabelDatetime": "Дата и Време",
|
||||
"LabelDeleteFromFileSystemCheckbox": "Изтрий от файловата система (отмени за да бъдат премахни само от базата данни)",
|
||||
"LabelDescription": "Описание",
|
||||
"LabelDeselectAll": "Премахни всички",
|
||||
"LabelDevice": "Устройство",
|
||||
"LabelDeviceInfo": "Информация за Устройство",
|
||||
"LabelDeviceIsAvailableTo": "Устройството е достъпно за ...",
|
||||
"LabelDirectory": "Директория",
|
||||
"LabelDiscFromFilename": "Диск от Име на Файл",
|
||||
"LabelDiscFromMetadata": "Диск от Метаданни",
|
||||
"LabelDiscover": "Открий",
|
||||
"LabelDownload": "Сваляне",
|
||||
"LabelDownloadNEpisodes": "Свали {0} епизоди",
|
||||
"LabelDuration": "Продължителност",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "Намерена продължителност:",
|
||||
"LabelEbook": "Електронна книга",
|
||||
"LabelEbooks": "Електронни книги",
|
||||
"LabelEdit": "Редакция",
|
||||
"LabelEmail": "Email",
|
||||
"LabelEmailSettingsFromAddress": "От Адрес",
|
||||
"LabelEmailSettingsSecure": "Сигурна",
|
||||
"LabelEmailSettingsSecureHelp": "Ако е вярно възката ще изполва TLS когате се свързва със сървъра. Ако не е то TLS ще се използва ако сървъра поддържа разширението STARTTLS. В повечето случаи задайте тази стойност на истина ако се свързвате към порт 465. За порт 587 или 25 оставете я на лъжа. (от nodemailer.com/smtp/#authentication)",
|
||||
"LabelEmailSettingsTestAddress": "Тестов Адрес",
|
||||
"LabelEmbeddedCover": "Вградена Корица",
|
||||
"LabelEnable": "Включи",
|
||||
"LabelEnd": "Край",
|
||||
"LabelEpisode": "Епизод",
|
||||
"LabelEpisodeTitle": "Заглавие на Епизод",
|
||||
"LabelEpisodeType": "Тип на Епизод",
|
||||
"LabelExample": "Пример",
|
||||
"LabelExplicit": "Експлицитно",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFetchingMetadata": "Взимане на Метаданни",
|
||||
"LabelFile": "Файл",
|
||||
"LabelFileBirthtime": "Дата на създаване на файла",
|
||||
"LabelFileModified": "Файлът променен",
|
||||
"LabelFilename": "Име на Файл",
|
||||
"LabelFilterByUser": "Филтриране по Потребител",
|
||||
"LabelFindEpisodes": "Намери Епизоди",
|
||||
"LabelFinished": "Завършено",
|
||||
"LabelFolder": "Папка",
|
||||
"LabelFolders": "Папки",
|
||||
"LabelFontBold": "Получерно",
|
||||
"LabelFontFamily": "Шрифт",
|
||||
"LabelFontItalic": "Курсив",
|
||||
"LabelFontScale": "Мащаб на Шрифта",
|
||||
"LabelFontStrikethrough": "Зачертан",
|
||||
"LabelFormat": "Формат",
|
||||
"LabelGenre": "Жанр",
|
||||
"LabelGenres": "Жанрове",
|
||||
"LabelHardDeleteFile": "Пълно Изтриване на Файл",
|
||||
"LabelHasEbook": "Има електронна книга",
|
||||
"LabelHasSupplementaryEbook": "Има допълнителна електронна книга",
|
||||
"LabelHighestPriority": "Най-висок Приоритет",
|
||||
"LabelHost": "Хост",
|
||||
"LabelHour": "Час",
|
||||
"LabelIcon": "Икона",
|
||||
"LabelImageURLFromTheWeb": "URL на Изображение от Интернет",
|
||||
"LabelIncludeInTracklist": "Включи в Списъка с Канали",
|
||||
"LabelIncomplete": "Незавършено",
|
||||
"LabelInProgress": "В Прогрес",
|
||||
"LabelInterval": "Интервал",
|
||||
"LabelIntervalCustomDailyWeekly": "Потребително дневно/седмично",
|
||||
"LabelIntervalEvery12Hours": "Всеки 12 часа",
|
||||
"LabelIntervalEvery15Minutes": "Всеки 15 минути",
|
||||
"LabelIntervalEvery2Hours": "Всеки 2 часа",
|
||||
"LabelIntervalEvery30Minutes": "Всеки 30 минути",
|
||||
"LabelIntervalEvery6Hours": "Всеки 6 часа",
|
||||
"LabelIntervalEveryDay": "Всеки ден",
|
||||
"LabelIntervalEveryHour": "Всеки час",
|
||||
"LabelInvert": "Обърни",
|
||||
"LabelItem": "Елемент",
|
||||
"LabelLanguage": "Език",
|
||||
"LabelLanguageDefaultServer": "Език по подразбиране на сървъра",
|
||||
"LabelLastBookAdded": "Последно Добавена Книга",
|
||||
"LabelLastBookUpdated": "Последно Обновена Книга",
|
||||
"LabelLastSeen": "Последно Видян",
|
||||
"LabelLastTime": "Последно Време",
|
||||
"LabelLastUpdate": "Последно Обновяване",
|
||||
"LabelLayout": "Оформление",
|
||||
"LabelLayoutSinglePage": "Една Страница",
|
||||
"LabelLayoutSplitPage": "Разделена Страница",
|
||||
"LabelLess": "По-малко",
|
||||
"LabelLibrariesAccessibleToUser": "Библиотеки Достъпни за Потребителя",
|
||||
"LabelLibrary": "Библиотека",
|
||||
"LabelLibraryItem": "Елемент на Библиотека",
|
||||
"LabelLibraryName": "Име на Библиотека",
|
||||
"LabelLimit": "Лимит",
|
||||
"LabelLineSpacing": "Линейно Разстояние",
|
||||
"LabelListenAgain": "Слушай Отново",
|
||||
"LabelLogLevelDebug": "Дебъг",
|
||||
"LabelLogLevelInfo": "Информация",
|
||||
"LabelLogLevelWarn": "Предупреждение",
|
||||
"LabelLookForNewEpisodesAfterDate": "Търси нови епизоди след дата",
|
||||
"LabelLowestPriority": "Най-нисък Приоритет",
|
||||
"LabelMatchExistingUsersBy": "Съпостави съществуващи потребители по",
|
||||
"LabelMatchExistingUsersByDescription": "Използва се за свързване на съществуващи потребители. След свързване потребителите ще бъдат съпоставени по уникален идентификатор от вашия доставчик на SSO",
|
||||
"LabelMediaPlayer": "Медия Плейър",
|
||||
"LabelMediaType": "Тип на Медията",
|
||||
"LabelMetadataOrderOfPrecedenceDescription": "По-високите източници на метаданни ще заменят по-ниските",
|
||||
"LabelMetadataProvider": "Доставчик на Метаданни",
|
||||
"LabelMetaTag": "Мета Таг",
|
||||
"LabelMetaTags": "Мета Тагове",
|
||||
"LabelMinute": "Минута",
|
||||
"LabelMissing": "Липсващо",
|
||||
"LabelMissingEbook": "Has no ebook",
|
||||
"LabelMissingSupplementaryEbook": "Has no supplementary ebook",
|
||||
"LabelMobileRedirectURIs": "Позволени URI за Мобилно Пренасочване",
|
||||
"LabelMobileRedirectURIsDescription": "Това е whitelist на валидни URI за пренасочване за мобилни приложения. По подразбиране е <code>audiobookshelf://oauth</code>, който може да премахнете или допълните с допълнителни URI за интеграция на приложения от трети страни. Използването на звезда (<code>*</code>) като единствен запис позволява всеки URI.",
|
||||
"LabelMore": "Повече",
|
||||
"LabelMoreInfo": "Повече Информация",
|
||||
"LabelName": "Име",
|
||||
"LabelNarrator": "Разказвач",
|
||||
"LabelNarrators": "Разказвачи",
|
||||
"LabelNew": "Нови",
|
||||
"LabelNewestAuthors": "Най-нови Автори",
|
||||
"LabelNewestEpisodes": "Най-нови Епизоди",
|
||||
"LabelNewPassword": "Нова Парола",
|
||||
"LabelNextBackupDate": "Следваща Дата на Архивиране",
|
||||
"LabelNextScheduledRun": "Следващо Планирано Изпълнение",
|
||||
"LabelNoEpisodesSelected": "Няма избрани епизоди",
|
||||
"LabelNotes": "Бележки",
|
||||
"LabelNotFinished": "Не е завършено",
|
||||
"LabelNotificationAppriseURL": "Apprise URL-и",
|
||||
"LabelNotificationAvailableVariables": "Налични променливи",
|
||||
"LabelNotificationBodyTemplate": "Тяло на Шаблона",
|
||||
"LabelNotificationEvent": "Събитие за Известие",
|
||||
"LabelNotificationsMaxFailedAttempts": "Максимален брой неуспешни опити за известия",
|
||||
"LabelNotificationsMaxFailedAttemptsHelp": "Известията се деактивират след като не успеят да бъдат изпратени толкова пъти",
|
||||
"LabelNotificationsMaxQueueSize": "Максимален размер на опашката за известия",
|
||||
"LabelNotificationsMaxQueueSizeHelp": "Събитията са ограничени до изстрелване на 1 на секунда. Събитията ще бъдат игнорирани ако опашката е на максимален размер. Това предотвратява спамирането на известия.",
|
||||
"LabelNotificationTitleTemplate": "Заглавие на Шаблона",
|
||||
"LabelNotStarted": "Не е започнато",
|
||||
"LabelNumberOfBooks": "Брой на Книги",
|
||||
"LabelNumberOfEpisodes": "# Епизоди",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Name of the OpenID claim that contains advanced permissions for user actions within the application which will apply to non-admin roles (<b>if configured</b>). If the claim is missing from the response, access to ABS will be denied. If a single option is missing, it will be treated as <code>false</code>. Ensure the identity provider's claim matches the expected structure:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "Name of the OpenID claim that contains a list of the user's groups. Commonly referred to as <code>groups</code>. <b>If configured</b>, the application will automatically assign roles based on the user's group memberships, provided that these groups are named case-insensitively 'admin', 'user', or 'guest' in the claim. The claim should contain a list, and if a user belongs to multiple groups, the application will assign the role corresponding to the highest level of access. If no group matches, access will be denied.",
|
||||
"LabelOpenRSSFeed": "Отвори RSS Feed",
|
||||
"LabelOverwrite": "Презапиши",
|
||||
"LabelPassword": "Парола",
|
||||
"LabelPath": "Път",
|
||||
"LabelPermissionsAccessAllLibraries": "Може да достъпи до всички библиотеки",
|
||||
"LabelPermissionsAccessAllTags": "Може да достъпи всички тагове",
|
||||
"LabelPermissionsAccessExplicitContent": "Може да достъпи експлицитно съдържание",
|
||||
"LabelPermissionsDelete": "Може да трие",
|
||||
"LabelPermissionsDownload": "Може да сваля",
|
||||
"LabelPermissionsUpdate": "Може да обновява",
|
||||
"LabelPermissionsUpload": "Може да качва",
|
||||
"LabelPersonalYearReview": "Your Year in Review ({0})",
|
||||
"LabelPhotoPathURL": "Път/URL на Снимка",
|
||||
"LabelPlaylists": "Плейлисти",
|
||||
"LabelPlayMethod": "Метод на Пускане",
|
||||
"LabelPodcast": "Подкаст",
|
||||
"LabelPodcasts": "Подкасти",
|
||||
"LabelPodcastSearchRegion": "Регион за Търсене на Подкасти",
|
||||
"LabelPodcastType": "Тип на Подкаст",
|
||||
"LabelPort": "Порт",
|
||||
"LabelPrefixesToIgnore": "Префикси за Игнориране (без значение за главни/малки букви)",
|
||||
"LabelPreventIndexing": "Предотврати индексирането на вашия feed от iTunes и Google podcast директории",
|
||||
"LabelPrimaryEbook": "Основна Електронна Книга",
|
||||
"LabelProgress": "Прогрес",
|
||||
"LabelProvider": "Доставчик",
|
||||
"LabelPubDate": "Дата на Издаване",
|
||||
"LabelPublisher": "Издател",
|
||||
"LabelPublishYear": "Година на Издаване",
|
||||
"LabelRead": "Прочети",
|
||||
"LabelReadAgain": "Прочети Отново",
|
||||
"LabelReadEbookWithoutProgress": "Прочети електронната книга без записване прогрес",
|
||||
"LabelRecentlyAdded": "Наскоро Добавени",
|
||||
"LabelRecentSeries": "Скорошни Серии",
|
||||
"LabelRecommended": "Препоръчано",
|
||||
"LabelRedo": "Повтори",
|
||||
"LabelRegion": "Регион",
|
||||
"LabelReleaseDate": "Дата на Издаване",
|
||||
"LabelRemoveCover": "Премахни Корица",
|
||||
"LabelRowsPerPage": "Редове на Страница",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Потребителски собственик Email",
|
||||
"LabelRSSFeedCustomOwnerName": "Потребителски собственик Име",
|
||||
"LabelRSSFeedOpen": "RSS Feed Оптворен",
|
||||
"LabelRSSFeedPreventIndexing": "Предотврати индексиране",
|
||||
"LabelRSSFeedSlug": "RSS Feed слъг",
|
||||
"LabelRSSFeedURL": "RSS Feed URL",
|
||||
"LabelSearchTerm": "Търси Термин",
|
||||
"LabelSearchTitle": "Търси Заглавие",
|
||||
"LabelSearchTitleOrASIN": "Търси Заглавие или ASIN",
|
||||
"LabelSeason": "Сезон",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Избери всички епизоди",
|
||||
"LabelSelectEpisodesShowing": "Избери {0} епизоди показани",
|
||||
"LabelSelectUsers": "Избери Потребители",
|
||||
"LabelSendEbookToDevice": "Изпрати електронна книга до ...",
|
||||
"LabelSequence": "Последователност",
|
||||
"LabelSeries": "Серия",
|
||||
"LabelSeriesName": "Име на Серия",
|
||||
"LabelSeriesProgress": "Прогрес на Серия",
|
||||
"LabelServerYearReview": "Server Year in Review ({0})",
|
||||
"LabelSetEbookAsPrimary": "Задай като основна",
|
||||
"LabelSetEbookAsSupplementary": "Задай като допълнителна",
|
||||
"LabelSettingsAudiobooksOnly": "Само аудиокниги",
|
||||
"LabelSettingsAudiobooksOnlyHelp": "Активирането на тази настройка ще игнорира файловете на електронни книги, освен ако не са в папка с аудиокниги, в което случай ще бъдат зададени като допълнителни електронни книги",
|
||||
"LabelSettingsBookshelfViewHelp": "Скеуморфен дизайн с дървени рафтове",
|
||||
"LabelSettingsChromecastSupport": "Chromecast поддръжка",
|
||||
"LabelSettingsDateFormat": "Формат на Дата",
|
||||
"LabelSettingsDisableWatcher": "Изключи наблюдателя",
|
||||
"LabelSettingsDisableWatcherForLibrary": "Изключи наблюдателя за библиотека",
|
||||
"LabelSettingsDisableWatcherHelp": "Изключва автоматичното добавяне/обновяване на елементи, когато се открият промени във файловете. *Изисква рестарт на сървъра",
|
||||
"LabelSettingsEnableWatcher": "Включи наблюдателя",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Включи наблюдателя за библиотека",
|
||||
"LabelSettingsEnableWatcherHelp": "Включва автоматичното добавяне/обновяване на елементи, когато се открият промени във файловете. *Изисква рестарт на сървъра",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Експериментални Функции",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Функции в разработка, които могат да изискват вашето мнение и помощ за тестване. Кликнете за да отворите дискусия в github.",
|
||||
"LabelSettingsFindCovers": "Намери Корици",
|
||||
"LabelSettingsFindCoversHelp": "Ако аудиокнигата ви няма вградена корица или изображение на корицата в папката, скенерът ще опита да намери корица.<br>Забележка: Това ще удължи времето за сканиране",
|
||||
"LabelSettingsHideSingleBookSeries": "Скрий серии с една книга",
|
||||
"LabelSettingsHideSingleBookSeriesHelp": "Сериите с една книга ще бъдат скрити от страницата на серията и рафтовете на началната страница.",
|
||||
"LabelSettingsHomePageBookshelfView": "Начална страница изглед на рафт",
|
||||
"LabelSettingsLibraryBookshelfView": "Библиотека изглед на рафт",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Skip earlier books in Continue Series",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "The Continue Series home page shelf shows the first book not started in series that have at least one book finished and no books in progress. Enabling this setting will continue series from the furthest completed book instead of the first book not started.",
|
||||
"LabelSettingsParseSubtitles": "Извлечи подзаглавия",
|
||||
"LabelSettingsParseSubtitlesHelp": "Извлича подзаглавия от имената на папките на аудиокнигите.<br>Подзаглавията трябва да бъдат разделени с \" - \"<br>например \"Заглавие на Книга - Тук е Подзаглавито\" има подзаглавие \"Тук е Подзаглавито\"",
|
||||
"LabelSettingsPreferMatchedMetadata": "Предпочети съвпадащи метаданни",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Съвпадащите данни ще заменят детайлите на елемента при използване на Бързо Съпоставяне. По подразбиране Бързото Съпоставяне ще попълни само липсващите детайли.",
|
||||
"LabelSettingsSkipMatchingBooksWithASIN": "Пропусни съвпадащи книги, които вече имат ASIN",
|
||||
"LabelSettingsSkipMatchingBooksWithISBN": "Пропусни съвпадащи книги, които вече имат ISBN",
|
||||
"LabelSettingsSortingIgnorePrefixes": "Игнорирай Префикси при Сортиране",
|
||||
"LabelSettingsSortingIgnorePrefixesHelp": "например за префикс \"the\" заглавието на книгата \"The Book Title\" ще се сортира като \"Book Title, The\"",
|
||||
"LabelSettingsSquareBookCovers": "Квадратни Корици на Книги",
|
||||
"LabelSettingsSquareBookCoversHelp": "Предпочитайте квадратни корици пред стандартни 1.6:1 корици на книги",
|
||||
"LabelSettingsStoreCoversWithItem": "Запази кориците с елемента",
|
||||
"LabelSettingsStoreCoversWithItemHelp": "По подразбиране кориците се съхраняват в /metadata/items, като активирате тази настройка кориците ще се съхраняват в папката на елемента на вашата библиотека. Само един файл с име \"cover\" ще бъде запазен",
|
||||
"LabelSettingsStoreMetadataWithItem": "Запази метаданните с елемента",
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "По подразбиране метаданните се съхраняват в /metadata/items, като активирате тази настройка метаданните ще се съхраняват в папката на елемента на вашата библиотека",
|
||||
"LabelSettingsTimeFormat": "Формат на Време",
|
||||
"LabelShowAll": "Покажи Всички",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "Размер",
|
||||
"LabelSleepTimer": "Таймер за Сън",
|
||||
"LabelSlug": "Слъг",
|
||||
"LabelStart": "Старт",
|
||||
"LabelStarted": "Стартирано",
|
||||
"LabelStartedAt": "Стартирано на",
|
||||
"LabelStartTime": "Начално Време",
|
||||
"LabelStatsAudioTracks": "Аудио Канали",
|
||||
"LabelStatsAuthors": "Автори",
|
||||
"LabelStatsBestDay": "Най-добър Ден",
|
||||
"LabelStatsDailyAverage": "Дневна Средна Стойност",
|
||||
"LabelStatsDays": "Дни",
|
||||
"LabelStatsDaysListened": "Дни Слушани",
|
||||
"LabelStatsHours": "Часове",
|
||||
"LabelStatsInARow": "подред",
|
||||
"LabelStatsItemsFinished": "Завършени Елементи",
|
||||
"LabelStatsItemsInLibrary": "Елементи в Библиотеката",
|
||||
"LabelStatsMinutes": "минути",
|
||||
"LabelStatsMinutesListening": "Минути Слушани",
|
||||
"LabelStatsOverallDays": "Общо Дни",
|
||||
"LabelStatsOverallHours": "Общо Часове",
|
||||
"LabelStatsWeekListening": "Седмица Слушане",
|
||||
"LabelSubtitle": "Подзаглавие",
|
||||
"LabelSupportedFileTypes": "Поддържани Типове Файлове",
|
||||
"LabelTag": "Таг",
|
||||
"LabelTags": "Тагове",
|
||||
"LabelTagsAccessibleToUser": "Тагове Достъпни за Потребителя",
|
||||
"LabelTagsNotAccessibleToUser": "Тагове Недостъпни за Потребителя",
|
||||
"LabelTasks": "Вървящи Задачи",
|
||||
"LabelTextEditorBulletedList": "Лист с точки",
|
||||
"LabelTextEditorLink": "Свържи",
|
||||
"LabelTextEditorNumberedList": "Лист с номера",
|
||||
"LabelTextEditorUnlink": "Развържи",
|
||||
"LabelTheme": "Тема",
|
||||
"LabelThemeDark": "Тъмна",
|
||||
"LabelThemeLight": "Светла",
|
||||
"LabelTimeBase": "Времева Основа",
|
||||
"LabelTimeListened": "Време Слушано",
|
||||
"LabelTimeListenedToday": "Време Слушано Днес",
|
||||
"LabelTimeRemaining": "{0} оставащо време",
|
||||
"LabelTimeToShift": "Време за изместване в секунди",
|
||||
"LabelTitle": "Заглавие",
|
||||
"LabelToolsEmbedMetadata": "Вграждане на Метаданни",
|
||||
"LabelToolsEmbedMetadataDescription": "Вграждане на метаданни в аудио файлове, включително корица и глави.",
|
||||
"LabelToolsMakeM4b": "Направи M4B Аудиокнига Файл",
|
||||
"LabelToolsMakeM4bDescription": "Генериране на .M4B аудиокнига файл с вградени метаданни, корица и глави.",
|
||||
"LabelToolsSplitM4b": "Раздели M4B на MP3-ки",
|
||||
"LabelToolsSplitM4bDescription": "Създай MP3-ки от M4B разделени по глави с вградени метаданни, корица и глави.",
|
||||
"LabelTotalDuration": "Обща Продължителност",
|
||||
"LabelTotalTimeListened": "Общо Време Слушано",
|
||||
"LabelTrackFromFilename": "Канал от Име на Файл",
|
||||
"LabelTrackFromMetadata": "Канал от Метаданни",
|
||||
"LabelTracks": "Канали",
|
||||
"LabelTracksMultiTrack": "Многоканален",
|
||||
"LabelTracksNone": "Няма канали",
|
||||
"LabelTracksSingleTrack": "Единичен канал",
|
||||
"LabelType": "Тип",
|
||||
"LabelUnabridged": "Несъкратен",
|
||||
"LabelUndo": "Отмени",
|
||||
"LabelUnknown": "Неизвестно",
|
||||
"LabelUpdateCover": "Обнови Корица",
|
||||
"LabelUpdateCoverHelp": "Позволи презаписване на съществуващите корици за избраните книги, когато се намери съвпадение",
|
||||
"LabelUpdatedAt": "Обновено на",
|
||||
"LabelUpdateDetails": "Обнови Детайли",
|
||||
"LabelUpdateDetailsHelp": "Позволи презаписване на съществуващите детайли за избраните книги, когато се намери съвпадение",
|
||||
"LabelUploaderDragAndDrop": "Плъзни и Пусни Файлове или Папки",
|
||||
"LabelUploaderDropFiles": "Пусни Файлове",
|
||||
"LabelUploaderItemFetchMetadataHelp": "Автоматично вземи заглавие, автор и серия",
|
||||
"LabelUseChapterTrack": "Използвай канал за глава",
|
||||
"LabelUseFullTrack": "Използвай пълен канал",
|
||||
"LabelUser": "Потребител",
|
||||
"LabelUsername": "Потребителско Име",
|
||||
"LabelValue": "Стойност",
|
||||
"LabelVersion": "Версия",
|
||||
"LabelViewBookmarks": "Виж Отметки",
|
||||
"LabelViewChapters": "Виж Глави",
|
||||
"LabelViewQueue": "Виж Опашка",
|
||||
"LabelVolume": "Сила на Звука",
|
||||
"LabelWeekdaysToRun": "Делници за изпълнение",
|
||||
"LabelYearReviewHide": "Hide Year in Review",
|
||||
"LabelYearReviewShow": "See Year in Review",
|
||||
"LabelYourAudiobookDuration": "Продължителност на вашата аудиокнига",
|
||||
"LabelYourBookmarks": "Вашите Отметки",
|
||||
"LabelYourPlaylists": "Вашите Плейлисти",
|
||||
"LabelYourProgress": "Вашият Прогрес",
|
||||
"MessageAddToPlayerQueue": "Добави към опашката на плейъра",
|
||||
"MessageAppriseDescription": "За да ползвате тази функция трябва да имате активна инстанция на <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или на друго АПИ което да обработва тези заявки. <br />The Apprise API Url-а трябва дае пълния URL път за изпращане на известията, например, ако вашето АПИ ве подава от <code>http://192.168.1.1:8337</code> трябва да сложитев <code>http://192.168.1.1:8337/notify</code>.",
|
||||
"MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in <code>/metadata/items</code> & <code>/metadata/authors</code>. Backups <strong>do not</strong> include any files stored in your library folders.",
|
||||
"MessageBatchQuickMatchDescription": "Бързото Съпоставяне ще опита да добави липсващи корици и метаданни за избраните елементи. Активирайте опциите по-долу, за да позволите на Бързото съпоставяне да презапише съществуващите корици и/или метаданни.",
|
||||
"MessageBookshelfNoCollections": "Все още нямате създадени колекции",
|
||||
"MessageBookshelfNoResultsForFilter": "Няма резултат за филтер \"{0}: {1}\"",
|
||||
"MessageBookshelfNoRSSFeeds": "Няма отворени RSS feed-ове",
|
||||
"MessageBookshelfNoSeries": "Нямаш сеЗЙ",
|
||||
"MessageChapterEndIsAfter": "Краят на главата е след края на вашата аудиокнига",
|
||||
"MessageChapterErrorFirstNotZero": "Първата глава трябва да започва от 0",
|
||||
"MessageChapterErrorStartGteDuration": "Началото на главата трябва да бъде по-малко от продължителността на аудиокнигата",
|
||||
"MessageChapterErrorStartLtPrev": "Началото на главата трябва да бъде по-голямо или равно на края на предишната глава",
|
||||
"MessageChapterStartIsAfter": "Началото на главата е след края на вашата аудиокнига",
|
||||
"MessageCheckingCron": "Проверяване на cron...",
|
||||
"MessageConfirmCloseFeed": "Сигурни ли сте, че искате да затворите този feed?",
|
||||
"MessageConfirmDeleteBackup": "Сигурни ли сте, че искате да изтриете този архив {0}?",
|
||||
"MessageConfirmDeleteFile": "Това ще изтрие файла от файловата Ви система. Сигурни ли сте?",
|
||||
"MessageConfirmDeleteLibrary": "Сигурни ли сте, че искате да изтриете за винаги библиотека \"{0}\"?",
|
||||
"MessageConfirmDeleteLibraryItem": "Това ще изтрие елемента от базата данни и файловата Ви система. Сигурни ли сте?",
|
||||
"MessageConfirmDeleteLibraryItems": "Това ще изтрие {0} елемента от базата данни и файловата Ви система. Сигурни ли сте?",
|
||||
"MessageConfirmDeleteSession": "Сигурни ли сте, че искате да изтриете тази сесия?",
|
||||
"MessageConfirmForceReScan": "Сигурни ли сте, че искате да принудите повторно сканиране?",
|
||||
"MessageConfirmMarkAllEpisodesFinished": "Сигурни ли сте, че искате да маркирате всички епизоди като завършени?",
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Сигурни ли сте, че искате да маркирате всички епизоди като незавършени?",
|
||||
"MessageConfirmMarkSeriesFinished": "Сигурни ли сте, че искате да маркирате всички книги в тази серия като завършени?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Сигурни ли сте, че искате да маркирате всички книги в тази серия като незавършени?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "Внимание! Бързото вграждане няма да архивира вашите аудио файлове. Уверете се, че имате резервно копие на вашите аудио файлове. <br><br>Искате ли да продължите?",
|
||||
"MessageConfirmRemoveAllChapters": "Сигурни ли сте, че искате да премахнете всички глави?",
|
||||
"MessageConfirmRemoveAuthor": "Сигурни ли сте, че искате да премахнете автор \"{0}\"?",
|
||||
"MessageConfirmRemoveCollection": "Сигурни ли сте, че искате да премахнете колекция \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisode": "Сигурни ли сте, че искате да премахнете епизод \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisodes": "Сигурни ли сте, че искате да премахнете {0} епизода?",
|
||||
"MessageConfirmRemoveListeningSessions": "Сигурни ли сте, че искате да премахнете {0} слушателски сесии?",
|
||||
"MessageConfirmRemoveNarrator": "Сигурни ли сте, че искате да премахнете разказвач \"{0}\"?",
|
||||
"MessageConfirmRemovePlaylist": "Сигурни ли сте, че искате да премахнете плейлиста \"{0}\"?",
|
||||
"MessageConfirmRenameGenre": "Сигурни ли сте, че искате да преименувате жанра \"{0}\" на \"{1}\" за всички елементи?",
|
||||
"MessageConfirmRenameGenreMergeNote": "Забележка: Този жанр вече съществува и ще бъде слято.",
|
||||
"MessageConfirmRenameGenreWarning": "Внимание! Вече съществува подобен жанр с различно писане \"{0}\".",
|
||||
"MessageConfirmRenameTag": "Сигурни ли сте, че искате да преименувате таг \"{0}\" на \"{1}\" за всички елементи?",
|
||||
"MessageConfirmRenameTagMergeNote": "Забележка: Този таг вече съществува и ще бъде слято.",
|
||||
"MessageConfirmRenameTagWarning": "Внимание! Вече съществува подобен таг с различно писане \"{0}\".",
|
||||
"MessageConfirmReScanLibraryItems": "Сигурни ли сте, че искате да сканирате отново {0} елемента?",
|
||||
"MessageConfirmSendEbookToDevice": "Сигурни ли сте, че искате да изпратите {0} електронна книга \"{1}\" до устройство \"{2}\"?",
|
||||
"MessageDownloadingEpisode": "Изтегляне на епизод",
|
||||
"MessageDragFilesIntoTrackOrder": "Плъзнете файлове в правилния ред на каналите",
|
||||
"MessageEmbedFinished": "Вграждането завърши!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} епизод(и) в опашка за изтегляне",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "Feed URL-a ще бъде {0}",
|
||||
"MessageFetching": "Взимане...",
|
||||
"MessageForceReScanDescription": "ще сканира всички файлове отново като прясно сканиране. Аудио файлове ID3 тагове, OPF файлове и текстови файлове ще бъдат сканирани като нови.",
|
||||
"MessageImportantNotice": "Важно Съобщение!",
|
||||
"MessageInsertChapterBelow": "Вмъкни глава под",
|
||||
"MessageItemsSelected": "{0} избрани",
|
||||
"MessageItemsUpdated": "{0} елемента обновени",
|
||||
"MessageJoinUsOn": "Присъединете се към нас",
|
||||
"MessageListeningSessionsInTheLastYear": "{0} слушателски сесии през последната година",
|
||||
"MessageLoading": "Зареждане...",
|
||||
"MessageLoadingFolders": "Зареждане на Папки...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B Провалено!",
|
||||
"MessageM4BFinished": "M4B Завършено!",
|
||||
"MessageMapChapterTitles": "Съпостави заглавията на главите със съществуващите глави на аудиокнигата без да променяш времената",
|
||||
"MessageMarkAllEpisodesFinished": "Маркирай всички епизоди като завършени",
|
||||
"MessageMarkAllEpisodesNotFinished": "Маркирай всички епизоди като незавършени",
|
||||
"MessageMarkAsFinished": "Маркирай като Завършено",
|
||||
"MessageMarkAsNotFinished": "Маркирай като Незавършено",
|
||||
"MessageMatchBooksDescription": "ще се опита да съпостави книги в библиотеката с книга от избрания доставчик за търсене и ще попълни празни детайли и корици. Не презаписва детайлите.",
|
||||
"MessageNoAudioTracks": "Няма аудио канали",
|
||||
"MessageNoAuthors": "Няма Автори",
|
||||
"MessageNoBackups": "Няма архиви",
|
||||
"MessageNoBookmarks": "Няма Отметки",
|
||||
"MessageNoChapters": "Няма Глави",
|
||||
"MessageNoCollections": "Няма Колекции",
|
||||
"MessageNoCoversFound": "Не са намерени корици",
|
||||
"MessageNoDescription": "Няма описание",
|
||||
"MessageNoDownloadsInProgress": "Няма изтегляния в прогрес",
|
||||
"MessageNoDownloadsQueued": "Няма изтегляния в опашка",
|
||||
"MessageNoEpisodeMatchesFound": "Няма намерени съвпадения за епизоди",
|
||||
"MessageNoEpisodes": "Няма Епизоди",
|
||||
"MessageNoFoldersAvailable": "Няма налични папки",
|
||||
"MessageNoGenres": "Няма Жанрове",
|
||||
"MessageNoIssues": "Няма проблеми",
|
||||
"MessageNoItems": "Няма Елементи",
|
||||
"MessageNoItemsFound": "Няма намерени елементи",
|
||||
"MessageNoListeningSessions": "Няма слушателски сесии",
|
||||
"MessageNoLogs": "Няма логове",
|
||||
"MessageNoMediaProgress": "Няма прогрес на медията",
|
||||
"MessageNoNotifications": "Няма известия",
|
||||
"MessageNoPodcastsFound": "Няма намерени подкасти",
|
||||
"MessageNoResults": "Няма резултати",
|
||||
"MessageNoSearchResultsFor": "Няма резултати за \"{0}\"",
|
||||
"MessageNoSeries": "Няма Серии",
|
||||
"MessageNoTags": "Няма Тагове",
|
||||
"MessageNoTasksRunning": "Няма вършещи се задачи",
|
||||
"MessageNotYetImplemented": "Още не е изпълнено",
|
||||
"MessageNoUpdateNecessary": "Не е необходимо обновяване",
|
||||
"MessageNoUpdatesWereNecessary": "Не бяха необходими обновления",
|
||||
"MessageNoUserPlaylists": "Няма плейлисти на потребителя",
|
||||
"MessageOr": "или",
|
||||
"MessagePauseChapter": "Пауза на глава",
|
||||
"MessagePlayChapter": "Пусни налчалото на глава",
|
||||
"MessagePlaylistCreateFromCollection": "Създай плейлист от колекция",
|
||||
"MessagePodcastHasNoRSSFeedForMatching": "Подкастът няма URL адрес на RSS feed за използване за съпоставяне",
|
||||
"MessageQuickMatchDescription": "Попълни празните детайли и корици с първия резултат от '{0}'. Не презаписва детайлите, освен ако не е активирана настройката 'Предпочети съвпадащи метаданни' на сървъра.",
|
||||
"MessageRemoveChapter": "Премахни глава",
|
||||
"MessageRemoveEpisodes": "Премахни {0} епизод(и)",
|
||||
"MessageRemoveFromPlayerQueue": "Премахни от опашката на плейъра",
|
||||
"MessageRemoveUserWarning": "Сигурни ли сте, че искате да изтриете потребител \"{0}\" завинаги?",
|
||||
"MessageReportBugsAndContribute": "Съобщавайте за грешки, заявявайте функции и допринасяйте на",
|
||||
"MessageResetChaptersConfirm": "Сигурни ли сте, че искате да нулирате главите и да отмените промените, които сте направили?",
|
||||
"MessageRestoreBackupConfirm": "Сигурни ли сте, че искате да възстановите архива създаден на",
|
||||
"MessageRestoreBackupWarning": "Възстановяването на архив ще презапише цялата база данни, намираща се в /config и кориците в /metadata/items & /metadata/authors.<br /><br />Архивите не променят файловете в папките на вашата библиотека. Ако сте активирали настройките на сървъра за съхранение на корици и метаданни в папките на вашата библиотека, те няма да бъдат архивирани или презаписани.<br /><br />Всички клиенти, използващи вашия сървър, ще бъдат автоматично обновени.",
|
||||
"MessageSearchResultsFor": "Резултати от търсенето за",
|
||||
"MessageSelected": "{0} избрани",
|
||||
"MessageServerCouldNotBeReached": "Сървърът не може да бъде достигнат",
|
||||
"MessageSetChaptersFromTracksDescription": "Задайте глави, като използвате всеки аудио файл като глава и заглавие на главата като име на аудио файла",
|
||||
"MessageStartPlaybackAtTime": "Започни възпроизвеждане на \"{0}\" в {1}?",
|
||||
"MessageThinking": "Мисля...",
|
||||
"MessageUploaderItemFailed": "Неуспешно качване",
|
||||
"MessageUploaderItemSuccess": "Успешно качване!",
|
||||
"MessageUploading": "Качва се...",
|
||||
"MessageValidCronExpression": "Валиден cron expression",
|
||||
"MessageWatcherIsDisabledGlobally": "Наблюдателят е деактивиран глобално в настройките на сървъра",
|
||||
"MessageXLibraryIsEmpty": "{0} библиотеката е празна!",
|
||||
"MessageYourAudiobookDurationIsLonger": "Продължителността на вашата аудиокнига е по-дълга от намерената",
|
||||
"MessageYourAudiobookDurationIsShorter": "Продължителността на вашата аудиокнига е по-кратка от намерената",
|
||||
"NoteChangeRootPassword": "Root потребителят е единственият потребител, който може да има празна парола",
|
||||
"NoteChapterEditorTimes": "Забележка: Първото време на начало на главата трябва да остане на 0:00, а последното време на начало на главата не може да надвишава продължителността на тази аудиокнига.",
|
||||
"NoteFolderPicker": "Забележка: папките, които вече са картографирани, няма да бъдат показани",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Внимание: Повечето приложения за подкасти изискват URL адреса на RSS feed да използва HTTPS",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Внимание: 1 или повече от вашите епизоди нямат дата на публикуване. Някои приложения за подкасти изискват това",
|
||||
"NoteUploaderFoldersWithMediaFiles": "Папките с медийни файлове ще бъдат обработени като отделни елементи на библиотеката.",
|
||||
"NoteUploaderOnlyAudioFiles": "Ако качвате само аудио файлове, то всеки аудио файл ще бъде обработен като отделна аудиокнига.",
|
||||
"NoteUploaderUnsupportedFiles": "Неподдържаните файлове се игнорират. При избор или пускане на папка, други файлове, които не са в папка на елемент, се игнорират.",
|
||||
"PlaceholderNewCollection": "Ново име на колекцията",
|
||||
"PlaceholderNewFolderPath": "Нов път на папката",
|
||||
"PlaceholderNewPlaylist": "Ново име на плейлиста",
|
||||
"PlaceholderSearch": "Търсене...",
|
||||
"PlaceholderSearchEpisode": "Търсене на Епизоди...",
|
||||
"ToastAccountUpdateFailed": "Неуспешно обновяване на акаунта",
|
||||
"ToastAccountUpdateSuccess": "Успешно обновяване на акаунта",
|
||||
"ToastAuthorImageRemoveFailed": "Неуспешно премахване на авторска снимка",
|
||||
"ToastAuthorImageRemoveSuccess": "Авторската снимка е премахната",
|
||||
"ToastAuthorUpdateFailed": "Неуспешно обновяване на автора",
|
||||
"ToastAuthorUpdateMerged": "Обновяване на автора сливано",
|
||||
"ToastAuthorUpdateSuccess": "Автора обновен",
|
||||
"ToastAuthorUpdateSuccessNoImageFound": "Автор обновен (не е намерена снимка)",
|
||||
"ToastBackupCreateFailed": "Неуспешно създаване на архив",
|
||||
"ToastBackupCreateSuccess": "Архивът е създаден",
|
||||
"ToastBackupDeleteFailed": "Неуспешно изтриване на архив",
|
||||
"ToastBackupDeleteSuccess": "Архивът е изтрит",
|
||||
"ToastBackupRestoreFailed": "Неуспешно възстановяване на архив",
|
||||
"ToastBackupUploadFailed": "Неуспешно качване на архив",
|
||||
"ToastBackupUploadSuccess": "Архивът е качен",
|
||||
"ToastBatchUpdateFailed": "Batch update failed",
|
||||
"ToastBatchUpdateSuccess": "Batch update success",
|
||||
"ToastBookmarkCreateFailed": "Неуспешно създаване на отметка",
|
||||
"ToastBookmarkCreateSuccess": "Отметката е създадена",
|
||||
"ToastBookmarkRemoveFailed": "Неуспешно премахване на отметка",
|
||||
"ToastBookmarkRemoveSuccess": "Отметката е премахната",
|
||||
"ToastBookmarkUpdateFailed": "Неуспешно обновяване на отметка",
|
||||
"ToastBookmarkUpdateSuccess": "Отметката е обновена",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "Главите имат грешки",
|
||||
"ToastChaptersMustHaveTitles": "Главите трябва да имат заглавия",
|
||||
"ToastCollectionItemsRemoveFailed": "Неуспешно премахване на елемент(и) от колекция",
|
||||
"ToastCollectionItemsRemoveSuccess": "Елемент(и) премахнати от колекция",
|
||||
"ToastCollectionRemoveFailed": "Неуспешно премахване на колекция",
|
||||
"ToastCollectionRemoveSuccess": "Колекцията е премахната",
|
||||
"ToastCollectionUpdateFailed": "Неуспешно обновяване на колекция",
|
||||
"ToastCollectionUpdateSuccess": "Колекцията е обновена",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "Неуспешно обновяване на корица на елемент",
|
||||
"ToastItemCoverUpdateSuccess": "Корицата на елемента е обновена",
|
||||
"ToastItemDetailsUpdateFailed": "Неуспешно обновяване на детайли на елемент",
|
||||
"ToastItemDetailsUpdateSuccess": "Детайлите на елемента са обновени",
|
||||
"ToastItemDetailsUpdateUnneeded": "Не са необходими обновления на детайлите на елемента",
|
||||
"ToastItemMarkedAsFinishedFailed": "Неуспешно маркиране като завършено",
|
||||
"ToastItemMarkedAsFinishedSuccess": "Елементът е маркиран като завършен",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Неуспешно маркиране като незавършено",
|
||||
"ToastItemMarkedAsNotFinishedSuccess": "Елементът е маркиран като незавършен",
|
||||
"ToastLibraryCreateFailed": "Неуспешно създаване на библиотека",
|
||||
"ToastLibraryCreateSuccess": "Библиотеката \"{0}\" е създадена",
|
||||
"ToastLibraryDeleteFailed": "Неуспешно изтриване на библиотека",
|
||||
"ToastLibraryDeleteSuccess": "Библиотеката е изтрита",
|
||||
"ToastLibraryScanFailedToStart": "Неуспешно стартиране на сканиране",
|
||||
"ToastLibraryScanStarted": "Сканирането на библиотеката е стартирано",
|
||||
"ToastLibraryUpdateFailed": "Неуспешно обновяване на библиотека",
|
||||
"ToastLibraryUpdateSuccess": "Библиотеката \"{0}\" е обновена",
|
||||
"ToastPlaylistCreateFailed": "Неуспешно създаване на плейлист",
|
||||
"ToastPlaylistCreateSuccess": "Плейлистът е създаден",
|
||||
"ToastPlaylistRemoveFailed": "Неуспешно премахване на плейлист",
|
||||
"ToastPlaylistRemoveSuccess": "Плейлистът е премахнат",
|
||||
"ToastPlaylistUpdateFailed": "Неуспешно обновяване на плейлист",
|
||||
"ToastPlaylistUpdateSuccess": "Плейлистът е обновен",
|
||||
"ToastPodcastCreateFailed": "Неуспешно създаване на подкаст",
|
||||
"ToastPodcastCreateSuccess": "Подкастът е създаден",
|
||||
"ToastRemoveItemFromCollectionFailed": "Неуспешно премахване на елемент от колекция",
|
||||
"ToastRemoveItemFromCollectionSuccess": "Елементът е премахнат от колекция",
|
||||
"ToastRSSFeedCloseFailed": "Неуспешно затваряне на RSS feed",
|
||||
"ToastRSSFeedCloseSuccess": "RSS feed затворен",
|
||||
"ToastSendEbookToDeviceFailed": "Неуспешно изпращане на електронна книга до устройство",
|
||||
"ToastSendEbookToDeviceSuccess": "Електронната книга е изпратена до устройство \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "Неуспешно обновяване на серия",
|
||||
"ToastSeriesUpdateSuccess": "Серията е обновена",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "Неуспешно изтриване на сесия",
|
||||
"ToastSessionDeleteSuccess": "Сесията е изтрита",
|
||||
"ToastSocketConnected": "Свързан сокет",
|
||||
"ToastSocketDisconnected": "Сокетът е прекъснат",
|
||||
"ToastSocketFailedToConnect": "Неуспешно свързване на сокет",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "Неуспешно изтриване на потребител",
|
||||
"ToastUserDeleteSuccess": "Потребителят е изтрит"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "সারি থেকে মুছে ফেলুন",
|
||||
"ButtonQuickMatch": "দ্রুত ম্যাচ",
|
||||
"ButtonRead": "পড়ুন",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "রিফ্রেশ",
|
||||
"ButtonRemove": "মুছে ফেলুন",
|
||||
"ButtonRemoveAll": "সব মুছে ফেলুন",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "বাৎসরিক পর্যালোচনা {0}",
|
||||
"HeaderYourStats": "আপনার পরিসংখ্যান",
|
||||
"LabelAbridged": "সংক্ষিপ্ত",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "অ্যাকাউন্টের প্রকার",
|
||||
"LabelAccountTypeAdmin": "প্রশাসন",
|
||||
"LabelAccountTypeGuest": "অতিথি",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "বিটরেট",
|
||||
"LabelBooks": "বইগুলো",
|
||||
"LabelButtonText": "ঘর পাঠ্য",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "পাসওয়ার্ড পরিবর্তন করুন",
|
||||
"LabelChannels": "চ্যানেল",
|
||||
"LabelChapters": "অধ্যায়",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "ডাউনলোড করুন",
|
||||
"LabelDownloadNEpisodes": "{0}টি পর্ব ডাউনলোড করুন",
|
||||
"LabelDuration": "সময়কাল",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "সময়কাল পাওয়া গেছে:",
|
||||
"LabelEbook": "ই-বই",
|
||||
"LabelEbooks": "ই-বইগুলো",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "পর্বের ধরন",
|
||||
"LabelExample": "উদাহরণ",
|
||||
"LabelExplicit": "বিশদ",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "ফিড ইউআরএল",
|
||||
"LabelFetchingMetadata": "মেটাডেটা আনা হচ্ছে",
|
||||
"LabelFile": "ফাইল",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "অনুসন্ধান শিরোনাম",
|
||||
"LabelSearchTitleOrASIN": "অনুসন্ধান শিরোনাম বা ASIN",
|
||||
"LabelSeason": "সেশন",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "সমস্ত পর্ব নির্বাচন করুন",
|
||||
"LabelSelectEpisodesShowing": "দেখানো {0}টি পর্ব নির্বাচন করুন",
|
||||
"LabelSelectUsers": "ব্যবহারকারী নির্বাচন করুন",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "প্রহরী সক্ষম করুন",
|
||||
"LabelSettingsEnableWatcherForLibrary": "লাইব্রেরির জন্য ফোল্ডার প্রহরী সক্ষম করুন",
|
||||
"LabelSettingsEnableWatcherHelp": "ফাইলের পরিবর্তন শনাক্ত হলে আইটেমগুলির স্বয়ংক্রিয় যোগ/আপডেট সক্ষম করবে। *সার্ভার পুনরায় চালু করতে হবে",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "পরীক্ষামূলক বৈশিষ্ট্য",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "ফিচারের বৈশিষ্ট্য যা আপনার প্রতিক্রিয়া ব্যবহার করতে পারে এবং পরীক্ষায় সহায়তা করতে পারে। গিটহাব আলোচনা খুলতে ক্লিক করুন।",
|
||||
"LabelSettingsFindCovers": "কভার খুঁজুন",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "ডিফল্টরূপে মেটাডেটা ফাইলগুলি /মেটাডাটা/আইটেমগুলি -এ সংরক্ষণ করা হয়, এই সেটিংটি সক্ষম করলে মেটাডেটা ফাইলগুলি আপনার লাইব্রেরি আইটেম ফোল্ডারে সংরক্ষণ করা হবে",
|
||||
"LabelSettingsTimeFormat": "সময় বিন্যাস",
|
||||
"LabelShowAll": "সব দেখান",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "আকার",
|
||||
"LabelSleepTimer": "স্লিপ টাইমার",
|
||||
"LabelSlug": "স্লাগ",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "আপনি কি নিশ্চিত যে আপনি সমস্ত পর্বকে শেষ হয়নি বলে চিহ্নিত করতে চান?",
|
||||
"MessageConfirmMarkSeriesFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে সমাপ্ত হিসাবে চিহ্নিত করতে চান?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "আপনি কি নিশ্চিত যে আপনি এই সিরিজের সমস্ত বইকে শেষ হয়নি বলে চিহ্নিত করতে চান?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "সতর্কতা! দ্রুত এম্বেড আপনার অডিও ফাইলের ব্যাকআপ করবে না। নিশ্চিত করুন যে আপনার অডিও ফাইলগুলির একটি ব্যাকআপ আছে। <br><br>আপনি কি চালিয়ে যেতে চান?",
|
||||
"MessageConfirmRemoveAllChapters": "আপনি কি নিশ্চিত যে আপনি সমস্ত অধ্যায় সরাতে চান?",
|
||||
"MessageConfirmRemoveAuthor": "আপনি কি নিশ্চিত যে আপনি লেখক \"{0}\" অপসারণ করতে চান?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "সঠিক ট্র্যাক অর্ডারে ফাইল টেনে আনুন",
|
||||
"MessageEmbedFinished": "এম্বেড করা শেষ!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} পর্ব(গুলি) ডাউনলোডের জন্য সারিবদ্ধ",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "ফিড URL হবে {0}",
|
||||
"MessageFetching": "আনয় হচ্ছে...",
|
||||
"MessageForceReScanDescription": "সকল ফাইল আবার নতুন স্ক্যানের মত স্ক্যান করবে। অডিও ফাইল ID3 ট্যাগ, OPF ফাইল, এবং টেক্সট ফাইলগুলি নতুন হিসাবে স্ক্যান করা হবে।",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "গত বছরে {0}টি শোনার সেশন",
|
||||
"MessageLoading": "লোড হচ্ছে...",
|
||||
"MessageLoadingFolders": "ফোল্ডার লোড হচ্ছে...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B ব্যর্থ!",
|
||||
"MessageM4BFinished": "M4B সমাপ্ত!",
|
||||
"MessageMapChapterTitles": "টাইমস্ট্যাম্প সামঞ্জস্য না করে আপনার বিদ্যমান অডিওবুক অধ্যায়গুলিতে অধ্যায়ের শিরোনাম ম্যাপ করুন",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "বুকমার্ক সরানো হয়েছে",
|
||||
"ToastBookmarkUpdateFailed": "বুকমার্ক আপডেট করতে ব্যর্থ",
|
||||
"ToastBookmarkUpdateSuccess": "বুকমার্ক আপডেট করা হয়েছে",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "অধ্যায়ে ত্রুটি আছে",
|
||||
"ToastChaptersMustHaveTitles": "অধ্যায়ের শিরোনাম থাকতে হবে",
|
||||
"ToastCollectionItemsRemoveFailed": "সংগ্রহ থেকে আইটেম(গুলি) সরাতে ব্যর্থ",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "সংগ্রহ সরানো হয়েছে",
|
||||
"ToastCollectionUpdateFailed": "সংগ্রহ আপডেট করতে ব্যর্থ",
|
||||
"ToastCollectionUpdateSuccess": "সংগ্রহ আপডেট করা হয়েছে",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "আইটেম কভার আপডেট করতে ব্যর্থ হয়েছে",
|
||||
"ToastItemCoverUpdateSuccess": "আইটেম কভার আপডেট করা হয়েছে",
|
||||
"ToastItemDetailsUpdateFailed": "আইটেমের বিবরণ আপডেট করতে ব্যর্থ",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "ইবুক \"{0}\" ডিভাইসে পাঠানো হয়েছে",
|
||||
"ToastSeriesUpdateFailed": "সিরিজ আপডেট ব্যর্থ হয়েছে",
|
||||
"ToastSeriesUpdateSuccess": "সিরিজ আপডেট সাফল্য",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "সেশন মুছে ফেলতে ব্যর্থ",
|
||||
"ToastSessionDeleteSuccess": "সেশন মুছে ফেলা হয়েছে",
|
||||
"ToastSocketConnected": "সকেট সংযুক্ত",
|
||||
"ToastSocketDisconnected": "সকেট সংযোগ বিচ্ছিন্ন",
|
||||
"ToastSocketFailedToConnect": "সকেট সংযোগ করতে ব্যর্থ হয়েছে",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "ব্যবহারকারী মুছতে ব্যর্থ",
|
||||
"ToastUserDeleteSuccess": "ব্যবহারকারী মুছে ফেলা হয়েছে"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "Odstranit z fronty",
|
||||
"ButtonQuickMatch": "Rychlé přiřazení",
|
||||
"ButtonRead": "Číst",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonRemove": "Odstranit",
|
||||
"ButtonRemoveAll": "Odstranit vše",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Vaše statistiky",
|
||||
"LabelAbridged": "Zkráceno",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Typ účtu",
|
||||
"LabelAccountTypeAdmin": "Správce",
|
||||
"LabelAccountTypeGuest": "Host",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Datový tok",
|
||||
"LabelBooks": "Knihy",
|
||||
"LabelButtonText": "Button Text",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Změnit heslo",
|
||||
"LabelChannels": "Kanály",
|
||||
"LabelChapters": "Kapitoly",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Stáhnout",
|
||||
"LabelDownloadNEpisodes": "Stáhnout {0} epizody",
|
||||
"LabelDuration": "Doba trvání",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "Doba trvání nalezena:",
|
||||
"LabelEbook": "Elektronická kniha",
|
||||
"LabelEbooks": "Elektronické knihy",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Typ epizody",
|
||||
"LabelExample": "Příklad",
|
||||
"LabelExplicit": "Explicitní",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "URL zdroje",
|
||||
"LabelFetchingMetadata": "Fetching Metadata",
|
||||
"LabelFile": "Soubor",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Vyhledat název",
|
||||
"LabelSearchTitleOrASIN": "Vyhledat název nebo ASIN",
|
||||
"LabelSeason": "Sezóna",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Vybrat všechny epizody",
|
||||
"LabelSelectEpisodesShowing": "Vyberte {0} epizody, které se zobrazují",
|
||||
"LabelSelectUsers": "Vybrat uživatele",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Povolit sledování",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Povolit sledování složky pro knihovnu",
|
||||
"LabelSettingsEnableWatcherHelp": "Povoluje automatické přidávání/aktualizaci položek, když jsou zjištěny změny souborů. *Vyžaduje restart serveru",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Experimentální funkce",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Funkce ve vývoji, které by mohly využít vaši zpětnou vazbu a pomoc s testováním. Kliknutím otevřete diskuzi na githubu.",
|
||||
"LabelSettingsFindCovers": "Najít obálky",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Ve výchozím nastavení jsou soubory metadat uloženy v adresáři /metadata/items, povolením tohoto nastavení budou soubory metadat uloženy ve složkách položek knihovny",
|
||||
"LabelSettingsTimeFormat": "Formát času",
|
||||
"LabelShowAll": "Zobrazit vše",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "Velikost",
|
||||
"LabelSleepTimer": "Časovač vypnutí",
|
||||
"LabelSlug": "Slug",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Opravdu chcete označit všechny epizody 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": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "Varování! Rychlé vložení nezálohuje vaše zvukové soubory. Ujistěte se, že máte zálohu zvukových souborů. <br><br>Chcete pokračovat?",
|
||||
"MessageConfirmRemoveAllChapters": "Opravdu chcete odstranit všechny kapitoly?",
|
||||
"MessageConfirmRemoveAuthor": "Opravdu chcete odstranit autora \"{0}\"?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Přetáhněte soubory do správného pořadí stop",
|
||||
"MessageEmbedFinished": "Vložení dokončeno!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} epizody zařazené do fronty ke stažení",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "URL zdroje bude {0}",
|
||||
"MessageFetching": "Stahová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é.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} poslechových relací za poslední rok",
|
||||
"MessageLoading": "Načítá se...",
|
||||
"MessageLoadingFolders": "Načítám složky...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B se nezdařil!",
|
||||
"MessageM4BFinished": "M4B dokončen!",
|
||||
"MessageMapChapterTitles": "Mapování názvů kapitol ke stávajícím kapitolám audioknihy bez úpravy časových razítek",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Záložka odstraněna",
|
||||
"ToastBookmarkUpdateFailed": "Aktualizace záložky se nezdařila",
|
||||
"ToastBookmarkUpdateSuccess": "Záložka aktualizována",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "Kapitoly obsahují chyby",
|
||||
"ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy",
|
||||
"ToastCollectionItemsRemoveFailed": "Nepodařilo se odstranit položky z kolekce",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Kolekce odstraněna",
|
||||
"ToastCollectionUpdateFailed": "Aktualizace kolekce se nezdařila",
|
||||
"ToastCollectionUpdateSuccess": "Kolekce aktualizována",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "Aktualizace obálky se nezdařila",
|
||||
"ToastItemCoverUpdateSuccess": "Obálka předmětu byl aktualizována",
|
||||
"ToastItemDetailsUpdateFailed": "Nepodařilo se aktualizovat podrobnosti o položce",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "E-kniha odeslána do zařízení \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "Aktualizace série se nezdařila",
|
||||
"ToastSeriesUpdateSuccess": "Aktualizace série byla úspěšná",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "Nepodařilo se smazat relaci",
|
||||
"ToastSessionDeleteSuccess": "Relace smazána",
|
||||
"ToastSocketConnected": "Socket připojen",
|
||||
"ToastSocketDisconnected": "Socket odpojen",
|
||||
"ToastSocketFailedToConnect": "Socket se nepodařilo připojit",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "Nepodařilo se smazat uživatele",
|
||||
"ToastUserDeleteSuccess": "Uživatel smazán"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "Fjern fra kø",
|
||||
"ButtonQuickMatch": "Hurtig Match",
|
||||
"ButtonRead": "Læs",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonRemove": "Fjern",
|
||||
"ButtonRemoveAll": "Fjern Alle",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Dine Statistikker",
|
||||
"LabelAbridged": "Abridged",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Kontotype",
|
||||
"LabelAccountTypeAdmin": "Administrator",
|
||||
"LabelAccountTypeGuest": "Gæst",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bitrate",
|
||||
"LabelBooks": "Bøger",
|
||||
"LabelButtonText": "Button Text",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Ændre Adgangskode",
|
||||
"LabelChannels": "Kanaler",
|
||||
"LabelChapters": "Kapitler",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Download",
|
||||
"LabelDownloadNEpisodes": "Download {0} episoder",
|
||||
"LabelDuration": "Varighed",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "Fundet varighed:",
|
||||
"LabelEbook": "E-bog",
|
||||
"LabelEbooks": "E-bøger",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Episodetype",
|
||||
"LabelExample": "Eksempel",
|
||||
"LabelExplicit": "Eksplisit",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFetchingMetadata": "Fetching Metadata",
|
||||
"LabelFile": "Fil",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Søg efter titel",
|
||||
"LabelSearchTitleOrASIN": "Søg efter titel eller ASIN",
|
||||
"LabelSeason": "Sæson",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Vælg alle episoder",
|
||||
"LabelSelectEpisodesShowing": "Vælg {0} episoder vist",
|
||||
"LabelSelectUsers": "Select users",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Aktiver overvågning",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Aktiver mappeovervågning for bibliotek",
|
||||
"LabelSettingsEnableWatcherHelp": "Aktiverer automatisk tilføjelse/opdatering af elementer, når filændringer registreres. *Kræver servergenstart",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Eksperimentelle funktioner",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Funktioner under udvikling, der kunne bruge din feedback og hjælp til test. Klik for at åbne Github-diskussionen.",
|
||||
"LabelSettingsFindCovers": "Find omslag",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Som standard gemmes metadatafiler i /metadata/items, aktivering af denne indstilling vil gemme metadatafiler i dine bibliotekselementmapper",
|
||||
"LabelSettingsTimeFormat": "Tidsformat",
|
||||
"LabelShowAll": "Vis alle",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "Størrelse",
|
||||
"LabelSleepTimer": "Søvntimer",
|
||||
"LabelSlug": "Slug",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Er du sikker på, at du vil markere alle episoder som ikke afsluttet?",
|
||||
"MessageConfirmMarkSeriesFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som afsluttet?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som ikke afsluttet?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
|
||||
"MessageConfirmRemoveAllChapters": "Er du sikker på, at du vil fjerne alle kapitler?",
|
||||
"MessageConfirmRemoveAuthor": "Er du sikker på, at du vil fjerne forfatteren \"{0}\"?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Træk filer ind i korrekt spororden",
|
||||
"MessageEmbedFinished": "Indlejring færdig!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} episoder er sat i kø til download",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "Feed-URL vil være {0}",
|
||||
"MessageFetching": "Henter...",
|
||||
"MessageForceReScanDescription": "vil scanne alle filer igen som en frisk scanning. Lydfilens ID3-tags, OPF-filer og tekstfiler scannes som nye.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} lyttesessioner i det sidste år",
|
||||
"MessageLoading": "Indlæser...",
|
||||
"MessageLoadingFolders": "Indlæser mapper...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B mislykkedes!",
|
||||
"MessageM4BFinished": "M4B afsluttet!",
|
||||
"MessageMapChapterTitles": "Tilknyt kapiteloverskrifter til dine eksisterende lydbogskapitler uden at justere tidsstempler",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Bogmærke fjernet",
|
||||
"ToastBookmarkUpdateFailed": "Mislykkedes opdatering af bogmærke",
|
||||
"ToastBookmarkUpdateSuccess": "Bogmærke opdateret",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "Kapitler har fejl",
|
||||
"ToastChaptersMustHaveTitles": "Kapitler skal have titler",
|
||||
"ToastCollectionItemsRemoveFailed": "Mislykkedes fjernelse af element(er) fra samlingen",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Samling fjernet",
|
||||
"ToastCollectionUpdateFailed": "Mislykkedes opdatering af samling",
|
||||
"ToastCollectionUpdateSuccess": "Samling opdateret",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "Mislykkedes opdatering af varens omslag",
|
||||
"ToastItemCoverUpdateSuccess": "Varens omslag opdateret",
|
||||
"ToastItemDetailsUpdateFailed": "Mislykkedes opdatering af varedetaljer",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "E-bog afsendt til enhed \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "Mislykkedes opdatering af serie",
|
||||
"ToastSeriesUpdateSuccess": "Serieopdatering lykkedes",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "Mislykkedes sletning af session",
|
||||
"ToastSessionDeleteSuccess": "Session slettet",
|
||||
"ToastSocketConnected": "Socket forbundet",
|
||||
"ToastSocketDisconnected": "Socket afbrudt",
|
||||
"ToastSocketFailedToConnect": "Socket kunne ikke oprettes",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "Mislykkedes sletning af bruger",
|
||||
"ToastUserDeleteSuccess": "Bruger slettet"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "Aus der Warteschlange entfernen",
|
||||
"ButtonQuickMatch": "Schnellabgleich",
|
||||
"ButtonRead": "Lesen",
|
||||
"ButtonReadLess": "Weniger anzeigen",
|
||||
"ButtonReadMore": "Mehr anzeigen",
|
||||
"ButtonRefresh": "Neu Laden",
|
||||
"ButtonRemove": "Löschen",
|
||||
"ButtonRemoveAll": "Alles löschen",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Jahr {0} in Übersicht",
|
||||
"HeaderYourStats": "Eigene Statistiken",
|
||||
"LabelAbridged": "Gekürzt",
|
||||
"LabelAbridgedChecked": "Gekürzt (angehakt)",
|
||||
"LabelAbridgedUnchecked": "Ungekürzt (nicht angehakt)",
|
||||
"LabelAccessibleBy": "Zugänglich für",
|
||||
"LabelAccountType": "Kontoart",
|
||||
"LabelAccountTypeAdmin": "Admin",
|
||||
"LabelAccountTypeGuest": "Gast",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bitrate",
|
||||
"LabelBooks": "Bücher",
|
||||
"LabelButtonText": "Button Text",
|
||||
"LabelByAuthor": "von {0}",
|
||||
"LabelChangePassword": "Passwort ändern",
|
||||
"LabelChannels": "Kanäle",
|
||||
"LabelChapters": "Kapitel",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Herunterladen",
|
||||
"LabelDownloadNEpisodes": "Download {0} Episoden",
|
||||
"LabelDuration": "Laufzeit",
|
||||
"LabelDurationComparisonExactMatch": "(genauer Treffer)",
|
||||
"LabelDurationComparisonLonger": "({0} länger)",
|
||||
"LabelDurationComparisonShorter": "({0} kürzer)",
|
||||
"LabelDurationFound": "Gefundene Laufzeit:",
|
||||
"LabelEbook": "E-Book",
|
||||
"LabelEbooks": "E-Books",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Episodentyp",
|
||||
"LabelExample": "Beispiel",
|
||||
"LabelExplicit": "Explizit (Altersbeschränkung)",
|
||||
"LabelExplicitChecked": "Explicit (Altersbeschränkung) (angehakt)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (Altersbeschränkung) (nicht angehakt)",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFetchingMetadata": "Abholen der Metadaten",
|
||||
"LabelFile": "Datei",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Titel suchen",
|
||||
"LabelSearchTitleOrASIN": "Titel oder ASIN suchen",
|
||||
"LabelSeason": "Staffel",
|
||||
"LabelSelectAll": "Alles auswählen",
|
||||
"LabelSelectAllEpisodes": "Alle Episoden auswählen",
|
||||
"LabelSelectEpisodesShowing": "{0} ausgewählte Episoden werden angezeigt",
|
||||
"LabelSelectUsers": "Benutzer auswählen",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Überwachung aktivieren",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Ordnerüberwachung für die Bibliothek aktivieren",
|
||||
"LabelSettingsEnableWatcherHelp": "Aktiviert das automatische Hinzufügen/Aktualisieren von Elementen, wenn Dateiänderungen erkannt werden. *Erfordert einen Server-Neustart",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Skriptinhalte in Epubs zulassen",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Erlaube Epub-Dateien, Skripte auszuführen. Es wird empfohlen, diese Einstellung deaktiviert zu lassen, es sei denn, du vertraust der Quelle der Epub-Dateien.",
|
||||
"LabelSettingsExperimentalFeatures": "Experimentelle Funktionen",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Funktionen welche sich in der Entwicklung befinden, benötigen dein Feedback und deine Hilfe beim Testen. Klicke hier, um die Github-Diskussion zu öffnen.",
|
||||
"LabelSettingsFindCovers": "Suche Titelbilder",
|
||||
@@ -485,8 +499,9 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Standardmäßig werden die Metadaten in /metadata/items gespeichert. Wenn diese Option aktiviert ist, werden die Metadaten als OPF-Datei (Textdatei) in dem gleichen Ordner gespeichert in welchem sich auch das Medium befindet",
|
||||
"LabelSettingsTimeFormat": "Zeitformat",
|
||||
"LabelShowAll": "Alles anzeigen",
|
||||
"LabelShowSeconds": "Zeige Sekunden",
|
||||
"LabelSize": "Größe",
|
||||
"LabelSleepTimer": "Sleep-Timer",
|
||||
"LabelSleepTimer": "Schlummerfunktion",
|
||||
"LabelSlug": "URL Teil",
|
||||
"LabelStart": "Start",
|
||||
"LabelStarted": "Gestartet",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Alle Episoden werden als nicht abgeschlossen markiert! Bist du dir sicher?",
|
||||
"MessageConfirmMarkSeriesFinished": "Alle Medien dieser Reihe werden als abgeschlossen markiert! Bist du dir sicher?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Alle Medien dieser Reihe werden als nicht abgeschlossen markiert! Bist du dir sicher?",
|
||||
"MessageConfirmPurgeCache": "Cache leeren wird das ganze Verzeichnis <code>/metadata/cache</code> löschen. <br /><br />Bist du dir sicher, dass das Cache Verzeichnis gelöscht werden soll?",
|
||||
"MessageConfirmQuickEmbed": "Warnung! Audiodateien werden bei der Schnelleinbettung nicht gesichert! Achte darauf, dass du eine Sicherungskopie der Audiodateien besitzt. <br><br>Möchtest du fortfahren?",
|
||||
"MessageConfirmRemoveAllChapters": "Alle Kapitel werden entfernt! Bist du dir sicher?",
|
||||
"MessageConfirmRemoveAuthor": "Autor \"{0}\" wird enfernt! Bist du dir sicher?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Verschiebe die Dateien in die richtige Reihenfolge",
|
||||
"MessageEmbedFinished": "Einbettung abgeschlossen!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episode(n) in der Warteschlange zum Herunterladen",
|
||||
"MessageEreaderDevices": "Um die Zustellung von E-Books sicherzustellen, musst du eventuell die oben genannte E-Mail-Adresse als gültigen Absender für jedes unten aufgeführte Gerät hinzufügen.",
|
||||
"MessageFeedURLWillBe": "Feed-URL wird {0} sein",
|
||||
"MessageFetching": "Abrufen...",
|
||||
"MessageForceReScanDescription": "Durchsucht alle Dateien erneut, wie bei einem frischen Scan. ID3-Tags von Audiodateien, OPF-Dateien und Textdateien werden neu durchsucht.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} Ereignisse im letzten Jahr",
|
||||
"MessageLoading": "Laden...",
|
||||
"MessageLoadingFolders": "Lade Ordner...",
|
||||
"MessageLogsDescription": "Die Logs werdern in <code>/metadata/logs</code> als JSON Dateien gespeichert. Crash logs werden in <code>/metadata/logs/crash_logs.txt</code> gespeichert.",
|
||||
"MessageM4BFailed": "M4B fehlgeschlagen!",
|
||||
"MessageM4BFinished": "M4B beendet!",
|
||||
"MessageMapChapterTitles": "Zuordnen von Kapiteltiteln zu deinen vorhandenen Medienkapiteln ohne Anpassung der Zeitangaben",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Lesezeichen gelöscht",
|
||||
"ToastBookmarkUpdateFailed": "Lesezeichenaktualisierung fehlgeschlagen",
|
||||
"ToastBookmarkUpdateSuccess": "Lesezeichen aktualisiert",
|
||||
"ToastCachePurgeFailed": "Cache leeren fehlgeschlagen",
|
||||
"ToastCachePurgeSuccess": "Cache geleert",
|
||||
"ToastChaptersHaveErrors": "Kapitel sind fehlerhaft",
|
||||
"ToastChaptersMustHaveTitles": "Kapitel benötigen eindeutige Namen",
|
||||
"ToastCollectionItemsRemoveFailed": "Fehler beim Entfernen der Medien aus der Sammlung",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Sammlung gelöscht",
|
||||
"ToastCollectionUpdateFailed": "Sammlung konnte nicht aktualisiert werden",
|
||||
"ToastCollectionUpdateSuccess": "Sammlung aktualisiert",
|
||||
"ToastDeleteFileFailed": "Die Datei konnte nicht gelöscht werden",
|
||||
"ToastDeleteFileSuccess": "Datei gelöscht",
|
||||
"ToastFailedToLoadData": "Daten laden fehlgeschlagen",
|
||||
"ToastItemCoverUpdateFailed": "Fehler bei der Aktualisierung des Titelbildes",
|
||||
"ToastItemCoverUpdateSuccess": "Titelbild aktualisiert",
|
||||
"ToastItemDetailsUpdateFailed": "Fehler bei der Aktualisierung der Artikeldetails",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "E-Book 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",
|
||||
"ToastSessionDeleteFailed": "Sitzung konnte nicht gelöscht werden",
|
||||
"ToastSessionDeleteSuccess": "Sitzung gelöscht",
|
||||
"ToastSocketConnected": "Verbindung zum WebSocket hergestellt",
|
||||
"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)",
|
||||
"ToastUserDeleteFailed": "Benutzer konnte nicht gelöscht werden",
|
||||
"ToastUserDeleteSuccess": "Benutzer gelöscht"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "Remove from queue",
|
||||
"ButtonQuickMatch": "Quick Match",
|
||||
"ButtonRead": "Read",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonRemove": "Remove",
|
||||
"ButtonRemoveAll": "Remove All",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Your Stats",
|
||||
"LabelAbridged": "Abridged",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Account Type",
|
||||
"LabelAccountTypeAdmin": "Admin",
|
||||
"LabelAccountTypeGuest": "Guest",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bitrate",
|
||||
"LabelBooks": "Books",
|
||||
"LabelButtonText": "Button Text",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Change Password",
|
||||
"LabelChannels": "Channels",
|
||||
"LabelChapters": "Chapters",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Download",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Duration",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "Duration found:",
|
||||
"LabelEbook": "Ebook",
|
||||
"LabelEbooks": "Ebooks",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Episode Type",
|
||||
"LabelExample": "Example",
|
||||
"LabelExplicit": "Explicit",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFetchingMetadata": "Fetching Metadata",
|
||||
"LabelFile": "File",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Search Title",
|
||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||
"LabelSeason": "Season",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Select all episodes",
|
||||
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||
"LabelSelectUsers": "Select users",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Experimental features",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
||||
"LabelSettingsFindCovers": "Find covers",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
|
||||
"LabelSettingsTimeFormat": "Time Format",
|
||||
"LabelShowAll": "Show All",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "Size",
|
||||
"LabelSleepTimer": "Sleep timer",
|
||||
"LabelSlug": "Slug",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
|
||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
||||
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
||||
"MessageEmbedFinished": "Embed Finished!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "Feed URL will be {0}",
|
||||
"MessageFetching": "Fetching...",
|
||||
"MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} listening sessions in the last year",
|
||||
"MessageLoading": "Loading...",
|
||||
"MessageLoadingFolders": "Loading folders...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B Failed!",
|
||||
"MessageM4BFinished": "M4B Finished!",
|
||||
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
||||
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
||||
"ToastBookmarkUpdateSuccess": "Bookmark updated",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
||||
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Collection removed",
|
||||
"ToastCollectionUpdateFailed": "Failed to update collection",
|
||||
"ToastCollectionUpdateSuccess": "Collection updated",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "Failed to update item cover",
|
||||
"ToastItemCoverUpdateSuccess": "Item cover updated",
|
||||
"ToastItemDetailsUpdateFailed": "Failed to update item details",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "Series update failed",
|
||||
"ToastSeriesUpdateSuccess": "Series update success",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "Failed to delete session",
|
||||
"ToastSessionDeleteSuccess": "Session deleted",
|
||||
"ToastSocketConnected": "Socket connected",
|
||||
"ToastSocketDisconnected": "Socket disconnected",
|
||||
"ToastSocketFailedToConnect": "Socket failed to connect",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "Failed to delete user",
|
||||
"ToastUserDeleteSuccess": "User deleted"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,9 @@
|
||||
"ButtonQueueRemoveItem": "Remover de la Fila",
|
||||
"ButtonQuickMatch": "Encontrar Rápido",
|
||||
"ButtonRead": "Leer",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonReadLess": "Lea menos",
|
||||
"ButtonReadMore": "Lea mas",
|
||||
"ButtonRefresh": "Refrecar",
|
||||
"ButtonRemove": "Remover",
|
||||
"ButtonRemoveAll": "Remover Todos",
|
||||
"ButtonRemoveAllLibraryItems": "Remover Todos los Elementos de la Biblioteca",
|
||||
@@ -70,7 +72,7 @@
|
||||
"ButtonRemoveSeriesFromContinueSeries": "Remover Serie de Continuar Series",
|
||||
"ButtonReScan": "Re-Escanear",
|
||||
"ButtonReset": "Reiniciar",
|
||||
"ButtonResetToDefault": "Reset to default",
|
||||
"ButtonResetToDefault": "Restaurar valores por defecto",
|
||||
"ButtonRestore": "Restaurar",
|
||||
"ButtonSave": "Guardar",
|
||||
"ButtonSaveAndClose": "Guardar y Cerrar",
|
||||
@@ -81,7 +83,7 @@
|
||||
"ButtonSelectFolderPath": "Seleccionar Ruta de Carpeta",
|
||||
"ButtonSeries": "Series",
|
||||
"ButtonSetChaptersFromTracks": "Seleccionar Capítulos Según las Pistas",
|
||||
"ButtonShare": "Share",
|
||||
"ButtonShare": "Compartir",
|
||||
"ButtonShiftTimes": "Desplazar Tiempos",
|
||||
"ButtonShow": "Mostrar",
|
||||
"ButtonStartM4BEncode": "Iniciar Codificación M4B",
|
||||
@@ -159,11 +161,11 @@
|
||||
"HeaderRemoveEpisodes": "Remover {0} Episodios",
|
||||
"HeaderRSSFeedGeneral": "Detalles RSS",
|
||||
"HeaderRSSFeedIsOpen": "Fuente RSS esta abierta",
|
||||
"HeaderRSSFeeds": "RSS Feeds",
|
||||
"HeaderRSSFeeds": "Fuentes RSS",
|
||||
"HeaderSavedMediaProgress": "Guardar Progreso de Multimedia",
|
||||
"HeaderSchedule": "Horario",
|
||||
"HeaderScheduleLibraryScans": "Programar Escaneo Automático de Biblioteca",
|
||||
"HeaderSession": "Session",
|
||||
"HeaderSession": "Sesión",
|
||||
"HeaderSetBackupSchedule": "Programar Respaldo",
|
||||
"HeaderSettings": "Configuraciones",
|
||||
"HeaderSettingsDisplay": "Interfaz",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Tus Estadísticas",
|
||||
"LabelAbridged": "Abreviado",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Tipo de Cuenta",
|
||||
"LabelAccountTypeAdmin": "Administrador",
|
||||
"LabelAccountTypeGuest": "Invitado",
|
||||
@@ -203,7 +208,7 @@
|
||||
"LabelAllUsers": "Todos los Usuarios",
|
||||
"LabelAllUsersExcludingGuests": "Todos los usuarios excepto invitados",
|
||||
"LabelAllUsersIncludingGuests": "Todos los usuarios e invitados",
|
||||
"LabelAlreadyInYourLibrary": "Ya en la Biblioteca",
|
||||
"LabelAlreadyInYourLibrary": "Ya existe en la Biblioteca",
|
||||
"LabelAppend": "Adjuntar",
|
||||
"LabelAuthor": "Autor",
|
||||
"LabelAuthorFirstLast": "Autor (Nombre Apellido)",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bitrate",
|
||||
"LabelBooks": "Libros",
|
||||
"LabelButtonText": "Texto del botón",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Cambiar Contraseña",
|
||||
"LabelChannels": "Canales",
|
||||
"LabelChapters": "Capítulos",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Descargar",
|
||||
"LabelDownloadNEpisodes": "Descargar {0} episodios",
|
||||
"LabelDuration": "Duración",
|
||||
"LabelDurationComparisonExactMatch": "(coincidencia exacta)",
|
||||
"LabelDurationComparisonLonger": "({0} más largo)",
|
||||
"LabelDurationComparisonShorter": "({0} más corto)",
|
||||
"LabelDurationFound": "Duración Comprobada:",
|
||||
"LabelEbook": "Ebook",
|
||||
"LabelEbooks": "Ebooks",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Tipo de Episodio",
|
||||
"LabelExample": "Ejemplo",
|
||||
"LabelExplicit": "Explicito",
|
||||
"LabelExplicitChecked": "Explícito (marcado)",
|
||||
"LabelExplicitUnchecked": "No Explícito (sin marcar)",
|
||||
"LabelFeedURL": "Fuente de URL",
|
||||
"LabelFetchingMetadata": "Obteniendo metadatos",
|
||||
"LabelFile": "Archivo",
|
||||
@@ -355,8 +366,8 @@
|
||||
"LabelMetaTags": "Metaetiquetas",
|
||||
"LabelMinute": "Minuto",
|
||||
"LabelMissing": "Ausente",
|
||||
"LabelMissingEbook": "Has no ebook",
|
||||
"LabelMissingSupplementaryEbook": "Has no supplementary ebook",
|
||||
"LabelMissingEbook": "No tiene ebook",
|
||||
"LabelMissingSupplementaryEbook": "No tiene ebook suplementario",
|
||||
"LabelMobileRedirectURIs": "URIs de redirección a móviles permitidos",
|
||||
"LabelMobileRedirectURIsDescription": "Esta es una lista de URIs válidos para redireccionamiento de apps móviles. La URI por defecto es <code>audiobookshelf://oauth</code>, la cual puedes remover or corroborar con URIs adicionales para la integración con apps de terceros. Utilizando un asterisco (<code>*</code>) como el único punto de entrada permite cualquier URI.",
|
||||
"LabelMore": "Más",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Buscar Titulo",
|
||||
"LabelSearchTitleOrASIN": "Buscar Título o ASIN",
|
||||
"LabelSeason": "Temporada",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Seleccionar todos los episodios",
|
||||
"LabelSelectEpisodesShowing": "Seleccionar los {0} episodios visibles",
|
||||
"LabelSelectUsers": "Seleccionar usuarios",
|
||||
@@ -458,7 +470,9 @@
|
||||
"LabelSettingsDisableWatcherHelp": "Deshabilitar la función de agregar/actualizar elementos automáticamente cuando se detectan cambios en los archivos. *Require Reiniciar el Servidor",
|
||||
"LabelSettingsEnableWatcher": "Habilitar Watcher",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Habilitar Watcher para la carpeta de esta biblioteca",
|
||||
"LabelSettingsEnableWatcherHelp": "Permite agregar/actualizar elementos automáticamente cuando se detectan cambios en los archivos. *Requires server restart",
|
||||
"LabelSettingsEnableWatcherHelp": "Permite agregar/actualizar elementos automáticamente cuando se detectan cambios en los archivos. *Requiere reiniciar el servidor",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Funciones Experimentales",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Funciones en desarrollo que se beneficiarían de sus comentarios y experiencias de prueba. Haga click aquí para abrir una conversación en Github.",
|
||||
"LabelSettingsFindCovers": "Buscar Portadas",
|
||||
@@ -468,7 +482,7 @@
|
||||
"LabelSettingsHomePageBookshelfView": "Usar la vista de librero en la página principal",
|
||||
"LabelSettingsLibraryBookshelfView": "Usar la vista de librero en la biblioteca",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Skip earlier books in Continue Series",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "The Continue Series home page shelf shows the first book not started in series that have at least one book finished and no books in progress. Enabling this setting will continue series from the furthest completed book instead of the first book not started.",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "El estante de la página de inicio de Continuar Serie muestra el primer libro no iniciado de una serie que tenga por lo menos un libro finalizado y no tenga libros en progreso. Habilitar esta opción le permitirá continuar series desde el último libro que ha completado en vez del primer libro que no ha empezado.",
|
||||
"LabelSettingsParseSubtitles": "Extraer Subtítulos",
|
||||
"LabelSettingsParseSubtitlesHelp": "Extraer subtítulos de los nombres de las carpetas de los audiolibros.<br>Los subtítulos deben estar separados por \" - \"<br>Por ejemplo: \"Ejemplo de Título - Subtítulo Aquí\" tiene el subtítulo \"Subtítulo Aquí\"",
|
||||
"LabelSettingsPreferMatchedMetadata": "Preferir metadatos encontrados",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Por defecto, los archivos de metadatos se almacenan en /metadata/items. Si habilita esta opción, los archivos de metadatos se guardarán en la carpeta de elementos de su biblioteca",
|
||||
"LabelSettingsTimeFormat": "Formato de Tiempo",
|
||||
"LabelShowAll": "Mostrar Todos",
|
||||
"LabelShowSeconds": "Mostrar segundos",
|
||||
"LabelSize": "Tamaño",
|
||||
"LabelSleepTimer": "Temporizador para Dormir",
|
||||
"LabelSlug": "Slug",
|
||||
@@ -564,8 +579,8 @@
|
||||
"LabelViewQueue": "Ver Fila del Reproductor",
|
||||
"LabelVolume": "Volumen",
|
||||
"LabelWeekdaysToRun": "Correr en Días de la Semana",
|
||||
"LabelYearReviewHide": "Hide Year in Review",
|
||||
"LabelYearReviewShow": "See Year in Review",
|
||||
"LabelYearReviewHide": "Ocultar Year in Review",
|
||||
"LabelYearReviewShow": "Ver Year in Review",
|
||||
"LabelYourAudiobookDuration": "Duración de tu Audiolibro",
|
||||
"LabelYourBookmarks": "Tus Marcadores",
|
||||
"LabelYourPlaylists": "Tus Listas",
|
||||
@@ -596,13 +611,14 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "¿Está seguro de que desea marcar todos los episodios como no terminados?",
|
||||
"MessageConfirmMarkSeriesFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como terminados?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como no terminados?",
|
||||
"MessageConfirmPurgeCache": "Purgar el caché eliminará el directorio completo ubicado en <code>/metadata/cache</code>. <br /><br />¿Está seguro que desea eliminar el directorio del caché?",
|
||||
"MessageConfirmQuickEmbed": "¡Advertencia! La integración rápida no realiza copias de seguridad a ninguno de tus archivos de audio. Asegúrate de haber realizado una copia de los mismos previamente. <br><br>¿Deseas continuar?",
|
||||
"MessageConfirmRemoveAllChapters": "¿Está seguro de que desea remover todos los capitulos?",
|
||||
"MessageConfirmRemoveAuthor": "¿Está seguro de que desea remover el autor \"{0}\"?",
|
||||
"MessageConfirmRemoveCollection": "¿Está seguro de que desea remover la colección \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisode": "¿Está seguro de que desea remover el episodio \"{0}\"?",
|
||||
"MessageConfirmRemoveEpisodes": "¿Está seguro de que desea remover {0} episodios?",
|
||||
"MessageConfirmRemoveListeningSessions": "Are you sure you want to remove {0} listening sessions?",
|
||||
"MessageConfirmRemoveListeningSessions": "¿Está seguro que desea remover {0} sesiones de escuchar?",
|
||||
"MessageConfirmRemoveNarrator": "¿Está seguro de que desea remover el narrador \"{0}\"?",
|
||||
"MessageConfirmRemovePlaylist": "¿Está seguro de que desea remover la lista de reproducción \"{0}\"?",
|
||||
"MessageConfirmRenameGenre": "¿Está seguro de que desea renombrar el genero \"{0}\" a \"{1}\" de todos los elementos?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Arrastra los archivos al orden correcto de las pistas.",
|
||||
"MessageEmbedFinished": "Incrustación Terminada!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episodio(s) en cola para descargar",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "URL de la fuente será {0}",
|
||||
"MessageFetching": "Buscando...",
|
||||
"MessageForceReScanDescription": "Escaneará todos los archivos como un nuevo escaneo. Archivos de audio con etiquetas ID3, archivos OPF y archivos de texto serán escaneados como nuevos.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} sesiones de escucha en el último año",
|
||||
"MessageLoading": "Cargando...",
|
||||
"MessageLoadingFolders": "Cargando archivos...",
|
||||
"MessageLogsDescription": "Logs son almacenados en <code>/metadata/logs</code> en archivos bajo formato JSON. Logs de fallos son almacenados en <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "¡Fallo de M4B!",
|
||||
"MessageM4BFinished": "¡M4B Terminado!",
|
||||
"MessageMapChapterTitles": "Asignar los nombres de capítulos a los capítulos existentes en tu audiolibro sin ajustar sus tiempos",
|
||||
@@ -675,7 +693,7 @@
|
||||
"MessageQuickMatchDescription": "Rellenar detalles de elementos vacíos y portada con los primeros resultados de '{0}'. No sobrescribe los detalles a menos que la opción \"Preferir Metadatos Encontrados\" del servidor esté habilitada.",
|
||||
"MessageRemoveChapter": "Remover capítulos",
|
||||
"MessageRemoveEpisodes": "Remover {0} episodio(s)",
|
||||
"MessageRemoveFromPlayerQueue": "Romover la cola de reproducción",
|
||||
"MessageRemoveFromPlayerQueue": "Remover la cola de reproducción",
|
||||
"MessageRemoveUserWarning": "¿Está seguro de que desea eliminar el usuario \"{0}\"?",
|
||||
"MessageReportBugsAndContribute": "Reporte erres, solicite funciones y contribuya en",
|
||||
"MessageResetChaptersConfirm": "¿Está seguro de que desea deshacer los cambios y revertir los capítulos a su estado original?",
|
||||
@@ -690,9 +708,9 @@
|
||||
"MessageUploaderItemFailed": "Error al Subir",
|
||||
"MessageUploaderItemSuccess": "¡Éxito al Subir!",
|
||||
"MessageUploading": "Subiendo...",
|
||||
"MessageValidCronExpression": "Expresión de Cron bálida",
|
||||
"MessageValidCronExpression": "Expresión de Cron válida",
|
||||
"MessageWatcherIsDisabledGlobally": "El watcher está desactivado globalmente en la configuración del servidor",
|
||||
"MessageXLibraryIsEmpty": "La biblioteca {0} está vacía!",
|
||||
"MessageXLibraryIsEmpty": "¡La biblioteca {0} está vacía!",
|
||||
"MessageYourAudiobookDurationIsLonger": "La duración de su audiolibro es más larga que la duración encontrada",
|
||||
"MessageYourAudiobookDurationIsShorter": "La duración de su audiolibro es más corta que la duración encontrada",
|
||||
"NoteChangeRootPassword": "El usuario Root es el único usuario que puede no tener una contraseña",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Marcador eliminado",
|
||||
"ToastBookmarkUpdateFailed": "Error al actualizar el marcador",
|
||||
"ToastBookmarkUpdateSuccess": "Marcador actualizado",
|
||||
"ToastCachePurgeFailed": "Error al purgar el caché",
|
||||
"ToastCachePurgeSuccess": "Caché purgado de manera exitosa",
|
||||
"ToastChaptersHaveErrors": "Los capítulos tienen errores",
|
||||
"ToastChaptersMustHaveTitles": "Los capítulos tienen que tener un título",
|
||||
"ToastCollectionItemsRemoveFailed": "Error al remover elemento(s) de la colección",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Colección removida",
|
||||
"ToastCollectionUpdateFailed": "Error al actualizar la colección",
|
||||
"ToastCollectionUpdateSuccess": "Colección actualizada",
|
||||
"ToastDeleteFileFailed": "Error el eliminar archivo",
|
||||
"ToastDeleteFileSuccess": "Archivo eliminado",
|
||||
"ToastFailedToLoadData": "Error al cargar data",
|
||||
"ToastItemCoverUpdateFailed": "Error al actualizar la portada del elemento",
|
||||
"ToastItemCoverUpdateSuccess": "Portada del elemento actualizada",
|
||||
"ToastItemDetailsUpdateFailed": "Error al actualizar los detalles del elemento",
|
||||
@@ -772,11 +795,16 @@
|
||||
"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",
|
||||
"ToastSessionDeleteFailed": "Error al eliminar sesión",
|
||||
"ToastSessionDeleteSuccess": "Sesión eliminada",
|
||||
"ToastSocketConnected": "Socket conectado",
|
||||
"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)",
|
||||
"ToastUserDeleteFailed": "Error al eliminar el usuario",
|
||||
"ToastUserDeleteSuccess": "Usuario eliminado"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "Eemalda järjekorrast",
|
||||
"ButtonQuickMatch": "Kiire sobitamine",
|
||||
"ButtonRead": "Loe",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonRemove": "Eemalda",
|
||||
"ButtonRemoveAll": "Eemalda kõik",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Sinu statistika",
|
||||
"LabelAbridged": "Kärbitud",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Konto tüüp",
|
||||
"LabelAccountTypeAdmin": "Administraator",
|
||||
"LabelAccountTypeGuest": "Külaline",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bittkiirus",
|
||||
"LabelBooks": "Raamatud",
|
||||
"LabelButtonText": "Nupu tekst",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Muuda parooli",
|
||||
"LabelChannels": "Kanalid",
|
||||
"LabelChapters": "Peatükid",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Lae alla",
|
||||
"LabelDownloadNEpisodes": "Lae alla {0} episoodi",
|
||||
"LabelDuration": "Kestus",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "Leitud kestus:",
|
||||
"LabelEbook": "E-raamat",
|
||||
"LabelEbooks": "E-raamatud",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Episoodi tüüp",
|
||||
"LabelExample": "Näide",
|
||||
"LabelExplicit": "Vulgaarne",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "Voogu URL",
|
||||
"LabelFetchingMetadata": "Metaandmete hankimine",
|
||||
"LabelFile": "Fail",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Otsi pealkirja",
|
||||
"LabelSearchTitleOrASIN": "Otsi pealkirja või ASIN-i",
|
||||
"LabelSeason": "Hooaeg",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Vali kõik episoodid",
|
||||
"LabelSelectEpisodesShowing": "Valige {0} näidatavat episoodi",
|
||||
"LabelSelectUsers": "Valige kasutajad",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Luba vaatamine",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Luba kaustavaatamine raamatukogu jaoks",
|
||||
"LabelSettingsEnableWatcherHelp": "Lubab automaatset lisamist/uuendamist, kui tuvastatakse failimuudatused. *Nõuab serveri taaskäivitamist",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Eksperimentaalsed funktsioonid",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Arengus olevad funktsioonid, mis vajavad teie tagasisidet ja abi testimisel. Klõpsake GitHubi arutelu avamiseks.",
|
||||
"LabelSettingsFindCovers": "Leia ümbrised",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Vaikimisi salvestatakse metaandmed /metadata/items kausta. Selle seadistuse lubamine salvestab metaandmed teie raamatukogu üksuse kaustadesse",
|
||||
"LabelSettingsTimeFormat": "Kellaaja vorming",
|
||||
"LabelShowAll": "Näita kõiki",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "Suurus",
|
||||
"LabelSleepTimer": "Uinaku taimer",
|
||||
"LabelSlug": "Slug",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Olete kindel, et soovite kõik episoodid mitte lõpetatuks märkida?",
|
||||
"MessageConfirmMarkSeriesFinished": "Olete kindel, et soovite selle seeria kõik raamatud lõpetatuks märkida?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Olete kindel, et soovite selle seeria kõik raamatud mitte lõpetatuks märkida?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "Hoiatus! Quick Embed ei tee varukoopiaid teie helifailidest. Veenduge, et teil oleks varukoopia oma helifailidest. <br><br>Kas soovite jätkata?",
|
||||
"MessageConfirmRemoveAllChapters": "Olete kindel, et soovite eemaldada kõik peatükid?",
|
||||
"MessageConfirmRemoveAuthor": "Olete kindel, et soovite autori \"{0}\" eemaldada?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Lohistage failid õigesse järjekorda",
|
||||
"MessageEmbedFinished": "Manustamine lõpetatud!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episood(i) on allalaadimiseks järjekorras",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "Toite URL saab olema {0}",
|
||||
"MessageFetching": "Hangitakse...",
|
||||
"MessageForceReScanDescription": "skaneerib kõik failid uuesti nagu värsket skannimist. Heli faili ID3 silte, OPF faile ja tekstifaile skaneeritakse uuesti.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "Kuulamissessioone viimase aasta jooksul: {0}",
|
||||
"MessageLoading": "Laadimine...",
|
||||
"MessageLoadingFolders": "Kaustade laadimine...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B ebaõnnestus!",
|
||||
"MessageM4BFinished": "M4B lõpetatud!",
|
||||
"MessageMapChapterTitles": "Kaarda peatükkide pealkirjad olemasolevatele heliraamatu peatükkidele, ajatempe ei muudeta",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Järjehoidja eemaldatud",
|
||||
"ToastBookmarkUpdateFailed": "Järjehoidja värskendamine ebaõnnestus",
|
||||
"ToastBookmarkUpdateSuccess": "Järjehoidja värskendatud",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "Peatükkidel on vigu",
|
||||
"ToastChaptersMustHaveTitles": "Peatükkidel peab olema pealkiri",
|
||||
"ToastCollectionItemsRemoveFailed": "Üksuse(te) eemaldamine kogumist ebaõnnestus",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Kogum eemaldatud",
|
||||
"ToastCollectionUpdateFailed": "Kogumi värskendamine ebaõnnestus",
|
||||
"ToastCollectionUpdateSuccess": "Kogum värskendatud",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "Üksuse kaane värskendamine ebaõnnestus",
|
||||
"ToastItemCoverUpdateSuccess": "Üksuse kaas värskendatud",
|
||||
"ToastItemDetailsUpdateFailed": "Üksuse üksikasjade värskendamine ebaõnnestus",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "E-raamat saadetud seadmesse \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "Sarja värskendamine ebaõnnestus",
|
||||
"ToastSeriesUpdateSuccess": "Sarja värskendamine õnnestus",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "Seansi kustutamine ebaõnnestus",
|
||||
"ToastSessionDeleteSuccess": "Sessioon kustutatud",
|
||||
"ToastSocketConnected": "Pesa ühendatud",
|
||||
"ToastSocketDisconnected": "Pesa ühendus katkenud",
|
||||
"ToastSocketFailedToConnect": "Pesa ühendamine ebaõnnestus",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "Kasutaja kustutamine ebaõnnestus",
|
||||
"ToastUserDeleteSuccess": "Kasutaja kustutatud"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "Supprimer de la liste de lecture",
|
||||
"ButtonQuickMatch": "Recherche rapide",
|
||||
"ButtonRead": "Lire",
|
||||
"ButtonReadLess": "Lire moins",
|
||||
"ButtonReadMore": "Lire la suite",
|
||||
"ButtonRefresh": "Rafraîchir",
|
||||
"ButtonRemove": "Supprimer",
|
||||
"ButtonRemoveAll": "Supprimer tout",
|
||||
@@ -184,9 +186,12 @@
|
||||
"HeaderUpdateDetails": "Mettre à jour les détails",
|
||||
"HeaderUpdateLibrary": "Mettre à jour la bibliothèque",
|
||||
"HeaderUsers": "Utilisateurs",
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYearReview": "Bilan de l’année {0}",
|
||||
"HeaderYourStats": "Vos statistiques",
|
||||
"LabelAbridged": "Version courte",
|
||||
"LabelAbridgedChecked": "Abrégé (vérifié)",
|
||||
"LabelAbridgedUnchecked": "Intégral (non vérifié)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Type de compte",
|
||||
"LabelAccountTypeAdmin": "Admin",
|
||||
"LabelAccountTypeGuest": "Invité",
|
||||
@@ -213,7 +218,7 @@
|
||||
"LabelAutoFetchMetadata": "Recherche automatique de métadonnées",
|
||||
"LabelAutoFetchMetadataHelp": "Récupère les métadonnées du titre, de l’auteur et de la série pour simplifier le téléchargement. Il se peut que des métadonnées supplémentaires doivent être ajoutées après le téléchargement.",
|
||||
"LabelAutoLaunch": "Lancement automatique",
|
||||
"LabelAutoLaunchDescription": "Redirection automatique vers le fournisseur d'authentification lors de la navigation vers la page de connexion (chemin de remplacement manuel <code>/login?autoLaunch=0</code>)",
|
||||
"LabelAutoLaunchDescription": "Redirection automatique vers le fournisseur d’authentification lors de la navigation vers la page de connexion (chemin de remplacement manuel <code>/login?autoLaunch=0</code>)",
|
||||
"LabelAutoRegister": "Enregistrement automatique",
|
||||
"LabelAutoRegisterDescription": "Créer automatiquement de nouveaux utilisateurs après la connexion",
|
||||
"LabelBackToUser": "Retour à l’utilisateur",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bitrate",
|
||||
"LabelBooks": "Livres",
|
||||
"LabelButtonText": "Texte du bouton",
|
||||
"LabelByAuthor": "par {0}",
|
||||
"LabelChangePassword": "Modifier le mot de passe",
|
||||
"LabelChannels": "Canaux",
|
||||
"LabelChapters": "Chapitres",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Téléchargement",
|
||||
"LabelDownloadNEpisodes": "Télécharger {0} épisode(s)",
|
||||
"LabelDuration": "Durée",
|
||||
"LabelDurationComparisonExactMatch": "(correspondance exacte)",
|
||||
"LabelDurationComparisonLonger": "({0} plus long)",
|
||||
"LabelDurationComparisonShorter": "({0} plus court)",
|
||||
"LabelDurationFound": "Durée trouvée :",
|
||||
"LabelEbook": "Livre numérique",
|
||||
"LabelEbooks": "Livres numériques",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Type de l’épisode",
|
||||
"LabelExample": "Exemple",
|
||||
"LabelExplicit": "Restriction",
|
||||
"LabelExplicitChecked": "Explicite (vérifié)",
|
||||
"LabelExplicitUnchecked": "Non explicite (non vérifié)",
|
||||
"LabelFeedURL": "URL du flux",
|
||||
"LabelFetchingMetadata": "Récupération des métadonnées",
|
||||
"LabelFile": "Fichier",
|
||||
@@ -355,10 +366,10 @@
|
||||
"LabelMetaTags": "Balises de métadonnée",
|
||||
"LabelMinute": "Minute",
|
||||
"LabelMissing": "Manquant",
|
||||
"LabelMissingEbook": "Has no ebook",
|
||||
"LabelMissingSupplementaryEbook": "Has no supplementary ebook",
|
||||
"LabelMissingEbook": "Ne possède pas de livre numérique",
|
||||
"LabelMissingSupplementaryEbook": "Ne possède pas de livre numérique supplémentaire",
|
||||
"LabelMobileRedirectURIs": "URI de redirection mobile autorisés",
|
||||
"LabelMobileRedirectURIsDescription": "Il s'agit d'une liste blanche d’URI de redirection valides pour les applications mobiles. Celui par défaut est <code>audiobookshelf://oauth</code>, que vous pouvez supprimer ou compléter avec des URIs supplémentaires pour l'intégration d'applications tierces. L’utilisation d’un astérisque (<code>*</code>) comme seule entrée autorise n’importe quel URI.",
|
||||
"LabelMobileRedirectURIsDescription": "Il s’agit d’une liste blanche d’URI de redirection valides pour les applications mobiles. Celui par défaut est <code>audiobookshelf://oauth</code>, que vous pouvez supprimer ou compléter avec des URIs supplémentaires pour l’intégration d’applications tierces. L’utilisation d’un astérisque (<code>*</code>) comme seule entrée autorise n’importe quel URI.",
|
||||
"LabelMore": "Plus",
|
||||
"LabelMoreInfo": "Plus d’informations",
|
||||
"LabelName": "Nom",
|
||||
@@ -385,9 +396,9 @@
|
||||
"LabelNotStarted": "Pas commencé",
|
||||
"LabelNumberOfBooks": "Nombre de livres",
|
||||
"LabelNumberOfEpisodes": "Nombre d’épisodes",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Name of the OpenID claim that contains advanced permissions for user actions within the application which will apply to non-admin roles (<b>if configured</b>). If the claim is missing from the response, access to ABS will be denied. If a single option is missing, it will be treated as <code>false</code>. Ensure the identity provider's claim matches the expected structure:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "Name of the OpenID claim that contains a list of the user's groups. Commonly referred to as <code>groups</code>. <b>If configured</b>, the application will automatically assign roles based on the user's group memberships, provided that these groups are named case-insensitively 'admin', 'user', or 'guest' in the claim. The claim should contain a list, and if a user belongs to multiple groups, the application will assign the role corresponding to the highest level of access. If no group matches, access will be denied.",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Nom de la demande OpenID qui contient des autorisations avancées pour les actions de l’utilisateur dans l’application, qui s’appliqueront à des rôles autres que celui d’administrateur (<b>s’il est configuré</b>). Si la demande est absente de la réponse, l’accès à ABS sera refusé. Si une seule option est manquante, elle sera considérée comme <code>false</code>. Assurez-vous que la demande du fournisseur d’identité correspond à la structure attendue :",
|
||||
"LabelOpenIDClaims": "Laissez les options suivantes vides pour désactiver l’attribution avancée de groupes et d’autorisations, en attribuant alors automatiquement le groupe « Utilisateur ».",
|
||||
"LabelOpenIDGroupClaimDescription": "Nom de la demande OpenID qui contient une liste des groupes de l’utilisateur. Communément appelé <code>groups</code>. <b>Si elle est configurée</b>, l’application attribuera automatiquement des rôles en fonction de l’appartenance de l’utilisateur à un groupe, à condition que ces groupes soient nommés -sensible à la casse- tel que « admin », « user » ou « guest » dans la demande. Elle doit contenir une liste, et si un utilisateur appartient à plusieurs groupes, l’application attribuera le rôle correspondant au niveau d’accès le plus élevé. Si aucun groupe ne correspond, l’accès sera refusé.",
|
||||
"LabelOpenRSSFeed": "Ouvrir le flux RSS",
|
||||
"LabelOverwrite": "Écraser",
|
||||
"LabelPassword": "Mot de passe",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Titre de recherche",
|
||||
"LabelSearchTitleOrASIN": "Recherche du titre ou ASIN",
|
||||
"LabelSeason": "Saison",
|
||||
"LabelSelectAll": "Tout sélectionner",
|
||||
"LabelSelectAllEpisodes": "Sélectionner tous les épisodes",
|
||||
"LabelSelectEpisodesShowing": "Sélectionner {0} episode(s) en cours",
|
||||
"LabelSelectUsers": "Sélectionner les utilisateurs",
|
||||
@@ -449,7 +461,7 @@
|
||||
"LabelSetEbookAsPrimary": "Définir comme principale",
|
||||
"LabelSetEbookAsSupplementary": "Définir comme supplémentaire",
|
||||
"LabelSettingsAudiobooksOnly": "Livres audios seulement",
|
||||
"LabelSettingsAudiobooksOnlyHelp": "L'activation de ce paramètre ignorera les fichiers de type « livre numériques », sauf s'ils se trouvent dans un dossier spécifique , auquel cas ils seront définis comme des livres numériques supplémentaires.",
|
||||
"LabelSettingsAudiobooksOnlyHelp": "L’activation de ce paramètre ignorera les fichiers de type « livre numériques », sauf s’ils se trouvent dans un dossier spécifique , auquel cas ils seront définis comme des livres numériques supplémentaires.",
|
||||
"LabelSettingsBookshelfViewHelp": "Interface skeuomorphique avec une étagère en bois",
|
||||
"LabelSettingsChromecastSupport": "Support du Chromecast",
|
||||
"LabelSettingsDateFormat": "Format de date",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Activer la veille",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Activer la surveillance des dossiers pour la bibliothèque",
|
||||
"LabelSettingsEnableWatcherHelp": "Active la mise à jour automatique automatique lorsque des modifications de fichiers sont détectées. * nécessite le redémarrage du serveur",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Fonctionnalités expérimentales",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Fonctionnalités en cours de développement sur lesquelles nous attendons votre retour et expérience. Cliquez pour ouvrir la discussion GitHub.",
|
||||
"LabelSettingsFindCovers": "Chercher des couvertures de livre",
|
||||
@@ -467,10 +481,10 @@
|
||||
"LabelSettingsHideSingleBookSeriesHelp": "Les séries qui ne comportent qu’un seul livre seront masquées sur la page de la série et sur les étagères de la page d’accueil.",
|
||||
"LabelSettingsHomePageBookshelfView": "La page d’accueil utilise la vue étagère",
|
||||
"LabelSettingsLibraryBookshelfView": "La bibliothèque utilise la vue étagère",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Skip earlier books in Continue Series",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "The Continue Series home page shelf shows the first book not started in series that have at least one book finished and no books in progress. Enabling this setting will continue series from the furthest completed book instead of the first book not started.",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Sauter les livres précédents dans « Continuer la série »",
|
||||
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "L’étagère de la page d’accueil « Continuer la série » affiche le premier livre non commencé dans les séries dont au moins un livre est terminé et aucun livre n’est en cours. L’activation de ce paramètre permet de poursuivre la série à partir du dernier livre terminé au lieu du premier livre non commencé.",
|
||||
"LabelSettingsParseSubtitles": "Analyser les sous-titres",
|
||||
"LabelSettingsParseSubtitlesHelp": "Extrait les sous-titres depuis le dossier du livre audio.<br>Les sous-titres doivent être séparés par « - »<br>c’est-à-dire : « Titre du livre - Ceci est un sous-titre » aura le sous-titre « Ceci est un sous-titre »",
|
||||
"LabelSettingsParseSubtitlesHelp": "Extrait les sous-titres depuis le dossier du livre audio.<br>Les sous-titres doivent être séparés par des « - »<br>c’est-à-dire : « Titre du livre - Ceci est un sous-titre » aura le sous-titre « Ceci est un sous-titre »",
|
||||
"LabelSettingsPreferMatchedMetadata": "Préférer les métadonnées par correspondance",
|
||||
"LabelSettingsPreferMatchedMetadataHelp": "Les métadonnées par correspondance écrase les détails de l’article lors d’une recherche par correspondance rapide. Par défaut, la recherche par correspondance rapide ne comblera que les éléments manquant.",
|
||||
"LabelSettingsSkipMatchingBooksWithASIN": "Ignorer la recherche par correspondance sur les livres ayant déjà un ASIN",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Par défaut, les métadonnées sont enregistrées dans /metadata/items",
|
||||
"LabelSettingsTimeFormat": "Format d’heure",
|
||||
"LabelShowAll": "Tout afficher",
|
||||
"LabelShowSeconds": "Afficher le seondes",
|
||||
"LabelSize": "Taille",
|
||||
"LabelSleepTimer": "Minuterie",
|
||||
"LabelSlug": "Balise",
|
||||
@@ -505,7 +520,7 @@
|
||||
"LabelStatsMinutes": "minutes",
|
||||
"LabelStatsMinutesListening": "Minutes d’écoute",
|
||||
"LabelStatsOverallDays": "Nombre total de jours",
|
||||
"LabelStatsOverallHours": "Nombre total d'heures",
|
||||
"LabelStatsOverallHours": "Nombre total d’heures",
|
||||
"LabelStatsWeekListening": "Écoute de la semaine",
|
||||
"LabelSubtitle": "Sous-titre",
|
||||
"LabelSupportedFileTypes": "Types de fichiers supportés",
|
||||
@@ -592,11 +607,12 @@
|
||||
"MessageConfirmDeleteLibraryItems": "Cette opération supprimera {0} éléments de la base de données et de votre système de fichiers. Êtes-vous sûr ?",
|
||||
"MessageConfirmDeleteSession": "Êtes-vous sûr de vouloir supprimer cette session ?",
|
||||
"MessageConfirmForceReScan": "Êtes-vous sûr de vouloir lancer une analyse forcée ?",
|
||||
"MessageConfirmMarkAllEpisodesFinished": "Êtes-vous sûr de marquer tous les épisodes comme terminés ?",
|
||||
"MessageConfirmMarkAllEpisodesFinished": "Êtes-vous sûr de marquer tous les épisodes comme terminés ?",
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Êtes-vous sûr de vouloir marquer tous les épisodes comme non terminés ?",
|
||||
"MessageConfirmMarkSeriesFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme terminées ?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Êtes-vous sûr de vouloir marquer tous les livres de cette série comme comme non terminés ?",
|
||||
"MessageConfirmQuickEmbed": "Attention ! L’intégration rapide ne sauvegardera pas vos fichiers audio. Assurez-vous d’avoir effectuer une sauvegarde de vos fichiers audio.<br><br>Souhaitez-vous continuer ?",
|
||||
"MessageConfirmPurgeCache": "La purge du cache supprimera l’intégralité du répertoire à <code>/metadata/cache</code>.<br><br>Êtes-vous sûr de vouloir supprimer le répertoire de cache ?",
|
||||
"MessageConfirmQuickEmbed": "Attention ! L’intégration rapide ne sauvegardera pas vos fichiers audio. Assurez-vous d’avoir effectuer une sauvegarde de vos fichiers audio.<br><br>Souhaitez-vous continuer ?",
|
||||
"MessageConfirmRemoveAllChapters": "Êtes-vous sûr de vouloir supprimer tous les chapitres ?",
|
||||
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
|
||||
"MessageConfirmRemoveCollection": "Êtes-vous sûr de vouloir supprimer la collection « {0} » ?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Faites glisser les fichiers dans l’ordre correct des pistes",
|
||||
"MessageEmbedFinished": "Intégration terminée !",
|
||||
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "L’URL du flux sera {0}",
|
||||
"MessageFetching": "Récupération…",
|
||||
"MessageForceReScanDescription": "analysera de nouveau tous les fichiers. Les étiquettes ID3 des fichiers audio, les fichiers OPF et les fichiers texte seront analysés comme s’ils étaient nouveaux.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} sessions d’écoute l’an dernier",
|
||||
"MessageLoading": "Chargement…",
|
||||
"MessageLoadingFolders": "Chargement des dossiers…",
|
||||
"MessageLogsDescription": "Les journaux sont stockés dans <code>/metadata/logs</code> sous forme de fichiers JSON. Les journaux d’incidents sont stockés dans <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B échec",
|
||||
"MessageM4BFinished": "M4B terminé",
|
||||
"MessageMapChapterTitles": "Faire correspondre les titres des chapitres aux chapitres existants de votre livre audio sans ajuster l’horodatage.",
|
||||
@@ -731,6 +749,8 @@
|
||||
"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",
|
||||
"ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs",
|
||||
"ToastChaptersMustHaveTitles": "Les chapitre doivent avoir un titre",
|
||||
"ToastCollectionItemsRemoveFailed": "Échec de la suppression de(s) article(s) de la collection",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Collection supprimée",
|
||||
"ToastCollectionUpdateFailed": "Échec de la mise à jour de la collection",
|
||||
"ToastCollectionUpdateSuccess": "Collection mise à jour",
|
||||
"ToastDeleteFileFailed": "Échec de la suppression du fichier",
|
||||
"ToastDeleteFileSuccess": "Fichier supprimé",
|
||||
"ToastFailedToLoadData": "Échec du chargement des données",
|
||||
"ToastItemCoverUpdateFailed": "Échec de la mise à jour de la couverture de l’article",
|
||||
"ToastItemCoverUpdateSuccess": "Couverture de l’article mise à jour",
|
||||
"ToastItemDetailsUpdateFailed": "Échec de la mise à jour des détails de l’article",
|
||||
@@ -772,11 +795,16 @@
|
||||
"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",
|
||||
"ToastSessionDeleteFailed": "Échec de la suppression de session",
|
||||
"ToastSessionDeleteSuccess": "Session supprimée",
|
||||
"ToastSocketConnected": "WebSocket connecté",
|
||||
"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)",
|
||||
"ToastUserDeleteFailed": "Échec de la suppression de l’utilisateur",
|
||||
"ToastUserDeleteSuccess": "Utilisateur supprimé"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "કતારથી કાઢી નાખો",
|
||||
"ButtonQuickMatch": "ઝડપી મેળ ખવડાવો",
|
||||
"ButtonRead": "વાંચો",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonRemove": "કાઢી નાખો",
|
||||
"ButtonRemoveAll": "બધું કાઢી નાખો",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Your Stats",
|
||||
"LabelAbridged": "Abridged",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Account Type",
|
||||
"LabelAccountTypeAdmin": "Admin",
|
||||
"LabelAccountTypeGuest": "Guest",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bitrate",
|
||||
"LabelBooks": "Books",
|
||||
"LabelButtonText": "Button Text",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Change Password",
|
||||
"LabelChannels": "Channels",
|
||||
"LabelChapters": "Chapters",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Download",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Duration",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "Duration found:",
|
||||
"LabelEbook": "Ebook",
|
||||
"LabelEbooks": "Ebooks",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Episode Type",
|
||||
"LabelExample": "Example",
|
||||
"LabelExplicit": "Explicit",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFetchingMetadata": "Fetching Metadata",
|
||||
"LabelFile": "File",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Search Title",
|
||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||
"LabelSeason": "Season",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Select all episodes",
|
||||
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||
"LabelSelectUsers": "Select users",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Experimental features",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
||||
"LabelSettingsFindCovers": "Find covers",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
|
||||
"LabelSettingsTimeFormat": "Time Format",
|
||||
"LabelShowAll": "Show All",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "Size",
|
||||
"LabelSleepTimer": "Sleep timer",
|
||||
"LabelSlug": "Slug",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
|
||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
||||
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
||||
"MessageEmbedFinished": "Embed Finished!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "Feed URL will be {0}",
|
||||
"MessageFetching": "Fetching...",
|
||||
"MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} listening sessions in the last year",
|
||||
"MessageLoading": "Loading...",
|
||||
"MessageLoadingFolders": "Loading folders...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B Failed!",
|
||||
"MessageM4BFinished": "M4B Finished!",
|
||||
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
||||
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
||||
"ToastBookmarkUpdateSuccess": "Bookmark updated",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
||||
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Collection removed",
|
||||
"ToastCollectionUpdateFailed": "Failed to update collection",
|
||||
"ToastCollectionUpdateSuccess": "Collection updated",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "Failed to update item cover",
|
||||
"ToastItemCoverUpdateSuccess": "Item cover updated",
|
||||
"ToastItemDetailsUpdateFailed": "Failed to update item details",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "Series update failed",
|
||||
"ToastSeriesUpdateSuccess": "Series update success",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "Failed to delete session",
|
||||
"ToastSessionDeleteSuccess": "Session deleted",
|
||||
"ToastSocketConnected": "Socket connected",
|
||||
"ToastSocketDisconnected": "Socket disconnected",
|
||||
"ToastSocketFailedToConnect": "Socket failed to connect",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "Failed to delete user",
|
||||
"ToastUserDeleteSuccess": "User deleted"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "הסר מהתור",
|
||||
"ButtonQuickMatch": "התאמה מהירה",
|
||||
"ButtonRead": "קרא",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "רענן",
|
||||
"ButtonRemove": "הסר",
|
||||
"ButtonRemoveAll": "הסר הכל",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "שנת {0} בסקירה",
|
||||
"HeaderYourStats": "הסטטיסטיקות שלך",
|
||||
"LabelAbridged": "מקוצר",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "סוג חשבון",
|
||||
"LabelAccountTypeAdmin": "מנהל",
|
||||
"LabelAccountTypeGuest": "אורח",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "קצב סיביות",
|
||||
"LabelBooks": "ספרים",
|
||||
"LabelButtonText": "טקסט לחצן",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "שינוי סיסמה",
|
||||
"LabelChannels": "ערוצים",
|
||||
"LabelChapters": "פרקים",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "הורד",
|
||||
"LabelDownloadNEpisodes": "הורד {0} פרקים",
|
||||
"LabelDuration": "משך",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "משך נמצא:",
|
||||
"LabelEbook": "ספר אלקטרוני",
|
||||
"LabelEbooks": "ספרים אלקטרוניים",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "סוג הפרק",
|
||||
"LabelExample": "דוגמה",
|
||||
"LabelExplicit": "בוטה",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "כתובת ערוץ",
|
||||
"LabelFetchingMetadata": "מושך מטא-נתונים",
|
||||
"LabelFile": "קובץ",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "כותרת חיפוש",
|
||||
"LabelSearchTitleOrASIN": "כותרת חיפוש או ASIN",
|
||||
"LabelSeason": "עונה",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "בחר את כל הפרקים",
|
||||
"LabelSelectEpisodesShowing": "בחר {0} פרקים המוצגים",
|
||||
"LabelSelectUsers": "בחר משתמשים",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "הפעל עוקב",
|
||||
"LabelSettingsEnableWatcherForLibrary": "הפעל עוקב תיקייה עבור ספרייה",
|
||||
"LabelSettingsEnableWatcherHelp": "מאפשר הוספת/עדכון אוטומטי של פריטים כאשר שינויי קבצים זוהים. *דורש איתחול שרת",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "תכונות ניסיוניות",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "תכונות בפיתוח שדורשות משובך ובדיקה. לחץ לפתיחת דיון ב-GitHub.",
|
||||
"LabelSettingsFindCovers": "מצא כריכות",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "כברירת מחדל, קבצי מטה-נתונים מאוחסנים ב- /metadata/items, הפעלת ההגדרה תאחסן קבצי מטה-נתונים בתיקיית פריט שלך בספרייה",
|
||||
"LabelSettingsTimeFormat": "פורמט זמן",
|
||||
"LabelShowAll": "הצג הכל",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "גודל",
|
||||
"LabelSleepTimer": "טיימר שינה",
|
||||
"LabelSlug": "Slug",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הפרקים כלא הסתיימו?",
|
||||
"MessageConfirmMarkSeriesFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כהסתיימו?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כלא הסתיימו?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "אזהרה! הטמעה מהירה לא תגבה גיבוי של קבצי האודיו שלך. וודא שיש לך גיבוי של קבצי האודיו שלך. <br><br>האם ברצונך להמשיך?",
|
||||
"MessageConfirmRemoveAllChapters": "האם אתה בטוח שברצונך להסיר את כל הפרקים?",
|
||||
"MessageConfirmRemoveAuthor": "האם אתה בטוח שברצונך להסיר את המחבר \"{0}\"?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "גרור קבצים לסדר ההשמעה נכון",
|
||||
"MessageEmbedFinished": "ההטמעה הושלמה!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} פרקים בתור להורדה",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "כתובת URL של העדכון תהיה {0}",
|
||||
"MessageFetching": "מושך...",
|
||||
"MessageForceReScanDescription": "תבוצע סריקה מחדש כמו סריקה חדש מאפס, תגי ID3 של קבצי קול, קבצי OPF, וקבצי טקסט ייסרקו כחדשים.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} מפגשי האזנה בשנה האחרונה",
|
||||
"MessageLoading": "טוען...",
|
||||
"MessageLoadingFolders": "טוען תיקיות...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B נכשל!",
|
||||
"MessageM4BFinished": "M4B הושלם!",
|
||||
"MessageMapChapterTitles": "מפה שמות פרקים לפרקי הספר השמורים שלך ללא שינוי תגי זמן",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "הסימניה הוסרה בהצלחה",
|
||||
"ToastBookmarkUpdateFailed": "עדכון הסימניה נכשל",
|
||||
"ToastBookmarkUpdateSuccess": "הסימניה עודכנה בהצלחה",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "פרקים מכילים שגיאות",
|
||||
"ToastChaptersMustHaveTitles": "פרקים חייבים לכלול כותרות",
|
||||
"ToastCollectionItemsRemoveFailed": "הסרת הפריט(ים) מהאוסף נכשלה",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "האוסף הוסר בהצלחה",
|
||||
"ToastCollectionUpdateFailed": "עדכון האוסף נכשל",
|
||||
"ToastCollectionUpdateSuccess": "האוסף עודכן בהצלחה",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "עדכון כריכת הפריט נכשל",
|
||||
"ToastItemCoverUpdateSuccess": "כריכת הפריט עודכנה בהצלחה",
|
||||
"ToastItemDetailsUpdateFailed": "עדכון פרטי הפריט נכשל",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "הספר נשלח אל המכשיר \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "עדכון הסדרה נכשל",
|
||||
"ToastSeriesUpdateSuccess": "הסדרה עודכנה בהצלחה",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "מחיקת הפעולה נכשלה",
|
||||
"ToastSessionDeleteSuccess": "הפעולה נמחקה בהצלחה",
|
||||
"ToastSocketConnected": "קצה תקשורת חובר",
|
||||
"ToastSocketDisconnected": "קצה תקשורת נותק",
|
||||
"ToastSocketFailedToConnect": "התחברות קצה התקשורת נכשלה",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "מחיקת המשתמש נכשלה",
|
||||
"ToastUserDeleteSuccess": "המשתמש נמחק בהצלחה"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "कतार से हटाएं",
|
||||
"ButtonQuickMatch": "जल्दी से समानता की तलाश करें",
|
||||
"ButtonRead": "पढ़ लिया",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonRemove": "हटाएं",
|
||||
"ButtonRemoveAll": "सभी हटाएं",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Your Stats",
|
||||
"LabelAbridged": "Abridged",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Account Type",
|
||||
"LabelAccountTypeAdmin": "Admin",
|
||||
"LabelAccountTypeGuest": "Guest",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bitrate",
|
||||
"LabelBooks": "Books",
|
||||
"LabelButtonText": "Button Text",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Change Password",
|
||||
"LabelChannels": "Channels",
|
||||
"LabelChapters": "Chapters",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Download",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Duration",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "Duration found:",
|
||||
"LabelEbook": "Ebook",
|
||||
"LabelEbooks": "Ebooks",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Episode Type",
|
||||
"LabelExample": "Example",
|
||||
"LabelExplicit": "Explicit",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFetchingMetadata": "Fetching Metadata",
|
||||
"LabelFile": "File",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Search Title",
|
||||
"LabelSearchTitleOrASIN": "Search Title or ASIN",
|
||||
"LabelSeason": "Season",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Select all episodes",
|
||||
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||
"LabelSelectUsers": "Select users",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Experimental features",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Features in development that could use your feedback and help testing. Click to open github discussion.",
|
||||
"LabelSettingsFindCovers": "Find covers",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
|
||||
"LabelSettingsTimeFormat": "Time Format",
|
||||
"LabelShowAll": "Show All",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "Size",
|
||||
"LabelSleepTimer": "Sleep timer",
|
||||
"LabelSlug": "Slug",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
|
||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
||||
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Drag files into correct track order",
|
||||
"MessageEmbedFinished": "Embed Finished!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "Feed URL will be {0}",
|
||||
"MessageFetching": "Fetching...",
|
||||
"MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} listening sessions in the last year",
|
||||
"MessageLoading": "Loading...",
|
||||
"MessageLoadingFolders": "Loading folders...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B Failed!",
|
||||
"MessageM4BFinished": "M4B Finished!",
|
||||
"MessageMapChapterTitles": "Map chapter titles to your existing audiobook chapters without adjusting timestamps",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Bookmark removed",
|
||||
"ToastBookmarkUpdateFailed": "Failed to update bookmark",
|
||||
"ToastBookmarkUpdateSuccess": "Bookmark updated",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
||||
"ToastCollectionItemsRemoveFailed": "Failed to remove item(s) from collection",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Collection removed",
|
||||
"ToastCollectionUpdateFailed": "Failed to update collection",
|
||||
"ToastCollectionUpdateSuccess": "Collection updated",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "Failed to update item cover",
|
||||
"ToastItemCoverUpdateSuccess": "Item cover updated",
|
||||
"ToastItemDetailsUpdateFailed": "Failed to update item details",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "Series update failed",
|
||||
"ToastSeriesUpdateSuccess": "Series update success",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "Failed to delete session",
|
||||
"ToastSessionDeleteSuccess": "Session deleted",
|
||||
"ToastSocketConnected": "Socket connected",
|
||||
"ToastSocketDisconnected": "Socket disconnected",
|
||||
"ToastSocketFailedToConnect": "Socket failed to connect",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "Failed to delete user",
|
||||
"ToastUserDeleteSuccess": "User deleted"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "Remove from queue",
|
||||
"ButtonQuickMatch": "Brzi match",
|
||||
"ButtonRead": "Pročitaj",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonRemove": "Ukloni",
|
||||
"ButtonRemoveAll": "Ukloni sve",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Tvoja statistika",
|
||||
"LabelAbridged": "Abridged",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Vrsta korisničkog računa",
|
||||
"LabelAccountTypeAdmin": "Administrator",
|
||||
"LabelAccountTypeGuest": "Gost",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bitrate",
|
||||
"LabelBooks": "Knjige",
|
||||
"LabelButtonText": "Button Text",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Promijeni lozinku",
|
||||
"LabelChannels": "Channels",
|
||||
"LabelChapters": "Chapters",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Preuzmi",
|
||||
"LabelDownloadNEpisodes": "Download {0} episodes",
|
||||
"LabelDuration": "Trajanje",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "Pronađeno trajanje:",
|
||||
"LabelEbook": "Ebook",
|
||||
"LabelEbooks": "Ebooks",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Vrsta epizode",
|
||||
"LabelExample": "Example",
|
||||
"LabelExplicit": "Explicit",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFetchingMetadata": "Fetching Metadata",
|
||||
"LabelFile": "Datoteka",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Traži naslov",
|
||||
"LabelSearchTitleOrASIN": "Traži naslov ili ASIN",
|
||||
"LabelSeason": "Sezona",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Select all episodes",
|
||||
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
|
||||
"LabelSelectUsers": "Select users",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Enable Watcher",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Enable folder watcher for library",
|
||||
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Eksperimentalni features",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Features u razvoju trebaju vaš feedback i pomoć pri testiranju. Klikni da odeš to Github discussionsa.",
|
||||
"LabelSettingsFindCovers": "Pronađi covers",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Po defaultu metapodatci su spremljeni u /metadata/items, uključujućite li ovu postavku, metapodatci će biti spremljeni u folderima od biblioteke",
|
||||
"LabelSettingsTimeFormat": "Time Format",
|
||||
"LabelShowAll": "Prikaži sve",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "Veličina",
|
||||
"LabelSleepTimer": "Sleep timer",
|
||||
"LabelSlug": "Slug",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
|
||||
"MessageConfirmMarkSeriesFinished": "Are you sure you want to mark all books in this series as finished?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Are you sure you want to mark all books in this series as not finished?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?",
|
||||
"MessageConfirmRemoveAllChapters": "Are you sure you want to remove all chapters?",
|
||||
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"MessageDragFilesIntoTrackOrder": "Povuci datoteke u pravilan redoslijed tracka.",
|
||||
"MessageEmbedFinished": "Embed završen!",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Epizoda/-e u redu za preuzimanje",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "Feed URL će biti {0}",
|
||||
"MessageFetching": "Dobavljam...",
|
||||
"MessageForceReScanDescription": "će skenirati sve datoteke ponovno kao svježi sken. ID3 tagovi od audio datoteka, OPF datoteke i tekst datoteke će biti skenirane kao da su nove.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} slušanja u prošloj godini",
|
||||
"MessageLoading": "Učitavam...",
|
||||
"MessageLoadingFolders": "Učitavam foldere...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B neuspješan!",
|
||||
"MessageM4BFinished": "M4B završio!",
|
||||
"MessageMapChapterTitles": "Mapiraj imena poglavlja u postoječa poglavlja bez izmijene timestampova.",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Knjižnja bilješka uklonjena",
|
||||
"ToastBookmarkUpdateFailed": "Aktualizacija knjižne bilješke neuspješna",
|
||||
"ToastBookmarkUpdateSuccess": "Knjižna bilješka aktualizirana",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "Chapters have errors",
|
||||
"ToastChaptersMustHaveTitles": "Chapters must have titles",
|
||||
"ToastCollectionItemsRemoveFailed": "Neuspješno brisanje stavke/-i iz kolekcije",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Kolekcija obrisana",
|
||||
"ToastCollectionUpdateFailed": "Aktualiziranje kolekcije neuspješno",
|
||||
"ToastCollectionUpdateSuccess": "Kolekcija aktualizirana",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"ToastItemCoverUpdateFailed": "Aktualiziranje covera stavke neuspješna",
|
||||
"ToastItemCoverUpdateSuccess": "Cover stavke aktualiziran",
|
||||
"ToastItemDetailsUpdateFailed": "Aktualiziranje detalja stavke neuspješno",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "Ebook sent to device \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "Series update failed",
|
||||
"ToastSeriesUpdateSuccess": "Series update success",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "Neuspješno brisanje serije",
|
||||
"ToastSessionDeleteSuccess": "Sesija obrisana",
|
||||
"ToastSocketConnected": "Socket connected",
|
||||
"ToastSocketDisconnected": "Socket disconnected",
|
||||
"ToastSocketFailedToConnect": "Socket failed to connect",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "Neuspješno brisanje korisnika",
|
||||
"ToastUserDeleteSuccess": "Korisnik obrisan"
|
||||
}
|
||||
@@ -61,6 +61,8 @@
|
||||
"ButtonQueueRemoveItem": "Eltávolítás a sorból",
|
||||
"ButtonQuickMatch": "Gyors egyeztetés",
|
||||
"ButtonRead": "Olvasás",
|
||||
"ButtonReadLess": "Read less",
|
||||
"ButtonReadMore": "Read more",
|
||||
"ButtonRefresh": "Refresh",
|
||||
"ButtonRemove": "Eltávolítás",
|
||||
"ButtonRemoveAll": "Összes eltávolítása",
|
||||
@@ -187,6 +189,9 @@
|
||||
"HeaderYearReview": "Year {0} in Review",
|
||||
"HeaderYourStats": "Saját statisztikák",
|
||||
"LabelAbridged": "Tömörített",
|
||||
"LabelAbridgedChecked": "Abridged (checked)",
|
||||
"LabelAbridgedUnchecked": "Unabridged (unchecked)",
|
||||
"LabelAccessibleBy": "Accessible by",
|
||||
"LabelAccountType": "Fióktípus",
|
||||
"LabelAccountTypeAdmin": "Admin",
|
||||
"LabelAccountTypeGuest": "Vendég",
|
||||
@@ -227,6 +232,7 @@
|
||||
"LabelBitrate": "Bitráta",
|
||||
"LabelBooks": "Könyvek",
|
||||
"LabelButtonText": "Gomb szövege",
|
||||
"LabelByAuthor": "by {0}",
|
||||
"LabelChangePassword": "Jelszó megváltoztatása",
|
||||
"LabelChannels": "Csatornák",
|
||||
"LabelChapters": "Fejezetek",
|
||||
@@ -264,6 +270,9 @@
|
||||
"LabelDownload": "Letöltés",
|
||||
"LabelDownloadNEpisodes": "{0} epizód letöltése",
|
||||
"LabelDuration": "Időtartam",
|
||||
"LabelDurationComparisonExactMatch": "(exact match)",
|
||||
"LabelDurationComparisonLonger": "({0} longer)",
|
||||
"LabelDurationComparisonShorter": "({0} shorter)",
|
||||
"LabelDurationFound": "Megtalált időtartam:",
|
||||
"LabelEbook": "E-könyv",
|
||||
"LabelEbooks": "E-könyvek",
|
||||
@@ -281,6 +290,8 @@
|
||||
"LabelEpisodeType": "Epizód típusa",
|
||||
"LabelExample": "Példa",
|
||||
"LabelExplicit": "Explicit",
|
||||
"LabelExplicitChecked": "Explicit (checked)",
|
||||
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
|
||||
"LabelFeedURL": "Hírcsatorna URL",
|
||||
"LabelFetchingMetadata": "Metaadatok lekérése",
|
||||
"LabelFile": "Fájl",
|
||||
@@ -437,6 +448,7 @@
|
||||
"LabelSearchTitle": "Cím keresése",
|
||||
"LabelSearchTitleOrASIN": "Cím vagy ASIN keresése",
|
||||
"LabelSeason": "Évad",
|
||||
"LabelSelectAll": "Select all",
|
||||
"LabelSelectAllEpisodes": "Összes epizód kiválasztása",
|
||||
"LabelSelectEpisodesShowing": "Kiválasztás {0} megjelenített epizód",
|
||||
"LabelSelectUsers": "Felhasználók kiválasztása",
|
||||
@@ -459,6 +471,8 @@
|
||||
"LabelSettingsEnableWatcher": "Figyelő engedélyezése",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Mappafigyelő engedélyezése a könyvtárban",
|
||||
"LabelSettingsEnableWatcherHelp": "Engedélyezi az automatikus elem hozzáadás/frissítés funkciót, amikor fájlváltozásokat észlel. *Szerver újraindítása szükséges",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Kísérleti funkciók",
|
||||
"LabelSettingsExperimentalFeaturesHelp": "Fejlesztés alatt álló funkciók, amelyek visszajelzésre és tesztelésre szorulnak. Kattintson a github megbeszélés megnyitásához.",
|
||||
"LabelSettingsFindCovers": "Borítók keresése",
|
||||
@@ -485,6 +499,7 @@
|
||||
"LabelSettingsStoreMetadataWithItemHelp": "Alapértelmezés szerint a metaadatfájlok a /metadata/items mappában vannak tárolva, ennek a beállításnak az engedélyezése a metaadatfájlokat a könyvtári elem mappáiban tárolja",
|
||||
"LabelSettingsTimeFormat": "Időformátum",
|
||||
"LabelShowAll": "Mindent mutat",
|
||||
"LabelShowSeconds": "Show seconds",
|
||||
"LabelSize": "Méret",
|
||||
"LabelSleepTimer": "Alvásidőzítő",
|
||||
"LabelSlug": "Rövid cím",
|
||||
@@ -596,6 +611,7 @@
|
||||
"MessageConfirmMarkAllEpisodesNotFinished": "Biztosan meg szeretné jelölni az összes epizódot nem befejezettnek?",
|
||||
"MessageConfirmMarkSeriesFinished": "Biztosan meg szeretné jelölni a sorozat összes könyvét befejezettnek?",
|
||||
"MessageConfirmMarkSeriesNotFinished": "Biztosan meg szeretné jelölni a sorozat összes könyvét nem befejezettnek?",
|
||||
"MessageConfirmPurgeCache": "Purge cache will delete the entire directory at <code>/metadata/cache</code>. <br /><br />Are you sure you want to remove the cache directory?",
|
||||
"MessageConfirmQuickEmbed": "Figyelem! A Gyors beágyazás nem készít biztonsági másolatot az audiofájlokról. Győződjön meg arról, hogy van biztonsági másolata az audiofájlokról. <br><br>Szeretné folytatni?",
|
||||
"MessageConfirmRemoveAllChapters": "Biztosan eltávolítja az összes fejezetet?",
|
||||
"MessageConfirmRemoveAuthor": "Biztosan eltávolítja a(z) \"{0}\" szerzőt?",
|
||||
@@ -617,6 +633,7 @@
|
||||
"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",
|
||||
"MessageEreaderDevices": "To ensure delivery of ebooks, you may need to add the above email address as a valid sender for each device listed below.",
|
||||
"MessageFeedURLWillBe": "A hírcsatorna URL-je {0} lesz",
|
||||
"MessageFetching": "Lekéré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.",
|
||||
@@ -628,6 +645,7 @@
|
||||
"MessageListeningSessionsInTheLastYear": "{0} hallgatási munkamenet az elmúlt évben",
|
||||
"MessageLoading": "Betöltés...",
|
||||
"MessageLoadingFolders": "Mappák betöltése...",
|
||||
"MessageLogsDescription": "Logs are stored in <code>/metadata/logs</code> as JSON files. Crash logs are stored in <code>/metadata/logs/crash_logs.txt</code>.",
|
||||
"MessageM4BFailed": "M4B sikertelen!",
|
||||
"MessageM4BFinished": "M4B befejeződött!",
|
||||
"MessageMapChapterTitles": "Fejezetcímek hozzárendelése a meglévő hangoskönyv fejezeteihez anélkül, hogy az időbélyegeket módosítaná",
|
||||
@@ -731,6 +749,8 @@
|
||||
"ToastBookmarkRemoveSuccess": "Könyvjelző eltávolítva",
|
||||
"ToastBookmarkUpdateFailed": "Könyvjelző frissítése sikertelen",
|
||||
"ToastBookmarkUpdateSuccess": "Könyvjelző frissítve",
|
||||
"ToastCachePurgeFailed": "Failed to purge cache",
|
||||
"ToastCachePurgeSuccess": "Cache purged successfully",
|
||||
"ToastChaptersHaveErrors": "A fejezetek hibákat tartalmaznak",
|
||||
"ToastChaptersMustHaveTitles": "A fejezeteknek címekkel kell rendelkezniük",
|
||||
"ToastCollectionItemsRemoveFailed": "Elem(ek) eltávolítása a gyűjteményből sikertelen",
|
||||
@@ -739,6 +759,9 @@
|
||||
"ToastCollectionRemoveSuccess": "Gyűjtemény eltávolítva",
|
||||
"ToastCollectionUpdateFailed": "Gyűjtemény frissítése sikertelen",
|
||||
"ToastCollectionUpdateSuccess": "Gyűjtemény frissítve",
|
||||
"ToastDeleteFileFailed": "Failed to delete file",
|
||||
"ToastDeleteFileSuccess": "File deleted",
|
||||
"ToastFailedToLoadData": "Failed to load data",
|
||||
"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",
|
||||
@@ -772,11 +795,16 @@
|
||||
"ToastSendEbookToDeviceSuccess": "E-könyv elküldve az eszközre \"{0}\"",
|
||||
"ToastSeriesUpdateFailed": "Sorozat frissítése sikertelen",
|
||||
"ToastSeriesUpdateSuccess": "Sorozat frissítése sikeres",
|
||||
"ToastServerSettingsUpdateFailed": "Failed to update server settings",
|
||||
"ToastServerSettingsUpdateSuccess": "Server settings updated",
|
||||
"ToastSessionDeleteFailed": "Munkamenet törlése sikertelen",
|
||||
"ToastSessionDeleteSuccess": "Munkamenet törölve",
|
||||
"ToastSocketConnected": "Socket csatlakoztatva",
|
||||
"ToastSocketDisconnected": "Socket lecsatlakoztatva",
|
||||
"ToastSocketFailedToConnect": "A Socket csatlakoztatása sikertelen",
|
||||
"ToastSortingPrefixesEmptyError": "Must have at least 1 sorting prefix",
|
||||
"ToastSortingPrefixesUpdateFailed": "Failed to update sorting prefixes",
|
||||
"ToastSortingPrefixesUpdateSuccess": "Sorting prefixes updated ({0} items)",
|
||||
"ToastUserDeleteFailed": "Felhasználó törlése sikertelen",
|
||||
"ToastUserDeleteSuccess": "Felhasználó törölve"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user