Compare commits

..

146 Commits

Author SHA1 Message Date
advplyr 725192fbc0 Version bump v2.19.1 2025-02-11 17:17:07 -06:00
advplyr 2915c072b5 Merge pull request #3931 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-02-11 16:52:14 -06:00
Troja 03a1d7da32 Translated using Weblate (Belarusian)
Currently translated at 19.4% (212 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2025-02-11 22:51:07 +00:00
Mario 1be1ce6f87 Translated using Weblate (German)
Currently translated at 99.9% (1088 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-02-11 22:51:07 +00:00
Troja 21b27c432c Translated using Weblate (Belarusian)
Currently translated at 16.0% (175 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2025-02-11 22:51:06 +00:00
Troja cbe5e3db8a Translated using Weblate (Belarusian)
Currently translated at 13.0% (142 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2025-02-11 22:51:05 +00:00
burghy86 08b4d4d7a2 Translated using Weblate (Italian)
Currently translated at 100.0% (1089 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-02-11 22:51:04 +00:00
Jan-Eric Myhrgren ac8324e595 Translated using Weblate (Swedish)
Currently translated at 90.1% (982 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-02-11 22:51:03 +00:00
Pepijn a14c6a3a8b Translated using Weblate (Dutch)
Currently translated at 99.8% (1087 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-02-11 22:51:03 +00:00
Jan-Eric Myhrgren 74b35ea9d1 Translated using Weblate (Swedish)
Currently translated at 88.7% (966 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-02-11 22:51:02 +00:00
Jan-Eric Myhrgren 78d8c83e6d Translated using Weblate (Swedish)
Currently translated at 85.9% (936 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-02-11 22:51:01 +00:00
Jan-Eric Myhrgren bf795d3662 Translated using Weblate (Swedish)
Currently translated at 85.9% (936 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-02-11 22:51:00 +00:00
Jan-Eric Myhrgren 1fbd090441 Translated using Weblate (Swedish)
Currently translated at 85.8% (935 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-02-11 22:50:59 +00:00
biuklija 70621e72e8 Translated using Weblate (Croatian)
Currently translated at 100.0% (1089 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-02-11 22:50:59 +00:00
advplyr d30a09f503 Merge pull request #3963 from mikiher/security-fix-GHSA-pg8v-5jcv-wrvw
Security fix for GHSA-pg8v-5jcv-wrvw
2025-02-11 16:50:52 -06:00
advplyr 39567c6c22 Update view feed modal to sort episodes by pub date ascending 2025-02-11 16:47:34 -06:00
advplyr ed3af5bdcd Fix server crash when feed cover image is requested but doesnt exist 2025-02-11 16:14:49 -06:00
advplyr 9e54b4f7ca Merge pull request #3952 from mikiher/query-performance
Improve book library page query performance on title, titleIgnorePrefix, and addedAt sort orders.
2025-02-11 15:41:59 -06:00
mikiher ec65376569 Security fix for GHSA-pg8v-5jcv-wrvw 2025-02-11 22:02:51 +02:00
advplyr 4e8cd6fba0 Update index.js dev fallback router base path 2025-02-10 17:58:18 -06:00
advplyr 1a3d70d041 Merge pull request #3958 from devnoname120/fix-apex-path-support
Fix `ROUTER_BASE_PATH` override for empty string
2025-02-10 10:16:47 -06:00
Paul 14e92435ec Fix ROUTER_BASE_PATH override for empty string
When the `ROUTER_BASE_PATH` env variable is set to an empty string it's mistakenly overriden to `/audiobookshelf` instead.
The `/audiobookshelf` fallback should only be used when the `ROUTER_BASE_PATH` env variable is undefined, not just an empty string.

Regression introduced in https://github.com/advplyr/audiobookshelf/pull/3810
See also: https://github.com/advplyr/audiobookshelf/pull/3810#discussion_r1948790937

Partially address https://github.com/advplyr/audiobookshelf/issues/3874
2025-02-10 12:08:49 +01:00
advplyr 0ccb88904a fix v2.15.0 migration test 2025-02-09 17:40:29 -06:00
mikiher 4cc300d6e9 Update changelog with v2.19.1 migration 2025-02-09 21:39:43 +02:00
advplyr 068ba84a8c Merge pull request #3954 from advplyr/fix_next_prev_edit_description
Fix next/prev buttons on edit modals not changing description when focused
2025-02-08 13:17:50 -06:00
advplyr 36ef675556 Fix edit episode next/prev buttons showing when editing from home page 2025-02-08 13:05:50 -06:00
advplyr 0dd57a8912 Fix using next/prev in edit modals while rich text input is focused #3951 2025-02-08 13:02:27 -06:00
advplyr ef45f844e5 Update upwards migration to be idempotent 2025-02-08 12:37:34 -06:00
advplyr 9a261195b7 Update server/models/Book.js 2025-02-08 10:19:13 -06:00
mikiher 3d08a35aa0 Add index on (libraryId, mediaType, createdAt) 2025-02-08 14:53:01 +02:00
mikiher a13143245b Improve page load queries on title, titleIgnorePrefix, and addedAt sort order 2025-02-08 12:29:23 +02:00
mikiher 52bb28669a Add a profile utility function 2025-02-08 10:41:56 +02:00
advplyr 25ae6dd59a Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2025-02-07 17:10:12 -06:00
advplyr a37fe3c3d2 Fix: Users with update permission unable to remove books from collection #3947 2025-02-07 17:09:48 -06:00
advplyr 59bcbe0dfa Merge pull request #3946 from advplyr/details_trim_whitespace
Trim whitespace from podcast/book/episode & batch edit text inputs
2025-02-06 17:51:49 -06:00
advplyr b5e69630de Update batch edit text inputs to trim whitespace 2025-02-06 17:29:27 -06:00
advplyr 0bba709124 Trim whitespace from book/podcast/episode details text inputs #3943 2025-02-06 17:27:33 -06:00
advplyr e93bb5cb07 Merge pull request #3941 from Vynce/accept-encoding
Add `Accept-Encoding` header to `getPodcastFeed()`
2025-02-06 17:01:31 -06:00
Michael Vincent 3f7af8acfb Add Accept-Encoding header to getPodcastFeed()
This commit adds the Accept-Encoding header to getPodcastFeed() with
gzip, compress, and deflate support. This allows servers to send a
compressed response that'll be decompressed by axios transparently.

Audiobookshelf is currently using axios v0.27.2, which enables the
decompress option by default. The decompress feature supports gzip,
compress, and deflate algorithms (see axios/lib/adapters/http.js).
axios v0.27.2 does not add the Accept-Encoding header to requests
automatically, so that's the responsibility of the caller.
2025-02-05 23:12:58 -06:00
advplyr 5e5a604d03 Fix name parser to not use "last, first" format when not using comma separators. Adds unit tests #3940 2025-02-05 17:25:31 -06:00
advplyr 201e12ecc3 Update downloadFile to debug log percentage complete 2025-02-05 16:15:00 -06:00
advplyr 24d6e390f0 Fix Book/Podcast updateFromRequest to support null values in string fields #3938 2025-02-05 15:31:57 -06:00
advplyr 0cf7a6abec Merge pull request #3929 from mikiher/fix-trix-resize
Add resize to trix editor
2025-02-04 17:22:30 -06:00
mikiher 76ac0d001b Add resize to trix editor 2025-02-04 09:54:28 +02:00
advplyr 00343a953b Update Collection/Playlist and batch quick match modal bg colors to be consistent with other modals 2025-02-03 17:47:10 -06:00
advplyr 82ab95ab02 Version bump v2.19.0 2025-02-02 15:39:46 -06:00
advplyr a1d8ebc01b Merge pull request #3893 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-02-02 15:36:43 -06:00
advplyr eeaae5f934 Added translation using Weblate (Turkish) 2025-02-02 22:06:22 +01:00
thehijacker 4464276a6e Translated using Weblate (Slovenian)
Currently translated at 100.0% (1089 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-02-02 00:07:53 +01:00
biuklija 3465790fe9 Translated using Weblate (Croatian)
Currently translated at 100.0% (1089 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-02-02 00:07:53 +01:00
Jonathan 5fa4c5a2c3 Translated using Weblate (German)
Currently translated at 99.3% (1082 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-02-02 00:07:52 +01:00
SunSpring 13f353596b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1089 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-02-02 00:07:51 +01:00
Simple16 3d9100e5b8 Translated using Weblate (Russian)
Currently translated at 100.0% (1089 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-02-02 00:07:50 +01:00
Максим Горпиніч b62309ead2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1089 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-02-02 00:07:50 +01:00
Andreas Morell-Reng 1fce94ad4a Translated using Weblate (Danish)
Currently translated at 100.0% (1089 of 1089 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-02-02 00:07:49 +01:00
thehijacker 9abd6698ae Translated using Weblate (Slovenian)
Currently translated at 100.0% (1087 of 1087 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-02-02 00:07:48 +01:00
Jan-Eric Myhrgren 88c10ad619 Translated using Weblate (Swedish)
Currently translated at 85.4% (929 of 1087 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-02-02 00:07:48 +01:00
biuklija c62a6fbffd Translated using Weblate (Croatian)
Currently translated at 100.0% (1087 of 1087 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-02-02 00:07:47 +01:00
Michel Neuba 989388d3ed Translated using Weblate (French)
Currently translated at 99.7% (1084 of 1087 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2025-02-02 00:07:46 +01:00
Will Forde 4cc97a22f6 Translated using Weblate (Japanese)
Currently translated at 0.1% (1 of 1087 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/
2025-02-02 00:07:45 +01:00
Максим Горпиніч 8bd336a4ba Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1087 of 1087 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-02-02 00:07:45 +01:00
thehijacker 437c8dd09c Translated using Weblate (Slovenian)
Currently translated at 100.0% (1086 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-02-02 00:07:44 +01:00
Максим Горпиніч f82697cbbf Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1086 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-02-02 00:07:43 +01:00
Andreas Morell-Reng 74c87a0bbd Translated using Weblate (Danish)
Currently translated at 100.0% (1086 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-02-02 00:07:43 +01:00
biuklija 35eb5bcfc0 Translated using Weblate (Croatian)
Currently translated at 100.0% (1086 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-02-02 00:07:42 +01:00
Simple16 0a29b549df Translated using Weblate (Russian)
Currently translated at 100.0% (1086 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-02-02 00:07:41 +01:00
SunSpring a38a92b948 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1086 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-02-02 00:07:40 +01:00
Jan-Eric Myhrgren d245c93da4 Translated using Weblate (Swedish)
Currently translated at 85.1% (925 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-02-02 00:07:40 +01:00
Илья Червонный bcf8f6b732 Translated using Weblate (Russian)
Currently translated at 100.0% (1086 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-02-02 00:07:39 +01:00
advplyr 40e11db5e5 Merge pull request #3921 from advplyr/fix_content_url_basepath
Fix API including basepath in tracks contentUrl
2025-02-01 17:07:29 -06:00
advplyr aebb3ff413 Fix API including basepath in tracks contentUrl 2025-02-01 16:47:36 -06:00
advplyr a58d486c44 Fix:Collapsed subseries showing parent series name on hover #3713 2025-01-31 17:18:23 -06:00
advplyr 4a76ba0226 Remove copy of series numbers on book cards 2025-01-31 17:11:57 -06:00
advplyr 7afff57b5e Merge pull request #3916 from nichwall/add_collection_help_text
Add collection and playlist help text
2025-01-30 17:50:21 -06:00
advplyr 2e13c5bd50 Fix no collections message, ui updates 2025-01-30 17:47:41 -06:00
advplyr 344de941ff Merge pull request #3919 from advplyr/fix_logger_fatal
Fix Logger.fatal to ensure crash_logs.txt is written to
2025-01-30 17:36:37 -06:00
advplyr c3aad9486c Fix Logger.fatal to be a promise to ensure crash_logs.txt write 2025-01-30 17:27:32 -06:00
Nicholas Wallace 5c0cd98cb3 Add: collection and playlist help text to modal 2025-01-29 22:55:34 -07:00
Nicholas Wallace 8974c582fc Add: collection and playlist link to guide 2025-01-29 22:46:53 -07:00
advplyr 5ee6005112 Merge pull request #3914 from advplyr/progress_bar_visibility
Adds box shadow to progress bar on covers for visibility #3825
2025-01-29 18:05:07 -06:00
advplyr 6a7469851d Adds box shadow to progress bar on covers for visibility #3825 2025-01-29 17:54:22 -06:00
advplyr 1d57daa9f9 Merge pull request #3907 from nichwall/close_blank_issues
Add: workflow to close blank issues
2025-01-29 17:01:20 -06:00
Nicholas Wallace caf2b664f1 Add: workflow to close blank issues 2025-01-28 20:22:46 -07:00
advplyr b3b2bd7772 Fix feeds for collection/series pub date increment #3442 2025-01-28 17:11:57 -06:00
advplyr 95864705dc Update clean database to remove invalid CollectionBook records 2025-01-28 16:58:42 -06:00
advplyr 0fbba3efbd Merge pull request #3906 from tharvik/master
server/podcast: stabilize random ID
2025-01-28 16:41:54 -06:00
tharvik 575927c101 server/podcast: stabilize random ID 2025-01-28 20:36:35 +01:00
advplyr 45aaaf9f0b Pass ChapterInfo to media session 2025-01-28 09:47:26 -06:00
advplyr 51704f41aa Merge pull request #3892 from glorenzen/feat/adjustable-playback-speed-increment-decrement
Add adjustable increment and decrement value for playback rate
2025-01-27 16:51:23 -06:00
advplyr e701a0a32e Update playback rate display value number of decimals 2025-01-27 16:46:32 -06:00
advplyr fbe186a925 Merge pull request #3899 from mikiher/pragma-from-env
Allows setting of some pragma values through environment variables
2025-01-27 16:21:40 -06:00
advplyr 6ed2b575b0 Merge pull request #3898 from mikiher/fix-batch-quick-match
Add bookSeries id attribute to findAllExpandedWhere
2025-01-26 13:27:41 -06:00
advplyr 558173e086 Update custom metadata provider results to sanitize html descriptions #3880 2025-01-26 10:51:18 -06:00
mikiher 23067e1818 Allows setting of some pragma values through environment variables 2025-01-26 13:44:57 +02:00
mikiher 9b4732c207 Add bookSeries id attribute to findAllExpandedWhere 2025-01-26 12:21:54 +02:00
advplyr e096da1b4d Update description to div tag #3880 2025-01-25 14:12:10 -06:00
advplyr a4d0f95ecc Merge pull request #3880 from mikiher/rich-text-book-descriptionss
Support rich text book descriptions
2025-01-25 13:42:37 -06:00
advplyr 922a5039ce Update descriptionPlain to only be available in json expanded 2025-01-25 13:30:30 -06:00
Greg Lorenzen f258782e2e Handle playback rate increment and decrmenet value in UI 2025-01-25 01:59:24 +00:00
Greg Lorenzen 1ea1e60d4b Add string for LabelPlaybackRateIncrementDecrement 2025-01-25 01:58:48 +00:00
Greg Lorenzen 7c4bcfb4f9 Add dropdown to player settings modal to set the playbackRateIncrementDecrement amount 2025-01-25 01:58:13 +00:00
Greg Lorenzen 3eefe937d9 Add user setting value for playbackRateIncrementDecrement 2025-01-25 01:57:41 +00:00
advplyr d4ba8b9d9f Fix server crash on failed to extract epub image #3889 2025-01-24 17:24:37 -06:00
advplyr c735fea8ba Merge pull request #3871 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-01-24 17:22:04 -06:00
advplyr 9e3010681e Added translation using Weblate (Japanese) 2025-01-24 23:21:26 +00:00
thehijacker c6f724edff Translated using Weblate (Slovenian)
Currently translated at 100.0% (1086 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-01-24 23:21:26 +00:00
Максим Горпиніч 358c3a15b5 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1086 of 1086 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-01-24 23:21:25 +00:00
Jan-Eric Myhrgren 32819860aa Translated using Weblate (Swedish)
Currently translated at 84.4% (914 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-01-24 23:21:25 +00:00
Jan-Eric Myhrgren 7dff571fd5 Translated using Weblate (Swedish)
Currently translated at 83.4% (903 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-01-24 23:21:24 +00:00
Lucas 36dd96fd87 Translated using Weblate (Spanish)
Currently translated at 100.0% (1082 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-01-24 23:21:24 +00:00
Lucas e6244b8676 Translated using Weblate (Spanish)
Currently translated at 99.9% (1081 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-01-24 23:21:23 +00:00
Milo Ivir 9b561e4367 Translated using Weblate (Croatian)
Currently translated at 100.0% (1082 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-01-24 23:21:23 +00:00
Charlie d25b46e9fa Translated using Weblate (French)
Currently translated at 100.0% (1082 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2025-01-24 23:21:22 +00:00
Andreas Morell-Reng 7a89836c3e Translated using Weblate (Danish)
Currently translated at 100.0% (1082 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-01-24 23:21:22 +00:00
Andreas Morell-Reng a9dd67cf75 Translated using Weblate (Danish)
Currently translated at 74.2% (803 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-01-24 23:21:21 +00:00
Andreas Morell-Reng 6f2384e4f2 Translated using Weblate (Danish)
Currently translated at 74.1% (802 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-01-24 23:21:20 +00:00
Andreas Morell-Reng 254558f7a6 Translated using Weblate (Danish)
Currently translated at 74.1% (802 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-01-24 23:21:20 +00:00
Andreas Morell-Reng a4a7cddcff Translated using Weblate (Danish)
Currently translated at 74.1% (802 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-01-24 23:21:19 +00:00
Jan-Eric Myhrgren fc116ce1ed Translated using Weblate (Swedish)
Currently translated at 83.4% (903 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-01-24 23:21:19 +00:00
Andreas Morell-Reng f77dd6b1ad Translated using Weblate (Danish)
Currently translated at 74.1% (802 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-01-24 23:21:18 +00:00
advplyr 647a722b06 Update readme for subdirectory 2025-01-24 17:21:11 -06:00
advplyr 6ec33f4bfa Merge pull request #3884 from adjokic/patch-1
Update README on using websockets with Apache as a reverse proxy
2025-01-24 17:16:22 -06:00
advplyr bb0cc1bb6f Merge pull request #3887 from advplyr/batch-edit-populate-map-details
Add populate map details from buttons to batch editor
2025-01-23 18:03:15 -06:00
advplyr abb5bd3a2d Update string order 2025-01-23 17:58:53 -06:00
advplyr 79acc41d16 Add populate from buttons to batch edit 2025-01-23 17:49:58 -06:00
adjokic 9fbf57bbef Update README on using websockets with Apache as a reverse proxy 2025-01-22 22:10:38 -06:00
advplyr 598a93d224 Update copy to clipboard buttons to be standardized 2025-01-22 17:56:46 -06:00
mikiher 286185329d Support rich text book descriptions 2025-01-22 08:53:23 +02:00
advplyr c3c846f82d Update rss feed copy to clipboard to show checkmark instead of toast 2025-01-21 17:58:10 -06:00
advplyr 66b90e0841 Version bump v2.18.1 2025-01-20 15:45:09 -06:00
advplyr 9b21812feb Merge pull request #3862 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-01-20 15:29:41 -06:00
Jan-Eric Myhrgren e9d8b62858 Translated using Weblate (Swedish)
Currently translated at 83.4% (903 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-01-20 22:08:48 +01:00
Nicky Larstrup 6d5aeaa42f Translated using Weblate (Danish)
Currently translated at 69.4% (751 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-01-20 22:08:48 +01:00
SunSpring 3fd9721da6 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1082 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-01-20 22:08:48 +01:00
Jan-Eric Myhrgren 63b2c6a3ea Translated using Weblate (Swedish)
Currently translated at 83.4% (903 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-01-20 22:08:48 +01:00
ugyes 1506589ec8 Translated using Weblate (Hungarian)
Currently translated at 97.8% (1059 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-01-20 22:08:48 +01:00
Losicek 035590236b Translated using Weblate (Czech)
Currently translated at 100.0% (1082 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2025-01-20 22:08:48 +01:00
SunSpring eea446e217 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1082 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-01-20 22:08:48 +01:00
J. Lavoie 63dc819728 Translated using Weblate (Italian)
Currently translated at 98.7% (1068 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-01-20 22:08:48 +01:00
J. Lavoie ff537de132 Translated using Weblate (French)
Currently translated at 99.9% (1081 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2025-01-20 22:08:48 +01:00
J. Lavoie 56550157d1 Translated using Weblate (German)
Currently translated at 100.0% (1082 of 1082 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-01-20 22:08:48 +01:00
advplyr 28681d3783 Merge pull request #3732 from Timtam/allow-mrss-item-enclosures-for-podcasts
check for mrss item media:content when extracting item enclosures
2025-01-20 15:08:43 -06:00
advplyr 24ce4a208d Merge pull request #3867 from advplyr/feed_generator_updates
Updates to generated RSS feed & Fix series/collection feeds
2025-01-20 15:02:24 -06:00
Toni Barth bdd8e5bb58 Merge remote-tracking branch 'remotes/upstream/master' into allow-mrss-item-enclosures-for-podcasts 2025-01-20 10:28:09 +01:00
Toni Barth 18dfbdd983 Merge remote-tracking branch 'remotes/upstream/master' into allow-mrss-item-enclosures-for-podcasts 2025-01-02 17:10:09 +01:00
Toni Barth 4d2241769e also check for mrss item enclosures when extracting items 2024-12-18 19:15:09 +01:00
100 changed files with 2007 additions and 526 deletions
+42
View File
@@ -0,0 +1,42 @@
name: Close Issues not using a template
on:
issues:
types:
- opened
permissions:
issues: write
jobs:
close_issue:
runs-on: ubuntu-latest
steps:
- name: Check issue headings
uses: actions/github-script@v6
with:
script: |
const issueBody = context.payload.issue.body || "";
// Match Markdown headings (e.g., # Heading, ## Heading)
const headingRegex = /^(#{1,6})\s.+/gm;
const headings = [...issueBody.matchAll(headingRegex)];
if (headings.length < 3) {
// Post a comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: "Thank you for opening an issue! To help us review your request efficiently, please use one of the provided issue templates. If you're seeking information or have a general question, consider opening a Discussion or joining the conversation on our Discord. Thanks!"
});
// Close the issue
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
state: "closed"
});
}
+5 -3
View File
@@ -92,11 +92,10 @@
} }
/* Firefox */ /* Firefox */
input[type=number] { input[type='number'] {
-moz-appearance: textfield; -moz-appearance: textfield;
} }
.tracksTable { .tracksTable {
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
@@ -177,6 +176,10 @@ input[type=number] {
box-shadow: 4px 1px 8px #11111166, -4px 1px 8px #11111166, 1px -4px 8px #11111166; box-shadow: 4px 1px 8px #11111166, -4px 1px 8px #11111166, 1px -4px 8px #11111166;
} }
.box-shadow-progressbar {
box-shadow: 0px -1px 4px rgb(62, 50, 2, 0.5);
}
.shadow-height { .shadow-height {
height: calc(100% - 4px); height: calc(100% - 4px);
} }
@@ -204,7 +207,6 @@ Bookshelf Label
color: #fce3a6; color: #fce3a6;
} }
.cover-bg { .cover-bg {
width: calc(100% + 40px); width: calc(100% + 40px);
height: calc(100% + 40px); height: calc(100% + 40px);
+14 -1
View File
@@ -52,4 +52,17 @@
text-indent: 0px !important; text-indent: 0px !important;
text-align: start !important; text-align: start !important;
text-align-last: start !important; text-align-last: start !important;
} }
.default-style.less-spacing p {
margin-block-start: 0;
}
.default-style.less-spacing ul {
margin-block-start: 0;
}
.default-style.less-spacing ol {
margin-block-start: 0;
}
+9 -2
View File
@@ -446,7 +446,7 @@ trix-editor .attachment__metadata .attachment__size {
} }
.trix-content { .trix-content {
line-height: 1.5; line-height: inherit;
} }
.trix-content * { .trix-content * {
@@ -455,6 +455,13 @@ trix-editor .attachment__metadata .attachment__size {
padding: 0; padding: 0;
} }
.trix-content p {
box-sizing: border-box;
margin-top: 0;
margin-bottom: 0.5em;
padding: 0;
}
.trix-content h1 { .trix-content h1 {
font-size: 1.2em; font-size: 1.2em;
line-height: 1.2; line-height: 1.2;
@@ -560,4 +567,4 @@ trix-editor .attachment__metadata .attachment__size {
.trix-content .attachment-gallery.attachment-gallery--4 .attachment { .trix-content .attachment-gallery.attachment-gallery--4 .attachment {
flex-basis: 50%; flex-basis: 50%;
max-width: 50%; max-width: 50%;
} }
+1
View File
@@ -99,6 +99,7 @@ export default {
this.$store.commit('showEditModal', libraryItem) this.$store.commit('showEditModal', libraryItem)
}, },
editEpisode({ libraryItem, episode }) { editEpisode({ libraryItem, episode }) {
this.$store.commit('setEpisodeTableEpisodeIds', [episode.id])
this.$store.commit('setSelectedLibraryItem', libraryItem) this.$store.commit('setSelectedLibraryItem', libraryItem)
this.$store.commit('globals/setSelectedEpisode', episode) this.$store.commit('globals/setSelectedEpisode', episode)
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true) this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
+13
View File
@@ -19,6 +19,14 @@
</div> </div>
<div v-else-if="!totalShelves && initialized" class="w-full py-16"> <div v-else-if="!totalShelves && initialized" class="w-full py-16">
<p class="text-xl text-center">{{ emptyMessage }}</p> <p class="text-xl text-center">{{ emptyMessage }}</p>
<div v-if="entityName === 'collections' || entityName === 'playlists'" class="flex justify-center mt-4">
{{ emptyMessageHelp }}
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
<a href="https://www.audiobookshelf.org/guides/collections" target="_blank" class="inline-flex">
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
</a>
</ui-tooltip>
</div>
<!-- Clear filter only available on Library bookshelf --> <!-- Clear filter only available on Library bookshelf -->
<div v-if="entityName === 'items'" class="flex justify-center mt-2"> <div v-if="entityName === 'items'" class="flex justify-center mt-2">
<ui-btn v-if="hasFilter" color="primary" @click="clearFilter">{{ $strings.ButtonClearFilter }}</ui-btn> <ui-btn v-if="hasFilter" color="primary" @click="clearFilter">{{ $strings.ButtonClearFilter }}</ui-btn>
@@ -109,6 +117,11 @@ export default {
} }
return this.$strings.MessageNoResults return this.$strings.MessageNoResults
}, },
emptyMessageHelp() {
if (this.page === 'collections') return this.$strings.MessageBookshelfNoCollectionsHelp
if (this.page === 'playlists') return this.$strings.MessageNoUserPlaylistsHelp
return ''
},
entityName() { entityName() {
if (!this.page) return 'items' if (!this.page) return 'items'
return this.page return this.page
@@ -394,7 +394,8 @@ export default {
{ {
src: this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png', true) src: this.$store.getters['globals/getLibraryItemCoverSrc'](this.streamLibraryItem, '/Logo.png', true)
} }
] ],
chapterInfo
}) })
console.log('Set media session metadata', navigator.mediaSession.metadata) console.log('Set media session metadata', navigator.mediaSession.metadata)
+1 -1
View File
@@ -24,7 +24,7 @@
</div> </div>
</div> </div>
<div class="w-full max-h-12 overflow-hidden"> <div class="w-full max-h-12 overflow-hidden">
<p class="text-gray-500 text-xs">{{ book.description }}</p> <p class="text-gray-500 text-xs">{{ book.descriptionPlain }}</p>
</div> </div>
</div> </div>
<div v-else class="px-4 flex-grow"> <div v-else class="px-4 flex-grow">
+2 -8
View File
@@ -31,15 +31,8 @@
<p cy-id="placeholderAuthorText" aria-hidden="true" class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'em' }">{{ authorCleaned }}</p> <p cy-id="placeholderAuthorText" aria-hidden="true" class="text-center" style="color: rgb(247 223 187); opacity: 0.75" :style="{ fontSize: authorFontSize + 'em' }">{{ authorCleaned }}</p>
</div> </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 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }" style="background-color: #78350f">
<p :style="{ fontSize: 0.8 + 'em' }">#{{ 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 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }" style="background-color: #cd9d49dd">
<p :style="{ fontSize: 0.8 + 'em' }">{{ booksInSeries }}</p>
</div>
<!-- No progress shown for podcasts (unless showing podcast episode) --> <!-- No progress shown for podcasts (unless showing podcast episode) -->
<div cy-id="progressBar" v-if="!isPodcast || episodeProgress" class="absolute bottom-0 left-0 h-1e shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: coverWidth * userProgressPercent + 'px' }"></div> <div cy-id="progressBar" v-if="!isPodcast || episodeProgress" class="absolute bottom-0 left-0 h-1e max-w-full z-20 rounded-b box-shadow-progressbar" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: coverWidth * userProgressPercent + 'px' }"></div>
<!-- Overlay is not shown if collapsing series in library --> <!-- Overlay is not shown if collapsing series in library -->
<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="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">
@@ -244,6 +237,7 @@ export default {
return this.mediaMetadata.series return this.mediaMetadata.series
}, },
seriesName() { seriesName() {
if (this.collapsedSeries?.name) return this.collapsedSeries.name
return this.series?.name || null return this.series?.name || null
}, },
seriesSequence() { seriesSequence() {
+1 -1
View File
@@ -10,7 +10,7 @@
<p :style="{ fontSize: 0.8 + 'em' }" role="status" :aria-label="$strings.LabelNumberOfBooks">{{ books.length }}</p> <p :style="{ fontSize: 0.8 + 'em' }" role="status" :aria-label="$strings.LabelNumberOfBooks">{{ books.length }}</p>
</div> </div>
<div cy-id="seriesProgressBar" v-if="seriesPercentInProgress > 0" class="absolute bottom-0 left-0 h-1e 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-1e shadow-sm max-w-full z-10 rounded-b w-full box-shadow-progressbar" :class="isSeriesFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: seriesPercentInProgress * 100 + '%' }" />
<div cy-id="hoveringDisplayTitle" v-if="hasValidCovers" aria-hidden="true" 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: '1em' }"> <div cy-id="hoveringDisplayTitle" v-if="hasValidCovers" aria-hidden="true" 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: '1em' }">
<p :style="{ fontSize: 1.2 + 'em' }">{{ displayTitle }}</p> <p :style="{ fontSize: 1.2 + 'em' }">{{ displayTitle }}</p>
@@ -1,7 +1,7 @@
<template> <template>
<div ref="wrapper" class="relative ml-4 sm:ml-8" v-click-outside="clickOutside"> <div ref="wrapper" class="relative ml-4 sm:ml-8" v-click-outside="clickOutside">
<div class="flex items-center justify-center text-gray-300 cursor-pointer h-full" @mousedown.prevent @mouseup.prevent @click="setShowMenu(true)"> <div class="flex items-center justify-center text-gray-300 cursor-pointer h-full" @mousedown.prevent @mouseup.prevent @click="setShowMenu(true)">
<span class="text-gray-200 text-sm sm:text-base">{{ playbackRate.toFixed(1) }}<span class="text-base">x</span></span> <span class="text-gray-200 text-sm sm:text-base">{{ playbackRateDisplay }}<span class="text-base">x</span></span>
</div> </div>
<div v-show="showMenu" class="absolute -top-[5.5rem] z-20 bg-bg border-black-200 border shadow-xl rounded-lg" :style="{ left: menuLeft + 'px' }"> <div v-show="showMenu" class="absolute -top-[5.5rem] z-20 bg-bg border-black-200 border shadow-xl rounded-lg" :style="{ left: menuLeft + 'px' }">
<div class="absolute -bottom-1.5 right-0 w-full flex justify-center" :style="{ left: arrowLeft + 'px' }"> <div class="absolute -bottom-1.5 right-0 w-full flex justify-center" :style="{ left: arrowLeft + 'px' }">
@@ -19,7 +19,7 @@
<div class="w-full py-1 px-1"> <div class="w-full py-1 px-1">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<ui-icon-btn :disabled="!canDecrement" icon="remove" @click="decrement" /> <ui-icon-btn :disabled="!canDecrement" icon="remove" @click="decrement" />
<p class="px-2 text-2xl sm:text-3xl">{{ playbackRate }}<span class="text-2xl">x</span></p> <p class="px-2 text-2xl sm:text-3xl">{{ playbackRateDisplay }}<span class="text-2xl">x</span></p>
<ui-icon-btn :disabled="!canIncrement" icon="add" @click="increment" /> <ui-icon-btn :disabled="!canIncrement" icon="add" @click="increment" />
</div> </div>
</div> </div>
@@ -33,6 +33,10 @@ export default {
value: { value: {
type: [String, Number], type: [String, Number],
default: 1 default: 1
},
playbackRateIncrementDecrement: {
type: Number,
default: 0.1
} }
}, },
data() { data() {
@@ -58,10 +62,17 @@ export default {
return [0.5, 1, 1.2, 1.5, 2] return [0.5, 1, 1.2, 1.5, 2]
}, },
canIncrement() { canIncrement() {
return this.playbackRate + 0.1 <= this.MAX_SPEED return this.playbackRate + this.playbackRateIncrementDecrement <= this.MAX_SPEED
}, },
canDecrement() { canDecrement() {
return this.playbackRate - 0.1 >= this.MIN_SPEED return this.playbackRate - this.playbackRateIncrementDecrement >= this.MIN_SPEED
},
playbackRateDisplay() {
if (this.playbackRateIncrementDecrement == 0.05) return this.playbackRate.toFixed(2)
// For 0.1 increment: Only show 2 decimal places if the playback rate is 2 decimals
const numDecimals = String(this.playbackRate).split('.')[1]?.length || 0
if (numDecimals <= 1) return this.playbackRate.toFixed(1)
return this.playbackRate.toFixed(2)
} }
}, },
methods: { methods: {
@@ -73,14 +84,14 @@ export default {
this.$nextTick(() => this.setShowMenu(false)) this.$nextTick(() => this.setShowMenu(false))
}, },
increment() { increment() {
if (this.playbackRate + 0.1 > this.MAX_SPEED) return if (this.playbackRate + this.playbackRateIncrementDecrement > this.MAX_SPEED) return
var newPlaybackRate = this.playbackRate + 0.1 var newPlaybackRate = this.playbackRate + this.playbackRateIncrementDecrement
this.playbackRate = Number(newPlaybackRate.toFixed(1)) this.playbackRate = Number(newPlaybackRate.toFixed(2))
}, },
decrement() { decrement() {
if (this.playbackRate - 0.1 < this.MIN_SPEED) return if (this.playbackRate - this.playbackRateIncrementDecrement < this.MIN_SPEED) return
var newPlaybackRate = this.playbackRate - 0.1 var newPlaybackRate = this.playbackRate - this.playbackRateIncrementDecrement
this.playbackRate = Number(newPlaybackRate.toFixed(1)) this.playbackRate = Number(newPlaybackRate.toFixed(2))
}, },
updateMenuPositions() { updateMenuPositions() {
if (!this.$refs.wrapper) return if (!this.$refs.wrapper) return
@@ -90,8 +90,8 @@
<div class="relative"> <div class="relative">
<ui-textarea-with-label :value="prettyFfprobeData" readonly :rows="30" class="text-xs" /> <ui-textarea-with-label :value="prettyFfprobeData" readonly :rows="30" class="text-xs" />
<button class="absolute top-4 right-4" :class="copiedToClipboard ? 'text-success' : 'text-white/50 hover:text-white/80'" @click.stop="copyFfprobeData"> <button class="absolute top-4 right-4" :class="hasCopied ? 'text-success' : 'text-gray-400 hover:text-white'" @click.stop="copyToClipboard">
<span class="material-symbols">{{ copiedToClipboard ? 'check' : 'content_copy' }}</span> <span class="material-symbols">{{ hasCopied ? 'done' : 'content_copy' }}</span>
</button> </button>
</div> </div>
</div> </div>
@@ -113,14 +113,13 @@ export default {
return { return {
probingFile: false, probingFile: false,
ffprobeData: null, ffprobeData: null,
copiedToClipboard: false hasCopied: null
} }
}, },
watch: { watch: {
show(newVal) { show(newVal) {
if (newVal) { if (newVal) {
this.ffprobeData = null this.ffprobeData = null
this.copiedToClipboard = false
this.probingFile = false this.probingFile = false
} }
} }
@@ -165,8 +164,13 @@ export default {
this.probingFile = false this.probingFile = false
}) })
}, },
async copyFfprobeData() { copyToClipboard() {
this.copiedToClipboard = await this.$copyToClipboard(this.prettyFfprobeData) clearTimeout(this.hasCopied)
this.$copyToClipboard(this.prettyFfprobeData).then((success) => {
this.hasCopied = setTimeout(() => {
this.hasCopied = null
}, 2000)
})
} }
}, },
mounted() {} mounted() {}
@@ -6,7 +6,7 @@
</div> </div>
</template> </template>
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh"> <div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<div v-if="show" class="w-full h-full py-4"> <div v-if="show" class="w-full h-full py-4">
<div class="w-full overflow-y-auto overflow-x-hidden max-h-96"> <div class="w-full overflow-y-auto overflow-x-hidden max-h-96">
<div class="flex px-8 items-center py-2"> <div class="flex px-8 items-center py-2">
@@ -11,9 +11,12 @@
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<ui-select-input v-model="jumpForwardAmount" :label="$strings.LabelJumpForwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpForwardAmount" /> <ui-select-input v-model="jumpForwardAmount" :label="$strings.LabelJumpForwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpForwardAmount" />
</div> </div>
<div class="flex items-center"> <div class="flex items-center mb-4">
<ui-select-input v-model="jumpBackwardAmount" :label="$strings.LabelJumpBackwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpBackwardAmount" /> <ui-select-input v-model="jumpBackwardAmount" :label="$strings.LabelJumpBackwardAmount" menuMaxHeight="250px" :items="jumpValues" @input="setJumpBackwardAmount" />
</div> </div>
<div class="flex items-center mb-4">
<ui-select-input v-model="playbackRateIncrementDecrement" :label="$strings.LabelPlaybackRateIncrementDecrement" menuMaxHeight="250px" :items="playbackRateIncrementDecrementValues" @input="setPlaybackRateIncrementDecrementAmount" />
</div>
</div> </div>
</modals-modal> </modals-modal>
</template> </template>
@@ -35,7 +38,9 @@ export default {
{ text: this.$getString('LabelTimeDurationXMinutes', ['5']), value: 300 } { text: this.$getString('LabelTimeDurationXMinutes', ['5']), value: 300 }
], ],
jumpForwardAmount: 10, jumpForwardAmount: 10,
jumpBackwardAmount: 10 jumpBackwardAmount: 10,
playbackRateIncrementDecrementValues: [0.1, 0.05],
playbackRateIncrementDecrement: 0.1
} }
}, },
computed: { computed: {
@@ -60,10 +65,15 @@ export default {
this.jumpBackwardAmount = val this.jumpBackwardAmount = val
this.$store.dispatch('user/updateUserSettings', { jumpBackwardAmount: val }) this.$store.dispatch('user/updateUserSettings', { jumpBackwardAmount: val })
}, },
setPlaybackRateIncrementDecrementAmount(val) {
this.playbackRateIncrementDecrement = val
this.$store.dispatch('user/updateUserSettings', { playbackRateIncrementDecrement: val })
},
settingsUpdated() { settingsUpdated() {
this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') this.useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack')
this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount') this.jumpForwardAmount = this.$store.getters['user/getUserSetting']('jumpForwardAmount')
this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount') this.jumpBackwardAmount = this.$store.getters['user/getUserSetting']('jumpBackwardAmount')
this.playbackRateIncrementDecrement = this.$store.getters['user/getUserSetting']('playbackRateIncrementDecrement')
} }
}, },
mounted() { mounted() {
+1 -1
View File
@@ -16,7 +16,7 @@
<template v-if="currentShare"> <template v-if="currentShare">
<div class="w-full py-2"> <div class="w-full py-2">
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelShareURL }}</label> <label class="px-1 text-sm font-semibold block">{{ $strings.LabelShareURL }}</label>
<ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" /> <ui-text-input v-model="currentShareUrl" show-copy readonly />
</div> </div>
<div class="w-full py-2 px-1"> <div class="w-full py-2 px-1">
<p v-if="currentShare.isDownloadable" class="text-sm mb-2">{{ $strings.LabelDownloadable }}</p> <p v-if="currentShare.isDownloadable" class="text-sm mb-2">{{ $strings.LabelDownloadable }}</p>
@@ -6,7 +6,7 @@
</div> </div>
</template> </template>
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh"> <div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<div v-if="show" class="w-full h-full"> <div v-if="show" class="w-full h-full">
<div class="py-4 px-4"> <div class="py-4 px-4">
<h1 v-if="!showBatchCollectionModal" class="text-2xl">{{ $strings.LabelAddToCollection }}</h1> <h1 v-if="!showBatchCollectionModal" class="text-2xl">{{ $strings.LabelAddToCollection }}</h1>
@@ -19,9 +19,20 @@
</template> </template>
</transition-group> </transition-group>
</div> </div>
<div v-if="!collections.length" class="flex h-32 items-center justify-center"> <div v-if="!collections.length" class="flex h-32 items-center justify-center text-center px-2">
<p class="text-xl">{{ $strings.MessageNoCollections }}</p> <div>
<p class="text-xl mb-2">{{ $strings.MessageNoCollections }}</p>
<div class="text-sm flex items-center justify-center text-gray-200">
<p>{{ $strings.MessageBookshelfNoCollectionsHelp }}</p>
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
<a href="https://www.audiobookshelf.org/guides/collections" target="_blank" class="inline-flex">
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
</a>
</ui-tooltip>
</div>
</div>
</div> </div>
<div class="w-full h-px bg-white bg-opacity-10" /> <div class="w-full h-px bg-white bg-opacity-10" />
<form @submit.prevent="submitCreateCollection"> <form @submit.prevent="submitCreateCollection">
<div class="flex px-4 py-2 items-center text-center border-b border-white border-opacity-10 text-white text-opacity-80"> <div class="flex px-4 py-2 items-center text-center border-b border-white border-opacity-10 text-white text-opacity-80">
@@ -1,5 +1,5 @@
<template> <template>
<div class="flex items-center px-4 py-2 justify-start relative hover:bg-bg" @mouseover="mouseover" @mouseleave="mouseleave"> <div class="flex items-center px-4 py-2 justify-start relative hover:bg-black-400" @mouseover="mouseover" @mouseleave="mouseleave">
<div v-if="isBookIncluded" class="absolute top-0 left-0 h-full w-1 bg-success z-10" /> <div v-if="isBookIncluded" class="absolute top-0 left-0 h-full w-1 bg-success z-10" />
<div class="w-20 max-w-20 text-center"> <div class="w-20 max-w-20 text-center">
<covers-collection-cover :book-items="books" :width="80" :height="40 * bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" /> <covers-collection-cover :book-items="books" :width="80" :height="40 * bookCoverAspectRatio" :book-cover-aspect-ratio="bookCoverAspectRatio" />
+7 -1
View File
@@ -196,6 +196,9 @@ export default {
methods: { methods: {
async goPrevBook() { async goPrevBook() {
if (this.currentBookshelfIndex - 1 < 0) return if (this.currentBookshelfIndex - 1 < 0) return
// Remove focus from active input
document.activeElement?.blur?.()
var prevBookId = this.bookshelfBookIds[this.currentBookshelfIndex - 1] var prevBookId = this.bookshelfBookIds[this.currentBookshelfIndex - 1]
this.processing = true this.processing = true
var prevBook = await this.$axios.$get(`/api/items/${prevBookId}?expanded=1`).catch((error) => { var prevBook = await this.$axios.$get(`/api/items/${prevBookId}?expanded=1`).catch((error) => {
@@ -215,6 +218,9 @@ export default {
}, },
async goNextBook() { async goNextBook() {
if (this.currentBookshelfIndex >= this.bookshelfBookIds.length - 1) return if (this.currentBookshelfIndex >= this.bookshelfBookIds.length - 1) return
// Remove focus from active input
document.activeElement?.blur?.()
this.processing = true this.processing = true
var nextBookId = this.bookshelfBookIds[this.currentBookshelfIndex + 1] var nextBookId = this.bookshelfBookIds[this.currentBookshelfIndex + 1]
var nextBook = await this.$axios.$get(`/api/items/${nextBookId}?expanded=1`).catch((error) => { var nextBook = await this.$axios.$get(`/api/items/${nextBookId}?expanded=1`).catch((error) => {
@@ -300,4 +306,4 @@ export default {
.tab.tab-selected { .tab.tab-selected {
height: 41px; height: 41px;
} }
</style> </style>
+2 -2
View File
@@ -94,9 +94,9 @@
<div v-if="selectedMatchOrig.description" class="flex items-center py-2"> <div v-if="selectedMatchOrig.description" class="flex items-center py-2">
<ui-checkbox v-model="selectedMatchUsage.description" checkbox-bg="bg" @input="checkboxToggled" /> <ui-checkbox v-model="selectedMatchUsage.description" checkbox-bg="bg" @input="checkboxToggled" />
<div class="flex-grow ml-4"> <div class="flex-grow ml-4">
<ui-textarea-with-label v-model="selectedMatch.description" :rows="3" :disabled="!selectedMatchUsage.description" :label="$strings.LabelDescription" /> <ui-rich-text-editor v-model="selectedMatch.description" :disabled="!selectedMatchUsage.description" :label="$strings.LabelDescription" />
<p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60"> <p v-if="mediaMetadata.description" class="text-xs ml-1 text-white text-opacity-60">
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('description', mediaMetadata.description)">{{ mediaMetadata.description.substr(0, 100) + (mediaMetadata.description.length > 100 ? '...' : '') }}</a> {{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('description', mediaMetadata.description)">{{ mediaMetadata.descriptionPlain.substr(0, 100) + (mediaMetadata.descriptionPlain.length > 100 ? '...' : '') }}</a>
</p> </p>
</div> </div>
</div> </div>
@@ -6,7 +6,7 @@
</div> </div>
</template> </template>
<div ref="container" class="w-full rounded-lg bg-primary box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh"> <div ref="container" class="w-full rounded-lg bg-bg box-shadow-md overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
<div v-if="show" class="w-full h-full"> <div v-if="show" class="w-full h-full">
<div class="py-4 px-4"> <div class="py-4 px-4">
<h1 v-if="!isBatch" class="text-2xl">{{ $strings.LabelAddToPlaylist }}</h1> <h1 v-if="!isBatch" class="text-2xl">{{ $strings.LabelAddToPlaylist }}</h1>
@@ -19,8 +19,18 @@
</template> </template>
</transition-group> </transition-group>
</div> </div>
<div v-if="!playlists.length" class="flex h-32 items-center justify-center"> <div v-if="!playlists.length" class="flex h-32 items-center justify-center text-center px-2">
<p class="text-xl">{{ $strings.MessageNoUserPlaylists }}</p> <div>
<p class="text-xl mb-2">{{ $strings.MessageNoUserPlaylists }}</p>
<div class="text-sm flex items-center justify-center text-gray-200">
<p>{{ $strings.MessageNoUserPlaylistsHelp }}</p>
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
<a href="https://www.audiobookshelf.org/guides/collections" target="_blank" class="inline-flex">
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
</a>
</ui-tooltip>
</div>
</div>
</div> </div>
<div class="w-full h-px bg-white bg-opacity-10" /> <div class="w-full h-px bg-white bg-opacity-10" />
<form @submit.prevent="submitCreatePlaylist"> <form @submit.prevent="submitCreatePlaylist">
@@ -1,5 +1,5 @@
<template> <template>
<div class="flex items-center px-4 py-2 justify-start relative hover:bg-bg" @mouseover="mouseover" @mouseleave="mouseleave"> <div class="flex items-center px-4 py-2 justify-start relative hover:bg-black-400" @mouseover="mouseover" @mouseleave="mouseleave">
<div v-if="isItemIncluded" class="absolute top-0 left-0 h-full w-1 bg-success z-10" /> <div v-if="isItemIncluded" class="absolute top-0 left-0 h-full w-1 bg-success z-10" />
<div class="w-16 max-w-16 text-center"> <div class="w-16 max-w-16 text-center">
<covers-playlist-cover :items="items" :width="64" :height="64" /> <covers-playlist-cover :items="items" :width="64" :height="64" />
@@ -117,8 +117,12 @@ export default {
methods: { methods: {
async goPrevEpisode() { async goPrevEpisode() {
if (this.currentEpisodeIndex - 1 < 0) return if (this.currentEpisodeIndex - 1 < 0) return
// Remove focus from active input
document.activeElement?.blur?.()
const prevEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex - 1] const prevEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex - 1]
this.processing = true this.processing = true
const prevEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${prevEpisodeId}`).catch((error) => { const prevEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${prevEpisodeId}`).catch((error) => {
const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch episode' const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch episode'
this.$toast.error(errorMsg) this.$toast.error(errorMsg)
@@ -134,8 +138,12 @@ export default {
}, },
async goNextEpisode() { async goNextEpisode() {
if (this.currentEpisodeIndex >= this.episodeTableEpisodeIds.length - 1) return if (this.currentEpisodeIndex >= this.episodeTableEpisodeIds.length - 1) return
// Remove focus from active input
document.activeElement?.blur?.()
this.processing = true this.processing = true
const nextEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex + 1] const nextEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex + 1]
const nextEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${nextEpisodeId}`).catch((error) => { const nextEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${nextEpisodeId}`).catch((error) => {
const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch book' const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch book'
this.$toast.error(errorMsg) this.$toast.error(errorMsg)
@@ -16,7 +16,7 @@
</div> </div>
</div> </div>
<p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p> <p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p>
<div v-if="description" dir="auto" class="default-style" v-html="description" /> <div v-if="description" dir="auto" class="default-style less-spacing" v-html="description" />
<p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p> <p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p>
<div class="w-full h-px bg-white/5 my-4" /> <div class="w-full h-px bg-white/5 my-4" />
@@ -2,10 +2,10 @@
<div> <div>
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<div class="w-1/5 p-1"> <div class="w-1/5 p-1">
<ui-text-input-with-label v-model="newEpisode.season" :label="$strings.LabelSeason" /> <ui-text-input-with-label v-model="newEpisode.season" trim-whitespace :label="$strings.LabelSeason" />
</div> </div>
<div class="w-1/5 p-1"> <div class="w-1/5 p-1">
<ui-text-input-with-label v-model="newEpisode.episode" :label="$strings.LabelEpisode" /> <ui-text-input-with-label v-model="newEpisode.episode" trim-whitespace :label="$strings.LabelEpisode" />
</div> </div>
<div class="w-1/5 p-1"> <div class="w-1/5 p-1">
<ui-dropdown v-model="newEpisode.episodeType" :label="$strings.LabelEpisodeType" :items="episodeTypes" small /> <ui-dropdown v-model="newEpisode.episodeType" :label="$strings.LabelEpisodeType" :items="episodeTypes" small />
@@ -14,10 +14,10 @@
<ui-text-input-with-label v-model="pubDateInput" ref="pubdate" type="datetime-local" :label="$strings.LabelPubDate" @input="updatePubDate" /> <ui-text-input-with-label v-model="pubDateInput" ref="pubdate" type="datetime-local" :label="$strings.LabelPubDate" @input="updatePubDate" />
</div> </div>
<div class="w-full p-1"> <div class="w-full p-1">
<ui-text-input-with-label v-model="newEpisode.title" :label="$strings.LabelTitle" /> <ui-text-input-with-label v-model="newEpisode.title" :label="$strings.LabelTitle" trim-whitespace />
</div> </div>
<div class="w-full p-1"> <div class="w-full p-1">
<ui-textarea-with-label v-model="newEpisode.subtitle" :label="$strings.LabelSubtitle" :rows="3" /> <ui-textarea-with-label v-model="newEpisode.subtitle" :label="$strings.LabelSubtitle" :rows="3" trim-whitespace />
</div> </div>
<div class="w-full p-1"> <div class="w-full p-1">
<ui-rich-text-editor :label="$strings.LabelDescription" v-model="newEpisode.description" /> <ui-rich-text-editor :label="$strings.LabelDescription" v-model="newEpisode.description" />
@@ -10,9 +10,7 @@
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</p> <p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</p>
<div class="w-full relative"> <div class="w-full relative">
<ui-text-input :value="feedUrl" readonly /> <ui-text-input :value="feedUrl" readonly show-copy />
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feedUrl)">content_copy</span>
</div> </div>
<div v-if="currentFeed.meta" class="mt-5"> <div v-if="currentFeed.meta" class="mt-5">
@@ -160,9 +158,6 @@ export default {
this.processing = false this.processing = false
}) })
}, },
copyToClipboard(str) {
this.$copyToClipboard(str, this)
},
closeFeed() { closeFeed() {
this.processing = true this.processing = true
this.$axios this.$axios
@@ -5,8 +5,7 @@
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedGeneral }}</p> <p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedGeneral }}</p>
<div class="w-full relative"> <div class="w-full relative">
<ui-text-input :value="feedUrl" readonly /> <ui-text-input :value="feedUrl" readonly show-copy />
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feedUrl)">content_copy</span>
</div> </div>
<div v-if="feed.meta" class="mt-5"> <div v-if="feed.meta" class="mt-5">
@@ -74,13 +73,7 @@ export default {
feedUrl() { feedUrl() {
return this.feed ? `${window.origin}${this.$config.routerBasePath}${this.feed.feedUrl}` : '' return this.feed ? `${window.origin}${this.$config.routerBasePath}${this.feed.feedUrl}` : ''
} }
}, }
methods: {
copyToClipboard(str) {
this.$copyToClipboard(str, this)
}
},
mounted() {}
} }
</script> </script>
+6 -3
View File
@@ -2,7 +2,7 @@
<div class="w-full -mt-6"> <div class="w-full -mt-6">
<div class="w-full relative mb-1"> <div class="w-full relative mb-1">
<div class="absolute -top-10 lg:top-0 right-0 lg:right-2 flex items-center h-full"> <div class="absolute -top-10 lg:top-0 right-0 lg:right-2 flex items-center h-full">
<controls-playback-speed-control v-model="playbackRate" @input="setPlaybackRate" @change="playbackRateChanged" class="mx-2 block" /> <controls-playback-speed-control v-model="playbackRate" @input="setPlaybackRate" @change="playbackRateChanged" :playbackRateIncrementDecrement="playbackRateIncrementDecrement" class="mx-2 block" />
<ui-tooltip direction="bottom" :text="$strings.LabelVolume"> <ui-tooltip direction="bottom" :text="$strings.LabelVolume">
<controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden sm:block" /> <controls-volume-control ref="volumeControl" v-model="volume" @input="setVolume" class="mx-2 hidden sm:block" />
@@ -180,6 +180,9 @@ export default {
useChapterTrack() { useChapterTrack() {
const _useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') || false const _useChapterTrack = this.$store.getters['user/getUserSetting']('useChapterTrack') || false
return this.chapters.length ? _useChapterTrack : false return this.chapters.length ? _useChapterTrack : false
},
playbackRateIncrementDecrement() {
return this.$store.getters['user/getUserSetting']('playbackRateIncrementDecrement')
} }
}, },
methods: { methods: {
@@ -223,12 +226,12 @@ export default {
}, },
increasePlaybackRate() { increasePlaybackRate() {
if (this.playbackRate >= 10) return if (this.playbackRate >= 10) return
this.playbackRate = Number((this.playbackRate + 0.1).toFixed(1)) this.playbackRate = Number((this.playbackRate + this.playbackRateIncrementDecrement || 0.1).toFixed(2))
this.setPlaybackRate(this.playbackRate) this.setPlaybackRate(this.playbackRate)
}, },
decreasePlaybackRate() { decreasePlaybackRate() {
if (this.playbackRate <= 0.5) return if (this.playbackRate <= 0.5) return
this.playbackRate = Number((this.playbackRate - 0.1).toFixed(1)) this.playbackRate = Number((this.playbackRate - this.playbackRateIncrementDecrement || 0.1).toFixed(2))
this.setPlaybackRate(this.playbackRate) this.setPlaybackRate(this.playbackRate)
}, },
playbackRateChanged(playbackRate) { playbackRateChanged(playbackRate) {
+13 -3
View File
@@ -215,6 +215,10 @@ export default {
inputBlur() { inputBlur() {
if (!this.isFocused) return if (!this.isFocused) return
if (typeof this.textInput === 'string') {
this.textInput = this.textInput.trim()
}
setTimeout(() => { setTimeout(() => {
if (document.activeElement === this.$refs.input) { if (document.activeElement === this.$refs.input) {
return return
@@ -231,6 +235,11 @@ export default {
}, },
forceBlur() { forceBlur() {
this.isFocused = false this.isFocused = false
if (typeof this.textInput === 'string') {
this.textInput = this.textInput.trim()
}
if (this.textInput) this.submitForm() if (this.textInput) this.submitForm()
if (this.$refs.input) this.$refs.input.blur() if (this.$refs.input) this.$refs.input.blur()
}, },
@@ -289,11 +298,12 @@ export default {
this.selectedMenuItemIndex = null this.selectedMenuItemIndex = null
}, },
submitForm() { submitForm() {
if (!this.textInput) return if (!this.textInput || !this.textInput.trim?.()) return
this.textInput = this.textInput.trim()
const cleaned = this.textInput.trim()
const matchesItem = this.items.find((i) => { const matchesItem = this.items.find((i) => {
return i.name === cleaned return i.name === this.textInput
}) })
if (matchesItem) { if (matchesItem) {
+12 -39
View File
@@ -1,9 +1,9 @@
<template> <template>
<div class="default-style"> <div class="default-style">
<p v-if="label" class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': disabled }"> <p v-if="label" class="px-1 text-sm font-semibold" :class="{ 'text-gray-400': disabled }" style="margin-top: 0; margin-bottom: 0.125em">
{{ label }} {{ label }}
</p> </p>
<ui-vue-trix v-model="content" :config="config" :disabled-editor="disabled" @trix-file-accept="trixFileAccept" /> <ui-vue-trix ref="input" v-model="content" :disabled-editor="disabled" @trix-file-accept="trixFileAccept" />
</div> </div>
</template> </template>
@@ -12,7 +12,10 @@ export default {
props: { props: {
value: String, value: String,
label: String, label: String,
disabled: Boolean disabled: {
type: Boolean,
default: false
}
}, },
data() { data() {
return {} return {}
@@ -25,49 +28,19 @@ export default {
set(val) { set(val) {
this.$emit('input', val) this.$emit('input', val)
} }
},
config() {
return {
toolbar: {
getDefaultHTML: () => `<div class="trix-button-row">
<span class="trix-button-group trix-button-group--text-tools" data-trix-button-group="text-tools">
<button type="button" class="trix-button trix-button--icon trix-button--icon-bold" data-trix-attribute="bold" data-trix-key="b" title="${this.$strings.LabelFontBold}" tabindex="-1">${this.$strings.LabelFontBold}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-italic" data-trix-attribute="italic" data-trix-key="i" title="${this.$strings.LabelFontItalic}" tabindex="-1">${this.$strings.LabelFontItalic}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-strike" data-trix-attribute="strike" title="${this.$strings.LabelFontStrikethrough}" tabindex="-1">${this.$strings.LabelFontStrikethrough}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-link" data-trix-attribute="href" data-trix-action="link" data-trix-key="k" title="${this.$strings.LabelTextEditorLink}" tabindex="-1">${this.$strings.LabelTextEditorLink}</button>
</span>
<span class="trix-button-group trix-button-group--block-tools" data-trix-button-group="block-tools">
<button type="button" class="trix-button trix-button--icon trix-button--icon-bullet-list" data-trix-attribute="bullet" title="${this.$strings.LabelTextEditorBulletedList}" tabindex="-1">${this.$strings.LabelTextEditorBulletedList}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-number-list" data-trix-attribute="number" title="${this.$strings.LabelTextEditorNumberedList}" tabindex="-1">${this.$strings.LabelTextEditorNumberedList}</button>
</span>
<span class="trix-button-group-spacer"></span>
<span class="trix-button-group trix-button-group--history-tools" data-trix-button-group="history-tools">
<button type="button" class="trix-button trix-button--icon trix-button--icon-undo" data-trix-action="undo" data-trix-key="z" title="${this.$strings.LabelUndo}" tabindex="-1">${this.$strings.LabelUndo}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-redo" data-trix-action="redo" data-trix-key="shift+z" title="${this.$strings.LabelRedo}" tabindex="-1">${this.$strings.LabelRedo}</button>
</span>
</div>
<div class="trix-dialogs" data-trix-dialogs>
<div class="trix-dialog trix-dialog--link" data-trix-dialog="href" data-trix-dialog-attribute="href">
<div class="trix-dialog__link-fields">
<input type="url" name="href" class="trix-input trix-input--dialog" placeholder="" aria-label="URL" required data-trix-input>
<div class="trix-button-group">
<input type="button" class="trix-button trix-button--dialog" value="${this.$strings.LabelTextEditorLink}" data-trix-method="setAttribute">
<input type="button" class="trix-button trix-button--dialog" value="${this.$strings.LabelTextEditorUnlink}" data-trix-method="removeAttribute">
</div>
</div>
</div>
</div>`
}
}
} }
}, },
methods: { methods: {
trixFileAccept(e) { trixFileAccept(e) {
e.preventDefault() e.preventDefault()
},
blur() {
if (this.$refs.input && this.$refs.input.blur) {
this.$refs.input.blur()
}
} }
}, },
mounted() {}, mounted() {},
beforeDestroy() {} beforeDestroy() {}
} }
</script> </script>
+18 -9
View File
@@ -7,8 +7,8 @@
<div v-if="type === 'password' && isHovering" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center"> <div v-if="type === 'password' && isHovering" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center">
<span class="material-symbols text-gray-400 cursor-pointer text-lg" @click.stop.prevent="showPassword = !showPassword">{{ !showPassword ? 'visibility' : 'visibility_off' }}</span> <span class="material-symbols text-gray-400 cursor-pointer text-lg" @click.stop.prevent="showPassword = !showPassword">{{ !showPassword ? 'visibility' : 'visibility_off' }}</span>
</div> </div>
<div v-else-if="showCopy" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center"> <div v-else-if="showCopy" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center">
<span class="material-symbols text-gray-400 cursor-pointer text-lg" @click.stop.prevent="copyToClipboard">{{ !hasCopied ? 'content_copy' : 'done' }}</span> <span class="material-symbols cursor-pointer text-lg" :class="hasCopied ? 'text-success' : 'text-gray-400 hover:text-white'" @click.stop.prevent="copyToClipboard">{{ !hasCopied ? 'content_copy' : 'done' }}</span>
</div> </div>
</div> </div>
</template> </template>
@@ -40,14 +40,15 @@ export default {
showCopy: Boolean, showCopy: Boolean,
step: [String, Number], step: [String, Number],
min: [String, Number], min: [String, Number],
customInputClass: String customInputClass: String,
trimWhitespace: Boolean
}, },
data() { data() {
return { return {
showPassword: false, showPassword: false,
isHovering: false, isHovering: false,
isFocused: false, isFocused: false,
hasCopied: false, hasCopied: null,
isInvalidDate: false isInvalidDate: false
} }
}, },
@@ -62,7 +63,12 @@ export default {
}, },
classList() { classList() {
var _list = [] var _list = []
_list.push(`px-${this.paddingX}`) if (this.showCopy) {
_list.push('pl-3', 'pr-8')
} else {
_list.push(`px-${this.paddingX}`)
}
_list.push(`py-${this.paddingY}`) _list.push(`py-${this.paddingY}`)
if (this.noSpinner) _list.push('no-spinner') if (this.noSpinner) _list.push('no-spinner')
if (this.textCenter) _list.push('text-center') if (this.textCenter) _list.push('text-center')
@@ -80,11 +86,10 @@ export default {
}, },
methods: { methods: {
copyToClipboard() { copyToClipboard() {
if (this.hasCopied) return clearTimeout(this.hasCopied)
this.$copyToClipboard(this.inputValue).then((success) => { this.$copyToClipboard(this.inputValue).then((success) => {
this.hasCopied = success this.hasCopied = setTimeout(() => {
setTimeout(() => { this.hasCopied = null
this.hasCopied = false
}, 2000) }, 2000)
}) })
}, },
@@ -97,9 +102,13 @@ export default {
this.$emit('focus') this.$emit('focus')
}, },
blurred() { blurred() {
if (this.trimWhitespace && typeof this.inputValue === 'string') {
this.inputValue = this.inputValue.trim()
}
this.isFocused = false this.isFocused = false
this.$emit('blur') this.$emit('blur')
}, },
change(e) { change(e) {
this.$emit('change', e.target.value) this.$emit('change', e.target.value)
}, },
+4 -2
View File
@@ -6,7 +6,7 @@
<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em> <em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em>
</label> </label>
</slot> </slot>
<ui-text-input :placeholder="placeholder || label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" class="w-full" :class="inputClass" @blur="inputBlurred" /> <ui-text-input :placeholder="placeholder || label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" :show-copy="showCopy" class="w-full" :class="inputClass" :trim-whitespace="trimWhitespace" @blur="inputBlurred" />
</div> </div>
</template> </template>
@@ -23,7 +23,9 @@ export default {
}, },
readonly: Boolean, readonly: Boolean,
disabled: Boolean, disabled: Boolean,
inputClass: String inputClass: String,
showCopy: Boolean,
trimWhitespace: Boolean
}, },
data() { data() {
return {} return {}
+80 -25
View File
@@ -1,6 +1,37 @@
<template> <template>
<div> <div>
<trix-editor :contenteditable="!disabledEditor" :class="['trix-content']" ref="trix" :input="computedId" :placeholder="placeholder" @trix-change="handleContentChange" @trix-initialize="handleInitialize" @trix-focus="processTrixFocus" @trix-blur="processTrixBlur" /> <trix-toolbar :id="toolbarId">
<div v-show="!disabledEditor" class="trix-button-row">
<span class="trix-button-group trix-button-group--text-tools" data-trix-button-group="text-tools">
<button type="button" class="trix-button trix-button--icon trix-button--icon-bold" data-trix-attribute="bold" data-trix-key="b" :title="$strings.LabelFontBold" tabindex="-1">{{ $strings.LabelFontBold }}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-italic" data-trix-attribute="italic" data-trix-key="i" :title="$strings.LabelFontItalic" tabindex="-1">{{ $strings.LabelFontItalic }}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-strike" data-trix-attribute="strike" :title="$strings.LabelFontStrikethrough" tabindex="-1">{{ $strings.LabelFontStrikethrough }}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-link" data-trix-attribute="href" data-trix-action="link" data-trix-key="k" :title="$strings.LabelTextEditorLink" tabindex="-1">{{ $strings.LabelTextEditorLink }}</button>
</span>
<span class="trix-button-group trix-button-group--block-tools" data-trix-button-group="block-tools">
<button type="button" class="trix-button trix-button--icon trix-button--icon-bullet-list" data-trix-attribute="bullet" :title="$strings.LabelTextEditorBulletedList" tabindex="-1">{{ $strings.LabelTextEditorBulletedList }}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-number-list" data-trix-attribute="number" :title="$strings.LabelTextEditorNumberedList" tabindex="-1">{{ $strings.LabelTextEditorNumberedList }}</button>
</span>
<span class="trix-button-group-spacer"></span>
<span class="trix-button-group trix-button-group--history-tools" data-trix-button-group="history-tools">
<button type="button" class="trix-button trix-button--icon trix-button--icon-undo" data-trix-action="undo" data-trix-key="z" :title="$strings.LabelUndo" tabindex="-1">{{ $strings.LabelUndo }}</button>
<button type="button" class="trix-button trix-button--icon trix-button--icon-redo" data-trix-action="redo" data-trix-key="shift+z" :title="$strings.LabelRedo" tabindex="-1">{{ $strings.LabelRedo }}</button>
</span>
</div>
<div class="trix-dialogs" data-trix-dialogs>
<div class="trix-dialog trix-dialog--link" data-trix-dialog="href" data-trix-dialog-attribute="href">
<div class="trix-dialog__link-fields">
<input type="url" name="href" class="trix-input trix-input--dialog" placeholder="" aria-label="URL" required data-trix-input />
<div class="trix-button-group">
<input type="button" class="trix-button trix-button--dialog" :value="$strings.LabelTextEditorLink" data-trix-method="setAttribute" />
<input type="button" class="trix-button trix-button--dialog" :value="$strings.LabelTextEditorUnlink" data-trix-method="removeAttribute" />
</div>
</div>
</div>
</div>
</trix-toolbar>
<trix-editor :toolbar="toolbarId" :contenteditable="!disabledEditor" :class="['trix-content']" ref="trix" :input="computedId" :placeholder="placeholder" @trix-change="handleContentChange" @trix-initialize="handleInitialize" @trix-focus="processTrixFocus" @trix-blur="processTrixBlur" />
<input type="hidden" :name="inputName" :id="computedId" :value="editorContent" /> <input type="hidden" :name="inputName" :id="computedId" :value="editorContent" />
</div> </div>
</template> </template>
@@ -14,6 +45,30 @@
import Trix from 'trix' import Trix from 'trix'
import '@/assets/trix.css' import '@/assets/trix.css'
function enableBreakParagraphOnReturn() {
// Trix works with divs by default, we want paragraphs instead
Trix.config.blockAttributes.default.tagName = 'p'
// Enable break paragraph on Enter (Shift + Enter will still create a line break)
Trix.config.blockAttributes.default.breakOnReturn = true
// Hack to fix buggy paragraph breaks
// Copied from https://github.com/basecamp/trix/issues/680#issuecomment-735742942
Trix.Block.prototype.breaksOnReturn = function () {
const attr = this.getLastAttribute()
const config = Trix.getBlockConfig(attr ? attr : 'default')
return config ? config.breakOnReturn : false
}
Trix.LineBreakInsertion.prototype.shouldInsertBlockBreak = function () {
if (this.block.hasAttributes() && this.block.isListItem() && !this.block.isEmpty()) {
return this.startLocation.offset > 0
} else {
return !this.shouldBreakFormattedBlock() ? this.breaksOnReturn : false
}
}
}
enableBreakParagraphOnReturn()
export default { export default {
name: 'vue-trix', name: 'vue-trix',
model: { model: {
@@ -134,6 +189,9 @@ export default {
* Compute a random id of hidden input * Compute a random id of hidden input
* when it haven't been specified. * when it haven't been specified.
*/ */
toolbarId() {
return `trix-toolbar-${this.generateId}`
},
generateId() { generateId() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
var r = (Math.random() * 16) | 0 var r = (Math.random() * 16) | 0
@@ -223,13 +281,17 @@ export default {
decorateDisabledEditor(editorState) { decorateDisabledEditor(editorState) {
/** Disable toolbar and editor by pointer events styling */ /** Disable toolbar and editor by pointer events styling */
if (editorState) { if (editorState) {
this.$refs.trix.toolbarElement.style['pointer-events'] = 'none' this.$refs.trix.disabled = true
this.$refs.trix.contentEditable = false this.$refs.trix.contentEditable = false
this.$refs.trix.style['background'] = '#e9ecef' this.$refs.trix.style['pointer-events'] = 'none'
this.$refs.trix.style['background-color'] = '#444'
this.$refs.trix.style['color'] = '#bbb'
} else { } else {
this.$refs.trix.toolbarElement.style['pointer-events'] = 'unset' this.$refs.trix.disabled = false
this.$refs.trix.contentEditable = true
this.$refs.trix.style['pointer-events'] = 'unset' this.$refs.trix.style['pointer-events'] = 'unset'
this.$refs.trix.style['background'] = 'transparent' this.$refs.trix.style['background-color'] = ''
this.$refs.trix.style['color'] = ''
} }
}, },
overrideConfig(config) { overrideConfig(config) {
@@ -250,32 +312,15 @@ export default {
} }
return target return target
}, },
enableBreakParagraphOnReturn() { blur() {
// Trix works with divs by default, we want paragraphs instead if (this.$refs.trix && this.$refs.trix.blur) {
Trix.config.blockAttributes.default.tagName = 'p' this.$refs.trix.blur()
// Enable break paragraph on Enter (Shift + Enter will still create a line break)
Trix.config.blockAttributes.default.breakOnReturn = true
// Hack to fix buggy paragraph breaks
// Copied from https://github.com/basecamp/trix/issues/680#issuecomment-735742942
Trix.Block.prototype.breaksOnReturn = function () {
const attr = this.getLastAttribute()
const config = Trix.getBlockConfig(attr ? attr : 'default')
return config ? config.breakOnReturn : false
}
Trix.LineBreakInsertion.prototype.shouldInsertBlockBreak = function () {
if (this.block.hasAttributes() && this.block.isListItem() && !this.block.isEmpty()) {
return this.startLocation.offset > 0
} else {
return !this.shouldBreakFormattedBlock() ? this.breaksOnReturn : false
}
} }
} }
}, },
mounted() { mounted() {
/** Override editor configuration */ /** Override editor configuration */
this.overrideConfig(this.config) this.overrideConfig(this.config)
this.enableBreakParagraphOnReturn()
/** Check if editor read-only mode is required */ /** Check if editor read-only mode is required */
this.decorateDisabledEditor(this.disabledEditor) this.decorateDisabledEditor(this.disabledEditor)
this.$nextTick(() => { this.$nextTick(() => {
@@ -305,4 +350,14 @@ export default {
.trix_container .trix-content { .trix_container .trix-content {
background-color: white; background-color: white;
} }
trix-editor {
height: calc(4 * 1lh);
min-height: calc(4 * 1lh);
overflow-y: auto;
resize: vertical;
}
trix-editor * {
pointer-events: inherit;
}
</style> </style>
@@ -3,10 +3,10 @@
<form class="w-full h-full px-2 md:px-4 py-6" @submit.prevent="submitForm"> <form class="w-full h-full px-2 md:px-4 py-6" @submit.prevent="submitForm">
<div class="flex flex-wrap -mx-1"> <div class="flex flex-wrap -mx-1">
<div class="w-full md:w-1/2 px-1"> <div class="w-full md:w-1/2 px-1">
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" @input="handleInputChange" /> <ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" trim-whitespace @input="handleInputChange" />
</div> </div>
<div class="flex-grow px-1 mt-2 md:mt-0"> <div class="flex-grow px-1 mt-2 md:mt-0">
<ui-text-input-with-label ref="subtitleInput" v-model="details.subtitle" :label="$strings.LabelSubtitle" @input="handleInputChange" /> <ui-text-input-with-label ref="subtitleInput" v-model="details.subtitle" :label="$strings.LabelSubtitle" trim-whitespace @input="handleInputChange" />
</div> </div>
</div> </div>
@@ -26,7 +26,7 @@
</div> </div>
</div> </div>
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" @input="handleInputChange" /> <ui-rich-text-editor ref="descriptionInput" v-model="details.description" :label="$strings.LabelDescription" class="mt-2" @input="handleInputChange" />
<div class="flex flex-wrap mt-2 -mx-1"> <div class="flex flex-wrap mt-2 -mx-1">
<div class="w-full md:w-1/2 px-1"> <div class="w-full md:w-1/2 px-1">
@@ -42,19 +42,19 @@
<ui-multi-select ref="narratorsSelect" v-model="details.narrators" :label="$strings.LabelNarrators" :items="narrators" @input="handleInputChange" /> <ui-multi-select ref="narratorsSelect" v-model="details.narrators" :label="$strings.LabelNarrators" :items="narrators" @input="handleInputChange" />
</div> </div>
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0"> <div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
<ui-text-input-with-label ref="isbnInput" v-model="details.isbn" label="ISBN" @input="handleInputChange" /> <ui-text-input-with-label ref="isbnInput" v-model="details.isbn" label="ISBN" trim-whitespace @input="handleInputChange" />
</div> </div>
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0"> <div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
<ui-text-input-with-label ref="asinInput" v-model="details.asin" label="ASIN" @input="handleInputChange" /> <ui-text-input-with-label ref="asinInput" v-model="details.asin" label="ASIN" trim-whitespace @input="handleInputChange" />
</div> </div>
</div> </div>
<div class="flex flex-wrap mt-2 -mx-1"> <div class="flex flex-wrap mt-2 -mx-1">
<div class="w-full md:w-1/4 px-1"> <div class="w-full md:w-1/4 px-1">
<ui-text-input-with-label ref="publisherInput" v-model="details.publisher" :label="$strings.LabelPublisher" @input="handleInputChange" /> <ui-text-input-with-label ref="publisherInput" v-model="details.publisher" :label="$strings.LabelPublisher" trim-whitespace @input="handleInputChange" />
</div> </div>
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0"> <div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" @input="handleInputChange" /> <ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" trim-whitespace @input="handleInputChange" />
</div> </div>
<div class="flex-grow px-1 pt-6 mt-2 md:mt-0"> <div class="flex-grow px-1 pt-6 mt-2 md:mt-0">
<div class="flex justify-center"> <div class="flex justify-center">
+1
View File
@@ -124,6 +124,7 @@ export default {
this.updateSelectionMode(false) this.updateSelectionMode(false)
}, },
editEpisode({ libraryItem, episode }) { editEpisode({ libraryItem, episode }) {
this.$store.commit('setEpisodeTableEpisodeIds', [episode.id])
this.$store.commit('setSelectedLibraryItem', libraryItem) this.$store.commit('setSelectedLibraryItem', libraryItem)
this.$store.commit('globals/setSelectedEpisode', episode) this.$store.commit('globals/setSelectedEpisode', episode)
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true) this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
@@ -3,14 +3,14 @@
<form class="w-full h-full px-4 py-6" @submit.prevent="submitForm"> <form class="w-full h-full px-4 py-6" @submit.prevent="submitForm">
<div class="flex -mx-1"> <div class="flex -mx-1">
<div class="w-1/2 px-1"> <div class="w-1/2 px-1">
<ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" @input="handleInputChange" /> <ui-text-input-with-label ref="titleInput" v-model="details.title" :label="$strings.LabelTitle" trim-whitespace @input="handleInputChange" />
</div> </div>
<div class="flex-grow px-1"> <div class="flex-grow px-1">
<ui-text-input-with-label ref="authorInput" v-model="details.author" :label="$strings.LabelAuthor" @input="handleInputChange" /> <ui-text-input-with-label ref="authorInput" v-model="details.author" :label="$strings.LabelAuthor" trim-whitespace @input="handleInputChange" />
</div> </div>
</div> </div>
<ui-text-input-with-label ref="feedUrlInput" v-model="details.feedUrl" :label="$strings.LabelRSSFeedURL" class="mt-2" @input="handleInputChange" /> <ui-text-input-with-label ref="feedUrlInput" v-model="details.feedUrl" :label="$strings.LabelRSSFeedURL" trim-whitespace class="mt-2" @input="handleInputChange" />
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" @input="handleInputChange" /> <ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" :label="$strings.LabelDescription" class="mt-2" @input="handleInputChange" />
@@ -25,13 +25,13 @@
<div class="flex mt-2 -mx-1"> <div class="flex mt-2 -mx-1">
<div class="w-1/4 px-1"> <div class="w-1/4 px-1">
<ui-text-input-with-label ref="releaseDateInput" v-model="details.releaseDate" :label="$strings.LabelReleaseDate" @input="handleInputChange" /> <ui-text-input-with-label ref="releaseDateInput" v-model="details.releaseDate" :label="$strings.LabelReleaseDate" trim-whitespace @input="handleInputChange" />
</div> </div>
<div class="w-1/4 px-1"> <div class="w-1/4 px-1">
<ui-text-input-with-label ref="itunesIdInput" v-model="details.itunesId" label="iTunes ID" @input="handleInputChange" /> <ui-text-input-with-label ref="itunesIdInput" v-model="details.itunesId" label="iTunes ID" trim-whitespace @input="handleInputChange" />
</div> </div>
<div class="w-1/4 px-1"> <div class="w-1/4 px-1">
<ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" @input="handleInputChange" /> <ui-text-input-with-label ref="languageInput" v-model="details.language" :label="$strings.LabelLanguage" trim-whitespace @input="handleInputChange" />
</div> </div>
<div class="flex-grow px-1 pt-6"> <div class="flex-grow px-1 pt-6">
<div class="flex justify-center"> <div class="flex justify-center">
+1 -1
View File
@@ -1,6 +1,6 @@
const pkg = require('./package.json') const pkg = require('./package.json')
const routerBasePath = process.env.ROUTER_BASE_PATH || '/audiobookshelf' const routerBasePath = process.env.ROUTER_BASE_PATH ?? '/audiobookshelf'
const serverHostUrl = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:3333' const serverHostUrl = process.env.NODE_ENV === 'production' ? '' : 'http://localhost:3333'
const serverPaths = ['api/', 'public/', 'hls/', 'auth/', 'feed/', 'status', 'login', 'logout', 'init'] const serverPaths = ['api/', 'public/', 'hls/', 'auth/', 'feed/', 'status', 'login', 'logout', 'init']
const proxy = Object.fromEntries(serverPaths.map((path) => [`${routerBasePath}/${path}`, { target: process.env.NODE_ENV !== 'production' ? serverHostUrl : '/' }])) const proxy = Object.fromEntries(serverPaths.map((path) => [`${routerBasePath}/${path}`, { target: process.env.NODE_ENV !== 'production' ? serverHostUrl : '/' }]))
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "audiobookshelf-client", "name": "audiobookshelf-client",
"version": "2.18.0", "version": "2.19.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "audiobookshelf-client", "name": "audiobookshelf-client",
"version": "2.18.0", "version": "2.19.1",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@nuxtjs/axios": "^5.13.6", "@nuxtjs/axios": "^5.13.6",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf-client", "name": "audiobookshelf-client",
"version": "2.18.0", "version": "2.19.1",
"buildNumber": 1, "buildNumber": 1,
"description": "Self-hosted audiobook and podcast client", "description": "Self-hosted audiobook and podcast client",
"main": "index.js", "main": "index.js",
+1 -4
View File
@@ -414,11 +414,8 @@ export default {
const audioEl = this.audioEl || document.createElement('audio') const audioEl = this.audioEl || document.createElement('audio')
var src = audioTrack.contentUrl + `?token=${this.userToken}` var src = audioTrack.contentUrl + `?token=${this.userToken}`
if (this.$isDev) {
src = `${process.env.serverUrl}${src}`
}
audioEl.src = src audioEl.src = `${process.env.serverUrl}${src}`
audioEl.id = 'chapter-audio' audioEl.id = 'chapter-audio'
document.body.appendChild(audioEl) document.body.appendChild(audioEl)
+97 -5
View File
@@ -22,7 +22,7 @@
<div v-if="openMapOptions" class="flex flex-wrap"> <div v-if="openMapOptions" class="flex flex-wrap">
<div v-if="!isPodcastLibrary && !isMapAppend" class="flex items-center px-4 h-18 w-1/2"> <div v-if="!isPodcastLibrary && !isMapAppend" class="flex items-center px-4 h-18 w-1/2">
<ui-checkbox v-model="selectedBatchUsage.subtitle" /> <ui-checkbox v-model="selectedBatchUsage.subtitle" />
<ui-text-input-with-label ref="subtitleInput" v-model="batchDetails.subtitle" :disabled="!selectedBatchUsage.subtitle" :label="$strings.LabelSubtitle" class="mb-5 ml-4" /> <ui-text-input-with-label ref="subtitleInput" v-model="batchDetails.subtitle" :disabled="!selectedBatchUsage.subtitle" :label="$strings.LabelSubtitle" trim-whitespace class="mb-5 ml-4" />
</div> </div>
<div v-if="!isPodcastLibrary" class="flex items-center px-4 h-18 w-1/2"> <div v-if="!isPodcastLibrary" class="flex items-center px-4 h-18 w-1/2">
<ui-checkbox v-model="selectedBatchUsage.authors" /> <ui-checkbox v-model="selectedBatchUsage.authors" />
@@ -31,7 +31,7 @@
</div> </div>
<div v-if="!isPodcastLibrary && !isMapAppend" class="flex items-center px-4 h-18 w-1/2"> <div v-if="!isPodcastLibrary && !isMapAppend" class="flex items-center px-4 h-18 w-1/2">
<ui-checkbox v-model="selectedBatchUsage.publishedYear" /> <ui-checkbox v-model="selectedBatchUsage.publishedYear" />
<ui-text-input-with-label ref="publishedYearInput" v-model="batchDetails.publishedYear" :disabled="!selectedBatchUsage.publishedYear" :label="$strings.LabelPublishYear" class="mb-5 ml-4" /> <ui-text-input-with-label ref="publishedYearInput" v-model="batchDetails.publishedYear" :disabled="!selectedBatchUsage.publishedYear" :label="$strings.LabelPublishYear" trim-whitespace class="mb-5 ml-4" />
</div> </div>
<div v-if="!isPodcastLibrary" class="flex items-center px-4 h-18 w-1/2"> <div v-if="!isPodcastLibrary" class="flex items-center px-4 h-18 w-1/2">
<ui-checkbox v-model="selectedBatchUsage.series" /> <ui-checkbox v-model="selectedBatchUsage.series" />
@@ -51,11 +51,11 @@
</div> </div>
<div v-if="!isPodcastLibrary && !isMapAppend" class="flex items-center px-4 h-18 w-1/2"> <div v-if="!isPodcastLibrary && !isMapAppend" class="flex items-center px-4 h-18 w-1/2">
<ui-checkbox v-model="selectedBatchUsage.publisher" /> <ui-checkbox v-model="selectedBatchUsage.publisher" />
<ui-text-input-with-label ref="publisherInput" v-model="batchDetails.publisher" :disabled="!selectedBatchUsage.publisher" :label="$strings.LabelPublisher" class="mb-5 ml-4" /> <ui-text-input-with-label ref="publisherInput" v-model="batchDetails.publisher" :disabled="!selectedBatchUsage.publisher" :label="$strings.LabelPublisher" trim-whitespace class="mb-5 ml-4" />
</div> </div>
<div v-if="!isMapAppend" class="flex items-center px-4 h-18 w-1/2"> <div v-if="!isMapAppend" class="flex items-center px-4 h-18 w-1/2">
<ui-checkbox v-model="selectedBatchUsage.language" /> <ui-checkbox v-model="selectedBatchUsage.language" />
<ui-text-input-with-label ref="languageInput" v-model="batchDetails.language" :disabled="!selectedBatchUsage.language" :label="$strings.LabelLanguage" class="mb-5 ml-4" /> <ui-text-input-with-label ref="languageInput" v-model="batchDetails.language" :disabled="!selectedBatchUsage.language" :label="$strings.LabelLanguage" trim-whitespace class="mb-5 ml-4" />
</div> </div>
<div v-if="!isMapAppend" class="flex items-center px-4 h-18 w-1/2"> <div v-if="!isMapAppend" class="flex items-center px-4 h-18 w-1/2">
<ui-checkbox v-model="selectedBatchUsage.explicit" /> <ui-checkbox v-model="selectedBatchUsage.explicit" />
@@ -86,7 +86,12 @@
</div> </div>
</div> </div>
<div class="w-full flex items-center justify-end p-4"> <div class="w-full flex items-center p-4 space-x-2">
<ui-btn small @click.stop="resetMapDetails">{{ $strings.ButtonReset }}</ui-btn>
<ui-tooltip direction="bottom" :text="$strings.MessageBatchEditPopulateMapDetailsAllHelp">
<ui-btn small :disabled="!hasSelectedBatchUsage" @click.stop="populateFromExisting()">{{ $strings.ButtonBatchEditPopulateFromExisting }}</ui-btn>
</ui-tooltip>
<div class="flex-grow" />
<ui-btn color="success" :disabled="!hasSelectedBatchUsage" :padding-x="8" small class="text-base" :loading="isProcessing" @click="mapBatchDetails">{{ $strings.ButtonApply }}</ui-btn> <ui-btn color="success" :disabled="!hasSelectedBatchUsage" :padding-x="8" small class="text-base" :loading="isProcessing" @click="mapBatchDetails">{{ $strings.ButtonApply }}</ui-btn>
</div> </div>
</div> </div>
@@ -97,6 +102,11 @@
<div class="flex justify-center flex-wrap"> <div class="flex justify-center flex-wrap">
<template v-for="libraryItem in libraryItemCopies"> <template v-for="libraryItem in libraryItemCopies">
<div :key="libraryItem.id" class="w-full max-w-3xl border border-black-300 p-6 -ml-px -mt-px"> <div :key="libraryItem.id" class="w-full max-w-3xl border border-black-300 p-6 -ml-px -mt-px">
<div class="flex items-center justify-end">
<ui-tooltip direction="bottom" :text="$strings.MessageBatchEditPopulateMapDetailsItemHelp">
<ui-btn small :disabled="!hasSelectedBatchUsage" @click="populateFromExisting(libraryItem.id)">{{ $strings.ButtonBatchEditPopulateMapDetails }}</ui-btn>
</ui-tooltip>
</div>
<widgets-book-details-edit v-if="libraryItem.mediaType === 'book'" :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" /> <widgets-book-details-edit v-if="libraryItem.mediaType === 'book'" :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" />
<widgets-podcast-details-edit v-else :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" /> <widgets-podcast-details-edit v-else :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" />
</div> </div>
@@ -228,6 +238,88 @@ export default {
} }
}, },
methods: { methods: {
resetMapDetails() {
this.blurBatchForm()
this.batchDetails = {
subtitle: null,
authors: null,
publishedYear: null,
series: [],
genres: [],
tags: [],
narrators: [],
publisher: null,
language: null,
explicit: false,
abridged: false
}
this.selectedBatchUsage = {
subtitle: false,
authors: false,
publishedYear: false,
series: false,
genres: false,
tags: false,
narrators: false,
publisher: false,
language: false,
explicit: false,
abridged: false
}
},
populateFromExisting(libraryItemId) {
this.blurBatchForm()
let libraryItemsToMap = this.libraryItemCopies
if (libraryItemId) {
libraryItemsToMap = this.libraryItemCopies.filter((li) => li.id === libraryItemId)
}
for (const key in this.selectedBatchUsage) {
if (!this.selectedBatchUsage[key]) continue
if (this.isMapAppend && !this.appendableKeys.includes(key)) continue
let existingValues = undefined
libraryItemsToMap.forEach((li) => {
if (key === 'tags') {
if (!existingValues) existingValues = []
li.media.tags.forEach((tag) => {
if (!existingValues.includes(tag)) {
existingValues.push(tag)
}
})
} else if (key === 'authors') {
if (!existingValues) existingValues = []
li.media.metadata[key].forEach((entity) => {
if (!existingValues.some((au) => au.id === entity.id)) {
existingValues.push({
id: entity.id,
name: entity.name
})
}
})
} else if (key === 'series') {
if (!existingValues) existingValues = []
li.media.metadata[key].forEach((entity) => {
if (!existingValues.includes(entity.name)) {
existingValues.push(entity.name)
}
})
} else if (key === 'genres' || key === 'narrators') {
if (!existingValues) existingValues = []
li.media.metadata[key].forEach((item) => {
if (!existingValues.includes(item)) {
existingValues.push(item)
}
})
} else if (existingValues === undefined) {
existingValues = li.media.metadata[key]
}
})
this.batchDetails[key] = existingValues
}
},
handleItemChange(itemChange) { handleItemChange(itemChange) {
if (!itemChange.hasChanges) { if (!itemChange.hasChanges) {
this.itemsWithChanges = this.itemsWithChanges.filter((id) => id !== itemChange.libraryItemId) this.itemsWithChanges = this.itemsWithChanges.filter((id) => id !== itemChange.libraryItemId)
+10 -1
View File
@@ -137,7 +137,16 @@ export default {
this.$toast.error(this.$strings.ToastFailedToLoadData) this.$toast.error(this.$strings.ToastFailedToLoadData)
return return
} }
this.feeds = data.feeds this.feeds = data.feeds.map((feed) => ({
...feed,
episodes: [...feed.episodes].sort((a, b) => {
if (!a.pubDate) return 1 // null dates sort to end
if (!b.pubDate) return -1
const dateA = new Date(a.pubDate)
const dateB = new Date(b.pubDate)
return dateA - dateB
})
}))
}, },
init() { init() {
this.loadFeeds() this.loadFeeds()
+1 -8
View File
@@ -14,11 +14,7 @@
<h1 class="text-xl pl-2">{{ username }}</h1> <h1 class="text-xl pl-2">{{ username }}</h1>
</div> </div>
<div v-if="userToken" class="flex text-xs mt-4"> <div v-if="userToken" class="flex text-xs mt-4">
<ui-text-input-with-label :label="$strings.LabelApiToken" :value="userToken" readonly /> <ui-text-input-with-label :label="$strings.LabelApiToken" :value="userToken" readonly show-copy />
<div class="px-1 mt-8 cursor-pointer" @click="copyToClipboard(userToken)">
<span class="material-symbols pl-2 text-base">content_copy</span>
</div>
</div> </div>
<div class="w-full h-px bg-white bg-opacity-10 my-2" /> <div class="w-full h-px bg-white bg-opacity-10 my-2" />
<div class="py-2"> <div class="py-2">
@@ -140,9 +136,6 @@ export default {
} }
}, },
methods: { methods: {
copyToClipboard(str) {
this.$copyToClipboard(str, this)
},
async init() { async init() {
this.listeningSessions = await this.$axios this.listeningSessions = await this.$axios
.$get(`/api/users/${this.user.id}/listening-sessions?page=0&itemsPerPage=10`) .$get(`/api/users/${this.user.id}/listening-sessions?page=0&itemsPerPage=10`)
+3 -3
View File
@@ -123,7 +123,8 @@
</div> </div>
<div class="my-4 w-full"> <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> <div ref="description" id="item-description" dir="auto" class="default-style less-spacing text-base text-gray-100 whitespace-pre-line mb-1" :class="{ 'show-full': showFullDescription }" v-html="description" />
<button v-if="isDescriptionClamped" class="py-0.5 flex items-center text-slate-300 hover:text-white" @click="showFullDescription = !showFullDescription">{{ showFullDescription ? $strings.ButtonReadLess : $strings.ButtonReadMore }} <span class="material-symbols text-xl pl-1" v-html="showFullDescription ? 'expand_less' : '&#xe313;'" /></button> <button v-if="isDescriptionClamped" class="py-0.5 flex items-center text-slate-300 hover:text-white" @click="showFullDescription = !showFullDescription">{{ showFullDescription ? $strings.ButtonReadLess : $strings.ButtonReadMore }} <span class="material-symbols text-xl pl-1" v-html="showFullDescription ? 'expand_less' : '&#xe313;'" /></button>
</div> </div>
@@ -804,8 +805,7 @@ export default {
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 4; -webkit-line-clamp: 4;
max-height: 6.25rem; max-height: calc(6 * 1lh);
transition: all 0.3s ease-in-out;
} }
#item-description.show-full { #item-description.show-full {
-webkit-line-clamp: unset; -webkit-line-clamp: unset;
+10 -3
View File
@@ -1,5 +1,5 @@
export default class AudioTrack { export default class AudioTrack {
constructor(track, userToken) { constructor(track, userToken, routerBasePath) {
this.index = track.index || 0 this.index = track.index || 0
this.startOffset = track.startOffset || 0 // Total time of all previous tracks this.startOffset = track.startOffset || 0 // Total time of all previous tracks
this.duration = track.duration || 0 this.duration = track.duration || 0
@@ -9,20 +9,27 @@ export default class AudioTrack {
this.metadata = track.metadata || {} this.metadata = track.metadata || {}
this.userToken = userToken this.userToken = userToken
this.routerBasePath = routerBasePath || ''
} }
/**
* Used for CastPlayer
*/
get fullContentUrl() { get fullContentUrl() {
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}` return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
} }
return `${window.location.origin}${this.contentUrl}?token=${this.userToken}` return `${window.location.origin}${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
} }
/**
* Used for LocalPlayer
*/
get relativeContentUrl() { get relativeContentUrl() {
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
return this.contentUrl + `?token=${this.userToken}` return `${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
} }
} }
+1 -1
View File
@@ -226,7 +226,7 @@ export default class PlayerHandler {
console.log('[PlayerHandler] Preparing Session', session) console.log('[PlayerHandler] Preparing Session', session)
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken)) var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken, this.ctx.$config.routerBasePath))
this.ctx.playerLoading = true this.ctx.playerLoading = true
this.isHlsTranscode = true this.isHlsTranscode = true
+1 -3
View File
@@ -128,12 +128,11 @@ Vue.prototype.$sanitizeSlug = (str) => {
return str return str
} }
Vue.prototype.$copyToClipboard = (str, ctx) => { Vue.prototype.$copyToClipboard = (str) => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (navigator.clipboard) { if (navigator.clipboard) {
navigator.clipboard.writeText(str).then( navigator.clipboard.writeText(str).then(
() => { () => {
if (ctx) ctx.$toast.success('Copied to clipboard')
resolve(true) resolve(true)
}, },
(err) => { (err) => {
@@ -152,7 +151,6 @@ Vue.prototype.$copyToClipboard = (str, ctx) => {
document.execCommand('copy') document.execCommand('copy')
document.body.removeChild(el) document.body.removeChild(el)
if (ctx) ctx.$toast.success('Copied to clipboard')
resolve(true) resolve(true)
} }
}) })
+1
View File
@@ -5,6 +5,7 @@ export const state = () => ({
orderDesc: false, orderDesc: false,
filterBy: 'all', filterBy: 'all',
playbackRate: 1, playbackRate: 1,
playbackRateIncrementDecrement: 0.1,
bookshelfCoverSize: 120, bookshelfCoverSize: 120,
collapseSeries: false, collapseSeries: false,
collapseBookSeries: false, collapseBookSeries: false,
+100 -2
View File
@@ -10,6 +10,7 @@
"ButtonApplyChapters": "Ужыць раздзелы", "ButtonApplyChapters": "Ужыць раздзелы",
"ButtonAuthors": "Аўтары", "ButtonAuthors": "Аўтары",
"ButtonBack": "Назад", "ButtonBack": "Назад",
"ButtonBatchEditPopulateFromExisting": "Запоўніць з існуючага",
"ButtonBrowseForFolder": "Знайсці тэчку", "ButtonBrowseForFolder": "Знайсці тэчку",
"ButtonCancel": "Адмяніць", "ButtonCancel": "Адмяніць",
"ButtonCancelEncode": "Адмяніць кадзіраванне", "ButtonCancelEncode": "Адмяніць кадзіраванне",
@@ -35,14 +36,18 @@
"ButtonForceReScan": "Прымусовае паўторнае сканаванне", "ButtonForceReScan": "Прымусовае паўторнае сканаванне",
"ButtonFullPath": "Поўны шлях", "ButtonFullPath": "Поўны шлях",
"ButtonHide": "Схаваць", "ButtonHide": "Схаваць",
"ButtonHome": "Галоўная",
"ButtonIssues": "Праблемы", "ButtonIssues": "Праблемы",
"ButtonJumpBackward": "Перайсці назад", "ButtonJumpBackward": "Перайсці назад",
"ButtonJumpForward": "Перайсці наперад", "ButtonJumpForward": "Перайсці наперад",
"ButtonLatest": "Апошняе",
"ButtonLibrary": "Бібліятэка", "ButtonLibrary": "Бібліятэка",
"ButtonLogout": "Выйсці", "ButtonLogout": "Выйсці",
"ButtonLookup": "", "ButtonLookup": "",
"ButtonManageTracks": "Кіраванне дарожкамі",
"ButtonMapChapterTitles": "Супаставіць назвы раздзелаў", "ButtonMapChapterTitles": "Супаставіць назвы раздзелаў",
"ButtonMatchAllAuthors": "Супадзенне ўсіх аўтараў", "ButtonMatchAllAuthors": "Супадзенне ўсіх аўтараў",
"ButtonMatchBooks": "Падбор кніг",
"ButtonNevermind": "Няважна", "ButtonNevermind": "Няважна",
"ButtonNext": "Далей", "ButtonNext": "Далей",
"ButtonNextChapter": "Наступны раздзел", "ButtonNextChapter": "Наступны раздзел",
@@ -71,6 +76,9 @@
"ButtonRemove": "Выдаліць", "ButtonRemove": "Выдаліць",
"ButtonRemoveAll": "Выдаліць усе", "ButtonRemoveAll": "Выдаліць усе",
"ButtonRemoveAllLibraryItems": "Выдаліць усе элементы бібліятэкі", "ButtonRemoveAllLibraryItems": "Выдаліць усе элементы бібліятэкі",
"ButtonRemoveFromContinueListening": "Выдаліць з Працягваць слухаць",
"ButtonRemoveFromContinueReading": "Выдаліць з Працягваць чытанне",
"ButtonRemoveSeriesFromContinueSeries": "Выдаліць серыю з Працягваць серыю",
"ButtonReset": "Скінуць", "ButtonReset": "Скінуць",
"ButtonResetToDefault": "Скінуць па змаўчанні", "ButtonResetToDefault": "Скінуць па змаўчанні",
"ButtonRestore": "Аднавіць", "ButtonRestore": "Аднавіць",
@@ -100,9 +108,14 @@
"ButtonUserEdit": "Рэдагаваць карыстальніка {0}", "ButtonUserEdit": "Рэдагаваць карыстальніка {0}",
"ButtonViewAll": "Прагледзець усе", "ButtonViewAll": "Прагледзець усе",
"ButtonYes": "Так", "ButtonYes": "Так",
"ErrorUploadFetchMetadataAPI": "Памылка пры атрыманні метададзеных",
"ErrorUploadFetchMetadataNoResults": "Не ўдалося атрымаць метададзеныя – паспрабуйце абнавіць назву і/або аўтара",
"ErrorUploadLacksTitle": "Павінна быць назва",
"HeaderAccount": "Уліковы запіс", "HeaderAccount": "Уліковы запіс",
"HeaderAddCustomMetadataProvider": "Дадаць карыстальніцкага пастаўшчыка метаданных", "HeaderAddCustomMetadataProvider": "Дадаць карыстальніцкага пастаўшчыка метаданных",
"HeaderAdvanced": "Дадаткова",
"HeaderAppriseNotificationSettings": "Налады апавяшчэнняў Apprise", "HeaderAppriseNotificationSettings": "Налады апавяшчэнняў Apprise",
"HeaderAudioTracks": "Аўдыядарожкі",
"HeaderAudiobookTools": "Сродкі кіравання файламі аўдыякніг", "HeaderAudiobookTools": "Сродкі кіравання файламі аўдыякніг",
"HeaderAuthentication": "Аўтэнтыфікацыя", "HeaderAuthentication": "Аўтэнтыфікацыя",
"HeaderBackups": "Рэзервовыя копіі", "HeaderBackups": "Рэзервовыя копіі",
@@ -112,6 +125,91 @@
"HeaderCollection": "Калекцыя", "HeaderCollection": "Калекцыя",
"HeaderCollectionItems": "Элементы калекцыі", "HeaderCollectionItems": "Элементы калекцыі",
"HeaderCover": "Вокладка", "HeaderCover": "Вокладка",
"HeaderCurrentDownloads": "Бягучыя загрузкі", "HeaderCurrentDownloads": "Бягучыя спампоўкі",
"HeaderCustomMessageOnLogin": "Карыстальніцкае паведамленне пры ўваходзе" "HeaderCustomMessageOnLogin": "Карыстальніцкае паведамленне пры ўваходзе",
"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": "Падрабязнасці адлюстравання",
"HeaderNewAccount": "Новы ўліковы запіс",
"HeaderNewLibrary": "Новая бібліятэка",
"HeaderNotificationCreate": "Стварыць апавяшчэнне",
"HeaderNotificationUpdate": "Абнавіць апавяшчэнне",
"HeaderNotifications": "Апавяшчэнні",
"HeaderOpenListeningSessions": "Адкрыць сеансы праслухоўвання",
"HeaderScheduleEpisodeDownloads": "Расклад аўтаматычных спамповак эпізодаў",
"HeaderSettings": "Налады",
"HeaderSettingsDisplay": "Дысплей",
"HeaderSettingsExperimental": "Эксперыментальныя функцыі",
"HeaderSettingsGeneral": "Агульныя",
"HeaderSettingsScanner": "Сканер",
"HeaderSettingsWebClient": "Вэб-кліент",
"HeaderStatsTop10Authors": "10 лепшых аўтараў",
"HeaderStatsTop5Genres": "5 лепшых жанраў",
"HeaderTableOfContents": "Змест",
"HeaderTools": "Інструменты",
"HeaderUpdateAccount": "Абнавіць уліковы запіс",
"LabelAccountType": "Тып уліковага запіса",
"LabelAccountTypeAdmin": "Адміністратар",
"LabelAccountTypeGuest": "Госць",
"LabelAccountTypeUser": "Карыстальнік",
"LabelAudioBitrate": "Бітрэйт аўдыё (напрыклад, 128к)",
"LabelAudioChannels": "Аўдыёканалы (1 або 2)",
"LabelAudioCodec": "Аўдыёкодэк",
"LabelAutoDownloadEpisodes": "Аўтаматычнае спампаванне эпізодаў",
"LabelBackupAudioFiles": "Рэзервовае капіраванне аўдыёфайлаў",
"LabelContinueListening": "Працягваць слухаць",
"LabelDownload": "Спампаваць",
"LabelDownloadNEpisodes": "Спампована {0} эпізодаў",
"LabelDownloadable": "Спампоўваецца",
"LabelEncodingBackupLocation": "Рэзервовая копія вашых арыгінальных аўдыёфайлаў будзе захавана ў:",
"LabelEncodingChaptersNotEmbedded": "Раздзелы не ўбудаваны ў шматдарожкавыя аўдыякнігі.",
"LabelEncodingFinishedM4B": "Гатовы файл M4B будзе змешчаны ў вашу тэчку з аўдыякнігамі па адрасе:",
"LabelEncodingInfoEmbedded": "Метаданыя будуць убудаваны ў аўдыядарожкі ўнутры вашай тэчкі з аўдыякнігамі.",
"LabelMaxEpisodesToDownload": "Максімальная колькасць эпізодаў для спампоўкі. Выкарыстоўвайце 0 для неабмежаванай колькасці.",
"LabelMaxEpisodesToDownloadPerCheck": "Максімальная колькасць новых эпізодаў для спампоўкі за праверку",
"LabelMaxEpisodesToKeepHelp": "Значэнне 0 не ўстанаўлівае максімальнага абмежавання. Пасля аўтаматычнай спампоўкі новага эпізоду будзе выдалены самы стары эпізод, калі ў вас больш за X эпізодаў. Пры кожнай новай спампоўцы будзе выдаляцца толькі 1 эпізод.",
"LabelPermissionsDownload": "Можна спампаваць",
"LabelReAddSeriesToContinueListening": "Дадаць серыю зноў у Працягваць слухаць",
"LabelShareDownloadableHelp": "Дазваляе карыстальнікам, якія маюць спасылку на доступ, спампаваць ZIP-файл элемента бібліятэкі.",
"LabelStatsAudioTracks": "Аўдыядарожкі",
"LabelTracks": "Дарожкі",
"MessageConfirmRemoveListeningSessions": "Вы ўпэўнены, што жадаеце выдаліць {0} сеансаў праслухоўвання?",
"MessageDownloadingEpisode": "Спампоўка эпізоду",
"MessageEpisodesQueuedForDownload": "{0} эпізод(аў) у чарзе для спампоўкі",
"MessageNoDownloadsInProgress": "Зараз няма актыўных спамповак",
"MessageNoDownloadsQueued": "Няма спамповак у чарзе",
"MessageNoListeningSessions": "Няма сеансаў праслухоўвання",
"MessageTaskDownloadingEpisodeDescription": "Спампоўка эпізоду \"{0}\"",
"NotificationOnEpisodeDownloadedDescription": "Выклікаецца, калі эпізод падкаста аўтаматычна спампоўваецца",
"ToastAccountUpdateSuccess": "Уліковы запіс абноўлены",
"ToastEpisodeDownloadQueueClearFailed": "Не ўдалося ачысціць чаргу",
"ToastEpisodeDownloadQueueClearSuccess": "Чарга спампоўкі эпізодаў ачышчана",
"ToastInvalidMaxEpisodesToDownload": "Няправільная максімальная колькасць эпізодаў для спампоўкі",
"ToastNewUserCreatedFailed": "Не ўдалося стварыць уліковы запіс: \"{0}\"",
"ToastNewUserCreatedSuccess": "Новы ўліковы запіс створаны",
"ToastUserPasswordMustChange": "Новы пароль не можа супадаць са старым",
"ToastUserRootRequireName": "Неабходна ўвесці імя карыстальніка адміністратара"
} }
+78 -6
View File
@@ -300,6 +300,7 @@
"LabelDiscover": "Objevit", "LabelDiscover": "Objevit",
"LabelDownload": "Stáhnout", "LabelDownload": "Stáhnout",
"LabelDownloadNEpisodes": "Stáhnout {0} epizody", "LabelDownloadNEpisodes": "Stáhnout {0} epizody",
"LabelDownloadable": "Ke stažení",
"LabelDuration": "Délka trvání", "LabelDuration": "Délka trvání",
"LabelDurationComparisonExactMatch": "(přesná shoda)", "LabelDurationComparisonExactMatch": "(přesná shoda)",
"LabelDurationComparisonLonger": "({0} delší)", "LabelDurationComparisonLonger": "({0} delší)",
@@ -588,6 +589,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", "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", "LabelSettingsTimeFormat": "Formát času",
"LabelShare": "Sdílet", "LabelShare": "Sdílet",
"LabelShareDownloadableHelp": "Umožňuje uživatelům s odkazem na sdílení stáhnout soubor zip.",
"LabelShareOpen": "Otevřít sdílení", "LabelShareOpen": "Otevřít sdílení",
"LabelShareURL": "Sdílet URL", "LabelShareURL": "Sdílet URL",
"LabelShowAll": "Zobrazit vše", "LabelShowAll": "Zobrazit vše",
@@ -822,6 +824,7 @@
"MessagePlaylistCreateFromCollection": "Vytvořit seznam skladeb z kolekce", "MessagePlaylistCreateFromCollection": "Vytvořit seznam skladeb z kolekce",
"MessagePleaseWait": "Čekejte prosím...", "MessagePleaseWait": "Čekejte prosím...",
"MessagePodcastHasNoRSSFeedForMatching": "Podcast nemá žádnou adresu URL kanálu RSS, kterou by mohl použít pro porovnávání", "MessagePodcastHasNoRSSFeedForMatching": "Podcast nemá žádnou adresu URL kanálu RSS, kterou by mohl použít pro porovnávání",
"MessagePodcastSearchField": "Zadejte hledaný pojem pro RSS feed URL",
"MessageQuickEmbedInProgress": "Probíhá rychlé vkládání", "MessageQuickEmbedInProgress": "Probíhá rychlé vkládání",
"MessageQuickEmbedQueue": "Zařazeno do fronty pro rychlé vložení ({0} ve frontě)", "MessageQuickEmbedQueue": "Zařazeno do fronty pro rychlé vložení ({0} ve frontě)",
"MessageQuickMatchAllEpisodes": "Rychlá shoda všech epizod", "MessageQuickMatchAllEpisodes": "Rychlá shoda všech epizod",
@@ -834,6 +837,7 @@
"MessageResetChaptersConfirm": "Opravdu chcete resetovat kapitoly a vrátit zpět provedené změny?", "MessageResetChaptersConfirm": "Opravdu chcete resetovat kapitoly a vrátit zpět provedené změny?",
"MessageRestoreBackupConfirm": "Opravdu chcete obnovit zálohu vytvořenou dne", "MessageRestoreBackupConfirm": "Opravdu chcete obnovit zálohu vytvořenou dne",
"MessageRestoreBackupWarning": "Obnovení zálohy přepíše celou databázi umístěnou v /config a obálku obrázků v /metadata/items & /metadata/authors.<br /><br />Backups nezmění žádné soubory ve složkách knihovny. Pokud jste povolili nastavení serveru pro ukládání obrázků obalu a metadat do složek knihovny, nebudou zálohovány ani přepsány.<br /><br />Všichni klienti používající váš server budou automaticky obnoveni.", "MessageRestoreBackupWarning": "Obnovení zálohy přepíše celou databázi umístěnou v /config a obálku obrázků v /metadata/items & /metadata/authors.<br /><br />Backups nezmění žádné soubory ve složkách knihovny. Pokud jste povolili nastavení serveru pro ukládání obrázků obalu a metadat do složek knihovny, nebudou zálohovány ani přepsány.<br /><br />Všichni klienti používající váš server budou automaticky obnoveni.",
"MessageScheduleLibraryScanNote": "Většině uživatelů se doporučuje ponechat tuto funkci vypnutou a ponechat zapnuté nastavení sledování složek. Sledování složek automaticky zjistí změny ve složkách vaší knihovny. Sledování složek nefunguje pro každý souborový systém (jako je NFS), takže místo toho lze použít plánované skenování knihoven.",
"MessageSearchResultsFor": "Výsledky hledání pro", "MessageSearchResultsFor": "Výsledky hledání pro",
"MessageSelected": "{0} vybráno", "MessageSelected": "{0} vybráno",
"MessageServerCouldNotBeReached": "Server je nedostupný", "MessageServerCouldNotBeReached": "Server je nedostupný",
@@ -843,7 +847,7 @@
"MessageShareURLWillBe": "Sdílené URL bude <strong>{0}</strong>", "MessageShareURLWillBe": "Sdílené URL bude <strong>{0}</strong>",
"MessageStartPlaybackAtTime": "Spustit přehrávání pro \"{0}\" v {1}?", "MessageStartPlaybackAtTime": "Spustit přehrávání pro \"{0}\" v {1}?",
"MessageTaskAudioFileNotWritable": "Nelze zapisovat do audio souboru \"{0}\"", "MessageTaskAudioFileNotWritable": "Nelze zapisovat do audio souboru \"{0}\"",
"MessageTaskCanceledByUser": "Task zrušen uživatelem", "MessageTaskCanceledByUser": "Příkaz zrušen uživatelem",
"MessageTaskDownloadingEpisodeDescription": "Stahování epizody \"{0}\"", "MessageTaskDownloadingEpisodeDescription": "Stahování epizody \"{0}\"",
"MessageTaskEmbeddingMetadata": "Vkládání metadat", "MessageTaskEmbeddingMetadata": "Vkládání metadat",
"MessageTaskEmbeddingMetadataDescription": "Vkládání metadat do audioknihy \"{0}\"", "MessageTaskEmbeddingMetadataDescription": "Vkládání metadat do audioknihy \"{0}\"",
@@ -857,7 +861,7 @@
"MessageTaskFailedToMoveM4bFile": "Přesunutí m4b souboru selhalo", "MessageTaskFailedToMoveM4bFile": "Přesunutí m4b souboru selhalo",
"MessageTaskFailedToWriteMetadataFile": "Zápis souboru metadat selhal", "MessageTaskFailedToWriteMetadataFile": "Zápis souboru metadat selhal",
"MessageTaskMatchingBooksInLibrary": "Párování knih v knihovně „{0}“", "MessageTaskMatchingBooksInLibrary": "Párování knih v knihovně „{0}“",
"MessageTaskNoFilesToScan": "Žádné soubory ke skenování", "MessageTaskNoFilesToScan": "Žádné soubory k prohledání",
"MessageTaskOpmlImport": "Import OPML", "MessageTaskOpmlImport": "Import OPML",
"MessageTaskOpmlImportDescription": "Vytváření podcastů z {0} RSS feedů", "MessageTaskOpmlImportDescription": "Vytváření podcastů z {0} RSS feedů",
"MessageTaskOpmlImportFeed": "Importní zdroj OPML", "MessageTaskOpmlImportFeed": "Importní zdroj OPML",
@@ -869,6 +873,7 @@
"MessageTaskOpmlImportFinished": "Přidáno {0} podcastů", "MessageTaskOpmlImportFinished": "Přidáno {0} podcastů",
"MessageTaskOpmlParseFailed": "Selhalo parsování OPML souboru", "MessageTaskOpmlParseFailed": "Selhalo parsování OPML souboru",
"MessageTaskOpmlParseFastFail": "Neplatný OPML soubor <opml> tag nenalezen NEBO <outline> tag nenalezen", "MessageTaskOpmlParseFastFail": "Neplatný OPML soubor <opml> tag nenalezen NEBO <outline> tag nenalezen",
"MessageTaskOpmlParseNoneFound": "Feed nebyl nalezen v OPML souboru",
"MessageTaskScanItemsAdded": "{0} přidáno", "MessageTaskScanItemsAdded": "{0} přidáno",
"MessageTaskScanItemsMissing": "{0} chybí", "MessageTaskScanItemsMissing": "{0} chybí",
"MessageTaskScanItemsUpdated": "{0} aktualizováno", "MessageTaskScanItemsUpdated": "{0} aktualizováno",
@@ -876,7 +881,7 @@
"MessageTaskScanningFileChanges": "Skenování změn souborů v \"{0}\"", "MessageTaskScanningFileChanges": "Skenování změn souborů v \"{0}\"",
"MessageTaskScanningLibrary": "Skenování \"{0}\" knihovny", "MessageTaskScanningLibrary": "Skenování \"{0}\" knihovny",
"MessageTaskTargetDirectoryNotWritable": "Do cílové složky nelze zapisovat", "MessageTaskTargetDirectoryNotWritable": "Do cílové složky nelze zapisovat",
"MessageThinking": "Přemýšlení...", "MessageThinking": "Přemýšlím...",
"MessageUploaderItemFailed": "Nahrávání selhalo", "MessageUploaderItemFailed": "Nahrávání selhalo",
"MessageUploaderItemSuccess": "Úspěšně nahráno!", "MessageUploaderItemSuccess": "Úspěšně nahráno!",
"MessageUploading": "Nahrávám...", "MessageUploading": "Nahrávám...",
@@ -892,7 +897,7 @@
"NoteRSSFeedPodcastAppsPubDate": "Upozornění: 1 nebo více epizod nemá datum vydání. Některé podcastové aplikace to vyžadují.", "NoteRSSFeedPodcastAppsPubDate": "Upozornění: 1 nebo více epizod nemá datum vydání. Některé podcastové aplikace to vyžadují.",
"NoteUploaderFoldersWithMediaFiles": "Se složkami s multimediálními soubory bude zacházeno jako se samostatnými položkami knihovny.", "NoteUploaderFoldersWithMediaFiles": "Se složkami s multimediálními soubory bude zacházeno jako se samostatnými položkami knihovny.",
"NoteUploaderOnlyAudioFiles": "Pokud nahráváte pouze zvukové soubory, bude s každým zvukovým souborem zacházeno jako se samostatnou audioknihou.", "NoteUploaderOnlyAudioFiles": "Pokud nahráváte pouze zvukové soubory, bude s každým zvukovým souborem zacházeno jako se samostatnou audioknihou.",
"NoteUploaderUnsupportedFiles": "Nepodporované soubory jsou ignorovány. Při výběru nebo přetažení složky jsou ostatní soubory, které nejsou ve složce položek, ignorovány.", "NoteUploaderUnsupportedFiles": "Nepodporované soubory jsou ignorovány. Při výběru nebo přetažení složky jsou ostatní soubory, které nejsou ve složce, ignorovány.",
"NotificationOnBackupCompletedDescription": "Spuštěno po dokončení zálohování", "NotificationOnBackupCompletedDescription": "Spuštěno po dokončení zálohování",
"NotificationOnBackupFailedDescription": "Spuštěno pokud zálohování selže", "NotificationOnBackupFailedDescription": "Spuštěno pokud zálohování selže",
"NotificationOnEpisodeDownloadedDescription": "Spuštěno při automatickém stažení epizody podcastu", "NotificationOnEpisodeDownloadedDescription": "Spuštěno při automatickém stažení epizody podcastu",
@@ -909,7 +914,7 @@
"StatsBooksFinishedThisYear": "Některé knihy dokončené tento rok…", "StatsBooksFinishedThisYear": "Některé knihy dokončené tento rok…",
"StatsBooksListenedTo": "knih poslechnuto", "StatsBooksListenedTo": "knih poslechnuto",
"StatsCollectionGrewTo": "Vaše kolekce knih se rozrostla na…", "StatsCollectionGrewTo": "Vaše kolekce knih se rozrostla na…",
"StatsSessions": "sezení", "StatsSessions": "sezóna",
"StatsSpentListening": "stráveno posloucháním", "StatsSpentListening": "stráveno posloucháním",
"StatsTopAuthor": "TOP AUTOR", "StatsTopAuthor": "TOP AUTOR",
"StatsTopAuthors": "TOP AUTOŘI", "StatsTopAuthors": "TOP AUTOŘI",
@@ -942,6 +947,8 @@
"ToastBackupUploadSuccess": "Záloha nahrána", "ToastBackupUploadSuccess": "Záloha nahrána",
"ToastBatchDeleteFailed": "Hromadné smazání selhalo", "ToastBatchDeleteFailed": "Hromadné smazání selhalo",
"ToastBatchDeleteSuccess": "Hromadné smazání proběhlo úspěšně", "ToastBatchDeleteSuccess": "Hromadné smazání proběhlo úspěšně",
"ToastBatchQuickMatchFailed": "Rychlá schoda dávky se nezdařila!",
"ToastBatchQuickMatchStarted": "Začala rychlá shoda {0} knih!",
"ToastBatchUpdateFailed": "Dávková aktualizace se nezdařila", "ToastBatchUpdateFailed": "Dávková aktualizace se nezdařila",
"ToastBatchUpdateSuccess": "Dávková aktualizace proběhla úspěšně", "ToastBatchUpdateSuccess": "Dávková aktualizace proběhla úspěšně",
"ToastBookmarkCreateFailed": "Vytvoření záložky se nezdařilo", "ToastBookmarkCreateFailed": "Vytvoření záložky se nezdařilo",
@@ -952,9 +959,12 @@
"ToastChaptersHaveErrors": "Kapitoly obsahují chyby", "ToastChaptersHaveErrors": "Kapitoly obsahují chyby",
"ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy", "ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy",
"ToastChaptersRemoved": "Kapitoly odstraněny", "ToastChaptersRemoved": "Kapitoly odstraněny",
"ToastChaptersUpdated": "Kapitola aktualizována",
"ToastCollectionItemsAddFailed": "Přidávání položek do kolekce selhalo",
"ToastCollectionRemoveSuccess": "Kolekce odstraněna", "ToastCollectionRemoveSuccess": "Kolekce odstraněna",
"ToastCollectionUpdateSuccess": "Kolekce aktualizována", "ToastCollectionUpdateSuccess": "Kolekce aktualizována",
"ToastCoverUpdateFailed": "Aktualizace obálky selhala", "ToastCoverUpdateFailed": "Aktualizace obálky selhala",
"ToastDateTimeInvalidOrIncomplete": "Datum a čas jsou chybné nebo nekompletní",
"ToastDeleteFileFailed": "Nepodařilo se smazat soubor", "ToastDeleteFileFailed": "Nepodařilo se smazat soubor",
"ToastDeleteFileSuccess": "Soubor smazán", "ToastDeleteFileSuccess": "Soubor smazán",
"ToastDeviceAddFailed": "Přidání zařízení selhalo", "ToastDeviceAddFailed": "Přidání zařízení selhalo",
@@ -962,12 +972,18 @@
"ToastDeviceTestEmailFailed": "Odeslání testovacího emailu selhalo", "ToastDeviceTestEmailFailed": "Odeslání testovacího emailu selhalo",
"ToastDeviceTestEmailSuccess": "Testovací email byl odeslán", "ToastDeviceTestEmailSuccess": "Testovací email byl odeslán",
"ToastEmailSettingsUpdateSuccess": "Nastavení emailu aktualizována", "ToastEmailSettingsUpdateSuccess": "Nastavení emailu aktualizována",
"ToastEncodeCancelFailed": "Chyba zrušení kódování",
"ToastEncodeCancelSucces": "Kódování zrušeno",
"ToastEpisodeDownloadQueueClearFailed": "Vyčištění fronty selhalo", "ToastEpisodeDownloadQueueClearFailed": "Vyčištění fronty selhalo",
"ToastEpisodeDownloadQueueClearSuccess": "Fronta stahování epizod je prázdná",
"ToastEpisodeUpdateSuccess": "{0} epizod aktualizováno",
"ToastErrorCannotShare": "Na tomto zařízení nelze nativně sdílet", "ToastErrorCannotShare": "Na tomto zařízení nelze nativně sdílet",
"ToastFailedToLoadData": "Nepodařilo se načíst data", "ToastFailedToLoadData": "Nepodařilo se načíst data",
"ToastFailedToMatch": "Nepodařilo se spárovat",
"ToastFailedToShare": "Sdílení selhalo", "ToastFailedToShare": "Sdílení selhalo",
"ToastFailedToUpdate": "Aktualizace selhala", "ToastFailedToUpdate": "Aktualizace selhala",
"ToastInvalidImageUrl": "Neplatná URL obrázku", "ToastInvalidImageUrl": "Neplatná URL obrázku",
"ToastInvalidMaxEpisodesToDownload": "Neplatný maximální počet epizod ke stažení",
"ToastInvalidUrl": "Neplatná URL", "ToastInvalidUrl": "Neplatná URL",
"ToastItemCoverUpdateSuccess": "Obálka předmětu byl aktualizována", "ToastItemCoverUpdateSuccess": "Obálka předmětu byl aktualizována",
"ToastItemDeletedFailed": "Smazání položky selhalo", "ToastItemDeletedFailed": "Smazání položky selhalo",
@@ -985,28 +1001,84 @@
"ToastLibraryScanFailedToStart": "Nepodařilo se spustit kontrolu", "ToastLibraryScanFailedToStart": "Nepodařilo se spustit kontrolu",
"ToastLibraryScanStarted": "Kontrola knihovny spuštěna", "ToastLibraryScanStarted": "Kontrola knihovny spuštěna",
"ToastLibraryUpdateSuccess": "Knihovna \"{0}\" aktualizována", "ToastLibraryUpdateSuccess": "Knihovna \"{0}\" aktualizována",
"ToastMatchAllAuthorsFailed": "Nepodařilo se přiřadit všechny autory",
"ToastMetadataFilesRemovedError": "Při odstraňování souborů metadat.{0} došlo k chybě",
"ToastMetadataFilesRemovedNoneFound": "Žádná metadata.{0} nebyla nalezena v knihovně",
"ToastMetadataFilesRemovedNoneRemoved": "Žádná metadata.{0} počet odstraněných souborů",
"ToastMetadataFilesRemovedSuccess": "{0} metadata.{1} soubor odstraněn",
"ToastMustHaveAtLeastOnePath": "Musí mít minimálně jednu cestu",
"ToastNameEmailRequired": "Jméno a email jsou vyžadovány",
"ToastNameRequired": "Jméno je vyžadováno",
"ToastNewEpisodesFound": "{0} nových epizod bylo nalezeno",
"ToastNewUserCreatedFailed": "Chyba při vytváření účtu: \"{0}\"",
"ToastNewUserCreatedSuccess": "Vytvořen nový účet",
"ToastNewUserLibraryError": "Musíte vybrat alespoň jednu knihovnu",
"ToastNewUserPasswordError": "Musí mít heslo, pouze uživatel root může mít prázdné heslo",
"ToastNewUserTagError": "Musíte vybrat alespoň jeden tag",
"ToastNewUserUsernameError": "Zadej uživatelské jméno",
"ToastNoNewEpisodesFound": "Nebyla nalezena žádná nová epizoda",
"ToastNoRSSFeed": "Podcast nemá RSS Feed",
"ToastNoUpdatesNecessary": "Nejsou potřeba žádné aktualizace",
"ToastNotificationCreateFailed": "Chyba při vytváření upozornění",
"ToastNotificationDeleteFailed": "Chyba při odstranění upozornění",
"ToastNotificationFailedMaximum": "Maximální počet chybných pokusů >= 0",
"ToastNotificationQueueMaximum": "Maximální počet upozornění ve frontě musí být >= 0",
"ToastNotificationSettingsUpdateSuccess": "Nastavení upozornění aktualizováno",
"ToastNotificationTestTriggerFailed": "Chyba při spuštění testovacího upozornění",
"ToastNotificationTestTriggerSuccess": "Spuštěno testovací upozornění",
"ToastNotificationUpdateSuccess": "Upozornění aktualizováno",
"ToastPlaylistCreateFailed": "Vytvoření seznamu přehrávání se nezdařilo", "ToastPlaylistCreateFailed": "Vytvoření seznamu přehrávání se nezdařilo",
"ToastPlaylistCreateSuccess": "Seznam přehrávání vytvořen", "ToastPlaylistCreateSuccess": "Seznam přehrávání vytvořen",
"ToastPlaylistRemoveSuccess": "Seznam přehrávání odstraněn", "ToastPlaylistRemoveSuccess": "Seznam přehrávání odstraněn",
"ToastPlaylistUpdateSuccess": "Seznam přehrávání aktualizován", "ToastPlaylistUpdateSuccess": "Seznam přehrávání aktualizován",
"ToastPodcastCreateFailed": "Vytvoření podcastu se nezdařilo", "ToastPodcastCreateFailed": "Vytvoření podcastu se nezdařilo",
"ToastPodcastCreateSuccess": "Podcast byl úspěšně vytvořen", "ToastPodcastCreateSuccess": "Podcast byl úspěšně vytvořen",
"ToastPodcastGetFeedFailed": "Chyba při získání podcastového feedu",
"ToastPodcastNoEpisodesInFeed": "Žádné epizody nenalezeny v RSS feedu",
"ToastPodcastNoRssFeed": "Podcast nemá RSS feed",
"ToastProgressIsNotBeingSynced": "Progres není synchronizován, restartujte přehrávání",
"ToastProviderCreatedFailed": "Chyba při zadání poskytovatele",
"ToastProviderCreatedSuccess": "Nový poskytovatel přidán",
"ToastProviderNameAndUrlRequired": "Jméno a Url jsou vyžadovány",
"ToastProviderRemoveSuccess": "Poskytovatel odstraněn",
"ToastRSSFeedCloseFailed": "Nepodařilo se zavřít RSS kanál", "ToastRSSFeedCloseFailed": "Nepodařilo se zavřít RSS kanál",
"ToastRSSFeedCloseSuccess": "RSS kanál uzavřen", "ToastRSSFeedCloseSuccess": "RSS kanál uzavřen",
"ToastRemoveFailed": "Chyba při odstranění",
"ToastRemoveItemFromCollectionFailed": "Nepodařilo se odebrat položku z kolekce", "ToastRemoveItemFromCollectionFailed": "Nepodařilo se odebrat položku z kolekce",
"ToastRemoveItemFromCollectionSuccess": "Položka odstraněna z kolekce", "ToastRemoveItemFromCollectionSuccess": "Položka odstraněna z kolekce",
"ToastRemoveItemsWithIssuesFailed": "Chyba při odstranění položek v knihovně s chybami",
"ToastRemoveItemsWithIssuesSuccess": "Odstraněny položky knihovny s chybami",
"ToastRenameFailed": "Chyba při přejmenování",
"ToastRescanFailed": "Znovu prohledání selhalo z důvodu {0}",
"ToastRescanRemoved": "Znova skenování komplení - položka byla odsraněna",
"ToastRescanUpToDate": "Znovu prohledání kompletní - položka aktualizována",
"ToastRescanUpdated": "Znovu skenování komplení - položka byla aktualizována",
"ToastScanFailed": "Prohledání položek knihovny selhalo",
"ToastSelectAtLeastOneUser": "Vyberte alespoň jednoho uživatele",
"ToastSendEbookToDeviceFailed": "Odeslání e-knihy do zařízení se nezdařilo", "ToastSendEbookToDeviceFailed": "Odeslání e-knihy do zařízení se nezdařilo",
"ToastSendEbookToDeviceSuccess": "E-kniha odeslána do zařízení \"{0}\"", "ToastSendEbookToDeviceSuccess": "E-kniha odeslána do zařízení \"{0}\"",
"ToastSeriesUpdateFailed": "Aktualizace série se nezdařila", "ToastSeriesUpdateFailed": "Aktualizace série se nezdařila",
"ToastSeriesUpdateSuccess": "Aktualizace série byla úspěšná", "ToastSeriesUpdateSuccess": "Aktualizace série byla úspěšná",
"ToastServerSettingsUpdateSuccess": "Nastavení serveru aktualizováno", "ToastServerSettingsUpdateSuccess": "Nastavení serveru aktualizováno",
"ToastSessionCloseFailed": "Chyba při ukončení",
"ToastSessionDeleteFailed": "Nepodařilo se smazat relaci", "ToastSessionDeleteFailed": "Nepodařilo se smazat relaci",
"ToastSessionDeleteSuccess": "Relace smazána", "ToastSessionDeleteSuccess": "Relace smazána",
"ToastSleepTimerDone": "Uspání knížky ... zZzzZz",
"ToastSlugMustChange": "Slug (URL) obsahuje chybné znaky",
"ToastSlugRequired": "Slug (URL) je vyžadována",
"ToastSocketConnected": "Socket připojen", "ToastSocketConnected": "Socket připojen",
"ToastSocketDisconnected": "Socket odpojen", "ToastSocketDisconnected": "Socket odpojen",
"ToastSocketFailedToConnect": "Socket se nepodařilo připojit", "ToastSocketFailedToConnect": "Socket se nepodařilo připojit",
"ToastSortingPrefixesEmptyError": "Musí mít alespoň 1 třídicí předponu", "ToastSortingPrefixesEmptyError": "Musí mít alespoň 1 třídicí předponu",
"ToastSortingPrefixesUpdateSuccess": "Aktualizovány předpony třídění ({0} položek)", "ToastSortingPrefixesUpdateSuccess": "Aktualizovány předpony třídění ({0} položek)",
"ToastTitleRequired": "Titul je vyžadován",
"ToastUnknownError": "Neznámý error",
"ToastUnlinkOpenIdFailed": "Chyba při odpárování uživatele z OpenID",
"ToastUnlinkOpenIdSuccess": "Uživatel odpárován z uživatele z OpenID",
"ToastUserDeleteFailed": "Nepodařilo se smazat uživatele", "ToastUserDeleteFailed": "Nepodařilo se smazat uživatele",
"ToastUserDeleteSuccess": "Uživatel smazán" "ToastUserDeleteSuccess": "Uživatel smazán",
"ToastUserPasswordChangeSuccess": "Heslo bylo změněno úspěšně",
"ToastUserPasswordMismatch": "Hesla se neschodují",
"ToastUserPasswordMustChange": "Nové heslo se musí lišit od předchozího",
"ToastUserRootRequireName": "Musíte zadat uživatelské jméno root"
} }
+368 -15
View File
@@ -5,11 +5,13 @@
"ButtonAddLibrary": "Tilføj Bibliotek", "ButtonAddLibrary": "Tilføj Bibliotek",
"ButtonAddPodcasts": "Tilføj podcasts", "ButtonAddPodcasts": "Tilføj podcasts",
"ButtonAddUser": "Tilføj bruger", "ButtonAddUser": "Tilføj bruger",
"ButtonAddYourFirstLibrary": "Tilføj din første bibliotek", "ButtonAddYourFirstLibrary": "Tilføj dit første bibliotek",
"ButtonApply": "Anvend", "ButtonApply": "Anvend",
"ButtonApplyChapters": "Anvend kapitler", "ButtonApplyChapters": "Anvend kapitler",
"ButtonAuthors": "Forfattere", "ButtonAuthors": "Forfattere",
"ButtonBack": "Tilbage", "ButtonBack": "Tilbage",
"ButtonBatchEditPopulateFromExisting": "Opret fra eksisterende",
"ButtonBatchEditPopulateMapDetails": "Opret fra kortlægnings detaljer",
"ButtonBrowseForFolder": "Gennemse mappe", "ButtonBrowseForFolder": "Gennemse mappe",
"ButtonCancel": "Annuller", "ButtonCancel": "Annuller",
"ButtonCancelEncode": "Annuller kodning", "ButtonCancelEncode": "Annuller kodning",
@@ -51,7 +53,7 @@
"ButtonNext": "Næste", "ButtonNext": "Næste",
"ButtonNextChapter": "Næste Kapitel", "ButtonNextChapter": "Næste Kapitel",
"ButtonNextItemInQueue": "Næste Element i Køen", "ButtonNextItemInQueue": "Næste Element i Køen",
"ButtonOk": "OK", "ButtonOk": "Ok",
"ButtonOpenFeed": "Åbn feed", "ButtonOpenFeed": "Åbn feed",
"ButtonOpenManager": "Åbn manager", "ButtonOpenManager": "Åbn manager",
"ButtonPause": "Pause", "ButtonPause": "Pause",
@@ -71,6 +73,8 @@
"ButtonQuickMatch": "Hurtig Match", "ButtonQuickMatch": "Hurtig Match",
"ButtonReScan": "Gen-scan", "ButtonReScan": "Gen-scan",
"ButtonRead": "Læs", "ButtonRead": "Læs",
"ButtonReadLess": "Se mindre",
"ButtonReadMore": "Se mere",
"ButtonRefresh": "Genindlæs", "ButtonRefresh": "Genindlæs",
"ButtonRemove": "Fjern", "ButtonRemove": "Fjern",
"ButtonRemoveAll": "Fjern Alle", "ButtonRemoveAll": "Fjern Alle",
@@ -89,7 +93,7 @@
"ButtonScrollLeft": "Rul til Venstre", "ButtonScrollLeft": "Rul til Venstre",
"ButtonScrollRight": "Rul til Højre", "ButtonScrollRight": "Rul til Højre",
"ButtonSearch": "Søg", "ButtonSearch": "Søg",
"ButtonSelectFolderPath": "Vælg Mappen Sti", "ButtonSelectFolderPath": "Vælg Mappe Sti",
"ButtonSeries": "Serier", "ButtonSeries": "Serier",
"ButtonSetChaptersFromTracks": "Sæt kapitler fra spor", "ButtonSetChaptersFromTracks": "Sæt kapitler fra spor",
"ButtonShare": "Del", "ButtonShare": "Del",
@@ -211,7 +215,7 @@
"LabelAbridgedChecked": "Forkortet (kontrolleret)", "LabelAbridgedChecked": "Forkortet (kontrolleret)",
"LabelAbridgedUnchecked": "Uforkortet (ikke kontrolleret)", "LabelAbridgedUnchecked": "Uforkortet (ikke kontrolleret)",
"LabelAccessibleBy": "Tilgængelig af", "LabelAccessibleBy": "Tilgængelig af",
"LabelAccountType": "Kontotype", "LabelAccountType": "Brugertype",
"LabelAccountTypeAdmin": "Administrator", "LabelAccountTypeAdmin": "Administrator",
"LabelAccountTypeGuest": "Gæst", "LabelAccountTypeGuest": "Gæst",
"LabelAccountTypeUser": "Bruger", "LabelAccountTypeUser": "Bruger",
@@ -220,8 +224,9 @@
"LabelAddToCollectionBatch": "Tilføj {0} Bøger til Samling", "LabelAddToCollectionBatch": "Tilføj {0} Bøger til Samling",
"LabelAddToPlaylist": "Tilføj til Afspilningsliste", "LabelAddToPlaylist": "Tilføj til Afspilningsliste",
"LabelAddToPlaylistBatch": "Tilføj {0} Elementer til Afspilningsliste", "LabelAddToPlaylistBatch": "Tilføj {0} Elementer til Afspilningsliste",
"LabelAddedAt": "Tilføjet Kl.", "LabelAddedAt": "Tilføjet",
"LabelAdminUsersOnly": "Kun Administratorbrugere", "LabelAddedDate": "Tilføjet {0}",
"LabelAdminUsersOnly": "Kun Administratorer",
"LabelAll": "Alle", "LabelAll": "Alle",
"LabelAllUsers": "Alle Brugere", "LabelAllUsers": "Alle Brugere",
"LabelAllUsersExcludingGuests": "Alle bruger eksklusiv gæster", "LabelAllUsersExcludingGuests": "Alle bruger eksklusiv gæster",
@@ -241,23 +246,34 @@
"LabelAutoFetchMetadataHelp": "Henter metadata for titler, forfatter og serier for at strømligne uploading. Ekstra metadata har måske brug for at blive matchet efter upload.", "LabelAutoFetchMetadataHelp": "Henter metadata for titler, forfatter og serier for at strømligne uploading. Ekstra metadata har måske brug for at blive matchet efter upload.",
"LabelAutoLaunch": "Åben Automatisk", "LabelAutoLaunch": "Åben Automatisk",
"LabelAutoLaunchDescription": "Viderestil automatisk til login-udbyderen ved navigation til login-siden (manuel overstyring via <code>/login?autoLaunch=0</code>)", "LabelAutoLaunchDescription": "Viderestil automatisk til login-udbyderen ved navigation til login-siden (manuel overstyring via <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Registrer Automatisk",
"LabelAutoRegisterDescription": "Automatisk oprettelse af nye brugere efter login",
"LabelBackToUser": "Tilbage til Bruger", "LabelBackToUser": "Tilbage til Bruger",
"LabelBackupAudioFiles": "Sikkerhedskopier lydfiler",
"LabelBackupLocation": "Backup Placering", "LabelBackupLocation": "Backup Placering",
"LabelBackupsEnableAutomaticBackups": "Aktivér automatisk sikkerhedskopiering", "LabelBackupsEnableAutomaticBackups": "Aktivér automatisk sikkerhedskopiering",
"LabelBackupsEnableAutomaticBackupsHelp": "Sikkerhedskopier gemt i /metadata/backups", "LabelBackupsEnableAutomaticBackupsHelp": "Sikkerhedskopier gemt i /metadata/backups",
"LabelBackupsMaxBackupSize": "Maksimal sikkerhedskopistørrelse (i GB)", "LabelBackupsMaxBackupSize": "Maksimal sikkerhedskopistørrelse (i GB) (0 for ubegrænset)",
"LabelBackupsMaxBackupSizeHelp": "Som en beskyttelse mod fejlkonfiguration fejler sikkerhedskopier, hvis de overstiger den konfigurerede størrelse.", "LabelBackupsMaxBackupSizeHelp": "Som en beskyttelse mod fejlkonfiguration fejler sikkerhedskopier, hvis de overstiger den konfigurerede størrelse.",
"LabelBackupsNumberToKeep": "Antal sikkerhedskopier at beholde", "LabelBackupsNumberToKeep": "Antal sikkerhedskopier at beholde",
"LabelBackupsNumberToKeepHelp": "Kun 1 sikkerhedskopi fjernes ad gangen, så hvis du allerede har flere sikkerhedskopier end dette, skal du fjerne dem manuelt.", "LabelBackupsNumberToKeepHelp": "Kun 1 sikkerhedskopi fjernes ad gangen, så hvis du allerede har flere sikkerhedskopier end dette, skal du fjerne dem manuelt.",
"LabelBitrate": "Bitrate",
"LabelBonus": "Bonus",
"LabelBooks": "Bøger", "LabelBooks": "Bøger",
"LabelButtonText": "Knap tekst",
"LabelByAuthor": "af {0}", "LabelByAuthor": "af {0}",
"LabelChangePassword": "Ændre Adgangskode", "LabelChangePassword": "Ændre Adgangskode",
"LabelChannels": "Kanaler", "LabelChannels": "Kanaler",
"LabelChapterCount": "{0} Kapitler",
"LabelChapterTitle": "Kapitel Titel", "LabelChapterTitle": "Kapitel Titel",
"LabelChapters": "Kapitler", "LabelChapters": "Kapitler",
"LabelChaptersFound": "fundne kapitler", "LabelChaptersFound": "fundne kapitler",
"LabelClickForMoreInfo": "Klik for mere info",
"LabelClickToUseCurrentValue": "Klik for at bruge nuværende værdi",
"LabelClosePlayer": "Luk afspiller", "LabelClosePlayer": "Luk afspiller",
"LabelCodec": "Kodeks",
"LabelCollapseSeries": "Fold Serier Sammen", "LabelCollapseSeries": "Fold Serier Sammen",
"LabelCollapseSubSeries": "Fold underserie sammen",
"LabelCollection": "Samling", "LabelCollection": "Samling",
"LabelCollections": "Samlinger", "LabelCollections": "Samlinger",
"LabelComplete": "Fuldfør", "LabelComplete": "Fuldfør",
@@ -273,58 +289,100 @@
"LabelCurrently": "Aktuelt:", "LabelCurrently": "Aktuelt:",
"LabelCustomCronExpression": "Brugerdefineret Cron Udtryk:", "LabelCustomCronExpression": "Brugerdefineret Cron Udtryk:",
"LabelDatetime": "Dato og Tid", "LabelDatetime": "Dato og Tid",
"LabelDays": "Dage",
"LabelDeleteFromFileSystemCheckbox": "Slet fra filsystem (afmarker kun for at fjerne fra databasen)",
"LabelDescription": "Beskrivelse", "LabelDescription": "Beskrivelse",
"LabelDeselectAll": "Fravælg Alle", "LabelDeselectAll": "Fravælg Alle",
"LabelDevice": "Enheds", "LabelDevice": "Enheds",
"LabelDeviceInfo": "Enhedsinformation", "LabelDeviceInfo": "Enhedsinformation",
"LabelDeviceIsAvailableTo": "Enhed er tilgængelig for...",
"LabelDirectory": "Mappe", "LabelDirectory": "Mappe",
"LabelDiscFromFilename": "Disk fra Filnavn", "LabelDiscFromFilename": "Disk fra Filnavn",
"LabelDiscFromMetadata": "Disk fra Metadata", "LabelDiscFromMetadata": "Disk fra Metadata",
"LabelDiscover": "Opdag", "LabelDiscover": "Opdag",
"LabelDownload": "Download", "LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episoder", "LabelDownloadNEpisodes": "Download {0} episoder",
"LabelDownloadable": "Downloadbar",
"LabelDuration": "Varighed", "LabelDuration": "Varighed",
"LabelDurationComparisonExactMatch": "(præcis match)",
"LabelDurationComparisonLonger": "({0} længere)",
"LabelDurationComparisonShorter": "({0} kortere)",
"LabelDurationFound": "Fundet varighed:", "LabelDurationFound": "Fundet varighed:",
"LabelEbook": "E-bog", "LabelEbook": "E-bog",
"LabelEbooks": "E-bøger", "LabelEbooks": "E-bøger",
"LabelEdit": "Rediger", "LabelEdit": "Rediger",
"LabelEmail": "E-mail",
"LabelEmailSettingsFromAddress": "Fra Adresse", "LabelEmailSettingsFromAddress": "Fra Adresse",
"LabelEmailSettingsRejectUnauthorized": "Afvis uautoriserede certifikater",
"LabelEmailSettingsRejectUnauthorizedHelp": "Deaktivering af SSL certifikat validering kan udsætte din forbindelse for sikkerhedsrisici, eksempelvis man-in-the-middle angreb. Deaktiver kun denne indstilling hvis du forstår de potentielle implikationer og stoler på den mailserver du forbinder til.",
"LabelEmailSettingsSecure": "Sikker", "LabelEmailSettingsSecure": "Sikker",
"LabelEmailSettingsSecureHelp": "Hvis sandt, vil forbindelsen bruge TLS ved tilslutning til serveren. Hvis falsk, bruges TLS, hvis serveren understøtter STARTTLS-udvidelsen. I de fleste tilfælde skal denne værdi sættes til sandt, hvis du tilslutter til port 465. Til port 587 eller 25 skal du holde det falsk. (fra nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Hvis sandt, vil forbindelsen bruge TLS ved tilslutning til serveren. Hvis falsk, bruges TLS, hvis serveren understøtter STARTTLS-udvidelsen. I de fleste tilfælde skal denne værdi sættes til sandt, hvis du tilslutter til port 465. Til port 587 eller 25 skal du holde det falsk. (fra nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Adresse", "LabelEmailSettingsTestAddress": "Test Adresse",
"LabelEmbeddedCover": "Indlejret Omslag", "LabelEmbeddedCover": "Indlejret Omslag",
"LabelEnable": "Aktivér", "LabelEnable": "Aktivér",
"LabelEncodingBackupLocation": "En sikkerhedskopi af dine originale lydfiler vil blive gemt under:",
"LabelEncodingChaptersNotEmbedded": "Kapitler er ikke indlejret i multi spors lydbøger.",
"LabelEncodingClearItemCache": "Sørg for periodisk at rense indholdscachen.",
"LabelEncodingFinishedM4B": "Færdiggjort M4B som vil blive placeret i din lydbogsmappe ved:",
"LabelEncodingInfoEmbedded": "Metadata vil blive indlejret i lydfiler i lydbogsmappen.",
"LabelEncodingStartedNavigation": "Når opgaven er startet kan du navigere væk fra denne side.",
"LabelEncodingTimeWarning": "Indkodning kan tage op til 30 minutter.",
"LabelEncodingWarningAdvancedSettings": "Advarsel: Opdater ikke disse indstillinger med mindre du kender til ffmpeg indkodningsindstillinger.",
"LabelEncodingWatcherDisabled": "Hvis du har watcheren deaktiveret skal du gen-scanne denne lydbog bagefter.",
"LabelEnd": "Slut", "LabelEnd": "Slut",
"LabelEndOfChapter": "Slutningen af kapitel", "LabelEndOfChapter": "Slutningen af kapitel",
"LabelEpisode": "Episode", "LabelEpisode": "Afsnit",
"LabelEpisodeNotLinkedToRssFeed": "Afsnit er ikke koblet til RSS feed",
"LabelEpisodeNumber": "Afsnit #{0}",
"LabelEpisodeTitle": "Episodetitel", "LabelEpisodeTitle": "Episodetitel",
"LabelEpisodeType": "Episodetype", "LabelEpisodeType": "Episodetype",
"LabelEpisodeUrlFromRssFeed": "Afsnit URL fra RSS feed",
"LabelEpisodes": "Afsnit",
"LabelEpisodic": "Afsnit",
"LabelExample": "Eksempel", "LabelExample": "Eksempel",
"LabelExpandSeries": "Udfold serie",
"LabelExpandSubSeries": "Udfold underserie",
"LabelExplicit": "Eksplisit", "LabelExplicit": "Eksplisit",
"LabelExplicitChecked": "Eksplicit (markeret)",
"LabelExplicitUnchecked": "Ikke eksplicit (ikke markeret)",
"LabelExportOPML": "Eksport OPML",
"LabelFeedURL": "Feed URL", "LabelFeedURL": "Feed URL",
"LabelFetchingMetadata": "Henter metadata",
"LabelFile": "Fil", "LabelFile": "Fil",
"LabelFileBirthtime": "Oprettelsestidspunkt for fil", "LabelFileBirthtime": "Oprettelsestidspunkt for fil",
"LabelFileBornDate": "Født {0}",
"LabelFileModified": "Fil ændret", "LabelFileModified": "Fil ændret",
"LabelFileModifiedDate": "Opdateret {0}",
"LabelFilename": "Filnavn", "LabelFilename": "Filnavn",
"LabelFilterByUser": "Filtrér efter bruger", "LabelFilterByUser": "Filtrér efter bruger",
"LabelFindEpisodes": "Find episoder", "LabelFindEpisodes": "Find episoder",
"LabelFinished": "Færdig", "LabelFinished": "Færdig",
"LabelFolder": "Mappe", "LabelFolder": "Mappe",
"LabelFolders": "Mapper", "LabelFolders": "Mapper",
"LabelFontBold": "Fed",
"LabelFontBoldness": "Skrift tykkelse", "LabelFontBoldness": "Skrift tykkelse",
"LabelFontFamily": "Fontfamilie", "LabelFontFamily": "Fontfamilie",
"LabelFontItalic": "Kursiv",
"LabelFontScale": "Skriftstørrelse", "LabelFontScale": "Skriftstørrelse",
"LabelFontStrikethrough": "Gennemstreget",
"LabelFormat": "Format",
"LabelFull": "Fuld",
"LabelGenre": "Genre", "LabelGenre": "Genre",
"LabelGenres": "Genrer", "LabelGenres": "Genrer",
"LabelHardDeleteFile": "Permanent slet fil", "LabelHardDeleteFile": "Permanent slet fil",
"LabelHasEbook": "Har e-bog", "LabelHasEbook": "Har e-bog",
"LabelHasSupplementaryEbook": "Har supplerende e-bog", "LabelHasSupplementaryEbook": "Har supplerende e-bog",
"LabelHideSubtitles": "Skjul undertitler",
"LabelHighestPriority": "Højeste prioritet",
"LabelHost": "Vært", "LabelHost": "Vært",
"LabelHour": "Time", "LabelHour": "Time",
"LabelHours": "Timer",
"LabelIcon": "Ikon", "LabelIcon": "Ikon",
"LabelImageURLFromTheWeb": "Billede URL fra nettet",
"LabelInProgress": "I gang", "LabelInProgress": "I gang",
"LabelIncludeInTracklist": "Inkluder i afspilningsliste", "LabelIncludeInTracklist": "Inkluder i afspilningsliste",
"LabelIncomplete": "Ufuldstændig", "LabelIncomplete": "Ufuldstændig",
"LabelInterval": "Interval",
"LabelIntervalCustomDailyWeekly": "Tilpasset dagligt/ugentligt", "LabelIntervalCustomDailyWeekly": "Tilpasset dagligt/ugentligt",
"LabelIntervalEvery12Hours": "Hver 12. time", "LabelIntervalEvery12Hours": "Hver 12. time",
"LabelIntervalEvery15Minutes": "Hver 15. minut", "LabelIntervalEvery15Minutes": "Hver 15. minut",
@@ -335,8 +393,11 @@
"LabelIntervalEveryHour": "Hver time", "LabelIntervalEveryHour": "Hver time",
"LabelInvert": "Inverter", "LabelInvert": "Inverter",
"LabelItem": "Element", "LabelItem": "Element",
"LabelJumpBackwardAmount": "Spring bagud mængde",
"LabelJumpForwardAmount": "Spring fremad mængde",
"LabelLanguage": "Sprog", "LabelLanguage": "Sprog",
"LabelLanguageDefaultServer": "Standard server sprog", "LabelLanguageDefaultServer": "Standard server sprog",
"LabelLanguages": "Sprog",
"LabelLastBookAdded": "Senest tilføjede bog", "LabelLastBookAdded": "Senest tilføjede bog",
"LabelLastBookUpdated": "Senest opdaterede bog", "LabelLastBookUpdated": "Senest opdaterede bog",
"LabelLastSeen": "Sidst set", "LabelLastSeen": "Sidst set",
@@ -348,6 +409,7 @@
"LabelLess": "Mindre", "LabelLess": "Mindre",
"LabelLibrariesAccessibleToUser": "Biblioteker tilgængelige for bruger", "LabelLibrariesAccessibleToUser": "Biblioteker tilgængelige for bruger",
"LabelLibrary": "Bibliotek", "LabelLibrary": "Bibliotek",
"LabelLibraryFilterSublistEmpty": "Nej {0}",
"LabelLibraryItem": "Bibliotekselement", "LabelLibraryItem": "Bibliotekselement",
"LabelLibraryName": "Biblioteksnavn", "LabelLibraryName": "Biblioteksnavn",
"LabelLimit": "Grænse", "LabelLimit": "Grænse",
@@ -357,24 +419,38 @@
"LabelLogLevelInfo": "Information", "LabelLogLevelInfo": "Information",
"LabelLogLevelWarn": "Advarsel", "LabelLogLevelWarn": "Advarsel",
"LabelLookForNewEpisodesAfterDate": "Søg efter nye episoder efter denne dato", "LabelLookForNewEpisodesAfterDate": "Søg efter nye episoder efter denne dato",
"LabelLowestPriority": "Laveste prioritet",
"LabelMatchExistingUsersBy": "Match eksisterende brugere ved",
"LabelMatchExistingUsersByDescription": "Anvendt for at forbinde brugere. Når forbundet, brugere vil blive matchet ved unikt id fra din SSO udbyder",
"LabelMaxEpisodesToDownload": "Max # afsnit for at downloade. Anvend 0 for ubegrænset.",
"LabelMaxEpisodesToDownloadPerCheck": "Max # afsnit til at downloade per check",
"LabelMaxEpisodesToKeep": "Max # afsnit at beholde",
"LabelMaxEpisodesToKeepHelp": "Værdi af 0 sætter intet maks begrænsning. After et nyt afsnit er automatisk downloaded vil det ældste afsnit blive slettet hvis du har mere end X afsnit. Dette vil kun slette 1 afsnit for hvert nye download.",
"LabelMediaPlayer": "Medieafspiller", "LabelMediaPlayer": "Medieafspiller",
"LabelMediaType": "Medietype", "LabelMediaType": "Medietype",
"LabelMetaTag": "Meta-tag", "LabelMetaTag": "Meta-tag",
"LabelMetaTags": "Meta-tags", "LabelMetaTags": "Meta-tags",
"LabelMetadataOrderOfPrecedenceDescription": "Højeste prioritet metadata kilder vil overskrive de lavest prioriterede metadata kilder",
"LabelMetadataProvider": "Metadataudbyder", "LabelMetadataProvider": "Metadataudbyder",
"LabelMinute": "Minut", "LabelMinute": "Minut",
"LabelMinutes": "Minutter",
"LabelMissing": "Mangler", "LabelMissing": "Mangler",
"LabelMissingEbook": "Har ingen ebog",
"LabelMissingSupplementaryEbook": "Har ingen tillægsbog",
"LabelMobileRedirectURIs": "Godkendte mobil redirect URI'er",
"LabelMobileRedirectURIsDescription": "Dete vil whiteliste en gyldig omdirigerings URL for mobile apps. Den standarde er <code>audiobookshelf://oauth</code> som du kan fjerne eller supplere med flere URI'er for tredjeparts app integration. Anvend en stjerne (<code>*</code>) som den eneste indstilling for at tilade en hvilkensomhelst URI.",
"LabelMore": "Mere", "LabelMore": "Mere",
"LabelMoreInfo": "Mere info", "LabelMoreInfo": "Mere info",
"LabelName": "Navn", "LabelName": "Navn",
"LabelNarrator": "Fortæller", "LabelNarrator": "Fortæller",
"LabelNarrators": "Fortællere", "LabelNarrators": "Fortællere",
"LabelNew": "Ny", "LabelNew": "Ny",
"LabelNewPassword": "Nyt kodeord", "LabelNewPassword": "Ny adgangskode",
"LabelNewestAuthors": "Nyeste forfattere", "LabelNewestAuthors": "Nyeste forfattere",
"LabelNewestEpisodes": "Nyeste episoder", "LabelNewestEpisodes": "Nyeste episoder",
"LabelNextBackupDate": "Næste sikkerhedskopi dato", "LabelNextBackupDate": "Næste sikkerhedskopi dato",
"LabelNextScheduledRun": "Næste planlagte kørsel", "LabelNextScheduledRun": "Næste planlagte kørsel",
"LabelNoCustomMetadataProviders": "Ingen brugerdefinerede metadata udbydere",
"LabelNoEpisodesSelected": "Ingen episoder valgt", "LabelNoEpisodesSelected": "Ingen episoder valgt",
"LabelNotFinished": "Ikke færdig", "LabelNotFinished": "Ikke færdig",
"LabelNotStarted": "Ikke påbegyndt", "LabelNotStarted": "Ikke påbegyndt",
@@ -389,31 +465,48 @@
"LabelNotificationsMaxQueueSize": "Maksimal køstørrelse for meddelelseshændelser", "LabelNotificationsMaxQueueSize": "Maksimal køstørrelse for meddelelseshændelser",
"LabelNotificationsMaxQueueSizeHelp": "Hændelser begrænses til at udløse en gang pr. sekund. Hændelser ignoreres, hvis køen er fyldt. Dette forhindrer meddelelsesspam.", "LabelNotificationsMaxQueueSizeHelp": "Hændelser begrænses til at udløse en gang pr. sekund. Hændelser ignoreres, hvis køen er fyldt. Dette forhindrer meddelelsesspam.",
"LabelNumberOfBooks": "Antal bøger", "LabelNumberOfBooks": "Antal bøger",
"LabelNumberOfEpisodes": "Antal episoder", "LabelNumberOfEpisodes": "# afsnit",
"LabelOpenIDAdvancedPermsClaimDescription": "Navnet af OpenID claimet som indeholder avancerede brugerhandlinger inden i applikationen som vil gælde for ikke administrative roller (<b>hvis konfigureret</b>). Hvis et claim mangler fra svaret vil adgang til ABS blive nægtet. Hvis en enkelt indstilling/option mangler, vil det bliver behandlet som <code>false</code>. Sørg for at identity provider's claim matcher den forventede struktur:",
"LabelOpenIDClaims": "Efterlad de følgende indstillinger tomme for at deaktivere avanceret gruppe og adgangsindstilling, ved automatisk at assigne 'Bruger' grupper.",
"LabelOpenIDGroupClaimDescription": "Navnet af det OpenID claim som skal indeholde brugerens grupper. Mest kendt som <code>groups</code>. <b>hvis konfigureret</b>, vil applikationen automatiske tildele roller baseret p[ brugerens gruppemedlemsskaber, givet disse grupper er navngivet (uden forbehold for store og små bogstaver) 'admin', 'user' eller 'guest' i claimet. Claimet burde indeholde en liste (og hvis brugeren tilhøre flere grupper) som applikationen vil tildele roller med højeste adgangsnvieau. Hvis ingen grupper matcher vil adgang blive nægtet.",
"LabelOpenRSSFeed": "Åbn RSS-feed", "LabelOpenRSSFeed": "Åbn RSS-feed",
"LabelOverwrite": "Overskriv", "LabelOverwrite": "Overskriv",
"LabelPassword": "Kodeord", "LabelPaginationPageXOfY": "Side {0} af {1}",
"LabelPassword": "Adgangskode",
"LabelPath": "Sti", "LabelPath": "Sti",
"LabelPermanent": "Permanent",
"LabelPermissionsAccessAllLibraries": "Kan få adgang til alle biblioteker", "LabelPermissionsAccessAllLibraries": "Kan få adgang til alle biblioteker",
"LabelPermissionsAccessAllTags": "Kan få adgang til alle tags", "LabelPermissionsAccessAllTags": "Kan få adgang til alle tags",
"LabelPermissionsAccessExplicitContent": "Kan få adgang til eksplicit indhold", "LabelPermissionsAccessExplicitContent": "Kan få adgang til eksplicit indhold",
"LabelPermissionsCreateEreader": "Kan oprette elæser",
"LabelPermissionsDelete": "Kan slette", "LabelPermissionsDelete": "Kan slette",
"LabelPermissionsDownload": "Kan downloade", "LabelPermissionsDownload": "Kan downloade",
"LabelPermissionsUpdate": "Kan opdatere", "LabelPermissionsUpdate": "Kan opdatere",
"LabelPermissionsUpload": "Kan uploade", "LabelPermissionsUpload": "Kan uploade",
"LabelPersonalYearReview": "Dit år i review ({0})",
"LabelPhotoPathURL": "Foto sti/URL", "LabelPhotoPathURL": "Foto sti/URL",
"LabelPlayMethod": "Afspilningsmetode", "LabelPlayMethod": "Afspilningsmetode",
"LabelPlaybackRateIncrementDecrement": "Afspilningshastighed øges/sænkes med",
"LabelPlayerChapterNumberMarker": "{0} af {1}",
"LabelPlaylists": "Afspilningslister", "LabelPlaylists": "Afspilningslister",
"LabelPodcast": "Podcast",
"LabelPodcastSearchRegion": "Podcast søgeområde", "LabelPodcastSearchRegion": "Podcast søgeområde",
"LabelPodcastType": "Podcast type", "LabelPodcastType": "Podcast type",
"LabelPodcasts": "Podcast",
"LabelPort": "Port",
"LabelPrefixesToIgnore": "Præfikser der skal ignoreres (skal ikke skelne mellem store og små bogstaver)", "LabelPrefixesToIgnore": "Præfikser der skal ignoreres (skal ikke skelne mellem store og små bogstaver)",
"LabelPreventIndexing": "Forhindrer, at dit feed bliver indekseret af iTunes og Google podcastkataloger", "LabelPreventIndexing": "Forhindrer, at dit feed bliver indekseret af iTunes og Google podcastkataloger",
"LabelPrimaryEbook": "Primær e-bog", "LabelPrimaryEbook": "Primær e-bog",
"LabelProgress": "Fremskridt", "LabelProgress": "Fremskridt",
"LabelProvider": "Udbyder", "LabelProvider": "Udbyder",
"LabelProviderAuthorizationValue": "Authorization Header værdi",
"LabelPubDate": "Udgivelsesdato", "LabelPubDate": "Udgivelsesdato",
"LabelPublishYear": "Udgivelsesår", "LabelPublishYear": "Udgivelsesår",
"LabelPublishedDate": "Publiceret {0}",
"LabelPublishedDecade": "Publiceret årti",
"LabelPublishedDecades": "Publiceret årtier",
"LabelPublisher": "Forlag", "LabelPublisher": "Forlag",
"LabelPublishers": "Forlag",
"LabelRSSFeedCustomOwnerEmail": "Brugerdefineret ejerens e-mail", "LabelRSSFeedCustomOwnerEmail": "Brugerdefineret ejerens e-mail",
"LabelRSSFeedCustomOwnerName": "Brugerdefineret ejerens navn", "LabelRSSFeedCustomOwnerName": "Brugerdefineret ejerens navn",
"LabelRSSFeedOpen": "Åben RSS-feed", "LabelRSSFeedOpen": "Åben RSS-feed",
@@ -421,27 +514,42 @@
"LabelRSSFeedSlug": "RSS-feed-slug", "LabelRSSFeedSlug": "RSS-feed-slug",
"LabelRSSFeedURL": "RSS-feed-URL", "LabelRSSFeedURL": "RSS-feed-URL",
"LabelRandomly": "Tilfældigt", "LabelRandomly": "Tilfældigt",
"LabelReAddSeriesToContinueListening": "Gentilføj serier til Fortsæt Lytning",
"LabelRead": "Læst", "LabelRead": "Læst",
"LabelReadAgain": "Læs igen", "LabelReadAgain": "Læs Igen",
"LabelReadEbookWithoutProgress": "Læs e-bog uden at følge fremskridt", "LabelReadEbookWithoutProgress": "Læs e-bog uden at følge fremskridt",
"LabelRecentSeries": "Seneste serier", "LabelRecentSeries": "Seneste serier",
"LabelRecentlyAdded": "Senest tilføjet", "LabelRecentlyAdded": "Senest tilføjet",
"LabelRecommended": "Anbefalet", "LabelRecommended": "Anbefalet",
"LabelRedo": "Gøre igen",
"LabelRegion": "Region",
"LabelReleaseDate": "Udgivelsesdato", "LabelReleaseDate": "Udgivelsesdato",
"LabelRemoveAllMetadataAbs": "Fjern alle metadata.abs filer",
"LabelRemoveAllMetadataJson": "Fjern alle metadata.json filer",
"LabelRemoveCover": "Fjern omslag", "LabelRemoveCover": "Fjern omslag",
"LabelRemoveMetadataFile": "Fjern alle metadata filer i biblioteksmapper",
"LabelRemoveMetadataFileHelp": "Fjern alle metadata.json og metadata.abs filer i dine {0} mapper.",
"LabelRowsPerPage": "Rækker per side",
"LabelSearchTerm": "Søgeterm", "LabelSearchTerm": "Søgeterm",
"LabelSearchTitle": "Søg efter titel", "LabelSearchTitle": "Søg efter titel",
"LabelSearchTitleOrASIN": "Søg efter titel eller ASIN", "LabelSearchTitleOrASIN": "Søg efter titel eller ASIN",
"LabelSeason": "Sæson", "LabelSeason": "Sæson",
"LabelSeasonNumber": "Sæson {0}",
"LabelSelectAll": "Vælg alle",
"LabelSelectAllEpisodes": "Vælg alle episoder", "LabelSelectAllEpisodes": "Vælg alle episoder",
"LabelSelectEpisodesShowing": "Vælg {0} episoder vist", "LabelSelectEpisodesShowing": "Vælg {0} episoder vist",
"LabelSelectUsers": "Valgte brugere",
"LabelSendEbookToDevice": "Send e-bog til...", "LabelSendEbookToDevice": "Send e-bog til...",
"LabelSequence": "Sekvens", "LabelSequence": "Sekvens",
"LabelSerial": "Seriel",
"LabelSeries": "Serie", "LabelSeries": "Serie",
"LabelSeriesName": "Serienavn", "LabelSeriesName": "Serienavn",
"LabelSeriesProgress": "Seriefremskridt", "LabelSeriesProgress": "Seriefremskridt",
"LabelServerLogLevel": "Server log niveau",
"LabelServerYearReview": "Server år i review ({0})",
"LabelSetEbookAsPrimary": "Indstil som primær", "LabelSetEbookAsPrimary": "Indstil som primær",
"LabelSetEbookAsSupplementary": "Indstil som supplerende", "LabelSetEbookAsSupplementary": "Indstil som supplerende",
"LabelSettingsAllowIframe": "Tillad embedding i en iframe",
"LabelSettingsAudiobooksOnly": "Kun lydbøger", "LabelSettingsAudiobooksOnly": "Kun lydbøger",
"LabelSettingsAudiobooksOnlyHelp": "Aktivering af denne indstilling vil ignorere e-bogsfiler, medmindre de er inde i en lydbogmappe, hvor de vil blive indstillet som supplerende e-bøger", "LabelSettingsAudiobooksOnlyHelp": "Aktivering af denne indstilling vil ignorere e-bogsfiler, medmindre de er inde i en lydbogmappe, hvor de vil blive indstillet som supplerende e-bøger",
"LabelSettingsBookshelfViewHelp": "Skeumorfisk design med træhylder", "LabelSettingsBookshelfViewHelp": "Skeumorfisk design med træhylder",
@@ -453,6 +561,8 @@
"LabelSettingsEnableWatcher": "Aktiver overvågning", "LabelSettingsEnableWatcher": "Aktiver overvågning",
"LabelSettingsEnableWatcherForLibrary": "Aktiver mappeovervågning for bibliotek", "LabelSettingsEnableWatcherForLibrary": "Aktiver mappeovervågning for bibliotek",
"LabelSettingsEnableWatcherHelp": "Aktiverer automatisk tilføjelse/opdatering af elementer, når filændringer registreres. *Kræver servergenstart", "LabelSettingsEnableWatcherHelp": "Aktiverer automatisk tilføjelse/opdatering af elementer, når filændringer registreres. *Kræver servergenstart",
"LabelSettingsEpubsAllowScriptedContent": "Tillad scriptet indhold i epub",
"LabelSettingsEpubsAllowScriptedContentHelp": "Tillad epub filer at køre scripts. Det anbefales at holde denne indstilling deaktiveret med mindre du stoler på kilderne af epub filerne.",
"LabelSettingsExperimentalFeatures": "Eksperimentelle funktioner", "LabelSettingsExperimentalFeatures": "Eksperimentelle funktioner",
"LabelSettingsExperimentalFeaturesHelp": "Funktioner under udvikling, der kunne bruge din feedback og hjælp til test. Klik for at åbne Github-diskussionen.", "LabelSettingsExperimentalFeaturesHelp": "Funktioner under udvikling, der kunne bruge din feedback og hjælp til test. Klik for at åbne Github-diskussionen.",
"LabelSettingsFindCovers": "Find omslag", "LabelSettingsFindCovers": "Find omslag",
@@ -461,12 +571,17 @@
"LabelSettingsHideSingleBookSeriesHelp": "Serier med en enkelt bog vil blive skjult fra serie-siden og hjemmesidehylder.", "LabelSettingsHideSingleBookSeriesHelp": "Serier med en enkelt bog vil blive skjult fra serie-siden og hjemmesidehylder.",
"LabelSettingsHomePageBookshelfView": "Brug bogreolvisning på startside", "LabelSettingsHomePageBookshelfView": "Brug bogreolvisning på startside",
"LabelSettingsLibraryBookshelfView": "Brug bogreolvisning i biblioteket", "LabelSettingsLibraryBookshelfView": "Brug bogreolvisning i biblioteket",
"LabelSettingsParseSubtitles": "Fortolk undertekster", "LabelSettingsLibraryMarkAsFinishedPercentComplete": "Procent gennemført er større end",
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Tid tilbage er mindre end (sekunder)",
"LabelSettingsLibraryMarkAsFinishedWhen": "Marker medie indhold som færdigt når",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Spring til tidligere bøger i Fortsæt serie",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Fortsæt Serien siden hylde viser de første bøger som ikke er startet i serier med mindst en bog som ikke er startet og ingen bøger i gang. Aktivering af denne indstilling vil fortsætte serien fra den sidst gennemførte bog modsat den først ikke startede bog.",
"LabelSettingsParseSubtitles": "Fortolk undertitler",
"LabelSettingsParseSubtitlesHelp": "Udtræk undertekster fra lydbogsmappenavne.<br>Undertitler skal adskilles af \" - \"<br>f.eks. \"Bogtitel - En undertitel her\" har undertitlen \"En undertitel her\"", "LabelSettingsParseSubtitlesHelp": "Udtræk undertekster fra lydbogsmappenavne.<br>Undertitler skal adskilles af \" - \"<br>f.eks. \"Bogtitel - En undertitel her\" har undertitlen \"En undertitel her\"",
"LabelSettingsPreferMatchedMetadata": "Foretræk matchede metadata", "LabelSettingsPreferMatchedMetadata": "Foretræk matchede metadata",
"LabelSettingsPreferMatchedMetadataHelp": "Matchede data vil tilsidesætte elementdetaljer ved brug af Hurtig Match. Som standard udfylder Hurtig Match kun manglende detaljer.", "LabelSettingsPreferMatchedMetadataHelp": "Matchede data vil tilsidesætte elementdetaljer ved brug af Hurtig Match. Som standard udfylder Hurtig Match kun manglende detaljer.",
"LabelSettingsSkipMatchingBooksWithASIN": "Spring over matchende bøger, der allerede har en ASIN", "LabelSettingsSkipMatchingBooksWithASIN": "Spring over matchende bøger, der allerede har en ASIN",
"LabelSettingsSkipMatchingBooksWithISBN": "Spring over matchende bøger, der allerede har en ISBN", "LabelSettingsSkipMatchingBooksWithISBN": "Spring matchende bøger over, som allerede har et ISBN-nummer",
"LabelSettingsSortingIgnorePrefixes": "Ignorer præfikser ved sortering", "LabelSettingsSortingIgnorePrefixes": "Ignorer præfikser ved sortering",
"LabelSettingsSortingIgnorePrefixesHelp": "f.eks. for præfikset \"the\" vil bogtitlen \"The Book Title\" blive sorteret som \"Book Title, The\"", "LabelSettingsSortingIgnorePrefixesHelp": "f.eks. for præfikset \"the\" vil bogtitlen \"The Book Title\" blive sorteret som \"Book Title, The\"",
"LabelSettingsSquareBookCovers": "Brug kvadratiske bogomslag", "LabelSettingsSquareBookCovers": "Brug kvadratiske bogomslag",
@@ -476,9 +591,19 @@
"LabelSettingsStoreMetadataWithItem": "Gem metadata med element", "LabelSettingsStoreMetadataWithItem": "Gem metadata med element",
"LabelSettingsStoreMetadataWithItemHelp": "Som standard gemmes metadatafiler i /metadata/items, aktivering af denne indstilling vil gemme metadatafiler i dine bibliotekselementmapper", "LabelSettingsStoreMetadataWithItemHelp": "Som standard gemmes metadatafiler i /metadata/items, aktivering af denne indstilling vil gemme metadatafiler i dine bibliotekselementmapper",
"LabelSettingsTimeFormat": "Tidsformat", "LabelSettingsTimeFormat": "Tidsformat",
"LabelShare": "Del",
"LabelShareDownloadableHelp": "Tillad brugere at dele link til at downloade en zip fil af dette biblioteksindhold.",
"LabelShareOpen": "Del åben",
"LabelShareURL": "Del URL",
"LabelShowAll": "Vis alle", "LabelShowAll": "Vis alle",
"LabelShowSeconds": "Vis sekunder",
"LabelShowSubtitles": "Vis undertitler",
"LabelSize": "Størrelse", "LabelSize": "Størrelse",
"LabelSleepTimer": "Søvntimer", "LabelSleepTimer": "Søvntimer",
"LabelSlug": "Snegl",
"LabelSortAscending": "Stigende",
"LabelSortDescending": "Faldende",
"LabelStart": "Start",
"LabelStartTime": "Starttid", "LabelStartTime": "Starttid",
"LabelStarted": "Startet", "LabelStarted": "Startet",
"LabelStartedAt": "Startet klokken", "LabelStartedAt": "Startet klokken",
@@ -504,10 +629,19 @@
"LabelTagsAccessibleToUser": "Mærker tilgængelige for bruger", "LabelTagsAccessibleToUser": "Mærker tilgængelige for bruger",
"LabelTagsNotAccessibleToUser": "Mærker ikke tilgængelige for bruger", "LabelTagsNotAccessibleToUser": "Mærker ikke tilgængelige for bruger",
"LabelTasks": "Kører opgaver", "LabelTasks": "Kører opgaver",
"LabelTextEditorBulletedList": "Punktopstilling",
"LabelTextEditorLink": "Link",
"LabelTextEditorNumberedList": "Nummeropstilling",
"LabelTextEditorUnlink": "Aflink",
"LabelTheme": "Tema", "LabelTheme": "Tema",
"LabelThemeDark": "Mørk", "LabelThemeDark": "Mørk",
"LabelThemeLight": "Lys", "LabelThemeLight": "Lys",
"LabelTimeBase": "Tidsbase", "LabelTimeBase": "Tidsbase",
"LabelTimeDurationXHours": "{0} timer",
"LabelTimeDurationXMinutes": "{0} minutter",
"LabelTimeDurationXSeconds": "{0} sekunder",
"LabelTimeInMinutes": "Tid i minutter",
"LabelTimeLeft": "{0} tilbage",
"LabelTimeListened": "Tid hørt", "LabelTimeListened": "Tid hørt",
"LabelTimeListenedToday": "Tid hørt i dag", "LabelTimeListenedToday": "Tid hørt i dag",
"LabelTimeRemaining": "{0} tilbage", "LabelTimeRemaining": "{0} tilbage",
@@ -515,6 +649,7 @@
"LabelTitle": "Titel", "LabelTitle": "Titel",
"LabelToolsEmbedMetadata": "Indlejre metadata", "LabelToolsEmbedMetadata": "Indlejre metadata",
"LabelToolsEmbedMetadataDescription": "Indlejr metadata i lydfiler, inklusive omslag og kapitler.", "LabelToolsEmbedMetadataDescription": "Indlejr metadata i lydfiler, inklusive omslag og kapitler.",
"LabelToolsM4bEncoder": "M4B indkoder",
"LabelToolsMakeM4b": "Lav M4B lydbogsfil", "LabelToolsMakeM4b": "Lav M4B lydbogsfil",
"LabelToolsMakeM4bDescription": "Generer en .M4B lydbogsfil med indlejret metadata, omslag og kapitler.", "LabelToolsMakeM4bDescription": "Generer en .M4B lydbogsfil med indlejret metadata, omslag og kapitler.",
"LabelToolsSplitM4b": "Opdel M4B til MP3'er", "LabelToolsSplitM4b": "Opdel M4B til MP3'er",
@@ -527,25 +662,41 @@
"LabelTracksMultiTrack": "Flerspors", "LabelTracksMultiTrack": "Flerspors",
"LabelTracksNone": "Ingen spor", "LabelTracksNone": "Ingen spor",
"LabelTracksSingleTrack": "Enkeltspors", "LabelTracksSingleTrack": "Enkeltspors",
"LabelTrailer": "Trailer",
"LabelType": "Type",
"LabelUnabridged": "Uforkortet", "LabelUnabridged": "Uforkortet",
"LabelUndo": "Fortryd",
"LabelUnknown": "Ukendt", "LabelUnknown": "Ukendt",
"LabelUnknownPublishDate": "Ukendt publiceringsdato",
"LabelUpdateCover": "Opdater omslag", "LabelUpdateCover": "Opdater omslag",
"LabelUpdateCoverHelp": "Tillad overskrivning af eksisterende omslag for de valgte bøger, når der findes en match", "LabelUpdateCoverHelp": "Tillad overskrivning af eksisterende omslag for de valgte bøger, når der findes en match",
"LabelUpdateDetails": "Opdater detaljer", "LabelUpdateDetails": "Opdater detaljer",
"LabelUpdateDetailsHelp": "Tillad overskrivning af eksisterende detaljer for de valgte bøger, når der findes en match", "LabelUpdateDetailsHelp": "Tillad overskrivning af eksisterende detaljer for de valgte bøger, når der findes en match",
"LabelUpdatedAt": "Opdateret ved", "LabelUpdatedAt": "Opdateret ved",
"LabelUploaderDragAndDrop": "Træk og slip filer eller mapper", "LabelUploaderDragAndDrop": "Træk og slip filer eller mapper",
"LabelUploaderDragAndDropFilesOnly": "Træk og slip filer",
"LabelUploaderDropFiles": "Smid filer", "LabelUploaderDropFiles": "Smid filer",
"LabelUploaderItemFetchMetadataHelp": "Automatisk hent titel, forfatter og serie",
"LabelUseAdvancedOptions": "Anvend avancerede indstillinger",
"LabelUseChapterTrack": "Brug kapitel-spor", "LabelUseChapterTrack": "Brug kapitel-spor",
"LabelUseFullTrack": "Brug fuldt spor", "LabelUseFullTrack": "Brug fuldt spor",
"LabelUseZeroForUnlimited": "Anvend 0 for ubegrænset",
"LabelUser": "Bruger", "LabelUser": "Bruger",
"LabelUsername": "Brugernavn", "LabelUsername": "Brugernavn",
"LabelValue": "Værdi", "LabelValue": "Værdi",
"LabelVersion": "Version",
"LabelViewBookmarks": "Se bogmærker", "LabelViewBookmarks": "Se bogmærker",
"LabelViewChapters": "Se kapitler", "LabelViewChapters": "Se kapitler",
"LabelViewPlayerSettings": "Vis afspiller indstillinger",
"LabelViewQueue": "Se afspilningskø", "LabelViewQueue": "Se afspilningskø",
"LabelVolume": "Volumen", "LabelVolume": "Volumen",
"LabelWebRedirectURLsDescription": "Godkend disse URL'er i din OAuth udgiver for at tillade omdirigering tilbage til hjemmesiden efter login:",
"LabelWebRedirectURLsSubfolder": "Undermapper for omdirigerings URL'er",
"LabelWeekdaysToRun": "Ugedage til kørsel", "LabelWeekdaysToRun": "Ugedage til kørsel",
"LabelXBooks": "{0} bøger",
"LabelXItems": "{0} genstande",
"LabelYearReviewHide": "Skjul år i review",
"LabelYearReviewShow": "Vis år i review",
"LabelYourAudiobookDuration": "Din lydbogsvarighed", "LabelYourAudiobookDuration": "Din lydbogsvarighed",
"LabelYourBookmarks": "Dine bogmærker", "LabelYourBookmarks": "Dine bogmærker",
"LabelYourPlaylists": "Dine spillelister", "LabelYourPlaylists": "Dine spillelister",
@@ -553,10 +704,17 @@
"MessageAddToPlayerQueue": "Tilføj til afspilningskø", "MessageAddToPlayerQueue": "Tilføj til afspilningskø",
"MessageAppriseDescription": "For at bruge denne funktion skal du have en instans af <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> kørende eller en API, der håndterer de samme anmodninger. <br /> Apprise API-webadressen skal være den fulde URL-sti for at sende underretningen, f.eks. hvis din API-instans er tilgængelig på <code>http://192.168.1.1:8337</code>, så skal du bruge <code>http://192.168.1.1:8337/notify</code>.", "MessageAppriseDescription": "For at bruge denne funktion skal du have en instans af <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> kørende eller en API, der håndterer de samme anmodninger. <br /> Apprise API-webadressen skal være den fulde URL-sti for at sende underretningen, f.eks. hvis din API-instans er tilgængelig på <code>http://192.168.1.1:8337</code>, så skal du bruge <code>http://192.168.1.1:8337/notify</code>.",
"MessageBackupsDescription": "Backups inkluderer brugere, brugerfremskridt, biblioteksvareoplysninger, serverindstillinger og billeder gemt i <code>/metadata/items</code> og <code>/metadata/authors</code>. Backups inkluderer <strong>ikke</strong> nogen filer gemt i dine biblioteksmapper.", "MessageBackupsDescription": "Backups inkluderer brugere, brugerfremskridt, biblioteksvareoplysninger, serverindstillinger og billeder gemt i <code>/metadata/items</code> og <code>/metadata/authors</code>. Backups inkluderer <strong>ikke</strong> nogen filer gemt i dine biblioteksmapper.",
"MessageBackupsLocationEditNote": "Note: Opdatering af backup sti vil ikke fjerne eller modificere eksisterende backups",
"MessageBackupsLocationNoEditNote": "Note: Backup sti er sat igennem miljøvariabel og kan ikke ændres her.",
"MessageBackupsLocationPathEmpty": "Backup sti kan ikke være tom",
"MessageBatchEditPopulateMapDetailsAllHelp": "Opret felter slået til med data fra alle genstande. Felter med flere værdier vil blive sammenflettet",
"MessageBatchEditPopulateMapDetailsItemHelp": "Opret kort med værdier der er slået til fra felter med data fra denne genstand",
"MessageBatchQuickMatchDescription": "Quick Match vil forsøge at tilføje manglende omslag og metadata til de valgte elementer. Aktivér indstillingerne nedenfor for at tillade Quick Match at overskrive eksisterende omslag og/eller metadata.", "MessageBatchQuickMatchDescription": "Quick Match vil forsøge at tilføje manglende omslag og metadata til de valgte elementer. Aktivér indstillingerne nedenfor for at tillade Quick Match at overskrive eksisterende omslag og/eller metadata.",
"MessageBookshelfNoCollections": "Du har ikke oprettet nogen samlinger endnu", "MessageBookshelfNoCollections": "Du har ikke oprettet nogen samlinger endnu",
"MessageBookshelfNoCollectionsHelp": "Samlinger er offentlige. Alle brugere med adgang til biblioteket kan se dem.",
"MessageBookshelfNoRSSFeeds": "Ingen RSS-feeds er åbne", "MessageBookshelfNoRSSFeeds": "Ingen RSS-feeds er åbne",
"MessageBookshelfNoResultsForFilter": "Ingen resultater for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Ingen resultater for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Intet resultat for query",
"MessageBookshelfNoSeries": "Du har ingen serier", "MessageBookshelfNoSeries": "Du har ingen serier",
"MessageChapterEndIsAfter": "Kapitelslutningen er efter slutningen af din lydbog", "MessageChapterEndIsAfter": "Kapitelslutningen er efter slutningen af din lydbog",
"MessageChapterErrorFirstNotZero": "Første kapitel skal starte ved 0", "MessageChapterErrorFirstNotZero": "Første kapitel skal starte ved 0",
@@ -566,19 +724,35 @@
"MessageCheckingCron": "Tjekker cron...", "MessageCheckingCron": "Tjekker cron...",
"MessageConfirmCloseFeed": "Er du sikker på, at du vil lukke dette feed?", "MessageConfirmCloseFeed": "Er du sikker på, at du vil lukke dette feed?",
"MessageConfirmDeleteBackup": "Er du sikker på, at du vil slette backup for {0}?", "MessageConfirmDeleteBackup": "Er du sikker på, at du vil slette backup for {0}?",
"MessageConfirmDeleteDevice": "Er du sikker på at du vil fjerne elæser enhed \"{0}\"?",
"MessageConfirmDeleteFile": "Dette vil slette filen fra dit filsystem. Er du sikker?", "MessageConfirmDeleteFile": "Dette vil slette filen fra dit filsystem. Er du sikker?",
"MessageConfirmDeleteLibrary": "Er du sikker på, at du vil slette biblioteket permanent \"{0}\"?", "MessageConfirmDeleteLibrary": "Er du sikker på, at du vil slette biblioteket permanent \"{0}\"?",
"MessageConfirmDeleteLibraryItem": "Dette vil slette biblioteksgenstanden fra databasen og dit filsystem. Er du sikker?",
"MessageConfirmDeleteLibraryItems": "Dette vil slette {0} biblioteksgenstande fra din database og filsystem. Er du sikker?",
"MessageConfirmDeleteMetadataProvider": "Er du sikker på at du vil fjerne brugerdefineret metadata udgiver \"{0}\"?",
"MessageConfirmDeleteNotification": "Er du sikker på at du vil fjerne denne notifikation?",
"MessageConfirmDeleteSession": "Er du sikker på, at du vil slette denne session?", "MessageConfirmDeleteSession": "Er du sikker på, at du vil slette denne session?",
"MessageConfirmEmbedMetadataInAudioFiles": "Er du sikker på at du vil indlejre metadata i {0} lydbogsfiler?",
"MessageConfirmForceReScan": "Er du sikker på, at du vil tvinge en genindlæsning?", "MessageConfirmForceReScan": "Er du sikker på, at du vil tvinge en genindlæsning?",
"MessageConfirmMarkAllEpisodesFinished": "Er du sikker på, at du vil markere alle episoder som afsluttet?", "MessageConfirmMarkAllEpisodesFinished": "Er du sikker på, at du vil markere alle episoder som afsluttet?",
"MessageConfirmMarkAllEpisodesNotFinished": "Er du sikker på, at du vil markere alle episoder som ikke afsluttet?", "MessageConfirmMarkAllEpisodesNotFinished": "Er du sikker på, at du vil markere alle episoder som ikke afsluttet?",
"MessageConfirmMarkItemFinished": "Er du sikker på at du vil markere \"{0}\" som færdig?",
"MessageConfirmMarkItemNotFinished": "Er du sikker på at du vil markere \"{0}\" som ikke færdige?",
"MessageConfirmMarkSeriesFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som 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?", "MessageConfirmMarkSeriesNotFinished": "Er du sikker på, at du vil markere alle bøger i denne serie som ikke afsluttet?",
"MessageConfirmNotificationTestTrigger": "Trigger denne notifikation med testdata?",
"MessageConfirmPurgeCache": "Rensning af cache vil slette hele mappen ved <code>/metadata/cache</code>.<br /><br />Er dy sikker på at du vil fjerne cache mappen?",
"MessageConfirmPurgeItemsCache": "Rensning af cache vil slette hele mappen ved <code>/metadata/cache/items</code>.<br />Er du sikker?",
"MessageConfirmQuickEmbed": "Advarsel! Hurtigindlejring vil ikke backe dine lydfiler op. S'rg for at du har en backup af dine lydfiler. <br /><br />Vil du fortsætte?",
"MessageConfirmQuickMatchEpisodes": "Hurtig match af afsnit vil overskrive detaljer givet et match kan findes. Kun ikke-matchede vil blive opdateret. Er du sikker?",
"MessageConfirmReScanLibraryItems": "Er du sikker på at du vil genscanne {0} genstande?",
"MessageConfirmRemoveAllChapters": "Er du sikker på, at du vil fjerne alle kapitler?", "MessageConfirmRemoveAllChapters": "Er du sikker på, at du vil fjerne alle kapitler?",
"MessageConfirmRemoveAuthor": "Er du sikker på, at du vil fjerne forfatteren \"{0}\"?", "MessageConfirmRemoveAuthor": "Er du sikker på, at du vil fjerne forfatteren \"{0}\"?",
"MessageConfirmRemoveCollection": "Er du sikker på, at du vil fjerne samlingen \"{0}\"?", "MessageConfirmRemoveCollection": "Er du sikker på, at du vil fjerne samlingen \"{0}\"?",
"MessageConfirmRemoveEpisode": "Er du sikker på, at du vil fjerne episoden \"{0}\"?", "MessageConfirmRemoveEpisode": "Er du sikker på, at du vil fjerne episoden \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Er du sikker på, at du vil fjerne {0} episoder?", "MessageConfirmRemoveEpisodes": "Er du sikker på, at du vil fjerne {0} episoder?",
"MessageConfirmRemoveListeningSessions": "Er du sikker på at du vil fjerne {0} lytte sessioner?",
"MessageConfirmRemoveMetadataFiles": "Er du sikker på at du vil fjerne alle metadata.{0} filer i dine biblioteksfoldere?",
"MessageConfirmRemoveNarrator": "Er du sikker på, at du vil fjerne fortælleren \"{0}\"?", "MessageConfirmRemoveNarrator": "Er du sikker på, at du vil fjerne fortælleren \"{0}\"?",
"MessageConfirmRemovePlaylist": "Er du sikker på, at du vil fjerne din spilleliste \"{0}\"?", "MessageConfirmRemovePlaylist": "Er du sikker på, at du vil fjerne din spilleliste \"{0}\"?",
"MessageConfirmRenameGenre": "Er du sikker på, at du vil omdøbe genre \"{0}\" til \"{1}\" for alle elementer?", "MessageConfirmRenameGenre": "Er du sikker på, at du vil omdøbe genre \"{0}\" til \"{1}\" for alle elementer?",
@@ -587,11 +761,17 @@
"MessageConfirmRenameTag": "Er du sikker på, at du vil omdøbe tag \"{0}\" til \"{1}\" for alle elementer?", "MessageConfirmRenameTag": "Er du sikker på, at du vil omdøbe tag \"{0}\" til \"{1}\" for alle elementer?",
"MessageConfirmRenameTagMergeNote": "Bemærk: Dette tag findes allerede, så de vil blive fusioneret.", "MessageConfirmRenameTagMergeNote": "Bemærk: Dette tag findes allerede, så de vil blive fusioneret.",
"MessageConfirmRenameTagWarning": "Advarsel! Et lignende tag med en anden skrivemåde eksisterer allerede \"{0}\".", "MessageConfirmRenameTagWarning": "Advarsel! Et lignende tag med en anden skrivemåde eksisterer allerede \"{0}\".",
"MessageConfirmResetProgress": "Er du sikker på at du vil nulstille dit fremskridt?",
"MessageConfirmSendEbookToDevice": "Er du sikker på, at du vil sende {0} e-bog \"{1}\" til enhed \"{2}\"?", "MessageConfirmSendEbookToDevice": "Er du sikker på, at du vil sende {0} e-bog \"{1}\" til enhed \"{2}\"?",
"MessageConfirmUnlinkOpenId": "Er du sikker på at du vil fjerne linket mellem denne bruger og OpenID?",
"MessageDaysListenedInTheLastYear": "{0} dage lyttet i løbet af det sidste år",
"MessageDownloadingEpisode": "Downloader episode", "MessageDownloadingEpisode": "Downloader episode",
"MessageDragFilesIntoTrackOrder": "Træk filer ind i korrekt spororden", "MessageDragFilesIntoTrackOrder": "Træk filer ind i korrekt spororden",
"MessageEmbedFailed": "Indlejring fejlede!",
"MessageEmbedFinished": "Indlejring færdig!", "MessageEmbedFinished": "Indlejring færdig!",
"MessageEmbedQueue": "Sat i kø for metadata indlejring ({0} i kø)",
"MessageEpisodesQueuedForDownload": "{0} episoder er sat i kø til download", "MessageEpisodesQueuedForDownload": "{0} episoder er sat i kø til download",
"MessageEreaderDevices": "For at sikre levering af ebøger, skal du eventuelt tilføje mailadressen som en gyldig afsender for hver enhed angivet forneden.",
"MessageFeedURLWillBe": "Feed-URL vil være {0}", "MessageFeedURLWillBe": "Feed-URL vil være {0}",
"MessageFetching": "Henter...", "MessageFetching": "Henter...",
"MessageForceReScanDescription": "vil scanne alle filer igen som en frisk scanning. Lydfilens ID3-tags, OPF-filer og tekstfiler scannes som nye.", "MessageForceReScanDescription": "vil scanne alle filer igen som en frisk scanning. Lydfilens ID3-tags, OPF-filer og tekstfiler scannes som nye.",
@@ -602,6 +782,7 @@
"MessageJoinUsOn": "Deltag i os på", "MessageJoinUsOn": "Deltag i os på",
"MessageLoading": "Indlæser...", "MessageLoading": "Indlæser...",
"MessageLoadingFolders": "Indlæser mapper...", "MessageLoadingFolders": "Indlæser mapper...",
"MessageLogsDescription": "Logfiler er gemt i <code>/metadata/logs</code> som JSON filer. Crash log er gemt i <code>/metadata/logs/crash_logs.txt</code>.",
"MessageM4BFailed": "M4B mislykkedes!", "MessageM4BFailed": "M4B mislykkedes!",
"MessageM4BFinished": "M4B afsluttet!", "MessageM4BFinished": "M4B afsluttet!",
"MessageMapChapterTitles": "Tilknyt kapiteloverskrifter til dine eksisterende lydbogskapitler uden at justere tidsstempler", "MessageMapChapterTitles": "Tilknyt kapiteloverskrifter til dine eksisterende lydbogskapitler uden at justere tidsstempler",
@@ -618,6 +799,7 @@
"MessageNoCollections": "Ingen samlinger", "MessageNoCollections": "Ingen samlinger",
"MessageNoCoversFound": "Ingen omslag fundet", "MessageNoCoversFound": "Ingen omslag fundet",
"MessageNoDescription": "Ingen beskrivelse", "MessageNoDescription": "Ingen beskrivelse",
"MessageNoDevices": "Ingen enheder",
"MessageNoDownloadsInProgress": "Ingen downloads i gang lige nu", "MessageNoDownloadsInProgress": "Ingen downloads i gang lige nu",
"MessageNoDownloadsQueued": "Ingen downloads i kø", "MessageNoDownloadsQueued": "Ingen downloads i kø",
"MessageNoEpisodeMatchesFound": "Ingen episode-matcher fundet", "MessageNoEpisodeMatchesFound": "Ingen episode-matcher fundet",
@@ -631,6 +813,7 @@
"MessageNoLogs": "Ingen logfiler", "MessageNoLogs": "Ingen logfiler",
"MessageNoMediaProgress": "Ingen medieforløb", "MessageNoMediaProgress": "Ingen medieforløb",
"MessageNoNotifications": "Ingen meddelelser", "MessageNoNotifications": "Ingen meddelelser",
"MessageNoPodcastFeed": "Invalid podcast: Intet feed",
"MessageNoPodcastsFound": "Ingen podcasts fundet", "MessageNoPodcastsFound": "Ingen podcasts fundet",
"MessageNoResults": "Ingen resultater", "MessageNoResults": "Ingen resultater",
"MessageNoSearchResultsFor": "Ingen søgeresultater for \"{0}\"", "MessageNoSearchResultsFor": "Ingen søgeresultater for \"{0}\"",
@@ -639,12 +822,19 @@
"MessageNoTasksRunning": "Ingen opgaver kører", "MessageNoTasksRunning": "Ingen opgaver kører",
"MessageNoUpdatesWereNecessary": "Ingen opdateringer var nødvendige", "MessageNoUpdatesWereNecessary": "Ingen opdateringer var nødvendige",
"MessageNoUserPlaylists": "Du har ingen afspilningslister", "MessageNoUserPlaylists": "Du har ingen afspilningslister",
"MessageNoUserPlaylistsHelp": "Playlister er private. Kun brugere som opretter dem kan se dem.",
"MessageNotYetImplemented": "Endnu ikke implementeret", "MessageNotYetImplemented": "Endnu ikke implementeret",
"MessageOpmlPreviewNote": "Note: Dette er en forhåndsvisning af den indlæste OPML fil. Podcast titel vil blive taget fra RSS feedet.",
"MessageOr": "eller", "MessageOr": "eller",
"MessagePauseChapter": "Pause kapitelafspilning", "MessagePauseChapter": "Pause kapitelafspilning",
"MessagePlayChapter": "Lyt til begyndelsen af kapitlet", "MessagePlayChapter": "Lyt til begyndelsen af kapitlet",
"MessagePlaylistCreateFromCollection": "Opret afspilningsliste fra samling", "MessagePlaylistCreateFromCollection": "Opret afspilningsliste fra samling",
"MessagePleaseWait": "Vent venligst...",
"MessagePodcastHasNoRSSFeedForMatching": "Podcast har ingen RSS-feed-URL at bruge til matchning", "MessagePodcastHasNoRSSFeedForMatching": "Podcast har ingen RSS-feed-URL at bruge til matchning",
"MessagePodcastSearchField": "Indtast søgeterm eller RSS URL",
"MessageQuickEmbedInProgress": "Hurtig indlejring igang",
"MessageQuickEmbedQueue": "I kø for hurtigindlejring ({0} i kø)",
"MessageQuickMatchAllEpisodes": "Hurtig match alle afsnit",
"MessageQuickMatchDescription": "Udfyld tomme elementoplysninger og omslag med første matchresultat fra '{0}'. Overskriver ikke oplysninger, medmindre serverindstillingen 'Foretræk matchet metadata' er aktiveret.", "MessageQuickMatchDescription": "Udfyld tomme elementoplysninger og omslag med første matchresultat fra '{0}'. Overskriver ikke oplysninger, medmindre serverindstillingen 'Foretræk matchet metadata' er aktiveret.",
"MessageRemoveChapter": "Fjern kapitel", "MessageRemoveChapter": "Fjern kapitel",
"MessageRemoveEpisodes": "Fjern {0} episode(r)", "MessageRemoveEpisodes": "Fjern {0} episode(r)",
@@ -654,10 +844,50 @@
"MessageResetChaptersConfirm": "Er du sikker på, at du vil nulstille kapitler og annullere ændringerne, du har foretaget?", "MessageResetChaptersConfirm": "Er du sikker på, at du vil nulstille kapitler og annullere ændringerne, du har foretaget?",
"MessageRestoreBackupConfirm": "Er du sikker på, at du vil gendanne sikkerhedskopien oprettet den", "MessageRestoreBackupConfirm": "Er du sikker på, at du vil gendanne sikkerhedskopien oprettet den",
"MessageRestoreBackupWarning": "Gendannelse af en sikkerhedskopi vil overskrive hele databasen, som er placeret på /config, og omslagsbilleder i /metadata/items & /metadata/authors.<br /><br />Sikkerhedskopier ændrer ikke nogen filer i dine biblioteksmapper. Hvis du har aktiveret serverindstillinger for at gemme omslagskunst og metadata i dine biblioteksmapper, sikkerhedskopieres eller overskrives disse ikke.<br /><br />Alle klienter, der bruger din server, opdateres automatisk.", "MessageRestoreBackupWarning": "Gendannelse af en sikkerhedskopi vil overskrive hele databasen, som er placeret på /config, og omslagsbilleder i /metadata/items & /metadata/authors.<br /><br />Sikkerhedskopier ændrer ikke nogen filer i dine biblioteksmapper. Hvis du har aktiveret serverindstillinger for at gemme omslagskunst og metadata i dine biblioteksmapper, sikkerhedskopieres eller overskrives disse ikke.<br /><br />Alle klienter, der bruger din server, opdateres automatisk.",
"MessageScheduleLibraryScanNote": "For de fleste brugere, er det anbefalet at efterlade denne funktion deaktiveret for at holde mappe lurer indstilling aktiveret. Mappe lureren vil automatisk opdage ændringer i biblioteksmapper. Mappe lureren virker ikke for alle filsystemer (så som NFS) så schedulerede biblioteksscans vil blive anvendt.",
"MessageSearchResultsFor": "Søgeresultater for", "MessageSearchResultsFor": "Søgeresultater for",
"MessageSelected": "{0} valgt",
"MessageServerCouldNotBeReached": "Serveren kunne ikke nås", "MessageServerCouldNotBeReached": "Serveren kunne ikke nås",
"MessageSetChaptersFromTracksDescription": "Indstil kapitler ved at bruge hver lydfil som et kapitel og kapiteloverskrift som lydfilnavn", "MessageSetChaptersFromTracksDescription": "Indstil kapitler ved at bruge hver lydfil som et kapitel og kapiteloverskrift som lydfilnavn",
"MessageShareExpirationWillBe": "Udløb vil være <strong>{0}</strong>",
"MessageShareExpiresIn": "Udløber om {0}",
"MessageShareURLWillBe": "Del URL vil være <strong>{0}</strong>",
"MessageStartPlaybackAtTime": "Start afspilning for \"{0}\" kl. {1}?", "MessageStartPlaybackAtTime": "Start afspilning for \"{0}\" kl. {1}?",
"MessageTaskAudioFileNotWritable": "Lydbogsfil \"{0}\" er ikke skrivebar",
"MessageTaskCanceledByUser": "Opgave annulleret af bruger",
"MessageTaskDownloadingEpisodeDescription": "Download afsnit \"{0}\"",
"MessageTaskEmbeddingMetadata": "Indlejring af metadata",
"MessageTaskEmbeddingMetadataDescription": "Indlejring af metadata i lydbog \"{0}\"",
"MessageTaskEncodingM4b": "Indkodning M4B",
"MessageTaskEncodingM4bDescription": "Indkodning lydog \"{0}\" ind i en enkelt M4B fil",
"MessageTaskFailed": "Fejlet",
"MessageTaskFailedToBackupAudioFile": "Fejlede backup af lydbogsfil \"{0}\"",
"MessageTaskFailedToCreateCacheDirectory": "Fejlede at oprette cache mappe",
"MessageTaskFailedToEmbedMetadataInFile": "Fejlede at indkode metadata i fil \"{0}\"",
"MessageTaskFailedToMergeAudioFiles": "Fejlede at sammenflette lydbogsfiler",
"MessageTaskFailedToMoveM4bFile": "Fejlede i at flytte M4B fil",
"MessageTaskFailedToWriteMetadataFile": "Fejlede i at skrive metadata fil",
"MessageTaskMatchingBooksInLibrary": "Matchede bøger i bibliotek \"{0}\"",
"MessageTaskNoFilesToScan": "Ingen filer at scanne",
"MessageTaskOpmlImport": "OPML import",
"MessageTaskOpmlImportDescription": "Oprettelse af podcasts fra {0} RSS feeds",
"MessageTaskOpmlImportFeed": "OPML importering fejlede",
"MessageTaskOpmlImportFeedDescription": "Importering af RSS feed \"{0}\"",
"MessageTaskOpmlImportFeedFailed": "Fejlede at hente podcast feed",
"MessageTaskOpmlImportFeedPodcastDescription": "Opretter podcast \"{0}\"",
"MessageTaskOpmlImportFeedPodcastExists": "Podcast ligger allerede på filsti",
"MessageTaskOpmlImportFeedPodcastFailed": "Fejlede i at oprette podcast",
"MessageTaskOpmlImportFinished": "Tilføjede {0} podcasts",
"MessageTaskOpmlParseFailed": "Fejlede i at læse OPML fil",
"MessageTaskOpmlParseFastFail": "Forkert OPML <opml> tag ikke fundet ELLER et <outline> tag var ikke fundet",
"MessageTaskOpmlParseNoneFound": "Ingen feeds fundet i OPML fil",
"MessageTaskScanItemsAdded": "{0} tilføjet",
"MessageTaskScanItemsMissing": "{0} mangler",
"MessageTaskScanItemsUpdated": "{0} opdateret",
"MessageTaskScanNoChangesNeeded": "Ingen ændringer nødvendigt",
"MessageTaskScanningFileChanges": "Scanner filændringer i \"{0}\"",
"MessageTaskScanningLibrary": "Scanning af \"{0}\" bibliotek",
"MessageTaskTargetDirectoryNotWritable": "Mål sti er ikke skrivebar",
"MessageThinking": "Tænker...", "MessageThinking": "Tænker...",
"MessageUploaderItemFailed": "Fejl ved upload", "MessageUploaderItemFailed": "Fejl ved upload",
"MessageUploaderItemSuccess": "Uploadet med succes!", "MessageUploaderItemSuccess": "Uploadet med succes!",
@@ -675,38 +905,102 @@
"NoteUploaderFoldersWithMediaFiles": "Mapper med mediefiler håndteres som separate bibliotekselementer.", "NoteUploaderFoldersWithMediaFiles": "Mapper med mediefiler håndteres som separate bibliotekselementer.",
"NoteUploaderOnlyAudioFiles": "Hvis du kun uploader lydfiler, håndteres hver lydfil som en separat lydbog.", "NoteUploaderOnlyAudioFiles": "Hvis du kun uploader lydfiler, håndteres hver lydfil som en separat lydbog.",
"NoteUploaderUnsupportedFiles": "Ikke-understøttede filer ignoreres. Når du vælger eller slipper en mappe, ignoreres andre filer, der ikke er i en emnemappe.", "NoteUploaderUnsupportedFiles": "Ikke-understøttede filer ignoreres. Når du vælger eller slipper en mappe, ignoreres andre filer, der ikke er i en emnemappe.",
"NotificationOnBackupCompletedDescription": "Udløst når backup er færdig",
"NotificationOnBackupFailedDescription": "Udløst når backup fejler",
"NotificationOnEpisodeDownloadedDescription": "Udløst når et podcast afsnit er automatisk downloadet",
"NotificationOnTestDescription": "Event for test af notifikationssystemet",
"PlaceholderNewCollection": "Nyt samlingnavn", "PlaceholderNewCollection": "Nyt samlingnavn",
"PlaceholderNewFolderPath": "Ny mappes sti", "PlaceholderNewFolderPath": "Ny mappes sti",
"PlaceholderNewPlaylist": "Nyt afspilningslistnavn", "PlaceholderNewPlaylist": "Nyt afspilningslistnavn",
"PlaceholderSearch": "Søg..", "PlaceholderSearch": "Søg..",
"PlaceholderSearchEpisode": "Søg efter episode..", "PlaceholderSearchEpisode": "Søg efter episode..",
"StatsAuthorsAdded": "forfattere tilføjet",
"StatsBooksAdded": "bøger tilføjet",
"StatsBooksAdditional": "Nogle tilføjelser inkludere…",
"StatsBooksFinished": "bøger færdige",
"StatsBooksFinishedThisYear": "Nogle bøger færdiggjort i år.…",
"StatsBooksListenedTo": "bøger lyttet til",
"StatsCollectionGrewTo": "Din bog kollektion voksede til…",
"StatsSessions": "sessioner",
"StatsSpentListening": "brugt at lytte",
"StatsTopAuthor": "TOP FORFATTER",
"StatsTopAuthors": "TOP FORFATTERE",
"StatsTopGenre": "TOP GENRE",
"StatsTopGenres": "TOP GENRER",
"StatsTopMonth": "TOP MÅNED",
"StatsTopNarrator": "TOP OPLÆSER",
"StatsTopNarrators": "TOP OPLÆSERE",
"StatsTotalDuration": "Med den totale varighed af…",
"StatsYearInReview": "ÅR I REVIEW",
"ToastAccountUpdateSuccess": "Konto opdateret", "ToastAccountUpdateSuccess": "Konto opdateret",
"ToastAppriseUrlRequired": "Skal indtaste en Apprise URL",
"ToastAsinRequired": "ASIN er påkrævet",
"ToastAuthorImageRemoveSuccess": "Forfatterbillede fjernet", "ToastAuthorImageRemoveSuccess": "Forfatterbillede fjernet",
"ToastAuthorNotFound": "Forfatter \"{0}\" ikke fundet",
"ToastAuthorRemoveSuccess": "Forfatter fjernet",
"ToastAuthorSearchNotFound": "Forfatter ikke fundet",
"ToastAuthorUpdateMerged": "Forfatter fusioneret", "ToastAuthorUpdateMerged": "Forfatter fusioneret",
"ToastAuthorUpdateSuccess": "Forfatter opdateret", "ToastAuthorUpdateSuccess": "Forfatter opdateret",
"ToastAuthorUpdateSuccessNoImageFound": "Forfatter opdateret (ingen billede fundet)", "ToastAuthorUpdateSuccessNoImageFound": "Forfatter opdateret (ingen billede fundet)",
"ToastBackupAppliedSuccess": "Backup indlæst",
"ToastBackupCreateFailed": "Mislykkedes oprettelse af sikkerhedskopi", "ToastBackupCreateFailed": "Mislykkedes oprettelse af sikkerhedskopi",
"ToastBackupCreateSuccess": "Sikkerhedskopi oprettet", "ToastBackupCreateSuccess": "Sikkerhedskopi oprettet",
"ToastBackupDeleteFailed": "Mislykkedes sletning af sikkerhedskopi", "ToastBackupDeleteFailed": "Mislykkedes sletning af sikkerhedskopi",
"ToastBackupDeleteSuccess": "Sikkerhedskopi slettet", "ToastBackupDeleteSuccess": "Sikkerhedskopi slettet",
"ToastBackupInvalidMaxKeep": "Forkert antal backups at beholde",
"ToastBackupInvalidMaxSize": "Forkert maks backup størrelse",
"ToastBackupRestoreFailed": "Mislykkedes gendannelse af sikkerhedskopi", "ToastBackupRestoreFailed": "Mislykkedes gendannelse af sikkerhedskopi",
"ToastBackupUploadFailed": "Mislykkedes upload af sikkerhedskopi", "ToastBackupUploadFailed": "Mislykkedes upload af sikkerhedskopi",
"ToastBackupUploadSuccess": "Sikkerhedskopi uploadet", "ToastBackupUploadSuccess": "Sikkerhedskopi uploadet",
"ToastBatchDeleteFailed": "Batch slet fejlede",
"ToastBatchDeleteSuccess": "Batch slet succes",
"ToastBatchQuickMatchFailed": "Batch Hurtig Match fejlede!",
"ToastBatchQuickMatchStarted": "Batch Hurtig Match af {0} bøger startet!",
"ToastBatchUpdateFailed": "Mislykkedes batchopdatering", "ToastBatchUpdateFailed": "Mislykkedes batchopdatering",
"ToastBatchUpdateSuccess": "Batchopdatering lykkedes", "ToastBatchUpdateSuccess": "Batchopdatering lykkedes",
"ToastBookmarkCreateFailed": "Mislykkedes oprettelse af bogmærke", "ToastBookmarkCreateFailed": "Mislykkedes oprettelse af bogmærke",
"ToastBookmarkCreateSuccess": "Bogmærke tilføjet", "ToastBookmarkCreateSuccess": "Bogmærke tilføjet",
"ToastBookmarkRemoveSuccess": "Bogmærke fjernet", "ToastBookmarkRemoveSuccess": "Bogmærke fjernet",
"ToastCachePurgeFailed": "Fejlede at opryde cache",
"ToastCachePurgeSuccess": "Cache ryddet op i succesfuldt",
"ToastChaptersHaveErrors": "Kapitler har fejl", "ToastChaptersHaveErrors": "Kapitler har fejl",
"ToastChaptersMustHaveTitles": "Kapitler skal have titler", "ToastChaptersMustHaveTitles": "Kapitler skal have titler",
"ToastChaptersRemoved": "Kapitler fjernet",
"ToastChaptersUpdated": "Kapitler opdateret",
"ToastCollectionItemsAddFailed": "Genstand(e) tilføjet til kollektion fejlet",
"ToastCollectionRemoveSuccess": "Samling fjernet", "ToastCollectionRemoveSuccess": "Samling fjernet",
"ToastCollectionUpdateSuccess": "Samling opdateret", "ToastCollectionUpdateSuccess": "Samling opdateret",
"ToastCoverUpdateFailed": "Cover opdatering fejlede",
"ToastDateTimeInvalidOrIncomplete": "Dato og tid er forkert eller ufærdig",
"ToastDeleteFileFailed": "Slet fil fejlede",
"ToastDeleteFileSuccess": "Fil slettet",
"ToastDeviceAddFailed": "Fejlede at tilføje enhed",
"ToastDeviceNameAlreadyExists": "Elæser enhed med det navn eksistere allerede",
"ToastDeviceTestEmailFailed": "Fejlede at sende test mail",
"ToastDeviceTestEmailSuccess": "Test mail sendt",
"ToastEmailSettingsUpdateSuccess": "Mail indstillinger opdateret",
"ToastEncodeCancelFailed": "Fejlede at afbryde indkodning",
"ToastEncodeCancelSucces": "Indkodning afbrudt",
"ToastEpisodeDownloadQueueClearFailed": "Fejlede at rydde op i kø",
"ToastEpisodeDownloadQueueClearSuccess": "Afsnit download kø renset",
"ToastEpisodeUpdateSuccess": "{0} afsnit opdateret",
"ToastErrorCannotShare": "Kan ikke dele på denne enhed",
"ToastFailedToLoadData": "Fejlede at indlæse data",
"ToastFailedToMatch": "Fejlet match",
"ToastFailedToShare": "Fejlet deling",
"ToastFailedToUpdate": "Fejlet opdatering",
"ToastInvalidImageUrl": "Forkert billede URL",
"ToastInvalidMaxEpisodesToDownload": "Forkert maks afsnit at hente",
"ToastInvalidUrl": "Forkert URL",
"ToastItemCoverUpdateSuccess": "Varens omslag opdateret", "ToastItemCoverUpdateSuccess": "Varens omslag opdateret",
"ToastItemDeletedFailed": "Fejlede at slette genstand",
"ToastItemDeletedSuccess": "Genstand slettet",
"ToastItemDetailsUpdateSuccess": "Varedetaljer opdateret", "ToastItemDetailsUpdateSuccess": "Varedetaljer opdateret",
"ToastItemMarkedAsFinishedFailed": "Mislykkedes markering som afsluttet", "ToastItemMarkedAsFinishedFailed": "Mislykkedes markering som afsluttet",
"ToastItemMarkedAsFinishedSuccess": "Vare markeret som afsluttet", "ToastItemMarkedAsFinishedSuccess": "Vare markeret som afsluttet",
"ToastItemMarkedAsNotFinishedFailed": "Mislykkedes markering som ikke afsluttet", "ToastItemMarkedAsNotFinishedFailed": "Mislykkedes markering som ikke afsluttet",
"ToastItemMarkedAsNotFinishedSuccess": "Vare markeret som ikke afsluttet", "ToastItemMarkedAsNotFinishedSuccess": "Vare markeret som ikke afsluttet",
"ToastItemUpdateSuccess": "Genstand opdateret",
"ToastLibraryCreateFailed": "Mislykkedes oprettelse af bibliotek", "ToastLibraryCreateFailed": "Mislykkedes oprettelse af bibliotek",
"ToastLibraryCreateSuccess": "Bibliotek \"{0}\" oprettet", "ToastLibraryCreateSuccess": "Bibliotek \"{0}\" oprettet",
"ToastLibraryDeleteFailed": "Mislykkedes sletning af bibliotek", "ToastLibraryDeleteFailed": "Mislykkedes sletning af bibliotek",
@@ -714,25 +1008,84 @@
"ToastLibraryScanFailedToStart": "Mislykkedes start af skanning", "ToastLibraryScanFailedToStart": "Mislykkedes start af skanning",
"ToastLibraryScanStarted": "Biblioteksskanning startet", "ToastLibraryScanStarted": "Biblioteksskanning startet",
"ToastLibraryUpdateSuccess": "Bibliotek \"{0}\" opdateret", "ToastLibraryUpdateSuccess": "Bibliotek \"{0}\" opdateret",
"ToastMatchAllAuthorsFailed": "Fejlede at matche alle forfattere",
"ToastMetadataFilesRemovedError": "Fejlet at fjerne metadata.{0} filer",
"ToastMetadataFilesRemovedNoneFound": "Ingen metadata.{0} filer fundet i bibliotek",
"ToastMetadataFilesRemovedNoneRemoved": "Ingen metadata.{0} filer slettet",
"ToastMetadataFilesRemovedSuccess": "{0} metadata.{1} filer slettet",
"ToastMustHaveAtLeastOnePath": "Skal have mindst en sti",
"ToastNameEmailRequired": "Navn og email påkrævet",
"ToastNameRequired": "Navn påkrævet",
"ToastNewEpisodesFound": "{0} nye afsnit fundet",
"ToastNewUserCreatedFailed": "Fejlede at oprette konto: \"{0}\"",
"ToastNewUserCreatedSuccess": "Ny konto oprettet",
"ToastNewUserLibraryError": "Skal vælge mindst et bibliotek",
"ToastNewUserPasswordError": "Skal have et password, kun root brugeren kan have et tomt password",
"ToastNewUserTagError": "Skal vælge mindst et tag",
"ToastNewUserUsernameError": "Angiv brugernavn",
"ToastNoNewEpisodesFound": "Ingen nye afsnit fundet",
"ToastNoRSSFeed": "Podcast har ingen RSS feed",
"ToastNoUpdatesNecessary": "Ingen opdateringer nødvendige",
"ToastNotificationCreateFailed": "Fejlede at oprette notifikation",
"ToastNotificationDeleteFailed": "Fejlede at slette notifikation",
"ToastNotificationFailedMaximum": "Maks forsøg skal være >= 0",
"ToastNotificationQueueMaximum": "Maks notifikationskø skal være >= 0",
"ToastNotificationSettingsUpdateSuccess": "Notifikationsindstillinger opdateret",
"ToastNotificationTestTriggerFailed": "Fejlede at oprette en test notifikation",
"ToastNotificationTestTriggerSuccess": "Test notifikation oprettet",
"ToastNotificationUpdateSuccess": "Notifikation opdateret",
"ToastPlaylistCreateFailed": "Mislykkedes oprettelse af afspilningsliste", "ToastPlaylistCreateFailed": "Mislykkedes oprettelse af afspilningsliste",
"ToastPlaylistCreateSuccess": "Afspilningsliste oprettet", "ToastPlaylistCreateSuccess": "Afspilningsliste oprettet",
"ToastPlaylistRemoveSuccess": "Afspilningsliste fjernet", "ToastPlaylistRemoveSuccess": "Afspilningsliste fjernet",
"ToastPlaylistUpdateSuccess": "Afspilningsliste opdateret", "ToastPlaylistUpdateSuccess": "Afspilningsliste opdateret",
"ToastPodcastCreateFailed": "Mislykkedes oprettelse af podcast", "ToastPodcastCreateFailed": "Mislykkedes oprettelse af podcast",
"ToastPodcastCreateSuccess": "Podcast oprettet med succes", "ToastPodcastCreateSuccess": "Podcast oprettet med succes",
"ToastPodcastGetFeedFailed": "Fejlede at hente podcast feed",
"ToastPodcastNoEpisodesInFeed": "Ingen nye afsnit fundet i RSS feed",
"ToastPodcastNoRssFeed": "Podcast har ingen RSS feed",
"ToastProgressIsNotBeingSynced": "Fremskridt ikke synkroniseret, genstart afspilning",
"ToastProviderCreatedFailed": "Fejlede at tilføje udbyder",
"ToastProviderCreatedSuccess": "Ny udbyder tilføjet",
"ToastProviderNameAndUrlRequired": "Navn og URL påkrævet",
"ToastProviderRemoveSuccess": "Udbyder fjernet",
"ToastRSSFeedCloseFailed": "Mislykkedes lukning af RSS-feed", "ToastRSSFeedCloseFailed": "Mislykkedes lukning af RSS-feed",
"ToastRSSFeedCloseSuccess": "RSS-feed lukket", "ToastRSSFeedCloseSuccess": "RSS-feed lukket",
"ToastRemoveFailed": "Fejlede at slette",
"ToastRemoveItemFromCollectionFailed": "Mislykkedes fjernelse af element fra samling", "ToastRemoveItemFromCollectionFailed": "Mislykkedes fjernelse af element fra samling",
"ToastRemoveItemFromCollectionSuccess": "Element fjernet fra samling", "ToastRemoveItemFromCollectionSuccess": "Element fjernet fra samling",
"ToastRemoveItemsWithIssuesFailed": "Fejlede at slette genstande med fejl",
"ToastRemoveItemsWithIssuesSuccess": "Slettede genstande med fejl",
"ToastRenameFailed": "Fejlede at omdøbe",
"ToastRescanFailed": "Genscan fejlede for {0}",
"ToastRescanRemoved": "Genscan gennemført, genstand blev fjernet",
"ToastRescanUpToDate": "Genscan gennemført, genstand var opdateret",
"ToastRescanUpdated": "Genscan gennemført, genstand blev opdateret",
"ToastScanFailed": "Fejlede at scanne biblioteksgenstand",
"ToastSelectAtLeastOneUser": "Vælg mindst en bruger",
"ToastSendEbookToDeviceFailed": "Mislykkedes afsendelse af e-bog til enhed", "ToastSendEbookToDeviceFailed": "Mislykkedes afsendelse af e-bog til enhed",
"ToastSendEbookToDeviceSuccess": "E-bog afsendt til enhed \"{0}\"", "ToastSendEbookToDeviceSuccess": "E-bog afsendt til enhed \"{0}\"",
"ToastSeriesUpdateFailed": "Mislykkedes opdatering af serie", "ToastSeriesUpdateFailed": "Mislykkedes opdatering af serie",
"ToastSeriesUpdateSuccess": "Serieopdatering lykkedes", "ToastSeriesUpdateSuccess": "Serieopdatering lykkedes",
"ToastServerSettingsUpdateSuccess": "Server indstillinger opdateret",
"ToastSessionCloseFailed": "Luk session fejlede",
"ToastSessionDeleteFailed": "Mislykkedes sletning af session", "ToastSessionDeleteFailed": "Mislykkedes sletning af session",
"ToastSessionDeleteSuccess": "Session slettet", "ToastSessionDeleteSuccess": "Session slettet",
"ToastSleepTimerDone": "Sleep timer færdig... zZzzZz",
"ToastSlugMustChange": "Snegl indeholder ugyldige karakterer",
"ToastSlugRequired": "Snegl påkrævet",
"ToastSocketConnected": "Socket forbundet", "ToastSocketConnected": "Socket forbundet",
"ToastSocketDisconnected": "Socket afbrudt", "ToastSocketDisconnected": "Socket afbrudt",
"ToastSocketFailedToConnect": "Socket kunne ikke oprettes", "ToastSocketFailedToConnect": "Socket kunne ikke oprettes",
"ToastSortingPrefixesEmptyError": "Skal indeholde mindst 1 sorteringspræfiks",
"ToastSortingPrefixesUpdateSuccess": "Sortering af præfiks opdateret ({0} genstande)",
"ToastTitleRequired": "Titel påkrævet",
"ToastUnknownError": "Ukendt fejl",
"ToastUnlinkOpenIdFailed": "Fejlede i af afkoble bruger fra OpenID",
"ToastUnlinkOpenIdSuccess": "Bruger afkoblet fra OpenID",
"ToastUserDeleteFailed": "Mislykkedes sletning af bruger", "ToastUserDeleteFailed": "Mislykkedes sletning af bruger",
"ToastUserDeleteSuccess": "Bruger slettet" "ToastUserDeleteSuccess": "Bruger slettet",
"ToastUserPasswordChangeSuccess": "Password ændret",
"ToastUserPasswordMismatch": "Passwords passer ikke sammen",
"ToastUserPasswordMustChange": "Nyt password må ikke være det gamle",
"ToastUserRootRequireName": "Skal indholde et root brugernavn"
} }
+9 -3
View File
@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Kapitel anwenden", "ButtonApplyChapters": "Kapitel anwenden",
"ButtonAuthors": "Autoren", "ButtonAuthors": "Autoren",
"ButtonBack": "Zurück", "ButtonBack": "Zurück",
"ButtonBatchEditPopulateFromExisting": "Auffüllen aus vorhandenem",
"ButtonBatchEditPopulateMapDetails": "Kartendetails auffüllen",
"ButtonBrowseForFolder": "Ordnersuche", "ButtonBrowseForFolder": "Ordnersuche",
"ButtonCancel": "Abbrechen", "ButtonCancel": "Abbrechen",
"ButtonCancelEncode": "Codierung abbrechen", "ButtonCancelEncode": "Codierung abbrechen",
@@ -484,6 +486,7 @@
"LabelPersonalYearReview": "Dein Jahr in Übersicht ({0})", "LabelPersonalYearReview": "Dein Jahr in Übersicht ({0})",
"LabelPhotoPathURL": "Foto Pfad/URL", "LabelPhotoPathURL": "Foto Pfad/URL",
"LabelPlayMethod": "Abspielmethode", "LabelPlayMethod": "Abspielmethode",
"LabelPlaybackRateIncrementDecrement": "Wiedergaberate der Erhöhung/Verminderung",
"LabelPlayerChapterNumberMarker": "{0} von {1}", "LabelPlayerChapterNumberMarker": "{0} von {1}",
"LabelPlaylists": "Wiedergabelisten", "LabelPlaylists": "Wiedergabelisten",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@@ -645,7 +648,7 @@
"LabelTimeToShift": "Zeit bis zum Wechsel in Sekunden", "LabelTimeToShift": "Zeit bis zum Wechsel in Sekunden",
"LabelTitle": "Titel", "LabelTitle": "Titel",
"LabelToolsEmbedMetadata": "Metadaten einbetten", "LabelToolsEmbedMetadata": "Metadaten einbetten",
"LabelToolsEmbedMetadataDescription": "Bettet die Metadaten einschließlich des Titelbildes und der Kapitel in die Audiodatein ein.", "LabelToolsEmbedMetadataDescription": "Bettet die Metadaten einschließlich des Titelbildes und der Kapitel in die Audiodateien ein.",
"LabelToolsM4bEncoder": "M4B Kodierer", "LabelToolsM4bEncoder": "M4B Kodierer",
"LabelToolsMakeM4b": "M4B-Datei erstellen", "LabelToolsMakeM4b": "M4B-Datei erstellen",
"LabelToolsMakeM4bDescription": "Erstellt eine M4B-Datei (Endung \".m4b\") welche mehrere mp3-Dateien in einer einzigen Datei inkl. derer Metadaten (Beschreibung, Titelbild, Kapitel, ...) zusammenfasst. M4B-Datei können darüber hinaus Lesezeichen speichern und mit einem Abspielschutz (Passwort) versehen werden.", "LabelToolsMakeM4bDescription": "Erstellt eine M4B-Datei (Endung \".m4b\") welche mehrere mp3-Dateien in einer einzigen Datei inkl. derer Metadaten (Beschreibung, Titelbild, Kapitel, ...) zusammenfasst. M4B-Datei können darüber hinaus Lesezeichen speichern und mit einem Abspielschutz (Passwort) versehen werden.",
@@ -704,8 +707,10 @@
"MessageBackupsLocationEditNote": "Hinweis: Durch das Aktualisieren des Backup-Speicherorts werden vorhandene Sicherungen nicht verschoben oder geändert", "MessageBackupsLocationEditNote": "Hinweis: Durch das Aktualisieren des Backup-Speicherorts werden vorhandene Sicherungen nicht verschoben oder geändert",
"MessageBackupsLocationNoEditNote": "Hinweis: Der Sicherungsspeicherort wird über eine Umgebungsvariable festgelegt und kann hier nicht geändert werden.", "MessageBackupsLocationNoEditNote": "Hinweis: Der Sicherungsspeicherort wird über eine Umgebungsvariable festgelegt und kann hier nicht geändert werden.",
"MessageBackupsLocationPathEmpty": "Der Backup-Pfad darf nicht leer sein", "MessageBackupsLocationPathEmpty": "Der Backup-Pfad darf nicht leer sein",
"MessageBatchEditPopulateMapDetailsAllHelp": "Fülle die aktivierten Felder mit Daten aus allen Elementen. Felder mit mehreren Werten werden zusammengeführt",
"MessageBatchQuickMatchDescription": "Der Schnellabgleich versucht, fehlende Titelbilder und Metadaten für die ausgewählten Artikel hinzuzufügen. Aktiviere die nachstehenden Optionen, damit der Schnellabgleich vorhandene Titelbilder und/oder Metadaten überschreiben kann.", "MessageBatchQuickMatchDescription": "Der Schnellabgleich versucht, fehlende Titelbilder und Metadaten für die ausgewählten Artikel hinzuzufügen. Aktiviere die nachstehenden Optionen, damit der Schnellabgleich vorhandene Titelbilder und/oder Metadaten überschreiben kann.",
"MessageBookshelfNoCollections": "Es wurden noch keine Sammlungen erstellt", "MessageBookshelfNoCollections": "Es wurden noch keine Sammlungen erstellt",
"MessageBookshelfNoCollectionsHelp": "Sammlungen sind öffentlich. Alle Benutzer mit Zugriff auf die Bibliothek können sie sehen.",
"MessageBookshelfNoRSSFeeds": "Keine RSS-Feeds geöffnet", "MessageBookshelfNoRSSFeeds": "Keine RSS-Feeds geöffnet",
"MessageBookshelfNoResultsForFilter": "Keine Ergebnisse für Filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Keine Ergebnisse für Filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Keine Ergebnisse für die Abfrage", "MessageBookshelfNoResultsForQuery": "Keine Ergebnisse für die Abfrage",
@@ -816,6 +821,7 @@
"MessageNoTasksRunning": "Keine laufenden Aufgaben", "MessageNoTasksRunning": "Keine laufenden Aufgaben",
"MessageNoUpdatesWereNecessary": "Keine Aktualisierungen waren notwendig", "MessageNoUpdatesWereNecessary": "Keine Aktualisierungen waren notwendig",
"MessageNoUserPlaylists": "Keine Wiedergabelisten vorhanden", "MessageNoUserPlaylists": "Keine Wiedergabelisten vorhanden",
"MessageNoUserPlaylistsHelp": "Wiedergabelisten sind privat. Nur der Benutzer, der sie erstellt hat, kann sie sehen.",
"MessageNotYetImplemented": "Noch nicht implementiert", "MessageNotYetImplemented": "Noch nicht implementiert",
"MessageOpmlPreviewNote": "Hinweis: Dies ist nur eine Vorschau der geparsten OPML Datei. Der eigentliche Podcast-Titel wird aus dem RSS-Feed übernommen.", "MessageOpmlPreviewNote": "Hinweis: Dies ist nur eine Vorschau der geparsten OPML Datei. Der eigentliche Podcast-Titel wird aus dem RSS-Feed übernommen.",
"MessageOr": "Oder", "MessageOr": "Oder",
@@ -964,7 +970,7 @@
"ToastCollectionRemoveSuccess": "Sammlung entfernt", "ToastCollectionRemoveSuccess": "Sammlung entfernt",
"ToastCollectionUpdateSuccess": "Sammlung aktualisiert", "ToastCollectionUpdateSuccess": "Sammlung aktualisiert",
"ToastCoverUpdateFailed": "Cover-Update fehlgeschlagen", "ToastCoverUpdateFailed": "Cover-Update fehlgeschlagen",
"ToastDateTimeInvalidOrIncomplete": "Datum und Zeit ist ungültig oder unvollständig", "ToastDateTimeInvalidOrIncomplete": "Datum und Zeit sind ungültig oder unvollständig",
"ToastDeleteFileFailed": "Die Datei konnte nicht gelöscht werden", "ToastDeleteFileFailed": "Die Datei konnte nicht gelöscht werden",
"ToastDeleteFileSuccess": "Datei gelöscht", "ToastDeleteFileSuccess": "Datei gelöscht",
"ToastDeviceAddFailed": "Gerät konnte nicht hinzugefügt werden", "ToastDeviceAddFailed": "Gerät konnte nicht hinzugefügt werden",
@@ -1017,7 +1023,7 @@
"ToastNewUserTagError": "Mindestens ein Tag muss ausgewählt sein", "ToastNewUserTagError": "Mindestens ein Tag muss ausgewählt sein",
"ToastNewUserUsernameError": "Nutzername eingeben", "ToastNewUserUsernameError": "Nutzername eingeben",
"ToastNoNewEpisodesFound": "Keine neuen Episoden gefunden", "ToastNoNewEpisodesFound": "Keine neuen Episoden gefunden",
"ToastNoRSSFeed": "Podcast hat keinen RSS Feed", "ToastNoRSSFeed": "Podcast hat keinen RSS-Feed",
"ToastNoUpdatesNecessary": "Keine Änderungen nötig", "ToastNoUpdatesNecessary": "Keine Änderungen nötig",
"ToastNotificationCreateFailed": "Fehler beim erstellen der Benachrichtig", "ToastNotificationCreateFailed": "Fehler beim erstellen der Benachrichtig",
"ToastNotificationDeleteFailed": "Fehler beim löschen der Benachrichtigung", "ToastNotificationDeleteFailed": "Fehler beim löschen der Benachrichtigung",
+7
View File
@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Apply Chapters", "ButtonApplyChapters": "Apply Chapters",
"ButtonAuthors": "Authors", "ButtonAuthors": "Authors",
"ButtonBack": "Back", "ButtonBack": "Back",
"ButtonBatchEditPopulateFromExisting": "Populate from existing",
"ButtonBatchEditPopulateMapDetails": "Populate map details",
"ButtonBrowseForFolder": "Browse for Folder", "ButtonBrowseForFolder": "Browse for Folder",
"ButtonCancel": "Cancel", "ButtonCancel": "Cancel",
"ButtonCancelEncode": "Cancel Encode", "ButtonCancelEncode": "Cancel Encode",
@@ -484,6 +486,7 @@
"LabelPersonalYearReview": "Your Year in Review ({0})", "LabelPersonalYearReview": "Your Year in Review ({0})",
"LabelPhotoPathURL": "Photo Path/URL", "LabelPhotoPathURL": "Photo Path/URL",
"LabelPlayMethod": "Play Method", "LabelPlayMethod": "Play Method",
"LabelPlaybackRateIncrementDecrement": "Playback Rate Increment/Decrement Amount",
"LabelPlayerChapterNumberMarker": "{0} of {1}", "LabelPlayerChapterNumberMarker": "{0} of {1}",
"LabelPlaylists": "Playlists", "LabelPlaylists": "Playlists",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@@ -704,8 +707,11 @@
"MessageBackupsLocationEditNote": "Note: Updating the backup location will not move or modify existing backups", "MessageBackupsLocationEditNote": "Note: Updating the backup location will not move or modify existing backups",
"MessageBackupsLocationNoEditNote": "Note: The backup location is set through an environment variable and cannot be changed here.", "MessageBackupsLocationNoEditNote": "Note: The backup location is set through an environment variable and cannot be changed here.",
"MessageBackupsLocationPathEmpty": "Backup location path cannot be empty", "MessageBackupsLocationPathEmpty": "Backup location path cannot be empty",
"MessageBatchEditPopulateMapDetailsAllHelp": "Populate enabled fields with data from all items. Fields with multiple values will be merged",
"MessageBatchEditPopulateMapDetailsItemHelp": "Populate enabled map details fields with data from this item",
"MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.", "MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.",
"MessageBookshelfNoCollections": "You haven't made any collections yet", "MessageBookshelfNoCollections": "You haven't made any collections yet",
"MessageBookshelfNoCollectionsHelp": "Collections are public. All users with access to the library can see them.",
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open", "MessageBookshelfNoRSSFeeds": "No RSS feeds are open",
"MessageBookshelfNoResultsForFilter": "No results for filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "No results for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query", "MessageBookshelfNoResultsForQuery": "No results for query",
@@ -816,6 +822,7 @@
"MessageNoTasksRunning": "No Tasks Running", "MessageNoTasksRunning": "No Tasks Running",
"MessageNoUpdatesWereNecessary": "No updates were necessary", "MessageNoUpdatesWereNecessary": "No updates were necessary",
"MessageNoUserPlaylists": "You have no playlists", "MessageNoUserPlaylists": "You have no playlists",
"MessageNoUserPlaylistsHelp": "Playlists are private. Only the user who creates them can see them.",
"MessageNotYetImplemented": "Not yet implemented", "MessageNotYetImplemented": "Not yet implemented",
"MessageOpmlPreviewNote": "Note: This is a preview of the parsed OPML file. The actual podcast title will be taken from the RSS feed.", "MessageOpmlPreviewNote": "Note: This is a preview of the parsed OPML file. The actual podcast title will be taken from the RSS feed.",
"MessageOr": "or", "MessageOr": "or",
+3 -2
View File
@@ -1,5 +1,5 @@
{ {
"ButtonAdd": "Agregaro", "ButtonAdd": "Agregar",
"ButtonAddChapters": "Agregar", "ButtonAddChapters": "Agregar",
"ButtonAddDevice": "Agregar Dispositivo", "ButtonAddDevice": "Agregar Dispositivo",
"ButtonAddLibrary": "Crear Biblioteca", "ButtonAddLibrary": "Crear Biblioteca",
@@ -51,7 +51,7 @@
"ButtonNext": "Siguiente", "ButtonNext": "Siguiente",
"ButtonNextChapter": "Siguiente Capítulo", "ButtonNextChapter": "Siguiente Capítulo",
"ButtonNextItemInQueue": "El siguiente elemento en cola", "ButtonNextItemInQueue": "El siguiente elemento en cola",
"ButtonOk": "De acuerdo", "ButtonOk": "Bueno",
"ButtonOpenFeed": "Abrir fuente", "ButtonOpenFeed": "Abrir fuente",
"ButtonOpenManager": "Abrir Editor", "ButtonOpenManager": "Abrir Editor",
"ButtonPause": "Pausar", "ButtonPause": "Pausar",
@@ -837,6 +837,7 @@
"MessageResetChaptersConfirm": "¿Está seguro de que desea deshacer los cambios y revertir los capítulos a su estado original?", "MessageResetChaptersConfirm": "¿Está seguro de que desea deshacer los cambios y revertir los capítulos a su estado original?",
"MessageRestoreBackupConfirm": "¿Está seguro de que desea para restaurar del respaldo creado en", "MessageRestoreBackupConfirm": "¿Está seguro de que desea para restaurar del respaldo creado en",
"MessageRestoreBackupWarning": "Restaurar sobrescribirá toda la base de datos localizada en /config y las imágenes de portadas en /metadata/items y /metadata/authors.<br /><br />El respaldo no modifica ningún archivo en las carpetas de su biblioteca. Si ha habilitado la opción del servidor para almacenar portadas y metadata en las carpetas de su biblioteca, esos archivos no se respaldan o sobrescriben.<br /><br />Todos los clientes que usen su servidor se actualizarán automáticamente.", "MessageRestoreBackupWarning": "Restaurar sobrescribirá toda la base de datos localizada en /config y las imágenes de portadas en /metadata/items y /metadata/authors.<br /><br />El respaldo no modifica ningún archivo en las carpetas de su biblioteca. Si ha habilitado la opción del servidor para almacenar portadas y metadata en las carpetas de su biblioteca, esos archivos no se respaldan o sobrescriben.<br /><br />Todos los clientes que usen su servidor se actualizarán automáticamente.",
"MessageScheduleLibraryScanNote": "Para la mayoría de los usuarios, se recomienda dejar esta función desactivada y mantener activada la configuración del observador de carpetas. El observador de carpetas detectará automáticamente los cambios en las carpetas de la biblioteca. El observador de carpetas no funciona para todos los sistemas de archivos (como NFS), por lo que se pueden utilizar exploraciones programadas de la biblioteca en su lugar.",
"MessageSearchResultsFor": "Resultados de la búsqueda de", "MessageSearchResultsFor": "Resultados de la búsqueda de",
"MessageSelected": "{0} seleccionado(s)", "MessageSelected": "{0} seleccionado(s)",
"MessageServerCouldNotBeReached": "No se pudo establecer la conexión con el servidor", "MessageServerCouldNotBeReached": "No se pudo establecer la conexión con el servidor",
+16
View File
@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Appliquer aux chapitres", "ButtonApplyChapters": "Appliquer aux chapitres",
"ButtonAuthors": "Auteurs", "ButtonAuthors": "Auteurs",
"ButtonBack": "Retour", "ButtonBack": "Retour",
"ButtonBatchEditPopulateFromExisting": "Remplir à partir de l'existant",
"ButtonBatchEditPopulateMapDetails": "Remplir les détails de la carte",
"ButtonBrowseForFolder": "Naviguer vers le répertoire", "ButtonBrowseForFolder": "Naviguer vers le répertoire",
"ButtonCancel": "Annuler", "ButtonCancel": "Annuler",
"ButtonCancelEncode": "Annuler lencodage", "ButtonCancelEncode": "Annuler lencodage",
@@ -88,6 +90,8 @@
"ButtonSaveTracklist": "Sauvegarder la liste de lecture", "ButtonSaveTracklist": "Sauvegarder la liste de lecture",
"ButtonScan": "Analyser", "ButtonScan": "Analyser",
"ButtonScanLibrary": "Analyser la bibliothèque", "ButtonScanLibrary": "Analyser la bibliothèque",
"ButtonScrollLeft": "Défiler vers la gauche",
"ButtonScrollRight": "Défiler vers la droite",
"ButtonSearch": "Chercher", "ButtonSearch": "Chercher",
"ButtonSelectFolderPath": "Sélectionner le chemin du dossier", "ButtonSelectFolderPath": "Sélectionner le chemin du dossier",
"ButtonSeries": "Séries", "ButtonSeries": "Séries",
@@ -190,6 +194,7 @@
"HeaderSettingsExperimental": "Fonctionnalités expérimentales", "HeaderSettingsExperimental": "Fonctionnalités expérimentales",
"HeaderSettingsGeneral": "Général", "HeaderSettingsGeneral": "Général",
"HeaderSettingsScanner": "Analyseur", "HeaderSettingsScanner": "Analyseur",
"HeaderSettingsWebClient": "Client Web",
"HeaderSleepTimer": "Minuterie", "HeaderSleepTimer": "Minuterie",
"HeaderStatsLargestItems": "Éléments les plus grands", "HeaderStatsLargestItems": "Éléments les plus grands",
"HeaderStatsLongestItems": "Éléments les plus long (hrs)", "HeaderStatsLongestItems": "Éléments les plus long (hrs)",
@@ -297,6 +302,7 @@
"LabelDiscover": "Découvrir", "LabelDiscover": "Découvrir",
"LabelDownload": "Téléchargement", "LabelDownload": "Téléchargement",
"LabelDownloadNEpisodes": "Télécharger {0} épisode(s)", "LabelDownloadNEpisodes": "Télécharger {0} épisode(s)",
"LabelDownloadable": "Téléchargeable",
"LabelDuration": "Durée", "LabelDuration": "Durée",
"LabelDurationComparisonExactMatch": "(correspondance exacte)", "LabelDurationComparisonExactMatch": "(correspondance exacte)",
"LabelDurationComparisonLonger": "({0} plus long)", "LabelDurationComparisonLonger": "({0} plus long)",
@@ -480,6 +486,7 @@
"LabelPersonalYearReview": "Bilan de lannée ({0})", "LabelPersonalYearReview": "Bilan de lannée ({0})",
"LabelPhotoPathURL": "Chemin / URL des photos", "LabelPhotoPathURL": "Chemin / URL des photos",
"LabelPlayMethod": "Méthode d’écoute", "LabelPlayMethod": "Méthode d’écoute",
"LabelPlaybackRateIncrementDecrement": "Augmentation/Diminition de la vitesse de lecture",
"LabelPlayerChapterNumberMarker": "{0} sur {1}", "LabelPlayerChapterNumberMarker": "{0} sur {1}",
"LabelPlaylists": "Listes de lecture", "LabelPlaylists": "Listes de lecture",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@@ -542,6 +549,7 @@
"LabelServerYearReview": "Bilan de lannée du serveur ({0})", "LabelServerYearReview": "Bilan de lannée du serveur ({0})",
"LabelSetEbookAsPrimary": "Définir comme principale", "LabelSetEbookAsPrimary": "Définir comme principale",
"LabelSetEbookAsSupplementary": "Définir comme supplémentaire", "LabelSetEbookAsSupplementary": "Définir comme supplémentaire",
"LabelSettingsAllowIframe": "Autoriser lintégration dans une iframe",
"LabelSettingsAudiobooksOnly": "Livres audios seulement", "LabelSettingsAudiobooksOnly": "Livres audios seulement",
"LabelSettingsAudiobooksOnlyHelp": "Lactivation de ce paramètre ignorera les fichiers de type « livre numériques », sauf sils se trouvent dans un dossier spécifique , auquel cas ils seront définis comme des livres numériques supplémentaires", "LabelSettingsAudiobooksOnlyHelp": "Lactivation de ce paramètre ignorera les fichiers de type « livre numériques », sauf sils se trouvent dans un dossier spécifique , auquel cas ils seront définis comme des livres numériques supplémentaires",
"LabelSettingsBookshelfViewHelp": "Interface skeumorphique avec étagères en bois", "LabelSettingsBookshelfViewHelp": "Interface skeumorphique avec étagères en bois",
@@ -584,6 +592,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Par défaut, les fichiers de métadonnées sont stockés dans /metadata/items. En activant ce paramètre, les fichiers de métadonnées seront stockés dans les dossiers des éléments de votre bibliothèque", "LabelSettingsStoreMetadataWithItemHelp": "Par défaut, les fichiers de métadonnées sont stockés dans /metadata/items. En activant ce paramètre, les fichiers de métadonnées seront stockés dans les dossiers des éléments de votre bibliothèque",
"LabelSettingsTimeFormat": "Format dheure", "LabelSettingsTimeFormat": "Format dheure",
"LabelShare": "Partager", "LabelShare": "Partager",
"LabelShareDownloadableHelp": "Permet aux utilisateurs de télécharger un fichier ZIP de l'élément de la bibliothèque.",
"LabelShareOpen": "Ouvrir le partage", "LabelShareOpen": "Ouvrir le partage",
"LabelShareURL": "Partager lURL", "LabelShareURL": "Partager lURL",
"LabelShowAll": "Tout afficher", "LabelShowAll": "Tout afficher",
@@ -681,6 +690,8 @@
"LabelViewPlayerSettings": "Afficher les paramètres du lecteur", "LabelViewPlayerSettings": "Afficher les paramètres du lecteur",
"LabelViewQueue": "Afficher la liste de lecture", "LabelViewQueue": "Afficher la liste de lecture",
"LabelVolume": "Volume", "LabelVolume": "Volume",
"LabelWebRedirectURLsDescription": "Autoriser ces URL dans votre fournisseur OAuth pour permettre la redirection vers l'application web après la connexion :",
"LabelWebRedirectURLsSubfolder": "Sous-dossier pour les URL de redirection",
"LabelWeekdaysToRun": "Jours de la semaine à exécuter", "LabelWeekdaysToRun": "Jours de la semaine à exécuter",
"LabelXBooks": "{0} livres", "LabelXBooks": "{0} livres",
"LabelXItems": "{0} éléments", "LabelXItems": "{0} éléments",
@@ -696,6 +707,7 @@
"MessageBackupsLocationEditNote": "Remarque: Mettre à jour l'emplacement de sauvegarde ne déplacera pas ou ne modifiera pas les sauvegardes existantes", "MessageBackupsLocationEditNote": "Remarque: Mettre à jour l'emplacement de sauvegarde ne déplacera pas ou ne modifiera pas les sauvegardes existantes",
"MessageBackupsLocationNoEditNote": "Remarque: lemplacement de sauvegarde est défini via une variable denvironnement et ne peut pas être modifié ici.", "MessageBackupsLocationNoEditNote": "Remarque: lemplacement de sauvegarde est défini via une variable denvironnement et ne peut pas être modifié ici.",
"MessageBackupsLocationPathEmpty": "L'emplacement de secours ne peut pas être vide", "MessageBackupsLocationPathEmpty": "L'emplacement de secours ne peut pas être vide",
"MessageBatchEditPopulateMapDetailsAllHelp": "Remplir les champs disponibles avec les données de tous les éléments. les champs avec des valeurs multiples seront fusionnés",
"MessageBatchQuickMatchDescription": "La recherche par correspondance rapide tentera dajouter les couvertures et métadonnées manquantes pour les éléments sélectionnés. Activez les options ci-dessous pour permettre la Recherche par correspondance d’écraser les couvertures et/ou métadonnées existantes.", "MessageBatchQuickMatchDescription": "La recherche par correspondance rapide tentera dajouter les couvertures et métadonnées manquantes pour les éléments sélectionnés. Activez les options ci-dessous pour permettre la Recherche par correspondance d’écraser les couvertures et/ou métadonnées existantes.",
"MessageBookshelfNoCollections": "Vous navez pas encore de collections", "MessageBookshelfNoCollections": "Vous navez pas encore de collections",
"MessageBookshelfNoRSSFeeds": "Aucun flux RSS nest ouvert", "MessageBookshelfNoRSSFeeds": "Aucun flux RSS nest ouvert",
@@ -750,6 +762,7 @@
"MessageConfirmResetProgress": "Êtes-vous sûr·e de vouloir réinitialiser votre progression?", "MessageConfirmResetProgress": "Êtes-vous sûr·e de vouloir réinitialiser votre progression?",
"MessageConfirmSendEbookToDevice": "Êtes-vous sûr·e de vouloir envoyer {0} livre numérique « {1} » à l'appareil « {2} » ?", "MessageConfirmSendEbookToDevice": "Êtes-vous sûr·e de vouloir envoyer {0} livre numérique « {1} » à l'appareil « {2} » ?",
"MessageConfirmUnlinkOpenId": "Êtes-vous sûr·e de vouloir dissocier cet utilisateur dOpenID?", "MessageConfirmUnlinkOpenId": "Êtes-vous sûr·e de vouloir dissocier cet utilisateur dOpenID?",
"MessageDaysListenedInTheLastYear": "{0} jours écoutés l'an dernier",
"MessageDownloadingEpisode": "Téléchargement de l’épisode", "MessageDownloadingEpisode": "Téléchargement de l’épisode",
"MessageDragFilesIntoTrackOrder": "Faites glisser les fichiers dans lordre correct des pistes", "MessageDragFilesIntoTrackOrder": "Faites glisser les fichiers dans lordre correct des pistes",
"MessageEmbedFailed": "Échec de lintégration!", "MessageEmbedFailed": "Échec de lintégration!",
@@ -828,6 +841,7 @@
"MessageResetChaptersConfirm": "Êtes-vous sûr·e de vouloir réinitialiser les chapitres et annuler les changements effectués?", "MessageResetChaptersConfirm": "Êtes-vous sûr·e de vouloir réinitialiser les chapitres et annuler les changements effectués?",
"MessageRestoreBackupConfirm": "Êtes-vous sûr·e de vouloir restaurer la sauvegarde créée le", "MessageRestoreBackupConfirm": "Êtes-vous sûr·e de vouloir restaurer la sauvegarde créée le",
"MessageRestoreBackupWarning": "Restaurer la sauvegarde écrasera la base de donnée située dans le dossier /config ainsi que les images sur /metadata/items et /metadata/authors.<br><br>Les sauvegardes ne touchent pas aux fichiers de la bibliothèque. Si vous avez activé le paramètre pour sauvegarder les métadonnées et les images de couverture dans le même dossier que les fichiers, ceux-ci ne ni sauvegardés, ni écrasés lors de la restauration.<br><br>Tous les clients utilisant votre serveur seront automatiquement mis à jour.", "MessageRestoreBackupWarning": "Restaurer la sauvegarde écrasera la base de donnée située dans le dossier /config ainsi que les images sur /metadata/items et /metadata/authors.<br><br>Les sauvegardes ne touchent pas aux fichiers de la bibliothèque. Si vous avez activé le paramètre pour sauvegarder les métadonnées et les images de couverture dans le même dossier que les fichiers, ceux-ci ne ni sauvegardés, ni écrasés lors de la restauration.<br><br>Tous les clients utilisant votre serveur seront automatiquement mis à jour.",
"MessageScheduleLibraryScanNote": "Pour la plupart des utilisateurs, il est recommandé de laisser cette fonctionnalité désactivée et de maintenir le réglage du moniteur de dossier activé. Le moniteur de dossier détectera automatiquement les changements dans vos dossiers de bibliothèque. Le moniteur de dossier ne fonctionne pas pour chaque système de fichiers (comme NFS) afin que les scans de bibliothèques programmés puissent être utilisés à la place.",
"MessageSearchResultsFor": "Résultats de recherche pour", "MessageSearchResultsFor": "Résultats de recherche pour",
"MessageSelected": "{0} sélectionnés", "MessageSelected": "{0} sélectionnés",
"MessageServerCouldNotBeReached": "Serveur inaccessible", "MessageServerCouldNotBeReached": "Serveur inaccessible",
@@ -954,6 +968,7 @@
"ToastCollectionRemoveSuccess": "Collection supprimée", "ToastCollectionRemoveSuccess": "Collection supprimée",
"ToastCollectionUpdateSuccess": "Collection mise à jour", "ToastCollectionUpdateSuccess": "Collection mise à jour",
"ToastCoverUpdateFailed": "Échec de la mise à jour de la couverture", "ToastCoverUpdateFailed": "Échec de la mise à jour de la couverture",
"ToastDateTimeInvalidOrIncomplete": "La date et l'heure sont invalides ou incomplètes",
"ToastDeleteFileFailed": "Échec de la suppression du fichier", "ToastDeleteFileFailed": "Échec de la suppression du fichier",
"ToastDeleteFileSuccess": "Fichier supprimé", "ToastDeleteFileSuccess": "Fichier supprimé",
"ToastDeviceAddFailed": "Échec de lajout de lappareil", "ToastDeviceAddFailed": "Échec de lajout de lappareil",
@@ -1006,6 +1021,7 @@
"ToastNewUserTagError": "Au moins une étiquette est requise", "ToastNewUserTagError": "Au moins une étiquette est requise",
"ToastNewUserUsernameError": "Entrez un nom dutilisateur", "ToastNewUserUsernameError": "Entrez un nom dutilisateur",
"ToastNoNewEpisodesFound": "Aucun nouvel épisode trouvé", "ToastNoNewEpisodesFound": "Aucun nouvel épisode trouvé",
"ToastNoRSSFeed": "Le podcast n'a pas de flux RSS",
"ToastNoUpdatesNecessary": "Aucune mise à jour nécessaire", "ToastNoUpdatesNecessary": "Aucune mise à jour nécessaire",
"ToastNotificationCreateFailed": "La création de la notification à échouée", "ToastNotificationCreateFailed": "La création de la notification à échouée",
"ToastNotificationDeleteFailed": "La suppression de la notification à échouée", "ToastNotificationDeleteFailed": "La suppression de la notification à échouée",
+13 -6
View File
@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Primijeni poglavlja", "ButtonApplyChapters": "Primijeni poglavlja",
"ButtonAuthors": "Autori", "ButtonAuthors": "Autori",
"ButtonBack": "Natrag", "ButtonBack": "Natrag",
"ButtonBatchEditPopulateFromExisting": "Popuni iz postojećeg",
"ButtonBatchEditPopulateMapDetails": "Popuni mapirane pojedinosti",
"ButtonBrowseForFolder": "Pronađi mapu", "ButtonBrowseForFolder": "Pronađi mapu",
"ButtonCancel": "Odustani", "ButtonCancel": "Odustani",
"ButtonCancelEncode": "Otkaži kodiranje", "ButtonCancelEncode": "Otkaži kodiranje",
@@ -51,7 +53,7 @@
"ButtonNext": "Sljedeće", "ButtonNext": "Sljedeće",
"ButtonNextChapter": "Sljedeće poglavlje", "ButtonNextChapter": "Sljedeće poglavlje",
"ButtonNextItemInQueue": "Sljedeća stavka u redu", "ButtonNextItemInQueue": "Sljedeća stavka u redu",
"ButtonOk": "OK", "ButtonOk": "U redu",
"ButtonOpenFeed": "Otvori izvor", "ButtonOpenFeed": "Otvori izvor",
"ButtonOpenManager": "Otvori Upravitelja", "ButtonOpenManager": "Otvori Upravitelja",
"ButtonPause": "Pauziraj", "ButtonPause": "Pauziraj",
@@ -288,7 +290,7 @@
"LabelCustomCronExpression": "Prilagođeni CRON izraz:", "LabelCustomCronExpression": "Prilagođeni CRON izraz:",
"LabelDatetime": "Datum i vrijeme", "LabelDatetime": "Datum i vrijeme",
"LabelDays": "Dani", "LabelDays": "Dani",
"LabelDeleteFromFileSystemCheckbox": "Izbriši datoteke (uklonite oznaku ako stavku želite izbrisati samo iz baze podataka)", "LabelDeleteFromFileSystemCheckbox": "Izbriši datoteke (uklonite kvačicu ako stavku želite izbrisati samo iz baze podataka)",
"LabelDescription": "Opis", "LabelDescription": "Opis",
"LabelDeselectAll": "Odznači sve", "LabelDeselectAll": "Odznači sve",
"LabelDevice": "Uređaj", "LabelDevice": "Uređaj",
@@ -399,7 +401,7 @@
"LabelLastBookAdded": "Zadnja dodana knjiga", "LabelLastBookAdded": "Zadnja dodana knjiga",
"LabelLastBookUpdated": "Zadnja ažurirana knjiga", "LabelLastBookUpdated": "Zadnja ažurirana knjiga",
"LabelLastSeen": "Zadnji puta viđen", "LabelLastSeen": "Zadnji puta viđen",
"LabelLastTime": "Zadnji puta", "LabelLastTime": "Zadnje vrijeme",
"LabelLastUpdate": "Zadnje ažuriranje", "LabelLastUpdate": "Zadnje ažuriranje",
"LabelLayout": "Prikaz", "LabelLayout": "Prikaz",
"LabelLayoutSinglePage": "Jedna stranica", "LabelLayoutSinglePage": "Jedna stranica",
@@ -484,6 +486,7 @@
"LabelPersonalYearReview": "Vaš godišnji pregled ({0})", "LabelPersonalYearReview": "Vaš godišnji pregled ({0})",
"LabelPhotoPathURL": "Putanja ili URL fotografije", "LabelPhotoPathURL": "Putanja ili URL fotografije",
"LabelPlayMethod": "Način reprodukcije", "LabelPlayMethod": "Način reprodukcije",
"LabelPlaybackRateIncrementDecrement": "Korak povećanja/smanjenja brzine reprodukcije",
"LabelPlayerChapterNumberMarker": "{0} od {1}", "LabelPlayerChapterNumberMarker": "{0} od {1}",
"LabelPlaylists": "Popisi za izvođenje", "LabelPlaylists": "Popisi za izvođenje",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@@ -638,10 +641,10 @@
"LabelTimeDurationXMinutes": "{0} minuta", "LabelTimeDurationXMinutes": "{0} minuta",
"LabelTimeDurationXSeconds": "{0} sekundi", "LabelTimeDurationXSeconds": "{0} sekundi",
"LabelTimeInMinutes": "Vrijeme u minutama", "LabelTimeInMinutes": "Vrijeme u minutama",
"LabelTimeLeft": "{0} preostalo", "LabelTimeLeft": "preostalo {0}",
"LabelTimeListened": "Vremena odslušano", "LabelTimeListened": "Vremena odslušano",
"LabelTimeListenedToday": "Vremena odslušano danas", "LabelTimeListenedToday": "Vremena odslušano danas",
"LabelTimeRemaining": "{0} preostalo", "LabelTimeRemaining": "preostalo {0}",
"LabelTimeToShift": "Vrijeme za pomjeriti u sekundama", "LabelTimeToShift": "Vrijeme za pomjeriti u sekundama",
"LabelTitle": "Naslov", "LabelTitle": "Naslov",
"LabelToolsEmbedMetadata": "Ugradi meta-podatke", "LabelToolsEmbedMetadata": "Ugradi meta-podatke",
@@ -704,8 +707,11 @@
"MessageBackupsLocationEditNote": "Napomena: Uređivanje lokacije za sigurnosne kopije ne premješta ili mijenja postojeće sigurnosne kopije", "MessageBackupsLocationEditNote": "Napomena: Uređivanje lokacije za sigurnosne kopije ne premješta ili mijenja postojeće sigurnosne kopije",
"MessageBackupsLocationNoEditNote": "Napomena: Lokacija za sigurnosne kopije zadana je kroz varijablu okoline i ovdje se ne može izmijeniti.", "MessageBackupsLocationNoEditNote": "Napomena: Lokacija za sigurnosne kopije zadana je kroz varijablu okoline i ovdje se ne može izmijeniti.",
"MessageBackupsLocationPathEmpty": "Putanja do lokacije za sigurnosne kopije ne može ostati prazna", "MessageBackupsLocationPathEmpty": "Putanja do lokacije za sigurnosne kopije ne može ostati prazna",
"MessageBatchEditPopulateMapDetailsAllHelp": "Nadopunjuje omogućena polja podatcima iz svih stavki. Polja s višestrukim podatcima će se spojiti",
"MessageBatchEditPopulateMapDetailsItemHelp": "Popuni omogućena polja mapiranih pojedinosti s podatcima iz ove stavke",
"MessageBatchQuickMatchDescription": "Brzo prepoznavanje za odabrane će stavke pokušati dodati naslovnice i meta-podatke koji nedostaju. Uključite donje opcije ako želite da Brzo prepoznavanje prepiše postojeće naslovnice i/ili meta-podatke.", "MessageBatchQuickMatchDescription": "Brzo prepoznavanje za odabrane će stavke pokušati dodati naslovnice i meta-podatke koji nedostaju. Uključite donje opcije ako želite da Brzo prepoznavanje prepiše postojeće naslovnice i/ili meta-podatke.",
"MessageBookshelfNoCollections": "Niste izradili niti jednu zbirku", "MessageBookshelfNoCollections": "Niste izradili niti jednu zbirku",
"MessageBookshelfNoCollectionsHelp": "Zbirke su javne. Svi korisnici s pristupom knjižnici mogu ih vidjeti.",
"MessageBookshelfNoRSSFeeds": "Nema otvorenih RSS izvora", "MessageBookshelfNoRSSFeeds": "Nema otvorenih RSS izvora",
"MessageBookshelfNoResultsForFilter": "Nema rezultata za filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Nema rezultata za filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Vaš upit nema rezultata", "MessageBookshelfNoResultsForQuery": "Vaš upit nema rezultata",
@@ -721,7 +727,7 @@
"MessageConfirmDeleteDevice": "Sigurno želite izbrisati e-čitač \"{0}\"?", "MessageConfirmDeleteDevice": "Sigurno želite izbrisati e-čitač \"{0}\"?",
"MessageConfirmDeleteFile": "Ovo će izbrisati datoteke s datotečnog sustava. Jeste li sigurni?", "MessageConfirmDeleteFile": "Ovo će izbrisati datoteke s datotečnog sustava. Jeste li sigurni?",
"MessageConfirmDeleteLibrary": "Sigurno želite trajno izbrisati knjižnicu \"{0}\"?", "MessageConfirmDeleteLibrary": "Sigurno želite trajno izbrisati knjižnicu \"{0}\"?",
"MessageConfirmDeleteLibraryItem": "Ovo će izbrisati knjižničku stavku iz datoteke i vašeg datotečnog sustava. Jeste li sigurni?", "MessageConfirmDeleteLibraryItem": "Ovo će izbrisati knjižničku stavku iz baze podataka i s datotečnog sustava. Jeste li sigurni?",
"MessageConfirmDeleteLibraryItems": "Ovo će izbrisati {0} knjižničkih stavki iz baze podataka i datotečnog sustava. Jeste li sigurni?", "MessageConfirmDeleteLibraryItems": "Ovo će izbrisati {0} knjižničkih stavki iz baze podataka i datotečnog sustava. Jeste li sigurni?",
"MessageConfirmDeleteMetadataProvider": "Sigurno želite izbrisati prilagođenog pružatelja meta-podataka \"{0}\"?", "MessageConfirmDeleteMetadataProvider": "Sigurno želite izbrisati prilagođenog pružatelja meta-podataka \"{0}\"?",
"MessageConfirmDeleteNotification": "Sigurno želite izbrisati ovu obavijest?", "MessageConfirmDeleteNotification": "Sigurno želite izbrisati ovu obavijest?",
@@ -816,6 +822,7 @@
"MessageNoTasksRunning": "Nema zadataka koji se izvode", "MessageNoTasksRunning": "Nema zadataka koji se izvode",
"MessageNoUpdatesWereNecessary": "Ažuriranje nije bilo potrebno", "MessageNoUpdatesWereNecessary": "Ažuriranje nije bilo potrebno",
"MessageNoUserPlaylists": "Nemate popisa za izvođenje", "MessageNoUserPlaylists": "Nemate popisa za izvođenje",
"MessageNoUserPlaylistsHelp": "Popisi za izvođenje su privatni. Može ih vidjeti samo korisnik koji ih je izradio.",
"MessageNotYetImplemented": "Još nije implementirano", "MessageNotYetImplemented": "Još nije implementirano",
"MessageOpmlPreviewNote": "Napomena: Ovo je pretpregled raščlanjene OPML datoteke. Stvarni naslov podcasta preuzet će se iz RSS izvora.", "MessageOpmlPreviewNote": "Napomena: Ovo je pretpregled raščlanjene OPML datoteke. Stvarni naslov podcasta preuzet će se iz RSS izvora.",
"MessageOr": "ili", "MessageOr": "ili",
+1 -1
View File
@@ -51,7 +51,7 @@
"ButtonNext": "Következő", "ButtonNext": "Következő",
"ButtonNextChapter": "Következő fejezet", "ButtonNextChapter": "Következő fejezet",
"ButtonNextItemInQueue": "Következő elem a sorban", "ButtonNextItemInQueue": "Következő elem a sorban",
"ButtonOk": "Oké", "ButtonOk": "Ok",
"ButtonOpenFeed": "Hírcsatorna megnyitása", "ButtonOpenFeed": "Hírcsatorna megnyitása",
"ButtonOpenManager": "Kezelő megnyitása", "ButtonOpenManager": "Kezelő megnyitása",
"ButtonPause": "Szünet", "ButtonPause": "Szünet",
+33 -11
View File
@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Applica", "ButtonApplyChapters": "Applica",
"ButtonAuthors": "Autori", "ButtonAuthors": "Autori",
"ButtonBack": "Indietro", "ButtonBack": "Indietro",
"ButtonBatchEditPopulateFromExisting": "Popola da esistente",
"ButtonBatchEditPopulateMapDetails": "Inserisci i dettagli della mappa",
"ButtonBrowseForFolder": "Per Cartella", "ButtonBrowseForFolder": "Per Cartella",
"ButtonCancel": "Cancella", "ButtonCancel": "Cancella",
"ButtonCancelEncode": "Ferma la codifica", "ButtonCancelEncode": "Ferma la codifica",
@@ -88,6 +90,8 @@
"ButtonSaveTracklist": "Salva Tracklist", "ButtonSaveTracklist": "Salva Tracklist",
"ButtonScan": "Scansiona", "ButtonScan": "Scansiona",
"ButtonScanLibrary": "Scansiona Libreria", "ButtonScanLibrary": "Scansiona Libreria",
"ButtonScrollLeft": "Scorri verso sinistra",
"ButtonScrollRight": "Scorri verso destra",
"ButtonSearch": "Cerca", "ButtonSearch": "Cerca",
"ButtonSelectFolderPath": "Seleziona percorso cartella", "ButtonSelectFolderPath": "Seleziona percorso cartella",
"ButtonSeries": "Serie", "ButtonSeries": "Serie",
@@ -190,6 +194,7 @@
"HeaderSettingsExperimental": "Opzioni Sperimentali", "HeaderSettingsExperimental": "Opzioni Sperimentali",
"HeaderSettingsGeneral": "Generale", "HeaderSettingsGeneral": "Generale",
"HeaderSettingsScanner": "Scanner", "HeaderSettingsScanner": "Scanner",
"HeaderSettingsWebClient": "Web Client",
"HeaderSleepTimer": "Sveglia", "HeaderSleepTimer": "Sveglia",
"HeaderStatsLargestItems": "File pesanti", "HeaderStatsLargestItems": "File pesanti",
"HeaderStatsLongestItems": "libri più lunghi (ore)", "HeaderStatsLongestItems": "libri più lunghi (ore)",
@@ -289,32 +294,33 @@
"LabelDescription": "Descrizione", "LabelDescription": "Descrizione",
"LabelDeselectAll": "Deseleziona Tutto", "LabelDeselectAll": "Deseleziona Tutto",
"LabelDevice": "Dispositivo", "LabelDevice": "Dispositivo",
"LabelDeviceInfo": "Info Dispositivo", "LabelDeviceInfo": "Info dispositivo",
"LabelDeviceIsAvailableTo": "Il dispositivo e disponibile su...", "LabelDeviceIsAvailableTo": "Il dispositivo e disponibile su",
"LabelDirectory": "Elenco", "LabelDirectory": "Elenco",
"LabelDiscFromFilename": "Disco dal nome file", "LabelDiscFromFilename": "Disco dal nome file",
"LabelDiscFromMetadata": "Disco dal Metadata", "LabelDiscFromMetadata": "Disco dai metadati",
"LabelDiscover": "Scopri", "LabelDiscover": "Scopri",
"LabelDownload": "Scarica", "LabelDownload": "Scarica",
"LabelDownloadNEpisodes": "Download {0} episodi", "LabelDownloadNEpisodes": "Scarica {0} episodi",
"LabelDownloadable": "Scaricabile",
"LabelDuration": "Durata", "LabelDuration": "Durata",
"LabelDurationComparisonExactMatch": "(corrispondenza esatta)", "LabelDurationComparisonExactMatch": "(corrispondenza esatta)",
"LabelDurationComparisonLonger": "({0} lungo)", "LabelDurationComparisonLonger": "({0} lungo)",
"LabelDurationComparisonShorter": "({0} corto)", "LabelDurationComparisonShorter": "({0} corto)",
"LabelDurationFound": "Durata Trovata:", "LabelDurationFound": "Durata trovata:",
"LabelEbook": "Libro digitale", "LabelEbook": "Libro digitale",
"LabelEbooks": "Libri digitali", "LabelEbooks": "Libri digitali",
"LabelEdit": "Modifica", "LabelEdit": "Modifica",
"LabelEmail": "E-mail", "LabelEmail": "E-mail",
"LabelEmailSettingsFromAddress": "Da Indirizzo", "LabelEmailSettingsFromAddress": "Indirizzo del mittente",
"LabelEmailSettingsRejectUnauthorized": "Rifiuta i certificati non autorizzati", "LabelEmailSettingsRejectUnauthorized": "Rifiuta i certificati non autorizzati",
"LabelEmailSettingsRejectUnauthorizedHelp": "La disattivazione della convalida del certificato SSL può esporre la tua connessione a rischi per la sicurezza, come attacchi man-in-the-middle. Disattiva questa opzione solo se ne comprendi le implicazioni e ti fidi del server di posta a cui ti stai connettendo.", "LabelEmailSettingsRejectUnauthorizedHelp": "La disattivazione della convalida del certificato SSL può esporre la tua connessione a rischi per la sicurezza, come attacchi man-in-the-middle. Disattiva questa opzione solo se ne comprendi le implicazioni e ti fidi del server di posta a cui ti stai connettendo.",
"LabelEmailSettingsSecure": "SSL", "LabelEmailSettingsSecure": "Sicuro",
"LabelEmailSettingsSecureHelp": "Se vero, la connessione utilizzerà TLS durante la connessione al server. Se false, viene utilizzato TLS se il server supporta l'estensione STARTTLS. Nella maggior parte dei casi impostare questo valore su true se ci si connette alla porta 465. Per la porta 587 o 25 mantenerlo false. (da nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Se vero, la connessione utilizzerà TLS durante la connessione al server. Se false, viene utilizzato TLS se il server supporta l'estensione STARTTLS. Nella maggior parte dei casi impostare questo valore su true se ci si connette alla porta 465. Per la porta 587 o 25 mantenerlo false. (da nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Indirizzo di test", "LabelEmailSettingsTestAddress": "Indirizzo di test",
"LabelEmbeddedCover": "Cover Integrata", "LabelEmbeddedCover": "Copertina integrata",
"LabelEnable": "Abilita", "LabelEnable": "Abilita",
"LabelEncodingBackupLocation": "il backup dei file audio verrà archiviato in:", "LabelEncodingBackupLocation": "Un backup dei file audio verrà archiviato in:",
"LabelEncodingChaptersNotEmbedded": "Negli audiolibri multitraccia i capitoli non sono incorporati.", "LabelEncodingChaptersNotEmbedded": "Negli audiolibri multitraccia i capitoli non sono incorporati.",
"LabelEncodingClearItemCache": "Assicurati di svuotare periodicamente la cache degli oggetti.", "LabelEncodingClearItemCache": "Assicurati di svuotare periodicamente la cache degli oggetti.",
"LabelEncodingFinishedM4B": "L'M4B completato verrà inserito nella cartella:", "LabelEncodingFinishedM4B": "L'M4B completato verrà inserito nella cartella:",
@@ -428,7 +434,7 @@
"LabelMetadataProvider": "Metadata Provider", "LabelMetadataProvider": "Metadata Provider",
"LabelMinute": "Minuto", "LabelMinute": "Minuto",
"LabelMinutes": "Minuti", "LabelMinutes": "Minuti",
"LabelMissing": "Altro", "LabelMissing": "Mancante",
"LabelMissingEbook": "Non ha libri digitali", "LabelMissingEbook": "Non ha libri digitali",
"LabelMissingSupplementaryEbook": "Non ha un libro digitale supplementare", "LabelMissingSupplementaryEbook": "Non ha un libro digitale supplementare",
"LabelMobileRedirectURIs": "URI di reindirizzamento mobile consentiti", "LabelMobileRedirectURIs": "URI di reindirizzamento mobile consentiti",
@@ -459,7 +465,7 @@
"LabelNotificationsMaxQueueSize": "Coda Massima di notifiche eventi", "LabelNotificationsMaxQueueSize": "Coda Massima di notifiche eventi",
"LabelNotificationsMaxQueueSizeHelp": "Le notifiche sono limitate per 1 al secondo, per evitare lo spamming le notifiche verrano ignorare se superano la coda.", "LabelNotificationsMaxQueueSizeHelp": "Le notifiche sono limitate per 1 al secondo, per evitare lo spamming le notifiche verrano ignorare se superano la coda.",
"LabelNumberOfBooks": "Numero di libri", "LabelNumberOfBooks": "Numero di libri",
"LabelNumberOfEpisodes": "# degli episodi", "LabelNumberOfEpisodes": "Numero di episodi",
"LabelOpenIDAdvancedPermsClaimDescription": "Nome dell'attestazione OpenID che contiene autorizzazioni avanzate per le azioni dell'utente all'interno dell'applicazione che verranno applicate ai ruoli non amministratori (<b>se configurato</b>). Se il reclamo manca nella risposta, l'accesso ad ABS verrà negato. Se manca una singola opzione, verrà trattata come<code>falsa</code>. Assicurati che l'attestazione del provider di identità corrisponda alla struttura prevista:", "LabelOpenIDAdvancedPermsClaimDescription": "Nome dell'attestazione OpenID che contiene autorizzazioni avanzate per le azioni dell'utente all'interno dell'applicazione che verranno applicate ai ruoli non amministratori (<b>se configurato</b>). Se il reclamo manca nella risposta, l'accesso ad ABS verrà negato. Se manca una singola opzione, verrà trattata come<code>falsa</code>. Assicurati che l'attestazione del provider di identità corrisponda alla struttura prevista:",
"LabelOpenIDClaims": "Lasciare vuote le seguenti opzioni per disabilitare l'assegnazione avanzata di gruppi e autorizzazioni, assegnando quindi automaticamente il gruppo \"Utente\".", "LabelOpenIDClaims": "Lasciare vuote le seguenti opzioni per disabilitare l'assegnazione avanzata di gruppi e autorizzazioni, assegnando quindi automaticamente il gruppo \"Utente\".",
"LabelOpenIDGroupClaimDescription": "Nome dell'attestazione OpenID che contiene un elenco dei gruppi dell'utente. Comunemente indicato come <code>gruppo</code>. <b>se configurato</b>, l'applicazione assegnerà automaticamente i ruoli in base alle appartenenze ai gruppi dell'utente, a condizione che tali gruppi siano denominati \"admin\", \"utente\" o \"ospite\" senza distinzione tra maiuscole e minuscole nell'attestazione. L'attestazione deve contenere un elenco e, se un utente appartiene a più gruppi, l'applicazione assegnerà il ruolo corrispondente al livello di accesso più alto. Se nessun gruppo corrisponde, l'accesso verrà negato.", "LabelOpenIDGroupClaimDescription": "Nome dell'attestazione OpenID che contiene un elenco dei gruppi dell'utente. Comunemente indicato come <code>gruppo</code>. <b>se configurato</b>, l'applicazione assegnerà automaticamente i ruoli in base alle appartenenze ai gruppi dell'utente, a condizione che tali gruppi siano denominati \"admin\", \"utente\" o \"ospite\" senza distinzione tra maiuscole e minuscole nell'attestazione. L'attestazione deve contenere un elenco e, se un utente appartiene a più gruppi, l'applicazione assegnerà il ruolo corrispondente al livello di accesso più alto. Se nessun gruppo corrisponde, l'accesso verrà negato.",
@@ -480,6 +486,7 @@
"LabelPersonalYearReview": "Il tuo anno in rassegna ({0})", "LabelPersonalYearReview": "Il tuo anno in rassegna ({0})",
"LabelPhotoPathURL": "foto Path/URL", "LabelPhotoPathURL": "foto Path/URL",
"LabelPlayMethod": "Metodo di riproduzione", "LabelPlayMethod": "Metodo di riproduzione",
"LabelPlaybackRateIncrementDecrement": "Valore incremento/decremento velocità di riproduzione",
"LabelPlayerChapterNumberMarker": "{0} di {1}", "LabelPlayerChapterNumberMarker": "{0} di {1}",
"LabelPlaylists": "Playlist", "LabelPlaylists": "Playlist",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@@ -542,6 +549,7 @@
"LabelServerYearReview": "Anno del server in sintesi({0})", "LabelServerYearReview": "Anno del server in sintesi({0})",
"LabelSetEbookAsPrimary": "Imposta come primario", "LabelSetEbookAsPrimary": "Imposta come primario",
"LabelSetEbookAsSupplementary": "Imposta come suplementare", "LabelSetEbookAsSupplementary": "Imposta come suplementare",
"LabelSettingsAllowIframe": "Consenti l'incorporamento in un iframe",
"LabelSettingsAudiobooksOnly": "Solo Audiolibri", "LabelSettingsAudiobooksOnly": "Solo Audiolibri",
"LabelSettingsAudiobooksOnlyHelp": "L'abilitazione di questa impostazione ignorerà i file di libro digitale a meno che non si trovino all'interno di una cartella di audiolibri, nel qual caso verranno impostati come libri digitali supplementari", "LabelSettingsAudiobooksOnlyHelp": "L'abilitazione di questa impostazione ignorerà i file di libro digitale a meno che non si trovino all'interno di una cartella di audiolibri, nel qual caso verranno impostati come libri digitali supplementari",
"LabelSettingsBookshelfViewHelp": "Design con scaffali in legno", "LabelSettingsBookshelfViewHelp": "Design con scaffali in legno",
@@ -584,6 +592,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Di default, i metadati sono salvati dentro /metadata/items, abilitando questa opzione si memorizzeranno i metadata nella cartella della libreria", "LabelSettingsStoreMetadataWithItemHelp": "Di default, i metadati sono salvati dentro /metadata/items, abilitando questa opzione si memorizzeranno i metadata nella cartella della libreria",
"LabelSettingsTimeFormat": "Formato Ora", "LabelSettingsTimeFormat": "Formato Ora",
"LabelShare": "Condividi", "LabelShare": "Condividi",
"LabelShareDownloadableHelp": "Consente agli utenti dotati del link di condivisione di scaricare un file zip dell'elemento della libreria.",
"LabelShareOpen": "Apri Condivisioni", "LabelShareOpen": "Apri Condivisioni",
"LabelShareURL": "Condividi URL", "LabelShareURL": "Condividi URL",
"LabelShowAll": "Mostra tutto", "LabelShowAll": "Mostra tutto",
@@ -592,6 +601,8 @@
"LabelSize": "Dimensione", "LabelSize": "Dimensione",
"LabelSleepTimer": "Temporizzatore", "LabelSleepTimer": "Temporizzatore",
"LabelSlug": "Lento", "LabelSlug": "Lento",
"LabelSortAscending": "Crescente",
"LabelSortDescending": "Discendente",
"LabelStart": "Inizo", "LabelStart": "Inizo",
"LabelStartTime": "Tempo di inizio", "LabelStartTime": "Tempo di inizio",
"LabelStarted": "Iniziato", "LabelStarted": "Iniziato",
@@ -663,6 +674,7 @@
"LabelUpdateDetailsHelp": "Consenti la sovrascrittura dei dettagli esistenti per i libri selezionati quando viene individuata una corrispondenza", "LabelUpdateDetailsHelp": "Consenti la sovrascrittura dei dettagli esistenti per i libri selezionati quando viene individuata una corrispondenza",
"LabelUpdatedAt": "Aggiornato alle", "LabelUpdatedAt": "Aggiornato alle",
"LabelUploaderDragAndDrop": "Drag & drop file o Cartelle", "LabelUploaderDragAndDrop": "Drag & drop file o Cartelle",
"LabelUploaderDragAndDropFilesOnly": "Drag & drop files",
"LabelUploaderDropFiles": "Elimina file", "LabelUploaderDropFiles": "Elimina file",
"LabelUploaderItemFetchMetadataHelp": "Recupera automaticamente titolo, autore e serie", "LabelUploaderItemFetchMetadataHelp": "Recupera automaticamente titolo, autore e serie",
"LabelUseAdvancedOptions": "Usa le opzioni avanzate", "LabelUseAdvancedOptions": "Usa le opzioni avanzate",
@@ -678,6 +690,8 @@
"LabelViewPlayerSettings": "Mostra Impostazioni player", "LabelViewPlayerSettings": "Mostra Impostazioni player",
"LabelViewQueue": "Visualizza coda", "LabelViewQueue": "Visualizza coda",
"LabelVolume": "Volume", "LabelVolume": "Volume",
"LabelWebRedirectURLsDescription": "Autorizza questi URL nel tuo provider OAuth per consentire il reindirizzamento all'app Web dopo l'accesso:",
"LabelWebRedirectURLsSubfolder": "Sottocartella per URL di reindirizzamento",
"LabelWeekdaysToRun": "Giorni feriali da eseguire", "LabelWeekdaysToRun": "Giorni feriali da eseguire",
"LabelXBooks": "{0} libri", "LabelXBooks": "{0} libri",
"LabelXItems": "{0} oggetti", "LabelXItems": "{0} oggetti",
@@ -693,8 +707,11 @@
"MessageBackupsLocationEditNote": "Nota: l'aggiornamento della posizione di backup non sposterà o modificherà i backup esistenti", "MessageBackupsLocationEditNote": "Nota: l'aggiornamento della posizione di backup non sposterà o modificherà i backup esistenti",
"MessageBackupsLocationNoEditNote": "Nota: la posizione del backup viene impostata tramite una variabile di ambiente e non può essere modificata qui.", "MessageBackupsLocationNoEditNote": "Nota: la posizione del backup viene impostata tramite una variabile di ambiente e non può essere modificata qui.",
"MessageBackupsLocationPathEmpty": "Il percorso del backup non può essere vuoto", "MessageBackupsLocationPathEmpty": "Il percorso del backup non può essere vuoto",
"MessageBatchEditPopulateMapDetailsAllHelp": "Popola i campi abilitati con i dati di tutti gli elementi. I campi con più valori verranno uniti",
"MessageBatchEditPopulateMapDetailsItemHelp": "Compila i campi dei dettagli della mappa abilitati con i dati di questo elemento",
"MessageBatchQuickMatchDescription": "Quick Match tenterà di aggiungere copertine e metadati mancanti per gli elementi selezionati. Attiva l'opzione per consentire a Quick Match di sovrascrivere copertine e/o metadati esistenti.", "MessageBatchQuickMatchDescription": "Quick Match tenterà di aggiungere copertine e metadati mancanti per gli elementi selezionati. Attiva l'opzione per consentire a Quick Match di sovrascrivere copertine e/o metadati esistenti.",
"MessageBookshelfNoCollections": "Non hai ancora creato nessuna raccolta", "MessageBookshelfNoCollections": "Non hai ancora creato nessuna raccolta",
"MessageBookshelfNoCollectionsHelp": "le collezioni sono pubbliche. Tutti gli utenti con accesso alla biblioteca possono vederle.",
"MessageBookshelfNoRSSFeeds": "Nessun RSS feeds aperto", "MessageBookshelfNoRSSFeeds": "Nessun RSS feeds aperto",
"MessageBookshelfNoResultsForFilter": "Nessun risultato per il filtro \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Nessun risultato per il filtro \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Nessun risultato per la query", "MessageBookshelfNoResultsForQuery": "Nessun risultato per la query",
@@ -747,6 +764,7 @@
"MessageConfirmResetProgress": "Vuoi davvero azzerare i tuoi progressi?", "MessageConfirmResetProgress": "Vuoi davvero azzerare i tuoi progressi?",
"MessageConfirmSendEbookToDevice": "Sei sicuro/sicura di voler inviare {0} libro «{1}» al dispositivo «{2}»?", "MessageConfirmSendEbookToDevice": "Sei sicuro/sicura di voler inviare {0} libro «{1}» al dispositivo «{2}»?",
"MessageConfirmUnlinkOpenId": "Vuoi davvero scollegare questo utente da OpenID?", "MessageConfirmUnlinkOpenId": "Vuoi davvero scollegare questo utente da OpenID?",
"MessageDaysListenedInTheLastYear": "{0} giorni ascoltati nell'ultimo anno",
"MessageDownloadingEpisode": "Scaricamento dellepisodio in corso", "MessageDownloadingEpisode": "Scaricamento dellepisodio in corso",
"MessageDragFilesIntoTrackOrder": "Trascina i file nell'ordine di traccia corretto", "MessageDragFilesIntoTrackOrder": "Trascina i file nell'ordine di traccia corretto",
"MessageEmbedFailed": "Incorporamento non riuscito!", "MessageEmbedFailed": "Incorporamento non riuscito!",
@@ -804,6 +822,7 @@
"MessageNoTasksRunning": "Nessun processo in esecuzione", "MessageNoTasksRunning": "Nessun processo in esecuzione",
"MessageNoUpdatesWereNecessary": "Nessun aggiornamento necessario", "MessageNoUpdatesWereNecessary": "Nessun aggiornamento necessario",
"MessageNoUserPlaylists": "non hai nessuna Playlist", "MessageNoUserPlaylists": "non hai nessuna Playlist",
"MessageNoUserPlaylistsHelp": "Le playlist sono private. Solo l'utente che le crea può vederle.",
"MessageNotYetImplemented": "Non Ancora Implementato", "MessageNotYetImplemented": "Non Ancora Implementato",
"MessageOpmlPreviewNote": "Nota: questa è un'anteprima del file OPML analizzato. Il titolo effettivo del podcast verrà preso dal feed RSS.", "MessageOpmlPreviewNote": "Nota: questa è un'anteprima del file OPML analizzato. Il titolo effettivo del podcast verrà preso dal feed RSS.",
"MessageOr": "o", "MessageOr": "o",
@@ -825,6 +844,7 @@
"MessageResetChaptersConfirm": "Sei sicuro di voler reimpostare i capitoli e annullare le modifiche ?", "MessageResetChaptersConfirm": "Sei sicuro di voler reimpostare i capitoli e annullare le modifiche ?",
"MessageRestoreBackupConfirm": "Sei sicuro di voler ripristinare il backup creato su", "MessageRestoreBackupConfirm": "Sei sicuro di voler ripristinare il backup creato su",
"MessageRestoreBackupWarning": "Il ripristino di un backup sovrascriverà l'intero database situato in /config e sovrascrive le immagini in /metadata/items & /metadata/authors.<br /><br />I backup non modificano alcun file nelle cartelle della libreria. Se hai abilitato le impostazioni del server per archiviare copertine e metadati nelle cartelle della libreria, questi non vengono sottoposti a backup o sovrascritti.<br /><br />Tutti i client che utilizzano il tuo server verranno aggiornati automaticamente.", "MessageRestoreBackupWarning": "Il ripristino di un backup sovrascriverà l'intero database situato in /config e sovrascrive le immagini in /metadata/items & /metadata/authors.<br /><br />I backup non modificano alcun file nelle cartelle della libreria. Se hai abilitato le impostazioni del server per archiviare copertine e metadati nelle cartelle della libreria, questi non vengono sottoposti a backup o sovrascritti.<br /><br />Tutti i client che utilizzano il tuo server verranno aggiornati automaticamente.",
"MessageScheduleLibraryScanNote": "Per la maggior parte degli utenti, si consiglia di lasciare questa funzionalità disabilitata e di mantenere abilitata l'impostazione di folder watcher. Il folder watcher rileverà automaticamente le modifiche nelle cartelle della libreria. Il folder watcher non funziona per ogni file system (come NFS), quindi è possibile utilizzare le scansioni pianificate della libreria.",
"MessageSearchResultsFor": "cerca risultati per", "MessageSearchResultsFor": "cerca risultati per",
"MessageSelected": "{0} selezionati", "MessageSelected": "{0} selezionati",
"MessageServerCouldNotBeReached": "Impossibile raggiungere il server", "MessageServerCouldNotBeReached": "Impossibile raggiungere il server",
@@ -951,6 +971,7 @@
"ToastCollectionRemoveSuccess": "Collezione rimossa", "ToastCollectionRemoveSuccess": "Collezione rimossa",
"ToastCollectionUpdateSuccess": "Raccolta aggiornata", "ToastCollectionUpdateSuccess": "Raccolta aggiornata",
"ToastCoverUpdateFailed": "Aggiornamento cover fallito", "ToastCoverUpdateFailed": "Aggiornamento cover fallito",
"ToastDateTimeInvalidOrIncomplete": "Data e ora non sono valide o incomplete",
"ToastDeleteFileFailed": "Impossibile eliminare il file", "ToastDeleteFileFailed": "Impossibile eliminare il file",
"ToastDeleteFileSuccess": "File eliminato", "ToastDeleteFileSuccess": "File eliminato",
"ToastDeviceAddFailed": "Aggiunta dispositivo fallita", "ToastDeviceAddFailed": "Aggiunta dispositivo fallita",
@@ -1003,6 +1024,7 @@
"ToastNewUserTagError": "Devi selezionare almeno un tag", "ToastNewUserTagError": "Devi selezionare almeno un tag",
"ToastNewUserUsernameError": "Inserisci un nome utente", "ToastNewUserUsernameError": "Inserisci un nome utente",
"ToastNoNewEpisodesFound": "Nessun nuovo episodio trovato", "ToastNoNewEpisodesFound": "Nessun nuovo episodio trovato",
"ToastNoRSSFeed": "Il podcast non ha un feed RSS",
"ToastNoUpdatesNecessary": "Nessun aggiornamento necessario", "ToastNoUpdatesNecessary": "Nessun aggiornamento necessario",
"ToastNotificationCreateFailed": "Impossibile creare la notifica", "ToastNotificationCreateFailed": "Impossibile creare la notifica",
"ToastNotificationDeleteFailed": "Impossibile eliminare la notifica", "ToastNotificationDeleteFailed": "Impossibile eliminare la notifica",
+3
View File
@@ -0,0 +1,3 @@
{
"ButtonAdd": "追加"
}
+5
View File
@@ -484,6 +484,7 @@
"LabelPersonalYearReview": "Jouw jaar in review ({0})", "LabelPersonalYearReview": "Jouw jaar in review ({0})",
"LabelPhotoPathURL": "Foto pad/URL", "LabelPhotoPathURL": "Foto pad/URL",
"LabelPlayMethod": "Afspeelwijze", "LabelPlayMethod": "Afspeelwijze",
"LabelPlaybackRateIncrementDecrement": "Afspeel Snelheid Vermeerderen/Verminderen",
"LabelPlayerChapterNumberMarker": "{0} van {1}", "LabelPlayerChapterNumberMarker": "{0} van {1}",
"LabelPlaylists": "Afspeellijsten", "LabelPlaylists": "Afspeellijsten",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@@ -704,8 +705,11 @@
"MessageBackupsLocationEditNote": "Let op: het bijwerken van de back-uplocatie zal bestaande back-ups niet verplaatsen of wijzigen", "MessageBackupsLocationEditNote": "Let op: het bijwerken van de back-uplocatie zal bestaande back-ups niet verplaatsen of wijzigen",
"MessageBackupsLocationNoEditNote": "Let op: De back-uplocatie wordt ingesteld via een omgevingsvariabele en kan hier niet worden gewijzigd.", "MessageBackupsLocationNoEditNote": "Let op: De back-uplocatie wordt ingesteld via een omgevingsvariabele en kan hier niet worden gewijzigd.",
"MessageBackupsLocationPathEmpty": "Backup locatie pad kan niet leeg zijn", "MessageBackupsLocationPathEmpty": "Backup locatie pad kan niet leeg zijn",
"MessageBatchEditPopulateMapDetailsAllHelp": "Vul actieve velden in met data van alle items. Velden met meerdere waarden zullen worden samengevoegd",
"MessageBatchEditPopulateMapDetailsItemHelp": "Vul actieve folder detail velden met de data van dit item",
"MessageBatchQuickMatchDescription": "Quick Match zal proberen ontbrekende covers en metadata voor de geselecteerde onderdelen te matchten. Schakel de opties hieronder in om Quick Match toe te staan bestaande covers en/of metadata te overschrijven.", "MessageBatchQuickMatchDescription": "Quick Match zal proberen ontbrekende covers en metadata voor de geselecteerde onderdelen te matchten. Schakel de opties hieronder in om Quick Match toe te staan bestaande covers en/of metadata te overschrijven.",
"MessageBookshelfNoCollections": "Je hebt nog geen collecties gemaakt", "MessageBookshelfNoCollections": "Je hebt nog geen collecties gemaakt",
"MessageBookshelfNoCollectionsHelp": "Collecties zijn publiekelijk. Alle gebruikers met toegang tot de bibliotheek kunnen ze zien.",
"MessageBookshelfNoRSSFeeds": "Geen RSS-feeds geopend", "MessageBookshelfNoRSSFeeds": "Geen RSS-feeds geopend",
"MessageBookshelfNoResultsForFilter": "Geen resultaten voor filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Geen resultaten voor filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Geen resultaten voor query", "MessageBookshelfNoResultsForQuery": "Geen resultaten voor query",
@@ -816,6 +820,7 @@
"MessageNoTasksRunning": "Geen lopende taken", "MessageNoTasksRunning": "Geen lopende taken",
"MessageNoUpdatesWereNecessary": "Geen bijwerkingen waren noodzakelijk", "MessageNoUpdatesWereNecessary": "Geen bijwerkingen waren noodzakelijk",
"MessageNoUserPlaylists": "Je hebt geen afspeellijsten", "MessageNoUserPlaylists": "Je hebt geen afspeellijsten",
"MessageNoUserPlaylistsHelp": "Afspeellijsten zijn privaat. Alleen de gebruikers die ze hebben gemaakt kunnen ze zien.",
"MessageNotYetImplemented": "Nog niet geimplementeerd", "MessageNotYetImplemented": "Nog niet geimplementeerd",
"MessageOpmlPreviewNote": "Let op: Dit is een preview van het geparseerde OPML-bestand. De werkelijke podcasttitel wordt overgenomen uit de RSS-feed.", "MessageOpmlPreviewNote": "Let op: Dit is een preview van het geparseerde OPML-bestand. De werkelijke podcasttitel wordt overgenomen uit de RSS-feed.",
"MessageOr": "of", "MessageOr": "of",
+11 -4
View File
@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Применить главы", "ButtonApplyChapters": "Применить главы",
"ButtonAuthors": "Авторы", "ButtonAuthors": "Авторы",
"ButtonBack": "Назад", "ButtonBack": "Назад",
"ButtonBatchEditPopulateFromExisting": "Заполнить из существующих",
"ButtonBatchEditPopulateMapDetails": "Заполнить данные карты",
"ButtonBrowseForFolder": "Выбрать папку", "ButtonBrowseForFolder": "Выбрать папку",
"ButtonCancel": "Отмена", "ButtonCancel": "Отмена",
"ButtonCancelEncode": "Отменить кодирование", "ButtonCancelEncode": "Отменить кодирование",
@@ -301,7 +303,7 @@
"LabelDownload": "Скачать", "LabelDownload": "Скачать",
"LabelDownloadNEpisodes": "Скачать {0} эпизодов", "LabelDownloadNEpisodes": "Скачать {0} эпизодов",
"LabelDownloadable": "Загружаемый", "LabelDownloadable": "Загружаемый",
"LabelDuration": "Длина", "LabelDuration": "Продолжительность",
"LabelDurationComparisonExactMatch": "(точное совпадение)", "LabelDurationComparisonExactMatch": "(точное совпадение)",
"LabelDurationComparisonLonger": "({0} дольше)", "LabelDurationComparisonLonger": "({0} дольше)",
"LabelDurationComparisonShorter": "({0} короче)", "LabelDurationComparisonShorter": "({0} короче)",
@@ -432,7 +434,7 @@
"LabelMetadataProvider": "Провайдер", "LabelMetadataProvider": "Провайдер",
"LabelMinute": "Минуты", "LabelMinute": "Минуты",
"LabelMinutes": "Минуты", "LabelMinutes": "Минуты",
"LabelMissing": "Потеряно", "LabelMissing": "Отсутствует",
"LabelMissingEbook": "Нет e-книги", "LabelMissingEbook": "Нет e-книги",
"LabelMissingSupplementaryEbook": "Нет дополнительной e-книги", "LabelMissingSupplementaryEbook": "Нет дополнительной e-книги",
"LabelMobileRedirectURIs": "Разрешенные URI перенаправления с мобильных устройств", "LabelMobileRedirectURIs": "Разрешенные URI перенаправления с мобильных устройств",
@@ -463,7 +465,7 @@
"LabelNotificationsMaxQueueSize": "Макс. размер очереди для событий уведомлений", "LabelNotificationsMaxQueueSize": "Макс. размер очереди для событий уведомлений",
"LabelNotificationsMaxQueueSizeHelp": "События ограничены 1 в секунду. События будут игнорированы если в очереди максимальное количество. Это предотвращает спам сообщениями.", "LabelNotificationsMaxQueueSizeHelp": "События ограничены 1 в секунду. События будут игнорированы если в очереди максимальное количество. Это предотвращает спам сообщениями.",
"LabelNumberOfBooks": "Количество книг", "LabelNumberOfBooks": "Количество книг",
"LabelNumberOfEpisodes": "# Эпизодов", "LabelNumberOfEpisodes": "# из эпизодов",
"LabelOpenIDAdvancedPermsClaimDescription": "Имя утверждения OpenID, содержащего расширенные разрешения на действия пользователя в приложении, которые будут применяться к ролям, не являющимся администраторами (<b>если они настроены</b>). Если утверждение отсутствует в ответе, в доступе к ABS будет отказано. Если одна опция отсутствует, она будет рассматриваться как <code>false</code>. Убедитесь, что утверждение поставщика удостоверений соответствует ожидаемой структуре:", "LabelOpenIDAdvancedPermsClaimDescription": "Имя утверждения OpenID, содержащего расширенные разрешения на действия пользователя в приложении, которые будут применяться к ролям, не являющимся администраторами (<b>если они настроены</b>). Если утверждение отсутствует в ответе, в доступе к ABS будет отказано. Если одна опция отсутствует, она будет рассматриваться как <code>false</code>. Убедитесь, что утверждение поставщика удостоверений соответствует ожидаемой структуре:",
"LabelOpenIDClaims": "Оставьте следующие параметры пустыми, чтобы отключить расширенное назначение групп и разрешений, будет автоматически присвоена группа «Пользователь».", "LabelOpenIDClaims": "Оставьте следующие параметры пустыми, чтобы отключить расширенное назначение групп и разрешений, будет автоматически присвоена группа «Пользователь».",
"LabelOpenIDGroupClaimDescription": "Имя утверждения OpenID, содержащего список групп пользователя. Обычно их называют <code>groups</code>. <b>Если эта настройка</b> настроена, приложение будет автоматически назначать роли на основе членства пользователя в группах при условии, что эти группы названы в утверждении без учета регистра \"admin\", \"user\" или \"guest\". Утверждение должно содержать список, и если пользователь принадлежит к нескольким группам, то приложение назначит роль, соответствующую самому высокому уровню доступа. Если ни одна из групп не совпадает, доступ будет запрещен.", "LabelOpenIDGroupClaimDescription": "Имя утверждения OpenID, содержащего список групп пользователя. Обычно их называют <code>groups</code>. <b>Если эта настройка</b> настроена, приложение будет автоматически назначать роли на основе членства пользователя в группах при условии, что эти группы названы в утверждении без учета регистра \"admin\", \"user\" или \"guest\". Утверждение должно содержать список, и если пользователь принадлежит к нескольким группам, то приложение назначит роль, соответствующую самому высокому уровню доступа. Если ни одна из групп не совпадает, доступ будет запрещен.",
@@ -484,6 +486,7 @@
"LabelPersonalYearReview": "Итоги прошедшего года ({0})", "LabelPersonalYearReview": "Итоги прошедшего года ({0})",
"LabelPhotoPathURL": "Путь к фото/URL", "LabelPhotoPathURL": "Путь к фото/URL",
"LabelPlayMethod": "Метод воспроизведения", "LabelPlayMethod": "Метод воспроизведения",
"LabelPlaybackRateIncrementDecrement": "Величина увеличения/уменьшения скорости воспроизведения",
"LabelPlayerChapterNumberMarker": "{0} из {1}", "LabelPlayerChapterNumberMarker": "{0} из {1}",
"LabelPlaylists": "Плейлисты", "LabelPlaylists": "Плейлисты",
"LabelPodcast": "Подкаст", "LabelPodcast": "Подкаст",
@@ -651,7 +654,7 @@
"LabelToolsMakeM4bDescription": "Создает .M4B файл аудиокниги с встроенными метаданными, обложкой и главами.", "LabelToolsMakeM4bDescription": "Создает .M4B файл аудиокниги с встроенными метаданными, обложкой и главами.",
"LabelToolsSplitM4b": "Разделить M4B на MP3 файлы", "LabelToolsSplitM4b": "Разделить M4B на MP3 файлы",
"LabelToolsSplitM4bDescription": "Создает MP3 файла из M4B, разделяет на главы с встроенными метаданными, обложкой и главами.", "LabelToolsSplitM4bDescription": "Создает MP3 файла из M4B, разделяет на главы с встроенными метаданными, обложкой и главами.",
"LabelTotalDuration": "Общая длина", "LabelTotalDuration": "Общая продолжительность",
"LabelTotalTimeListened": "Всего прослушано", "LabelTotalTimeListened": "Всего прослушано",
"LabelTrackFromFilename": "Трек из Имени файла", "LabelTrackFromFilename": "Трек из Имени файла",
"LabelTrackFromMetadata": "Трек из Метаданных", "LabelTrackFromMetadata": "Трек из Метаданных",
@@ -704,8 +707,11 @@
"MessageBackupsLocationEditNote": "Примечание: Обновление местоположения резервной копии не приведет к перемещению или изменению существующих резервных копий", "MessageBackupsLocationEditNote": "Примечание: Обновление местоположения резервной копии не приведет к перемещению или изменению существующих резервных копий",
"MessageBackupsLocationNoEditNote": "Примечание: Местоположение резервного копирования задается с помощью переменной среды и не может быть изменено здесь.", "MessageBackupsLocationNoEditNote": "Примечание: Местоположение резервного копирования задается с помощью переменной среды и не может быть изменено здесь.",
"MessageBackupsLocationPathEmpty": "Путь к расположению резервной копии не может быть пустым", "MessageBackupsLocationPathEmpty": "Путь к расположению резервной копии не может быть пустым",
"MessageBatchEditPopulateMapDetailsAllHelp": "Заполнить включенные поля данными из всех элементов. Поля с несколькими значениями будут объединены",
"MessageBatchEditPopulateMapDetailsItemHelp": "Заполнить активированные поля сведений о карте данными из этого элемента",
"MessageBatchQuickMatchDescription": "Быстрый Поиск попытается добавить отсутствующие обложки и метаданные для выбранных элементов. Включите параметры ниже, чтобы разрешить Быстрому Поиску перезаписывать существующие обложки и/или метаданные.", "MessageBatchQuickMatchDescription": "Быстрый Поиск попытается добавить отсутствующие обложки и метаданные для выбранных элементов. Включите параметры ниже, чтобы разрешить Быстрому Поиску перезаписывать существующие обложки и/или метаданные.",
"MessageBookshelfNoCollections": "Вы еще не создали ни одной коллекции", "MessageBookshelfNoCollections": "Вы еще не создали ни одной коллекции",
"MessageBookshelfNoCollectionsHelp": "Коллекции являются общедоступными. Все пользователи, имеющие доступ к библиотеке, могут их просматривать.",
"MessageBookshelfNoRSSFeeds": "Нет открытых RSS-каналов", "MessageBookshelfNoRSSFeeds": "Нет открытых RSS-каналов",
"MessageBookshelfNoResultsForFilter": "Нет Результатов для фильтра \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Нет Результатов для фильтра \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Нет результатов для запроса", "MessageBookshelfNoResultsForQuery": "Нет результатов для запроса",
@@ -816,6 +822,7 @@
"MessageNoTasksRunning": "Нет выполняемых задач", "MessageNoTasksRunning": "Нет выполняемых задач",
"MessageNoUpdatesWereNecessary": "Обновления не требовались", "MessageNoUpdatesWereNecessary": "Обновления не требовались",
"MessageNoUserPlaylists": "У вас нет плейлистов", "MessageNoUserPlaylists": "У вас нет плейлистов",
"MessageNoUserPlaylistsHelp": "Списки воспроизведения являются конфиденциальными. Только пользователь, который их создает, может их видеть.",
"MessageNotYetImplemented": "Пока не реализовано", "MessageNotYetImplemented": "Пока не реализовано",
"MessageOpmlPreviewNote": "Примечание: Это предварительный просмотр разобранного файла OPML. Фактическое название подкаста будет взято из RSS-канала.", "MessageOpmlPreviewNote": "Примечание: Это предварительный просмотр разобранного файла OPML. Фактическое название подкаста будет взято из RSS-канала.",
"MessageOr": "или", "MessageOr": "или",
+8 -1
View File
@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Uveljavi poglavja", "ButtonApplyChapters": "Uveljavi poglavja",
"ButtonAuthors": "Avtorji", "ButtonAuthors": "Avtorji",
"ButtonBack": "Nazaj", "ButtonBack": "Nazaj",
"ButtonBatchEditPopulateFromExisting": "Napolni iz obstoječega",
"ButtonBatchEditPopulateMapDetails": "Izpolnite podrobnosti zemljevida",
"ButtonBrowseForFolder": "Prebrskaj pot do mape", "ButtonBrowseForFolder": "Prebrskaj pot do mape",
"ButtonCancel": "Prekliči", "ButtonCancel": "Prekliči",
"ButtonCancelEncode": "Prekliči prekodiranje", "ButtonCancelEncode": "Prekliči prekodiranje",
@@ -432,7 +434,7 @@
"LabelMetadataProvider": "Ponudnik metapodatkov", "LabelMetadataProvider": "Ponudnik metapodatkov",
"LabelMinute": "Minuta", "LabelMinute": "Minuta",
"LabelMinutes": "Minute", "LabelMinutes": "Minute",
"LabelMissing": "Manjkajoče", "LabelMissing": "Manjka",
"LabelMissingEbook": "Nima nobene e-knjige", "LabelMissingEbook": "Nima nobene e-knjige",
"LabelMissingSupplementaryEbook": "Nima nobene dodatne e-knjige", "LabelMissingSupplementaryEbook": "Nima nobene dodatne e-knjige",
"LabelMobileRedirectURIs": "Dovoljeni mobilni preusmeritveni URI-ji", "LabelMobileRedirectURIs": "Dovoljeni mobilni preusmeritveni URI-ji",
@@ -484,6 +486,7 @@
"LabelPersonalYearReview": "Pregled tvojega leta ({0})", "LabelPersonalYearReview": "Pregled tvojega leta ({0})",
"LabelPhotoPathURL": "Slika pot/URL", "LabelPhotoPathURL": "Slika pot/URL",
"LabelPlayMethod": "Metoda predvajanja", "LabelPlayMethod": "Metoda predvajanja",
"LabelPlaybackRateIncrementDecrement": "Korak povečanja/zmanjšanja hitrosti predvajanja",
"LabelPlayerChapterNumberMarker": "{0} od {1}", "LabelPlayerChapterNumberMarker": "{0} od {1}",
"LabelPlaylists": "Seznami predvajanja", "LabelPlaylists": "Seznami predvajanja",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
@@ -704,8 +707,11 @@
"MessageBackupsLocationEditNote": "Opomba: Posodabljanje lokacije varnostne kopije ne bo premaknilo ali spremenilo obstoječih varnostnih kopij", "MessageBackupsLocationEditNote": "Opomba: Posodabljanje lokacije varnostne kopije ne bo premaknilo ali spremenilo obstoječih varnostnih kopij",
"MessageBackupsLocationNoEditNote": "Opomba: Lokacija varnostne kopije je nastavljena s spremenljivko okolja in je tu ni mogoče spremeniti.", "MessageBackupsLocationNoEditNote": "Opomba: Lokacija varnostne kopije je nastavljena s spremenljivko okolja in je tu ni mogoče spremeniti.",
"MessageBackupsLocationPathEmpty": "Pot do lokacije varnostne kopije ne sme biti prazna", "MessageBackupsLocationPathEmpty": "Pot do lokacije varnostne kopije ne sme biti prazna",
"MessageBatchEditPopulateMapDetailsAllHelp": "Napolni omogočena polja s podatki iz vseh elementov. Polja z več vrednostmi bodo združena",
"MessageBatchEditPopulateMapDetailsItemHelp": "Napolni omogočena polja s podrobnostmi zemljevida s podatki iz tega elementa",
"MessageBatchQuickMatchDescription": "Hitro ujemanje bo poskušal dodati manjkajoče naslovnice in metapodatke za izbrane elemente. Omogočite spodnje možnosti, da omogočite hitremu ujemanju, da prepiše obstoječe naslovnice in/ali metapodatke.", "MessageBatchQuickMatchDescription": "Hitro ujemanje bo poskušal dodati manjkajoče naslovnice in metapodatke za izbrane elemente. Omogočite spodnje možnosti, da omogočite hitremu ujemanju, da prepiše obstoječe naslovnice in/ali metapodatke.",
"MessageBookshelfNoCollections": "Ustvaril nisi še nobene zbirke", "MessageBookshelfNoCollections": "Ustvaril nisi še nobene zbirke",
"MessageBookshelfNoCollectionsHelp": "Zbirke so javne. Vsi uporabniki z dostopom do knjižnice jih lahko vidijo.",
"MessageBookshelfNoRSSFeeds": "Noben vir RSS ni odprt", "MessageBookshelfNoRSSFeeds": "Noben vir RSS ni odprt",
"MessageBookshelfNoResultsForFilter": "Ni rezultatov za filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Ni rezultatov za filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Ni rezultatov za poizvedbo", "MessageBookshelfNoResultsForQuery": "Ni rezultatov za poizvedbo",
@@ -816,6 +822,7 @@
"MessageNoTasksRunning": "Nobeno opravili ne teče", "MessageNoTasksRunning": "Nobeno opravili ne teče",
"MessageNoUpdatesWereNecessary": "Posodobitve niso bile potrebne", "MessageNoUpdatesWereNecessary": "Posodobitve niso bile potrebne",
"MessageNoUserPlaylists": "Nimate seznamov predvajanja", "MessageNoUserPlaylists": "Nimate seznamov predvajanja",
"MessageNoUserPlaylistsHelp": "Seznami predvajanj so zasebni. Samo uporabniki, ki jih ustvarijo, jih lahko vidijo.",
"MessageNotYetImplemented": "Še ni implementirano", "MessageNotYetImplemented": "Še ni implementirano",
"MessageOpmlPreviewNote": "Opomba: To je predogled razčlenjene datoteke OPML. Dejanski naslov podcasta bo vzet iz vira RSS.", "MessageOpmlPreviewNote": "Opomba: To je predogled razčlenjene datoteke OPML. Dejanski naslov podcasta bo vzet iz vira RSS.",
"MessageOr": "ali", "MessageOr": "ali",
+192 -105
View File
@@ -10,11 +10,13 @@
"ButtonApplyChapters": "Tillämpa kapitel", "ButtonApplyChapters": "Tillämpa kapitel",
"ButtonAuthors": "Författare", "ButtonAuthors": "Författare",
"ButtonBack": "Tillbaka", "ButtonBack": "Tillbaka",
"ButtonBatchEditPopulateFromExisting": "Hämta befintlig information",
"ButtonBatchEditPopulateMapDetails": "Addera befintliga information",
"ButtonBrowseForFolder": "Bläddra efter mapp", "ButtonBrowseForFolder": "Bläddra efter mapp",
"ButtonCancel": "Avbryt", "ButtonCancel": "Avbryt",
"ButtonCancelEncode": "Avbryt kodning", "ButtonCancelEncode": "Avbryt omkodning",
"ButtonChangeRootPassword": "Ändra lösenordet för root", "ButtonChangeRootPassword": "Ändra lösenordet för root",
"ButtonCheckAndDownloadNewEpisodes": "Kontrollera och ladda ner nya avsnitt", "ButtonCheckAndDownloadNewEpisodes": "Sök & Ladda ner nya avsnitt",
"ButtonChooseAFolder": "Välj en mapp", "ButtonChooseAFolder": "Välj en mapp",
"ButtonChooseFiles": "Välj filer", "ButtonChooseFiles": "Välj filer",
"ButtonClearFilter": "Rensa filter", "ButtonClearFilter": "Rensa filter",
@@ -30,7 +32,7 @@
"ButtonEditChapters": "Redigera kapitel", "ButtonEditChapters": "Redigera kapitel",
"ButtonEditPodcast": "Redigera podcast", "ButtonEditPodcast": "Redigera podcast",
"ButtonEnable": "Aktivera", "ButtonEnable": "Aktivera",
"ButtonForceReScan": "Tvinga omstart", "ButtonForceReScan": "Starta ny skanning",
"ButtonFullPath": "Fullständig sökväg", "ButtonFullPath": "Fullständig sökväg",
"ButtonHide": "Dölj", "ButtonHide": "Dölj",
"ButtonHome": "Hem", "ButtonHome": "Hem",
@@ -49,7 +51,7 @@
"ButtonNext": "Nästa", "ButtonNext": "Nästa",
"ButtonNextChapter": "Nästa kapitel", "ButtonNextChapter": "Nästa kapitel",
"ButtonNextItemInQueue": "Nästa objekt i Kö", "ButtonNextItemInQueue": "Nästa objekt i Kö",
"ButtonOk": "Ok", "ButtonOk": "OK",
"ButtonOpenFeed": "Öppna flöde", "ButtonOpenFeed": "Öppna flöde",
"ButtonOpenManager": "Öppna Manager", "ButtonOpenManager": "Öppna Manager",
"ButtonPause": "Pausa", "ButtonPause": "Pausa",
@@ -64,8 +66,8 @@
"ButtonPurgeItemsCache": "Rensa cache för föremål", "ButtonPurgeItemsCache": "Rensa cache för föremål",
"ButtonQueueAddItem": "Lägg till i kön", "ButtonQueueAddItem": "Lägg till i kön",
"ButtonQueueRemoveItem": "Ta bort från kön", "ButtonQueueRemoveItem": "Ta bort från kön",
"ButtonQuickMatch": "Snabb matchning", "ButtonQuickMatch": "Snabbmatchning",
"ButtonReScan": "Omstart", "ButtonReScan": "Ny skanning",
"ButtonRead": "Läs", "ButtonRead": "Läs",
"ButtonReadLess": "Visa mindre", "ButtonReadLess": "Visa mindre",
"ButtonReadMore": "Visa mer", "ButtonReadMore": "Visa mer",
@@ -73,10 +75,10 @@
"ButtonRemove": "Ta bort", "ButtonRemove": "Ta bort",
"ButtonRemoveAll": "Ta bort alla", "ButtonRemoveAll": "Ta bort alla",
"ButtonRemoveAllLibraryItems": "Ta bort alla objekt i biblioteket", "ButtonRemoveAllLibraryItems": "Ta bort alla objekt i biblioteket",
"ButtonRemoveFromContinueListening": "Radera från 'Fortsätt läsa/lyssna'", "ButtonRemoveFromContinueListening": "Radera från 'Fortsätt lyssna'",
"ButtonRemoveFromContinueReading": "Ta bort från Fortsätt läsa", "ButtonRemoveFromContinueReading": "Radera från 'Fortsätt läsa'",
"ButtonRemoveSeriesFromContinueSeries": "Radera från 'Fortsätt med serien'", "ButtonRemoveSeriesFromContinueSeries": "Radera från 'Fortsätt med serien'",
"ButtonReset": "Återställ", "ButtonReset": "Tillbaka",
"ButtonResetToDefault": "Återställ till standard", "ButtonResetToDefault": "Återställ till standard",
"ButtonRestore": "Återställ", "ButtonRestore": "Återställ",
"ButtonSave": "Spara", "ButtonSave": "Spara",
@@ -91,14 +93,14 @@
"ButtonShare": "Dela", "ButtonShare": "Dela",
"ButtonShiftTimes": "Förskjut tider", "ButtonShiftTimes": "Förskjut tider",
"ButtonShow": "Visa", "ButtonShow": "Visa",
"ButtonStartM4BEncode": "Starta M4B-kodning", "ButtonStartM4BEncode": "Starta M4B-omkodning",
"ButtonStartMetadataEmbed": "Starta inbäddning av metadata", "ButtonStartMetadataEmbed": "Starta inbäddning av metadata",
"ButtonStats": "Statistik", "ButtonStats": "Statistik",
"ButtonSubmit": "Spara", "ButtonSubmit": "Spara",
"ButtonTest": "Testa", "ButtonTest": "Testa",
"ButtonUpload": "Ladda upp", "ButtonUpload": "Ladda upp",
"ButtonUploadBackup": "Ladda upp säkerhetskopia", "ButtonUploadBackup": "Läs in säkerhetskopia",
"ButtonUploadCover": "Ladda upp bokomslag", "ButtonUploadCover": "Ladda upp omslag",
"ButtonUploadOPMLFile": "Ladda upp OPML-fil", "ButtonUploadOPMLFile": "Ladda upp OPML-fil",
"ButtonUserDelete": "Radera användare {0}", "ButtonUserDelete": "Radera användare {0}",
"ButtonUserEdit": "Redigera användare {0}", "ButtonUserEdit": "Redigera användare {0}",
@@ -110,7 +112,7 @@
"HeaderAccount": "Konto", "HeaderAccount": "Konto",
"HeaderAddCustomMetadataProvider": "Addera egen källa för metadata", "HeaderAddCustomMetadataProvider": "Addera egen källa för metadata",
"HeaderAdvanced": "Avancerad", "HeaderAdvanced": "Avancerad",
"HeaderAppriseNotificationSettings": "Apprise Meddelandeinställningar", "HeaderAppriseNotificationSettings": "Inställningar av meddelanden med Apprise",
"HeaderAudioTracks": "Ljudspår", "HeaderAudioTracks": "Ljudspår",
"HeaderAudiobookTools": "Hantering av ljudboksfil", "HeaderAudiobookTools": "Hantering av ljudboksfil",
"HeaderAuthentication": "Autentisering", "HeaderAuthentication": "Autentisering",
@@ -120,7 +122,7 @@
"HeaderChooseAFolder": "Välj en mapp", "HeaderChooseAFolder": "Välj en mapp",
"HeaderCollection": "Samling", "HeaderCollection": "Samling",
"HeaderCollectionItems": "Böcker i samlingen", "HeaderCollectionItems": "Böcker i samlingen",
"HeaderCover": "Bokomslag", "HeaderCover": "Omslag",
"HeaderCurrentDownloads": "Aktuella nedladdningar", "HeaderCurrentDownloads": "Aktuella nedladdningar",
"HeaderCustomMetadataProviders": "Egen källa för metadata", "HeaderCustomMetadataProviders": "Egen källa för metadata",
"HeaderDetails": "Detaljer", "HeaderDetails": "Detaljer",
@@ -134,8 +136,8 @@
"HeaderFiles": "Filer", "HeaderFiles": "Filer",
"HeaderFindChapters": "Hitta kapitel", "HeaderFindChapters": "Hitta kapitel",
"HeaderIgnoredFiles": "Ignorerade filer", "HeaderIgnoredFiles": "Ignorerade filer",
"HeaderItemFiles": "Föremålsfiler", "HeaderItemFiles": "Filer",
"HeaderItemMetadataUtils": "Metadataverktyg för föremål", "HeaderItemMetadataUtils": "Metadataverktyg",
"HeaderLastListeningSession": "Senaste lyssningstillfället", "HeaderLastListeningSession": "Senaste lyssningstillfället",
"HeaderLatestEpisodes": "Senaste avsnitten", "HeaderLatestEpisodes": "Senaste avsnitten",
"HeaderLibraries": "Bibliotek", "HeaderLibraries": "Bibliotek",
@@ -147,12 +149,13 @@
"HeaderLogs": "Loggar", "HeaderLogs": "Loggar",
"HeaderManageGenres": "Hantera kategorier", "HeaderManageGenres": "Hantera kategorier",
"HeaderManageTags": "Hantera taggar", "HeaderManageTags": "Hantera taggar",
"HeaderMapDetails": "Karta detaljer", "HeaderMapDetails": "Gemensam information för samtliga objekt",
"HeaderMatch": "Matcha", "HeaderMatch": "Matcha",
"HeaderMetadataOrderOfPrecedence": "Prioriteringsordning vid inläsning av metadata", "HeaderMetadataOrderOfPrecedence": "Prioriteringsordning vid inläsning av metadata",
"HeaderMetadataToEmbed": "Metadata att bädda in", "HeaderMetadataToEmbed": "Metadata som kommer att adderas",
"HeaderNewAccount": "Nytt konto", "HeaderNewAccount": "Nytt konto",
"HeaderNewLibrary": "Nytt bibliotek", "HeaderNewLibrary": "Nytt bibliotek",
"HeaderNotificationCreate": "Addera ett meddelande",
"HeaderNotifications": "Meddelanden", "HeaderNotifications": "Meddelanden",
"HeaderOpenRSSFeed": "Öppna RSS-flöde", "HeaderOpenRSSFeed": "Öppna RSS-flöde",
"HeaderOtherFiles": "Andra filer", "HeaderOtherFiles": "Andra filer",
@@ -163,17 +166,17 @@
"HeaderPlaylist": "Spellista", "HeaderPlaylist": "Spellista",
"HeaderPlaylistItems": "Böcker i spellistan", "HeaderPlaylistItems": "Böcker i spellistan",
"HeaderPodcastsToAdd": "Podcaster att lägga till", "HeaderPodcastsToAdd": "Podcaster att lägga till",
"HeaderPreviewCover": "Förhandsgranska bokomslag", "HeaderPreviewCover": "Förhandsgranska omslag",
"HeaderRSSFeedGeneral": "RSS-information", "HeaderRSSFeedGeneral": "RSS-information",
"HeaderRSSFeedIsOpen": "RSS-flödet är öppet", "HeaderRSSFeedIsOpen": "RSS-flödet är öppet",
"HeaderRSSFeeds": "RSS-flöden", "HeaderRSSFeeds": "RSS-flöden",
"HeaderRemoveEpisode": "Ta bort avsnitt", "HeaderRemoveEpisode": "Radera avsnitt",
"HeaderRemoveEpisodes": "Ta bort {0} avsnitt", "HeaderRemoveEpisodes": "Radera {0} avsnitt",
"HeaderSavedMediaProgress": "Sparad historik", "HeaderSavedMediaProgress": "Sparad historik",
"HeaderSchedule": "Schema", "HeaderSchedule": "Schema",
"HeaderScheduleEpisodeDownloads": "Schemalägg automatiska avsnittsnedladdningar", "HeaderScheduleEpisodeDownloads": "Schemalägg automatiska nedladdning av avsnitt",
"HeaderScheduleLibraryScans": "Schema för skanning av biblioteket", "HeaderScheduleLibraryScans": "Schema för skanning av biblioteket",
"HeaderSession": "Session", "HeaderSession": "Tillfälle",
"HeaderSetBackupSchedule": "Ange schemaläggning för säkerhetskopia", "HeaderSetBackupSchedule": "Ange schemaläggning för säkerhetskopia",
"HeaderSettings": "Inställningar", "HeaderSettings": "Inställningar",
"HeaderSettingsDisplay": "Visning", "HeaderSettingsDisplay": "Visning",
@@ -197,16 +200,16 @@
"HeaderUsers": "Användare", "HeaderUsers": "Användare",
"HeaderYearReview": "Sammanställning av {0}", "HeaderYearReview": "Sammanställning av {0}",
"HeaderYourStats": "Din statistik", "HeaderYourStats": "Din statistik",
"LabelAbridged": "Förkortad", "LabelAbridged": "Förkortad version",
"LabelAccessibleBy": "Tillgänglig för", "LabelAccessibleBy": "Tillgänglig för",
"LabelAccountType": "Kontotyp", "LabelAccountType": "Kontotyp",
"LabelAccountTypeAdmin": "Administratör", "LabelAccountTypeAdmin": "Administratör",
"LabelAccountTypeGuest": "Gäst", "LabelAccountTypeGuest": "Gäst",
"LabelAccountTypeUser": "Användare", "LabelAccountTypeUser": "Användare",
"LabelActivity": "Aktivitet", "LabelActivity": "Aktivitet",
"LabelAddToCollection": "Lägg till i en Samling", "LabelAddToCollection": "Lägg till i en samling",
"LabelAddToCollectionBatch": "Lägg till {0} böcker i en Samling", "LabelAddToCollectionBatch": "Lägg till {0} böcker i samlingen",
"LabelAddToPlaylist": "Lägg till i Spellista", "LabelAddToPlaylist": "Lägg till i en spellista",
"LabelAddToPlaylistBatch": "Lägg till {0} objekt i Spellistan", "LabelAddToPlaylistBatch": "Lägg till {0} objekt i Spellistan",
"LabelAddedAt": "Datum adderad", "LabelAddedAt": "Datum adderad",
"LabelAddedDate": "Adderad {0}", "LabelAddedDate": "Adderad {0}",
@@ -215,9 +218,12 @@
"LabelAllUsers": "Alla användare", "LabelAllUsers": "Alla användare",
"LabelAllUsersExcludingGuests": "Alla användare utom gäster", "LabelAllUsersExcludingGuests": "Alla användare utom gäster",
"LabelAllUsersIncludingGuests": "Alla användare inklusive gäster", "LabelAllUsersIncludingGuests": "Alla användare inklusive gäster",
"LabelAlreadyInYourLibrary": "Redan i din samling", "LabelAlreadyInYourLibrary": "Finns redan i samlingen",
"LabelApiToken": "API-token", "LabelApiToken": "API-token",
"LabelAppend": "Lägg till", "LabelAppend": "Lägg till",
"LabelAudioBitrate": "Bitrate för ljud (t.ex. 128k)",
"LabelAudioChannels": "Ljudkanaler (1 eller 2)",
"LabelAudioCodec": "Codec för ljud",
"LabelAuthor": "Författare", "LabelAuthor": "Författare",
"LabelAuthorFirstLast": "Författare (För-, Efternamn)", "LabelAuthorFirstLast": "Författare (För-, Efternamn)",
"LabelAuthorLastFirst": "Författare (Efter-, Förnamn)", "LabelAuthorLastFirst": "Författare (Efter-, Förnamn)",
@@ -231,10 +237,10 @@
"LabelBackupLocation": "Plats för säkerhetskopia", "LabelBackupLocation": "Plats för säkerhetskopia",
"LabelBackupsEnableAutomaticBackups": "Aktivera automatisk säkerhetskopiering", "LabelBackupsEnableAutomaticBackups": "Aktivera automatisk säkerhetskopiering",
"LabelBackupsEnableAutomaticBackupsHelp": "Säkerhetskopior sparas i \"/metadata/backups\"", "LabelBackupsEnableAutomaticBackupsHelp": "Säkerhetskopior sparas i \"/metadata/backups\"",
"LabelBackupsMaxBackupSize": "Maximal storlek på säkerhetskopia (i GB) (0 = obegränsad)", "LabelBackupsMaxBackupSize": "Maximal storlek på säkerhetskopia i GigaByte (0 = obegränsad)",
"LabelBackupsMaxBackupSizeHelp": "Som ett skydd mot en felaktig konfiguration kommer säkerhetskopior inte att genomföras om de överskrider den konfigurerade storleken.", "LabelBackupsMaxBackupSizeHelp": "Som ett skydd mot en felaktig konfiguration kommer säkerhetskopior inte att genomföras om de överskrider den konfigurerade storleken.",
"LabelBackupsNumberToKeep": "Antal säkerhetskopior att behålla", "LabelBackupsNumberToKeep": "Antal säkerhetskopior att behålla",
"LabelBackupsNumberToKeepHelp": "Endast en gammal säkerhetskopia tas bort åt gången, så om du redan har fler säkerhetskopior än det angivna beloppet bör du ta bort dem manuellt.", "LabelBackupsNumberToKeepHelp": "Endast en gammal säkerhetskopia tas bort åt gången, så om du redan har fler säkerhetskopior än det angivna värdet bör du ta bort dem manuellt.",
"LabelBitrate": "Bitfrekvens", "LabelBitrate": "Bitfrekvens",
"LabelBonus": "Bonus", "LabelBonus": "Bonus",
"LabelBooks": "Böcker", "LabelBooks": "Böcker",
@@ -243,7 +249,7 @@
"LabelChangePassword": "Ändra lösenord", "LabelChangePassword": "Ändra lösenord",
"LabelChannels": "Kanaler", "LabelChannels": "Kanaler",
"LabelChapterCount": "{0} kapitel", "LabelChapterCount": "{0} kapitel",
"LabelChapterTitle": "Kapitelrubrik", "LabelChapterTitle": "Titel på kapitel",
"LabelChapters": "Kapitel", "LabelChapters": "Kapitel",
"LabelChaptersFound": "hittade kapitel", "LabelChaptersFound": "hittade kapitel",
"LabelClickForMoreInfo": "Klicka för mer information", "LabelClickForMoreInfo": "Klicka för mer information",
@@ -255,10 +261,10 @@
"LabelCollections": "Samlingar", "LabelCollections": "Samlingar",
"LabelComplete": "Komplett", "LabelComplete": "Komplett",
"LabelConfirmPassword": "Bekräfta lösenord", "LabelConfirmPassword": "Bekräfta lösenord",
"LabelContinueListening": "Fortsätt läsa/lyssna", "LabelContinueListening": "Fortsätt att lyssna",
"LabelContinueReading": "Fortsätt Läsa", "LabelContinueReading": "Fortsätt att läsa",
"LabelContinueSeries": "Fortsätt med serien", "LabelContinueSeries": "Fortsätt med serien",
"LabelCover": "Bokomslag", "LabelCover": "Omslag",
"LabelCoverImageURL": "URL till omslagsbild", "LabelCoverImageURL": "URL till omslagsbild",
"LabelCreatedAt": "Skapad", "LabelCreatedAt": "Skapad",
"LabelCronExpression": "Schemaläggning med hjälp av Cron (Cron Expression)", "LabelCronExpression": "Schemaläggning med hjälp av Cron (Cron Expression)",
@@ -267,7 +273,7 @@
"LabelCustomCronExpression": "Anpassat Cron-uttryck:", "LabelCustomCronExpression": "Anpassat Cron-uttryck:",
"LabelDatetime": "Datum och klockslag", "LabelDatetime": "Datum och klockslag",
"LabelDays": "Dagar", "LabelDays": "Dagar",
"LabelDeleteFromFileSystemCheckbox": "Ta bort från filsystem (avmarkera för att endast ta bort från databasen)", "LabelDeleteFromFileSystemCheckbox": "Ta även bort från filsystem (avmarkera = raderar endast från databasen)",
"LabelDescription": "Beskrivning", "LabelDescription": "Beskrivning",
"LabelDeselectAll": "Avmarkera alla", "LabelDeselectAll": "Avmarkera alla",
"LabelDevice": "Enhet", "LabelDevice": "Enhet",
@@ -276,7 +282,7 @@
"LabelDirectory": "Katalog", "LabelDirectory": "Katalog",
"LabelDiscFromFilename": "Skiva från filnamn", "LabelDiscFromFilename": "Skiva från filnamn",
"LabelDiscFromMetadata": "Skiva från metadata", "LabelDiscFromMetadata": "Skiva från metadata",
"LabelDiscover": "Upptäck", "LabelDiscover": "Några förslag",
"LabelDownload": "Ladda ner", "LabelDownload": "Ladda ner",
"LabelDownloadNEpisodes": "Ladda ner {0} avsnitt", "LabelDownloadNEpisodes": "Ladda ner {0} avsnitt",
"LabelDownloadable": "Nedladdningsbar", "LabelDownloadable": "Nedladdningsbar",
@@ -293,32 +299,46 @@
"LabelEmailSettingsSecure": "Säker", "LabelEmailSettingsSecure": "Säker",
"LabelEmailSettingsSecureHelp": "Om sant kommer anslutningen att använda TLS vid anslutning till servern. Om falskt används TLS om servern stöder STARTTLS-tillägget. I de flesta fall, om du ansluter till port 465, bör du ställa in detta värde till sant. För port 587 eller 25, låt det vara falskt. (från nodemailer.com/smtp/#authentication)", "LabelEmailSettingsSecureHelp": "Om sant kommer anslutningen att använda TLS vid anslutning till servern. Om falskt används TLS om servern stöder STARTTLS-tillägget. I de flesta fall, om du ansluter till port 465, bör du ställa in detta värde till sant. För port 587 eller 25, låt det vara falskt. (från nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "E-postadress för test", "LabelEmailSettingsTestAddress": "E-postadress för test",
"LabelEmbeddedCover": "Inbäddat bokomslag", "LabelEmbeddedCover": "Infogat omslag",
"LabelEnable": "Aktivera", "LabelEnable": "Aktivera",
"LabelEncodingBackupLocation": "En säkerhetskopia av dina orginalljudfiler kommer att lagras i:", "LabelEncodingBackupLocation": "En säkerhetskopia av dina orginalljudfiler kommer att placeras i katalogen:",
"LabelEncodingClearItemCache": "Kom ihåg att regelbundet radera cachen för föremål. Du hittar funktionen längst ner på sidan 'Inställningar'.",
"LabelEncodingFinishedM4B": "Den färdiga M4B-filen kommer att placeras i katalogen:",
"LabelEncodingInfoEmbedded": "Metadata kommer att adderas i ljudfilerna i mappen med ljudboken.",
"LabelEncodingStartedNavigation": "När du startad omkodningen kan du lämna denna sida. Omkodningen fortsätter i bakgrunden.",
"LabelEncodingTimeWarning": "Avkodningen kan ta upp till 30 minuter eller ännu längre för riktigt stora filer.",
"LabelEncodingWarningAdvancedSettings": "VARNING: Ändra inte inställningarna om du inte är bekant med inställningarna för omkodning med 'ffmpeg'.",
"LabelEncodingWatcherDisabled": "Om funktionen 'Watcher' är avstängd behöver du göra en ny skanning av ljudboken efteråt.",
"LabelEnd": "Slut", "LabelEnd": "Slut",
"LabelEndOfChapter": "Slut av kapitel", "LabelEndOfChapter": "Slut av kapitel",
"LabelEpisode": "Avsnitt", "LabelEpisode": "Avsnitt",
"LabelEpisodeTitle": "Avsnittsrubrik", "LabelEpisodeNumber": "Avsnitt #{0}",
"LabelEpisodeType": "Avsnittstyp", "LabelEpisodeTitle": "Titel på avsnittet",
"LabelEpisodeType": "Typ av avsnitt",
"LabelEpisodes": "Avsnitt",
"LabelEpisodic": "Uppdelad i avsnitt",
"LabelExample": "Exempel", "LabelExample": "Exempel",
"LabelExpandSeries": "Expandera serier", "LabelExpandSeries": "Expandera serier",
"LabelFeedURL": "Flödes-URL", "LabelExplicit": "Explicit version",
"LabelExplicitChecked": "Explicit version (markerad)",
"LabelExplicitUnchecked": "Ej Explicit version (ej markerad)",
"LabelExportOPML": "Exportera OPML-information",
"LabelFeedURL": "URL-adress för flödet",
"LabelFetchingMetadata": "Hämtar metadata", "LabelFetchingMetadata": "Hämtar metadata",
"LabelFile": "Fil", "LabelFile": "Fil",
"LabelFileBirthtime": "Tidpunkt, filen skapades", "LabelFileBirthtime": "Tidpunkt, fil skapad",
"LabelFileModified": "Tidpunkt, filen ändrades", "LabelFileModified": "Tidpunkt, fil ändrad",
"LabelFileModifiedDate": "Ändrad {0}", "LabelFileModifiedDate": "Ändrad {0}",
"LabelFilename": "Filnamn", "LabelFilename": "Filnamn",
"LabelFilterByUser": "Välj användare", "LabelFilterByUser": "Välj användare",
"LabelFindEpisodes": "Hitta avsnitt", "LabelFindEpisodes": "Sök avsnitt",
"LabelFinished": "Avslutad", "LabelFinished": "Avslutad",
"LabelFolder": "Mapp", "LabelFolder": "Mapp",
"LabelFolders": "Mappar", "LabelFolders": "Mappar",
"LabelFontBold": "Fetstil", "LabelFontBold": "Fetstil",
"LabelFontBoldness": "Fetstil", "LabelFontBoldness": "Fetstil",
"LabelFontFamily": "Typsnittsfamilj", "LabelFontFamily": "Typsnittsfamilj",
"LabelFontItalic": "Kursiverad", "LabelFontItalic": "Kursiv",
"LabelFontScale": "Skala på typsnitt", "LabelFontScale": "Skala på typsnitt",
"LabelFontStrikethrough": "Genomstruken", "LabelFontStrikethrough": "Genomstruken",
"LabelGenre": "Kategori", "LabelGenre": "Kategori",
@@ -352,7 +372,7 @@
"LabelLanguage": "Språk", "LabelLanguage": "Språk",
"LabelLanguageDefaultServer": "Standardspråk för server", "LabelLanguageDefaultServer": "Standardspråk för server",
"LabelLanguages": "Språk", "LabelLanguages": "Språk",
"LabelLastBookAdded": "Bok senast tillagd", "LabelLastBookAdded": "Bok senast adderad",
"LabelLastBookUpdated": "Bok senast uppdaterad", "LabelLastBookUpdated": "Bok senast uppdaterad",
"LabelLastSeen": "Senast inloggad", "LabelLastSeen": "Senast inloggad",
"LabelLastTime": "Senaste tillfället", "LabelLastTime": "Senaste tillfället",
@@ -367,16 +387,20 @@
"LabelLibraryName": "Biblioteksnamn", "LabelLibraryName": "Biblioteksnamn",
"LabelLimit": "Begränsning", "LabelLimit": "Begränsning",
"LabelLineSpacing": "Radavstånd", "LabelLineSpacing": "Radavstånd",
"LabelListenAgain": "Läs/Lyssna igen", "LabelListenAgain": "Lyssna igen",
"LabelLogLevelDebug": "Felsökning", "LabelLogLevelDebug": "Felsökning",
"LabelLogLevelInfo": "Information", "LabelLogLevelInfo": "Information",
"LabelLogLevelWarn": "Varningar", "LabelLogLevelWarn": "Varningar",
"LabelLookForNewEpisodesAfterDate": "Sök efter nya avsnitt efter detta datum", "LabelLookForNewEpisodesAfterDate": "Sök efter nya avsnitt efter detta datum",
"LabelLowestPriority": "Lägst prioritet", "LabelLowestPriority": "Lägst prioritet",
"LabelMaxEpisodesToDownload": "Maximalt antal avsnitt att ladda ner (0 = obegränsat).",
"LabelMaxEpisodesToDownloadPerCheck": "Maximalt antal nya avsnitt att ladda ner per tillfälle",
"LabelMaxEpisodesToKeep": "Maximalt antal avsnitt att behålla",
"LabelMaxEpisodesToKeepHelp": "'0' innebär obegränsat antal avsnitt. Efter att nya avsnitt laddats ner raderas det äldsta avsnittet om du har mer än maximalt antal avsnitt. Endast ett avsnitt kommer att raderas per tillfälle.",
"LabelMediaPlayer": "Mediaspelare", "LabelMediaPlayer": "Mediaspelare",
"LabelMediaType": "Mediatyp", "LabelMediaType": "Mediatyp",
"LabelMetaTag": "Metamärke", "LabelMetaTag": "Metadata",
"LabelMetaTags": "Metamärken", "LabelMetaTags": "Metadata",
"LabelMetadataOrderOfPrecedenceDescription": "Källor för metadata med högre prioritet har företräde före källor med lägre prioritet", "LabelMetadataOrderOfPrecedenceDescription": "Källor för metadata med högre prioritet har företräde före källor med lägre prioritet",
"LabelMetadataProvider": "Källa för metadata", "LabelMetadataProvider": "Källa för metadata",
"LabelMinute": "Minut", "LabelMinute": "Minut",
@@ -391,12 +415,12 @@
"LabelNarrators": "Uppläsare", "LabelNarrators": "Uppläsare",
"LabelNew": "Nytt", "LabelNew": "Nytt",
"LabelNewPassword": "Nytt lösenord", "LabelNewPassword": "Nytt lösenord",
"LabelNewestAuthors": "Senast adderade författare", "LabelNewestAuthors": "Senaste författarna",
"LabelNewestEpisodes": "Senast tillagda avsnitt", "LabelNewestEpisodes": "Senast adderade avsnitt",
"LabelNextBackupDate": "Nästa datum för säkerhetskopiering", "LabelNextBackupDate": "Nästa tillfälle för säkerhetskopiering",
"LabelNextScheduledRun": "Nästa schemalagda körning", "LabelNextScheduledRun": "Nästa schemalagda körning",
"LabelNoCustomMetadataProviders": "Ingen egen källa för metadata", "LabelNoCustomMetadataProviders": "Ingen egen källa för metadata",
"LabelNoEpisodesSelected": "Inga avsnitt valda", "LabelNoEpisodesSelected": "Inga avsnitt har valts",
"LabelNotFinished": "Ej avslutad", "LabelNotFinished": "Ej avslutad",
"LabelNotStarted": "Ej påbörjad", "LabelNotStarted": "Ej påbörjad",
"LabelNotes": "Anteckningar", "LabelNotes": "Anteckningar",
@@ -418,7 +442,7 @@
"LabelPath": "Sökväg", "LabelPath": "Sökväg",
"LabelPermissionsAccessAllLibraries": "Kan komma åt alla bibliotek", "LabelPermissionsAccessAllLibraries": "Kan komma åt alla bibliotek",
"LabelPermissionsAccessAllTags": "Kan komma åt alla taggar", "LabelPermissionsAccessAllTags": "Kan komma åt alla taggar",
"LabelPermissionsAccessExplicitContent": "Kan komma åt explicit innehåll", "LabelPermissionsAccessExplicitContent": "Kan komma åt explicit version",
"LabelPermissionsCreateEreader": "Kan addera e-läsarenhet", "LabelPermissionsCreateEreader": "Kan addera e-läsarenhet",
"LabelPermissionsDelete": "Kan radera", "LabelPermissionsDelete": "Kan radera",
"LabelPermissionsDownload": "Kan ladda ner", "LabelPermissionsDownload": "Kan ladda ner",
@@ -431,7 +455,7 @@
"LabelPlaylists": "Spellistor", "LabelPlaylists": "Spellistor",
"LabelPodcast": "Podcast", "LabelPodcast": "Podcast",
"LabelPodcastSearchRegion": "Podcast-sökområde", "LabelPodcastSearchRegion": "Podcast-sökområde",
"LabelPodcastType": "Podcasttyp", "LabelPodcastType": "Typ av postcast",
"LabelPodcasts": "Podcasts", "LabelPodcasts": "Podcasts",
"LabelPort": "Port", "LabelPort": "Port",
"LabelPrefixesToIgnore": "Prefix att ignorera (skiftlägesokänsligt)", "LabelPrefixesToIgnore": "Prefix att ignorera (skiftlägesokänsligt)",
@@ -454,13 +478,14 @@
"LabelReadAgain": "Läs igen", "LabelReadAgain": "Läs igen",
"LabelReadEbookWithoutProgress": "Läs e-bok utan att behålla framsteg", "LabelReadEbookWithoutProgress": "Läs e-bok utan att behålla framsteg",
"LabelRecentSeries": "Senaste serierna", "LabelRecentSeries": "Senaste serierna",
"LabelRecentlyAdded": "Nyligen tillagda", "LabelRecentlyAdded": "Nyligen adderade",
"LabelRecommended": "Rekommenderad", "LabelRecommended": "Rekommenderad",
"LabelRedo": "Gör om",
"LabelRegion": "Region", "LabelRegion": "Region",
"LabelReleaseDate": "Utgivningsdatum", "LabelReleaseDate": "Utgivningsdatum",
"LabelRemoveAllMetadataAbs": "Radera alla 'metadata.abs' filer", "LabelRemoveAllMetadataAbs": "Radera alla 'metadata.abs' filer",
"LabelRemoveAllMetadataJson": "Radera alla 'metadata.json' filer", "LabelRemoveAllMetadataJson": "Radera alla 'metadata.json' filer",
"LabelRemoveCover": "Ta bort bokomslag", "LabelRemoveCover": "Ta bort omslag",
"LabelRemoveMetadataFile": "Radera metadata-filer i alla mappar i biblioteket", "LabelRemoveMetadataFile": "Radera metadata-filer i alla mappar i biblioteket",
"LabelRemoveMetadataFileHelp": "Radera alla 'metadata.json' och 'metadata.abs' filer i dina {0} mappar.", "LabelRemoveMetadataFileHelp": "Radera alla 'metadata.json' och 'metadata.abs' filer i dina {0} mappar.",
"LabelRowsPerPage": "Antal rader per sida", "LabelRowsPerPage": "Antal rader per sida",
@@ -468,12 +493,13 @@
"LabelSearchTitle": "Titel", "LabelSearchTitle": "Titel",
"LabelSearchTitleOrASIN": "Sök titel eller ASIN-kod", "LabelSearchTitleOrASIN": "Sök titel eller ASIN-kod",
"LabelSeason": "Säsong", "LabelSeason": "Säsong",
"LabelSeasonNumber": "Säsong #{0}",
"LabelSelectAll": "Välj alla", "LabelSelectAll": "Välj alla",
"LabelSelectAllEpisodes": "Välj alla avsnitt", "LabelSelectAllEpisodes": "Välj alla avsnitt",
"LabelSelectEpisodesShowing": "Välj {0} avsnitt som visas", "LabelSelectEpisodesShowing": "Välj {0} avsnitt som visas",
"LabelSelectUsers": "Välj användare", "LabelSelectUsers": "Välj användare",
"LabelSendEbookToDevice": "Skicka e-bok till...", "LabelSendEbookToDevice": "Skicka e-bok till...",
"LabelSequence": "Sekvens", "LabelSequence": "Sekvensnummer",
"LabelSeries": "Serier", "LabelSeries": "Serier",
"LabelSeriesName": "Serienamn", "LabelSeriesName": "Serienamn",
"LabelSeriesProgress": "Status för serier", "LabelSeriesProgress": "Status för serier",
@@ -489,16 +515,16 @@
"LabelSettingsDateFormat": "Datumformat", "LabelSettingsDateFormat": "Datumformat",
"LabelSettingsDisableWatcher": "Inaktivera Watcher", "LabelSettingsDisableWatcher": "Inaktivera Watcher",
"LabelSettingsDisableWatcherForLibrary": "Inaktivera bevakning med Watcher för biblioteket", "LabelSettingsDisableWatcherForLibrary": "Inaktivera bevakning med Watcher för biblioteket",
"LabelSettingsDisableWatcherHelp": "Inaktiverar automatik att addera/uppdatera objekt<br>när ändringar av filer genomförs.<br>OBS: Kräver en omstart av servern", "LabelSettingsDisableWatcherHelp": "Inaktiverar automatik att addera/uppdatera<br> objekt när ändringar av filer genomförs.<br>OBS: Kräver en omstart av servern",
"LabelSettingsEnableWatcher": "Aktivera Watcher", "LabelSettingsEnableWatcher": "Aktivera Watcher",
"LabelSettingsEnableWatcherForLibrary": "Aktivera bevakning med Watcher för biblioteket", "LabelSettingsEnableWatcherForLibrary": "Aktivera bevakning med Watcher för biblioteket",
"LabelSettingsEnableWatcherHelp": "Aktiverar automatik att addera/uppdatera objekt<br>när ändringar av filer genomförs.<br>OBS: Kräver en omstart av servern", "LabelSettingsEnableWatcherHelp": "Aktiverar automatik att addera/uppdatera<br> objekt när ändringar av filer genomförs.<br>OBS: Kräver en omstart av servern",
"LabelSettingsEpubsAllowScriptedContent": "Tillåt e-böcker i epubs-format som innehåller script", "LabelSettingsEpubsAllowScriptedContent": "Tillåt e-böcker i epubs-format som innehåller script",
"LabelSettingsEpubsAllowScriptedContentHelp": "Tillåt att epub-filer får använda script.<br>Det rekommenderas att denna inställning är<br>avstängd när du inte litar på källan för epub-filerna.", "LabelSettingsEpubsAllowScriptedContentHelp": "Tillåt att epub-filer får använda script.<br>Det rekommenderas att denna inställning är<br>avstängd när du inte litar på källan för epub-filerna.",
"LabelSettingsExperimentalFeatures": "Experimentella funktioner", "LabelSettingsExperimentalFeatures": "Experimentella funktioner",
"LabelSettingsExperimentalFeaturesHelp": "Funktioner under utveckling som behöver din feedback och hjälp med testning. Klicka för att öppna diskussionen på GitHub.", "LabelSettingsExperimentalFeaturesHelp": "Funktioner under utveckling som behöver din feedback och hjälp med testning. Klicka för att öppna diskussionen på GitHub.",
"LabelSettingsFindCovers": "Hitta ett bokomslag", "LabelSettingsFindCovers": "Hitta ett omslag",
"LabelSettingsFindCoversHelp": "Om din bok inte har ett bokomslag inbäddat i filen eller<br>en fil med bokomslaget i mappen kommer<br>skannern att försöka hitta ett omslag.<br>OBS: Detta kommer att förlänga inläsningstiden", "LabelSettingsFindCoversHelp": "Om din bok INTE har ett omslag inbäddat i filen eller en fil med omslaget i mappen kommer skannern att försöka hitta ett omslag.<br>OBS: Detta kommer att förlänga inläsningstiden",
"LabelSettingsHideSingleBookSeries": "Dölj serier som endast innehåller en bok", "LabelSettingsHideSingleBookSeries": "Dölj serier som endast innehåller en bok",
"LabelSettingsHideSingleBookSeriesHelp": "Serier som endast har en bok kommer att<br>döljas från sidan 'Serier' och hyllorna på startsidan.", "LabelSettingsHideSingleBookSeriesHelp": "Serier som endast har en bok kommer att<br>döljas från sidan 'Serier' och hyllorna på startsidan.",
"LabelSettingsHomePageBookshelfView": "Använd vy liknande en bokhylla på startsidan", "LabelSettingsHomePageBookshelfView": "Använd vy liknande en bokhylla på startsidan",
@@ -509,17 +535,17 @@
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Hoppa över tidigare böcker i en serie", "LabelSettingsOnlyShowLaterBooksInContinueSeries": "Hoppa över tidigare böcker i en serie",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Sektionen 'Fortsätt med serien' på startsidan visar \"nästa bok\" i serien,<br>där åtminstone en bok avslutats, och ingen bok i serien har påbörjats.<br>Om detta alternativ aktiveras kommer efterföljande bok till den<br>avslutade att föreslås - istället för den första ej avslutade boken i serien.", "LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Sektionen 'Fortsätt med serien' på startsidan visar \"nästa bok\" i serien,<br>där åtminstone en bok avslutats, och ingen bok i serien har påbörjats.<br>Om detta alternativ aktiveras kommer efterföljande bok till den<br>avslutade att föreslås - istället för den första ej avslutade boken i serien.",
"LabelSettingsParseSubtitles": "Hämta undertitel från bokens mapp", "LabelSettingsParseSubtitles": "Hämta undertitel från bokens mapp",
"LabelSettingsParseSubtitlesHelp": "Hämtar undertiteln från namnet på mappen där boken lagras.<br>Undertiteln måste vara åtskilda med ett bindestreck ' - '.<br>En mapp med namnet 'Boktitel - Bokens undertitel'<br> får undertiteln \"Bokens undertitel\"", "LabelSettingsParseSubtitlesHelp": "Hämtar undertiteln från namnet<br> på mappen där boken lagras.<br>Undertiteln måste vara åtskilda med ett bindestreck ' - '.<br>En mapp med namnet 'Boktitel - Bokens undertitel'<br> får undertiteln \"Bokens undertitel\"",
"LabelSettingsPreferMatchedMetadata": "Prioritera matchad metadata", "LabelSettingsPreferMatchedMetadata": "Prioritera matchad metadata",
"LabelSettingsPreferMatchedMetadataHelp": "Matchad data kommer att åsidosätta objektdetaljer vid snabbmatchning. Som standard kommer snabbmatchning endast att fylla i saknade detaljer.", "LabelSettingsPreferMatchedMetadataHelp": "Matchad data kommer att ersätta befintliga uppgifter vid en snabbmatchning. Som standard kommer en snabbmatchning endast att fylla i saknade detaljer.",
"LabelSettingsSkipMatchingBooksWithASIN": "Hoppa över matchande böcker som har en ASIN-kod", "LabelSettingsSkipMatchingBooksWithASIN": "Hoppa över matchande böcker som har en ASIN-kod",
"LabelSettingsSkipMatchingBooksWithISBN": "Hoppa över matchande böcker som har en ISBN-kod", "LabelSettingsSkipMatchingBooksWithISBN": "Hoppa över matchande böcker som har en ISBN-kod",
"LabelSettingsSortingIgnorePrefixes": "Ignorera prefix vid sortering", "LabelSettingsSortingIgnorePrefixes": "Ignorera prefix vid sortering",
"LabelSettingsSortingIgnorePrefixesHelp": "För prefix som t.ex. \"the\" kommer boktiteln \"The Book Title\" att sorteras som \"Book Title, The\"", "LabelSettingsSortingIgnorePrefixesHelp": "För prefix som t.ex. \"the\" kommer boktiteln \"The Book Title\" att sorteras som \"Book Title, The\"",
"LabelSettingsSquareBookCovers": "Använd kvadratiska bokomslag", "LabelSettingsSquareBookCovers": "Använd kvadratiska omslag",
"LabelSettingsSquareBookCoversHelp": "Föredrar att använda kvadratiska bokomslag<br>före standardformatet 1.6:1", "LabelSettingsSquareBookCoversHelp": "Föredrar att använda kvadratiska omslag<br>före standardformatet 1.6:1",
"LabelSettingsStoreCoversWithItem": "Lagra bokomslag med objektet", "LabelSettingsStoreCoversWithItem": "Lagra omslag med objektet",
"LabelSettingsStoreCoversWithItemHelp": "Som standard lagras bokomslag i mappen '/metadata/items'.<br>Genom att aktivera detta alternativ kommer<br>omslagen att lagra i din biblioteksmapp.<br>Endast en fil med namnet 'cover' kommer att behållas", "LabelSettingsStoreCoversWithItemHelp": "Som standard lagras omslag i mappen '/metadata/items'.<br>Genom att aktivera detta alternativ kommer<br>omslagen att lagra i din biblioteksmapp.<br>Endast en fil med namnet 'cover' kommer att behållas",
"LabelSettingsStoreMetadataWithItem": "Lagra metadata med objektet", "LabelSettingsStoreMetadataWithItem": "Lagra metadata med objektet",
"LabelSettingsStoreMetadataWithItemHelp": "Som standard lagras metadatafiler i mappen '/metadata/items'. Genom att aktivera detta alternativ kommer metadatafilerna att lagra i din biblioteksmapp", "LabelSettingsStoreMetadataWithItemHelp": "Som standard lagras metadatafiler i mappen '/metadata/items'. Genom att aktivera detta alternativ kommer metadatafilerna att lagra i din biblioteksmapp",
"LabelSettingsTimeFormat": "Tidsformat", "LabelSettingsTimeFormat": "Tidsformat",
@@ -528,10 +554,10 @@
"LabelShowSeconds": "Visa sekunder", "LabelShowSeconds": "Visa sekunder",
"LabelShowSubtitles": "Visa underrubriker", "LabelShowSubtitles": "Visa underrubriker",
"LabelSize": "Storlek", "LabelSize": "Storlek",
"LabelSleepTimer": "Timer för sova", "LabelSleepTimer": "Sovtimer",
"LabelSortAscending": "Stigande", "LabelSortAscending": "Stigande",
"LabelSortDescending": "Fallande", "LabelSortDescending": "Fallande",
"LabelStart": "Starta", "LabelStart": "Start",
"LabelStartTime": "Starttid", "LabelStartTime": "Starttid",
"LabelStarted": "Startad", "LabelStarted": "Startad",
"LabelStartedAt": "Startades", "LabelStartedAt": "Startades",
@@ -556,8 +582,9 @@
"LabelTags": "Taggar", "LabelTags": "Taggar",
"LabelTagsAccessibleToUser": "Taggar användaren har tillgång till", "LabelTagsAccessibleToUser": "Taggar användaren har tillgång till",
"LabelTagsNotAccessibleToUser": "Taggar inte tillgängliga för användaren", "LabelTagsNotAccessibleToUser": "Taggar inte tillgängliga för användaren",
"LabelTasks": "Körande uppgifter", "LabelTasks": "Pågående aktivitet",
"LabelTextEditorBulletedList": "Punktlista", "LabelTextEditorBulletedList": "Punktlista",
"LabelTextEditorLink": "Länk",
"LabelTextEditorNumberedList": "Numrerad lista", "LabelTextEditorNumberedList": "Numrerad lista",
"LabelTheme": "Utseende", "LabelTheme": "Utseende",
"LabelThemeDark": "Mörkt", "LabelThemeDark": "Mörkt",
@@ -573,9 +600,9 @@
"LabelTimeRemaining": "{0} återstår", "LabelTimeRemaining": "{0} återstår",
"LabelTimeToShift": "Tid att skifta i sekunder", "LabelTimeToShift": "Tid att skifta i sekunder",
"LabelTitle": "Titel", "LabelTitle": "Titel",
"LabelToolsEmbedMetadata": "Bädda in metadata", "LabelToolsEmbedMetadata": "Infoga metadata",
"LabelToolsEmbedMetadataDescription": "Bädda in metadata i ljudfiler, inklusive omslagsbild och kapitel.", "LabelToolsEmbedMetadataDescription": "Bädda in metadata i ljudfiler, inklusive omslagsbild och kapitel.",
"LabelToolsMakeM4b": "Skapa M4B ljudbok", "LabelToolsMakeM4b": "Skapa ljudboksfil i M4B-format",
"LabelToolsMakeM4bDescription": "Skapa en ljudboksfil i M4B-format med inbäddad metadata, omslagsbild och kapitel.", "LabelToolsMakeM4bDescription": "Skapa en ljudboksfil i M4B-format med inbäddad metadata, omslagsbild och kapitel.",
"LabelToolsSplitM4b": "Dela upp M4B-fil i MP3-filer", "LabelToolsSplitM4b": "Dela upp M4B-fil i MP3-filer",
"LabelToolsSplitM4bDescription": "Skapa MP3-filer från en M4B-fil uppdelad i kapitel med inbäddad metadata, omslagsbild och kapitel.", "LabelToolsSplitM4bDescription": "Skapa MP3-filer från en M4B-fil uppdelad i kapitel med inbäddad metadata, omslagsbild och kapitel.",
@@ -592,8 +619,9 @@
"LabelUnabridged": "Oavkortad", "LabelUnabridged": "Oavkortad",
"LabelUndo": "Ångra", "LabelUndo": "Ångra",
"LabelUnknown": "Okänd", "LabelUnknown": "Okänd",
"LabelUpdateCover": "Uppdatera bokomslag", "LabelUnknownPublishDate": "Okänt publiceringsdatum",
"LabelUpdateCoverHelp": "Tillåt att befintliga bokomslag för de valda böckerna ersätts när en matchning hittas", "LabelUpdateCover": "Uppdatera omslag",
"LabelUpdateCoverHelp": "Tillåt att befintliga omslag för de valda böckerna ersätts när en matchning hittas",
"LabelUpdateDetails": "Uppdatera detaljer", "LabelUpdateDetails": "Uppdatera detaljer",
"LabelUpdateDetailsHelp": "Tillåt att befintliga detaljer för de valda böckerna ersätts när en matchning hittas", "LabelUpdateDetailsHelp": "Tillåt att befintliga detaljer för de valda böckerna ersätts när en matchning hittas",
"LabelUpdatedAt": "Uppdaterades", "LabelUpdatedAt": "Uppdaterades",
@@ -625,12 +653,15 @@
"LabelYourProgress": "Framsteg", "LabelYourProgress": "Framsteg",
"MessageAddToPlayerQueue": "Lägg till i spellistan", "MessageAddToPlayerQueue": "Lägg till i spellistan",
"MessageAppriseDescription": "För att använda den här funktionen behöver du ha en instans av <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> igång eller en API som hanterar dessa begäranden. <br />Apprise API-urlen bör vara hela URL-sökvägen för att skicka meddelandet, t.ex., om din API-instans är tillgänglig på <code>http://192.168.1.1:8337</code>, bör du ange <code>http://192.168.1.1:8337/notify</code>.", "MessageAppriseDescription": "För att använda den här funktionen behöver du ha en instans av <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> igång eller en API som hanterar dessa begäranden. <br />Apprise API-urlen bör vara hela URL-sökvägen för att skicka meddelandet, t.ex., om din API-instans är tillgänglig på <code>http://192.168.1.1:8337</code>, bör du ange <code>http://192.168.1.1:8337/notify</code>.",
"MessageBackupsDescription": "Säkerhetskopior inkluderar användare, användarnas framsteg, biblioteksobjekt, serverinställningar<br>och bilder lagrade i <code>/metadata/items</code> & <code>/metadata/authors</code>.<br>De inkluderar <strong>INTE</strong> några filer lagrade i dina biblioteksmappar.", "MessageBackupsDescription": "Säkerhetskopior inkluderar användare, användarnas framsteg, biblioteksobjekt,<br>serverinställningar och bilder lagrade i <code>/metadata/items</code> & <code>/metadata/authors</code>.<br>De inkluderar <strong>INTE</strong> några filer lagrade i dina biblioteksmappar.",
"MessageBackupsLocationEditNote": "OBS: När du ändrar plats för säkerhetskopiorna så flyttas INTE gamla säkerhetskopior dit", "MessageBackupsLocationEditNote": "OBS: När du ändrar plats för säkerhetskopiorna så flyttas INTE gamla säkerhetskopior dit.",
"MessageBackupsLocationNoEditNote": "OBS: Platsen där säkerhetskopiorna lagras bestäms av en central inställning och kan inte ändras här.", "MessageBackupsLocationNoEditNote": "OBS: Platsen där säkerhetskopiorna lagras bestäms av en central inställning och kan inte ändras här.",
"MessageBackupsLocationPathEmpty": "Uppgiften om platsen för lagring av säkerhetskopior kan inte lämnas tom", "MessageBackupsLocationPathEmpty": "Uppgiften om platsen för lagring av säkerhetskopior kan inte lämnas tom",
"MessageBatchEditPopulateMapDetailsAllHelp": "Adderar information från alla objekt nedan i de fält som aktiverats. Om fälten innehåller olika uppgifter kommer informationen att slås samman.",
"MessageBatchEditPopulateMapDetailsItemHelp": "Addera information från detta objekt i aktiva fält ovan",
"MessageBatchQuickMatchDescription": "Quick Match kommer försöka lägga till saknade omslag och metadata för de valda föremålen. Aktivera alternativen nedan för att tillåta Quick Match att överskriva befintliga omslag och/eller metadata.", "MessageBatchQuickMatchDescription": "Quick Match kommer försöka lägga till saknade omslag och metadata för de valda föremålen. Aktivera alternativen nedan för att tillåta Quick Match att överskriva befintliga omslag och/eller metadata.",
"MessageBookshelfNoCollections": "Du har ännu inte skapat några samlingar", "MessageBookshelfNoCollections": "Du har ännu inte skapat några samlingar",
"MessageBookshelfNoCollectionsHelp": "Samlingar är privata. Endast den användare som skapat en samling kan se den.",
"MessageBookshelfNoRSSFeeds": "Inga RSS-flöden är öppna", "MessageBookshelfNoRSSFeeds": "Inga RSS-flöden är öppna",
"MessageBookshelfNoResultsForFilter": "Inga resultat för filter \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Inga resultat för filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Sökningen gav inget resultat", "MessageBookshelfNoResultsForQuery": "Sökningen gav inget resultat",
@@ -646,11 +677,13 @@
"MessageConfirmDeleteDevice": "Är du säkert på att du vill radera enheten för e-böcker \"{0}\"?", "MessageConfirmDeleteDevice": "Är du säkert på att du vill radera enheten för e-böcker \"{0}\"?",
"MessageConfirmDeleteFile": "Detta kommer att radera filen från ditt filsystem. Är du säker?", "MessageConfirmDeleteFile": "Detta kommer att radera filen från ditt filsystem. Är du säker?",
"MessageConfirmDeleteLibrary": "Är du säker på att du vill radera biblioteket '{0}'?", "MessageConfirmDeleteLibrary": "Är du säker på att du vill radera biblioteket '{0}'?",
"MessageConfirmDeleteLibraryItem": "Detta kommer att radera biblioteksobjektet från databasen och ditt filsystem. Är du säker?", "MessageConfirmDeleteLibraryItem": "Detta kommer att radera objektet från databasen och ditt filsystem. Är du säker?",
"MessageConfirmDeleteLibraryItems": "Detta kommer att radera {0} biblioteksobjekt från databasen och ditt filsystem. Är du säker?", "MessageConfirmDeleteLibraryItems": "Detta kommer att radera {0} biblioteksobjekt från databasen och ditt filsystem. Är du säker?",
"MessageConfirmDeleteMetadataProvider": "Är du säker på att du vill radera din egen källa för metadata \"{0}\"?", "MessageConfirmDeleteMetadataProvider": "Är du säker på att du vill radera din egen källa för metadata \"{0}\"?",
"MessageConfirmDeleteNotification": "Är du säker på att du vill radera detta meddelande?",
"MessageConfirmDeleteSession": "Är du säker på att du vill radera detta lyssningstillfälle?", "MessageConfirmDeleteSession": "Är du säker på att du vill radera detta lyssningstillfälle?",
"MessageConfirmForceReScan": "Är du säker på att du vill tvinga omgenomsökning?", "MessageConfirmEmbedMetadataInAudioFiles": "Är du säker på att du vill infoga metadata i {0} ljudfiler?",
"MessageConfirmForceReScan": "Är du säker på att du vill starta en ny skanning?",
"MessageConfirmMarkAllEpisodesFinished": "Är du säker på att du vill markera alla avsnitt som avslutade?", "MessageConfirmMarkAllEpisodesFinished": "Är du säker på att du vill markera alla avsnitt som avslutade?",
"MessageConfirmMarkAllEpisodesNotFinished": "Är du säker på att du vill markera alla avsnitt som ej avslutade?", "MessageConfirmMarkAllEpisodesNotFinished": "Är du säker på att du vill markera alla avsnitt som ej avslutade?",
"MessageConfirmMarkItemFinished": "Är du säker på att du vill markera \"{0}\" som avslutad?", "MessageConfirmMarkItemFinished": "Är du säker på att du vill markera \"{0}\" som avslutad?",
@@ -660,14 +693,14 @@
"MessageConfirmPurgeCache": "När du rensar cashen kommer katalogen <code>/metadata/cache</code> att raderas. <br /><br />Är du säker på att du vill radera katalogen?", "MessageConfirmPurgeCache": "När du rensar cashen kommer katalogen <code>/metadata/cache</code> att raderas. <br /><br />Är du säker på att du vill radera katalogen?",
"MessageConfirmPurgeItemsCache": "När du rensar cashen för föremål kommer katalogen <code>/metadata/cache/items</code> att raderas. <br /><br />Är du säker på att du vill radera katalogen?", "MessageConfirmPurgeItemsCache": "När du rensar cashen för föremål kommer katalogen <code>/metadata/cache/items</code> att raderas. <br /><br />Är du säker på att du vill radera katalogen?",
"MessageConfirmQuickEmbed": "VARNING! Quick embed kommer inte att säkerhetskopiera dina ljudfiler. Se till att du har en säkerhetskopia av dina ljudfiler. <br><br>Vill du fortsätta?", "MessageConfirmQuickEmbed": "VARNING! Quick embed kommer inte att säkerhetskopiera dina ljudfiler. Se till att du har en säkerhetskopia av dina ljudfiler. <br><br>Vill du fortsätta?",
"MessageConfirmReScanLibraryItems": "Är du säker på att du vill göra en ny genomsökning för {0} objekt?", "MessageConfirmReScanLibraryItems": "Är du säker på att du vill göra en ny skanning för {0} objekt?",
"MessageConfirmRemoveAllChapters": "Är du säker på att du vill ta bort alla kapitel?", "MessageConfirmRemoveAllChapters": "Är du säker på att du vill ta bort alla kapitel?",
"MessageConfirmRemoveAuthor": "Är du säker på att du vill ta bort författaren \"{0}\"?", "MessageConfirmRemoveAuthor": "Är du säker på att du vill ta bort författaren \"{0}\"?",
"MessageConfirmRemoveCollection": "Är du säker på att du vill ta bort samlingen \"{0}\"?", "MessageConfirmRemoveCollection": "Är du säker på att du vill ta bort samlingen \"{0}\"?",
"MessageConfirmRemoveEpisode": "Är du säker på att du vill ta bort avsnittet \"{0}\"?", "MessageConfirmRemoveEpisode": "Är du säker på att du vill radera avsnittet \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Är du säker på att du vill ta bort {0} avsnitt?", "MessageConfirmRemoveEpisodes": "Är du säker på att du vill radera {0} avsnitt?",
"MessageConfirmRemoveListeningSessions": "Är du säker på att du vill radera {0} lyssningstillfällen?", "MessageConfirmRemoveListeningSessions": "Är du säker på att du vill radera {0} lyssningstillfällen?",
"MessageConfirmRemoveMetadataFiles": "Är du säker på att du vill radera 'metadata.{0}' filerna i alla mappar i ditt bibliotek?", "MessageConfirmRemoveMetadataFiles": "Är du säker på att du vill radera filerna 'metadata.{0}' i alla mappar i ditt bibliotek?",
"MessageConfirmRemoveNarrator": "Är du säker på att du vill ta bort uppläsaren \"{0}\"?", "MessageConfirmRemoveNarrator": "Är du säker på att du vill ta bort uppläsaren \"{0}\"?",
"MessageConfirmRemovePlaylist": "Är du säker på att du vill ta bort din spellista \"{0}\"?", "MessageConfirmRemovePlaylist": "Är du säker på att du vill ta bort din spellista \"{0}\"?",
"MessageConfirmRenameGenre": "Är du säker på att du vill byta namn på kategorin \"{0}\" till \"{1}\" för alla objekt?", "MessageConfirmRenameGenre": "Är du säker på att du vill byta namn på kategorin \"{0}\" till \"{1}\" för alla objekt?",
@@ -683,13 +716,13 @@
"MessageDragFilesIntoTrackOrder": "Dra filer till rätt spårordning", "MessageDragFilesIntoTrackOrder": "Dra filer till rätt spårordning",
"MessageEmbedFinished": "Inbäddning genomförd!", "MessageEmbedFinished": "Inbäddning genomförd!",
"MessageEpisodesQueuedForDownload": "{0} avsnitt i kö för nedladdning", "MessageEpisodesQueuedForDownload": "{0} avsnitt i kö för nedladdning",
"MessageEreaderDevices": "För att säkerställa överföring av e-böcker kan du bli tvungen<br>att addera ovanstående e-postadress som godkänd<br>avsändare för varje enhet angiven nedan.", "MessageEreaderDevices": "För att säkerställa överföring av e-böcker kan du bli tvungen<br>att addera ovanstående e-postadress som godkänd avsändare<br>för varje enhet angiven nedan.",
"MessageFeedURLWillBe": "Flödes-URL kommer att vara {0}", "MessageFeedURLWillBe": "Flödes-URL kommer att vara {0}",
"MessageFetching": "Hämtar...", "MessageFetching": "Hämtar...",
"MessageForceReScanDescription": "kommer att göra en omgångssökning av alla filer som en färsk sökning. ID3-taggar för ljudfiler, OPF-filer och textfiler kommer att sökas som nya.", "MessageForceReScanDescription": "kommer att göra en omgångssökning av alla filer som en färsk sökning. ID3-taggar för ljudfiler, OPF-filer och textfiler kommer att sökas som nya.",
"MessageImportantNotice": "Viktig meddelande!", "MessageImportantNotice": "Viktig meddelande!",
"MessageInsertChapterBelow": "Infoga kapitel nedanför", "MessageInsertChapterBelow": "Infoga kapitel nedanför",
"MessageItemsSelected": "{0} Objekt markerade", "MessageItemsSelected": "{0} objekt markerade",
"MessageItemsUpdated": "{0} Objekt uppdaterade", "MessageItemsUpdated": "{0} Objekt uppdaterade",
"MessageJoinUsOn": "Anslut dig till oss på", "MessageJoinUsOn": "Anslut dig till oss på",
"MessageLoading": "Laddar...", "MessageLoading": "Laddar...",
@@ -702,19 +735,19 @@
"MessageMarkAllEpisodesNotFinished": "Markera alla avsnitt som ej avslutade", "MessageMarkAllEpisodesNotFinished": "Markera alla avsnitt som ej avslutade",
"MessageMarkAsFinished": "Markera som avslutad", "MessageMarkAsFinished": "Markera som avslutad",
"MessageMarkAsNotFinished": "Markera som ej avslutad", "MessageMarkAsNotFinished": "Markera som ej avslutad",
"MessageMatchBooksDescription": "kommer att försöka matcha böcker i biblioteket med en bok från<br>den valda källan och fylla i uppgifter som saknas och bokomslag.<br>Inga befintliga uppgifter kommer att ersättas.", "MessageMatchBooksDescription": "kommer att försöka matcha böcker i biblioteket med en bok från<br/>den valda källan och fylla i uppgifter som saknas och omslag.<br/>Inga befintliga uppgifter kommer att ersättas.",
"MessageNoAudioTracks": "Inga ljudspår", "MessageNoAudioTracks": "Inga ljudspår har hittats",
"MessageNoAuthors": "Inga författare", "MessageNoAuthors": "Inga författare",
"MessageNoBackups": "Inga säkerhetskopior", "MessageNoBackups": "Inga säkerhetskopior",
"MessageNoBookmarks": "Inga bokmärken", "MessageNoBookmarks": "Inga bokmärken",
"MessageNoChapters": "Inga kapitel", "MessageNoChapters": "Inga kapitel",
"MessageNoCollections": "Inga samlingar", "MessageNoCollections": "Inga samlingar",
"MessageNoCoversFound": "Inga bokomslag hittades", "MessageNoCoversFound": "Inga omslag hittades",
"MessageNoDescription": "Ingen beskrivning", "MessageNoDescription": "Ingen beskrivning",
"MessageNoDevices": "Inga enheter angivna", "MessageNoDevices": "Inga enheter angivna",
"MessageNoDownloadsInProgress": "Inga nedladdningar pågår för närvarande", "MessageNoDownloadsInProgress": "Inga nedladdningar pågår för närvarande",
"MessageNoDownloadsQueued": "Inga nedladdningar i kö", "MessageNoDownloadsQueued": "Inga nedladdningar i kö",
"MessageNoEpisodeMatchesFound": "Inga matchande avsnitt hittades", "MessageNoEpisodeMatchesFound": "Inga matchande avsnitt kunde hittas",
"MessageNoEpisodes": "Inga avsnitt", "MessageNoEpisodes": "Inga avsnitt",
"MessageNoFoldersAvailable": "Inga mappar tillgängliga", "MessageNoFoldersAvailable": "Inga mappar tillgängliga",
"MessageNoGenres": "Inga kategorier", "MessageNoGenres": "Inga kategorier",
@@ -733,31 +766,52 @@
"MessageNoTasksRunning": "Inga pågående uppgifter", "MessageNoTasksRunning": "Inga pågående uppgifter",
"MessageNoUpdatesWereNecessary": "Inga uppdateringar var nödvändiga", "MessageNoUpdatesWereNecessary": "Inga uppdateringar var nödvändiga",
"MessageNoUserPlaylists": "Du har inga spellistor", "MessageNoUserPlaylists": "Du har inga spellistor",
"MessageNoUserPlaylistsHelp": "Spellistor är privata. Endast den användare som skapat listan kan se den.",
"MessageNotYetImplemented": "Ännu inte implementerad", "MessageNotYetImplemented": "Ännu inte implementerad",
"MessageOr": "eller", "MessageOr": "eller",
"MessagePauseChapter": "Pausa kapiteluppspelning", "MessagePauseChapter": "Pausa kapiteluppspelning",
"MessagePlayChapter": "Lyssna på kapitlets början", "MessagePlayChapter": "Lyssna på kapitlets början",
"MessagePlaylistCreateFromCollection": "Skapa spellista från samling", "MessagePlaylistCreateFromCollection": "Skapa en spellista från samlingen",
"MessagePleaseWait": "Vänta ett ögonblick...", "MessagePleaseWait": "Vänta ett ögonblick...",
"MessagePodcastHasNoRSSFeedForMatching": "Podcasten har ingen RSS-flödes-URL att använda för matchning", "MessagePodcastHasNoRSSFeedForMatching": "Podcasten har ingen RSS-flödes-URL att använda för matchning",
"MessagePodcastSearchField": "Skriv sökfrågan eller URL-adressen för RSS-flödet",
"MessageQuickMatchAllEpisodes": "Snabbmatchning av alla avsnitt",
"MessageQuickMatchDescription": "Adderar uppgifter som saknas samt en omslagsbild från<br>första träffen i resultatet vid sökningen från '{0}'.<br>Skriver inte över befintliga uppgifter om inte<br>inställningen 'Prioritera matchad metadata' är aktiverad.", "MessageQuickMatchDescription": "Adderar uppgifter som saknas samt en omslagsbild från<br>första träffen i resultatet vid sökningen från '{0}'.<br>Skriver inte över befintliga uppgifter om inte<br>inställningen 'Prioritera matchad metadata' är aktiverad.",
"MessageRemoveChapter": "Ta bort kapitel", "MessageRemoveChapter": "Ta bort kapitel",
"MessageRemoveEpisodes": "Ta bort {0} avsnitt", "MessageRemoveEpisodes": "Radera {0} avsnitt",
"MessageRemoveFromPlayerQueue": "Ta bort från spellistan", "MessageRemoveFromPlayerQueue": "Ta bort från spellistan",
"MessageRemoveUserWarning": "Är du säker på att du vill radera användaren \"{0}\"?", "MessageRemoveUserWarning": "Är du säker på att du vill radera användaren \"{0}\"?",
"MessageReportBugsAndContribute": "Rapportera buggar, begär funktioner och bidra på", "MessageReportBugsAndContribute": "Rapportera buggar, begär funktioner och bidra på",
"MessageResetChaptersConfirm": "Är du säker på att du vill återställa alla kapitel och ångra de ändringarna du gjort?", "MessageResetChaptersConfirm": "Är du säker på att du vill återställa alla kapitel och ångra de ändringarna du gjort?",
"MessageRestoreBackupConfirm": "Är du säker på att du vill läsa in säkerhetskopian som skapades den", "MessageRestoreBackupConfirm": "Är du säker på att du vill läsa in säkerhetskopian som skapades den",
"MessageRestoreBackupWarning": "Att återställa en säkerhetskopia kommer att skriva över hela databasen som finns i /config och omslagsbilder i /metadata/items & /metadata/authors.<br /><br />Säkerhetskopior ändrar inte några filer i dina biblioteksmappar. Om du har aktiverat serverinställningar för att lagra omslagskonst och metadata i dina biblioteksmappar säkerhetskopieras eller skrivs de inte över.<br /><br />Alla klienter som använder din server kommer att uppdateras automatiskt.", "MessageRestoreBackupWarning": "Att återställa en säkerhetskopia kommer att skriva över hela databasen som finns i /config och omslagsbilder i /metadata/items & /metadata/authors.<br /><br />Säkerhetskopior ändrar inte några filer i dina biblioteksmappar. Om du har aktiverat serverinställningar för att lagra omslagskonst och metadata i dina biblioteksmappar säkerhetskopieras eller skrivs de inte över.<br /><br />Alla klienter som använder din server kommer att uppdateras automatiskt.",
"MessageScheduleLibraryScanNote": "För de flesta användare rekommenderas att denna funktion ej aktiveras. Istället bör funktionen 'Watcher' vara aktiverad. Watcher kommer då automatiskt identifiera förändringar i biblioteket. För vissa filsystem (som t.ex. NFS) fungerar inte Watcher. Då kan schemalagda skanningar av biblioteken användas istället.",
"MessageSearchResultsFor": "Sökresultat för", "MessageSearchResultsFor": "Sökresultat för",
"MessageSelected": "{0} valda", "MessageSelected": "{0} valda",
"MessageServerCouldNotBeReached": "Servern kunde inte nås", "MessageServerCouldNotBeReached": "Servern kunde inte nås",
"MessageSetChaptersFromTracksDescription": "Ställ in kapitel med varje ljudfil som ett kapitel och kapitelrubrik som ljudfilens namn", "MessageSetChaptersFromTracksDescription": "Ställ in kapitel med varje ljudfil som ett kapitel och kapitelrubrik som ljudfilens namn",
"MessageStartPlaybackAtTime": "Starta uppspelning av \"{0}\" vid tidpunkt {1}?", "MessageStartPlaybackAtTime": "Starta uppspelning av \"{0}\" vid tidpunkt {1}?",
"MessageTaskCanceledByUser": "Uppgiften avslutades av användaren", "MessageTaskCanceledByUser": "Uppgiften avslutades av användaren",
"MessageTaskDownloadingEpisodeDescription": "Laddar ner avsnitt \"{0}\"",
"MessageTaskEmbeddingMetadata": "Infogar metadata",
"MessageTaskEmbeddingMetadataDescription": "Infogar metadata i ljudboken \"{0}\"",
"MessageTaskEncodingM4bDescription": "Omkodning av ljudbok \"{0}\" till en M4B-fil",
"MessageTaskFailed": "Misslyckades", "MessageTaskFailed": "Misslyckades",
"MessageTaskFailedToCreateCacheDirectory": "Misslyckades med att skapa bibliotek för cachen", "MessageTaskFailedToCreateCacheDirectory": "Misslyckades med att skapa bibliotek för cachen",
"MessageTaskFailedToEmbedMetadataInFile": "Misslyckades med att infoga metadata i \"{0}\"",
"MessageTaskFailedToMergeAudioFiles": "Misslyckades med att sammanfoga ljudfilerna",
"MessageTaskFailedToMoveM4bFile": "Misslyckades med att flytta M4B-filen",
"MessageTaskFailedToWriteMetadataFile": "Misslyckades med att skapa filen med metadata",
"MessageTaskMatchingBooksInLibrary": "Matchar böcker i biblioteket \"{0}\"", "MessageTaskMatchingBooksInLibrary": "Matchar böcker i biblioteket \"{0}\"",
"MessageTaskOpmlImportFinished": "Adderade {0} podcasts",
"MessageTaskOpmlParseFailed": "Misslyckades att tolka OPML-filen",
"MessageTaskOpmlParseFastFail": "Felaktig OPML-fil. Ingen <opml> tag eller <outline> tag finns i filen",
"MessageTaskOpmlParseNoneFound": "Inget flöde finns angivet i OPML-filen",
"MessageTaskScanItemsAdded": "{0} adderades",
"MessageTaskScanItemsMissing": "{0} saknades",
"MessageTaskScanItemsUpdated": "{0} uppdaterades",
"MessageTaskScanNoChangesNeeded": "Inget adderades eller uppdaterades",
"MessageTaskScanningLibrary": "Biblioteket \"{0}\" har skannats",
"MessageThinking": "Tänker...", "MessageThinking": "Tänker...",
"MessageUploaderItemFailed": "Misslyckades med att ladda upp", "MessageUploaderItemFailed": "Misslyckades med att ladda upp",
"MessageUploaderItemSuccess": "Uppladdning lyckades!", "MessageUploaderItemSuccess": "Uppladdning lyckades!",
@@ -767,15 +821,15 @@
"MessageXLibraryIsEmpty": "Biblioteket {0} är tomt!", "MessageXLibraryIsEmpty": "Biblioteket {0} är tomt!",
"MessageYourAudiobookDurationIsLonger": "Varaktigheten på din ljudbok är längre än den hittade varaktigheten", "MessageYourAudiobookDurationIsLonger": "Varaktigheten på din ljudbok är längre än den hittade varaktigheten",
"MessageYourAudiobookDurationIsShorter": "Varaktigheten på din ljudbok är kortare än den hittade varaktigheten", "MessageYourAudiobookDurationIsShorter": "Varaktigheten på din ljudbok är kortare än den hittade varaktigheten",
"NoteChangeRootPassword": "Rotanvändaren är den enda användaren som kan ha ett tomt lösenord", "NoteChangeRootPassword": "Användaren 'root' är den enda användaren som kan vara utan lösenord",
"NoteChapterEditorTimes": "OBS: Starttiden för första kapitlet måste vara 0:00 och starttiden för det sista kapitlet får inte överstiga ljudbokens totala varaktighet.", "NoteChapterEditorTimes": "OBS: Starttiden för första kapitlet måste vara 0:00 och starttiden för det sista kapitlet får inte överstiga ljudbokens totala varaktighet.",
"NoteFolderPicker": "Obs: Mappar som redan är kartlagda kommer inte att visas", "NoteFolderPicker": "OBS: Mappar som redan är kopplade kommer inte att visas",
"NoteRSSFeedPodcastAppsHttps": "Varning: De flesta podcastappar kräver att RSS-flödets URL används med HTTPS", "NoteRSSFeedPodcastAppsHttps": "VARNING: De flesta applikationer för podcasts kräver att URL:en för RSS-flödet använder HTTPS",
"NoteRSSFeedPodcastAppsPubDate": "Varning: 1 eller flera av dina avsnitt har inte ett publiceringsdatum. Vissa podcastappar kräver detta.", "NoteRSSFeedPodcastAppsPubDate": "VARNING: Ett eller flera av dina avsnitt saknar publiceringsdatum. Vissa applikationer för podcasts kräver detta.",
"NoteUploaderFoldersWithMediaFiles": "Mappar med flera mediefiler hanteras som separata objekt i biblioteket.", "NoteUploaderFoldersWithMediaFiles": "Mappar som innehåller mediefiler hanteras som separata objekt i biblioteket.",
"NoteUploaderOnlyAudioFiles": "<br>Om du bara laddar upp ljudfiler kommer varje ljudfil att hanteras som en separat ljudbok.", "NoteUploaderOnlyAudioFiles": "Om du bara laddar upp ljudfiler kommer varje ljudfil att hanteras som en separat ljudbok.",
"NoteUploaderUnsupportedFiles": "Oaccepterade filer ignoreras. När du väljer eller släpper en mapp ignoreras andra filer som inte finns i ett objektmapp.", "NoteUploaderUnsupportedFiles": "Oaccepterade filer ignoreras. När du väljer eller släpper en mapp ignoreras andra filer som inte finns i ett objektmapp.",
"PlaceholderNewCollection": "Nytt samlingsnamn", "PlaceholderNewCollection": "Nytt namn på samlingen",
"PlaceholderNewFolderPath": "Nytt sökväg till mappen", "PlaceholderNewFolderPath": "Nytt sökväg till mappen",
"PlaceholderNewPlaylist": "Nytt namn på spellistan", "PlaceholderNewPlaylist": "Nytt namn på spellistan",
"PlaceholderSearch": "Sök...", "PlaceholderSearch": "Sök...",
@@ -796,7 +850,7 @@
"StatsTopMonth": "Bästa månad", "StatsTopMonth": "Bästa månad",
"StatsTopNarrator": "Populäraste uppläsare", "StatsTopNarrator": "Populäraste uppläsare",
"StatsTopNarrators": "Populäraste uppläsarna", "StatsTopNarrators": "Populäraste uppläsarna",
"StatsTotalDuration": "Med en total varaktighet…", "StatsTotalDuration": "Med en total varaktighet av…",
"StatsYearInReview": "- SAMMANSTÄLLNING AV ÅRET", "StatsYearInReview": "- SAMMANSTÄLLNING AV ÅRET",
"ToastAccountUpdateSuccess": "Kontot har uppdaterats", "ToastAccountUpdateSuccess": "Kontot har uppdaterats",
"ToastAsinRequired": "En ASIN-kod krävs", "ToastAsinRequired": "En ASIN-kod krävs",
@@ -825,25 +879,36 @@
"ToastCachePurgeSuccess": "Rensning av cachen har genomförts", "ToastCachePurgeSuccess": "Rensning av cachen har genomförts",
"ToastChaptersHaveErrors": "Kapitlen har fel", "ToastChaptersHaveErrors": "Kapitlen har fel",
"ToastChaptersMustHaveTitles": "Kapitel måste ha titlar", "ToastChaptersMustHaveTitles": "Kapitel måste ha titlar",
"ToastChaptersRemoved": "Kapitlen har raderats",
"ToastChaptersUpdated": "Kapitlen har uppdaterats",
"ToastCollectionItemsAddFailed": "Misslyckades med att addera böcker till samlingen",
"ToastCollectionRemoveSuccess": "Samlingen har raderats", "ToastCollectionRemoveSuccess": "Samlingen har raderats",
"ToastCollectionUpdateSuccess": "Samlingen har uppdaterats", "ToastCollectionUpdateSuccess": "Samlingen har uppdaterats",
"ToastCoverUpdateFailed": "Uppdatering av bokomslag misslyckades", "ToastCoverUpdateFailed": "Uppdatering av omslag misslyckades",
"ToastDateTimeInvalidOrIncomplete": "Datum och klockslag är felaktigt eller ej komplett", "ToastDateTimeInvalidOrIncomplete": "Datum och klockslag är felaktigt eller ej komplett",
"ToastDeleteFileFailed": "Misslyckades att radera filen", "ToastDeleteFileFailed": "Misslyckades att radera filen",
"ToastDeleteFileSuccess": "Filen har raderats", "ToastDeleteFileSuccess": "Filen har raderats",
"ToastDeviceTestEmailFailed": "Misslyckades med att skicka ett testmail", "ToastDeviceTestEmailFailed": "Misslyckades med att skicka ett testmail",
"ToastDeviceTestEmailSuccess": "Ett testmail har skickats", "ToastDeviceTestEmailSuccess": "Ett testmail har skickats",
"ToastEmailSettingsUpdateSuccess": "Inställningarna av e-post har uppdaterats", "ToastEmailSettingsUpdateSuccess": "Inställningarna av e-post har uppdaterats",
"ToastEncodeCancelSucces": "Omkodningen avbruten",
"ToastEpisodeDownloadQueueClearFailed": "Misslyckades med att tömma kön",
"ToastEpisodeDownloadQueueClearSuccess": "Kö för nedladdning av avsnitt har tömts",
"ToastEpisodeUpdateSuccess": "{0} avsnitt uppdaterades",
"ToastFailedToLoadData": "Misslyckades med att ladda data", "ToastFailedToLoadData": "Misslyckades med att ladda data",
"ToastFailedToUpdate": "Misslyckades med att uppdatera",
"ToastInvalidImageUrl": "Felaktig URL-adress till omslagsbilden", "ToastInvalidImageUrl": "Felaktig URL-adress till omslagsbilden",
"ToastInvalidMaxEpisodesToDownload": "Ogiltigt maximalt antal avsnitt att ladda ner", "ToastInvalidMaxEpisodesToDownload": "Ogiltigt maximalt antal avsnitt att ladda ner",
"ToastInvalidUrl": "Felaktig URL-adress", "ToastInvalidUrl": "Felaktig URL-adress",
"ToastItemCoverUpdateSuccess": "Objektets bokomslag har uppdaterats", "ToastItemCoverUpdateSuccess": "Objektets omslag har uppdaterats",
"ToastItemDetailsUpdateSuccess": "Detaljerna om boken har uppdaterats", "ToastItemDeletedFailed": "Misslyckades med att radera objektet",
"ToastItemDeletedSuccess": "Objektet har raderats",
"ToastItemDetailsUpdateSuccess": "Informationen om objektet har uppdaterats",
"ToastItemMarkedAsFinishedFailed": "Misslyckades med att markera den som avslutad", "ToastItemMarkedAsFinishedFailed": "Misslyckades med att markera den som avslutad",
"ToastItemMarkedAsFinishedSuccess": "Den har markerat som avslutad", "ToastItemMarkedAsFinishedSuccess": "Den har markerat som avslutad",
"ToastItemMarkedAsNotFinishedFailed": "Misslyckades med att markera den som ej avslutad", "ToastItemMarkedAsNotFinishedFailed": "Misslyckades med att markera den som ej avslutad",
"ToastItemMarkedAsNotFinishedSuccess": "Den har markerats som ej avslutad", "ToastItemMarkedAsNotFinishedSuccess": "Den har markerats som ej avslutad",
"ToastItemUpdateSuccess": "Objektet har uppdaterats",
"ToastLibraryCreateFailed": "Det gick inte att skapa biblioteket", "ToastLibraryCreateFailed": "Det gick inte att skapa biblioteket",
"ToastLibraryCreateSuccess": "Biblioteket \"{0}\" har skapats", "ToastLibraryCreateSuccess": "Biblioteket \"{0}\" har skapats",
"ToastLibraryDeleteFailed": "Det gick inte att ta bort biblioteket", "ToastLibraryDeleteFailed": "Det gick inte att ta bort biblioteket",
@@ -851,28 +916,49 @@
"ToastLibraryScanFailedToStart": "Misslyckades med att starta skanningen", "ToastLibraryScanFailedToStart": "Misslyckades med att starta skanningen",
"ToastLibraryScanStarted": "Skanning av biblioteket påbörjad", "ToastLibraryScanStarted": "Skanning av biblioteket påbörjad",
"ToastLibraryUpdateSuccess": "Biblioteket \"{0}\" har uppdaterats", "ToastLibraryUpdateSuccess": "Biblioteket \"{0}\" har uppdaterats",
"ToastMatchAllAuthorsFailed": "Misslyckades med att matcha alla författare",
"ToastMetadataFilesRemovedError": "Misslyckades med att radera 'metadata.{0}' filerna", "ToastMetadataFilesRemovedError": "Misslyckades med att radera 'metadata.{0}' filerna",
"ToastMetadataFilesRemovedNoneFound": "Inga 'metadata.{0}' filer hittades i biblioteket", "ToastMetadataFilesRemovedNoneFound": "Inga 'metadata.{0}' filer hittades i biblioteket",
"ToastMetadataFilesRemovedNoneRemoved": "Inga 'metadata.{0}' filer raderades", "ToastMetadataFilesRemovedNoneRemoved": "Inga 'metadata.{0}' filer raderades",
"ToastMetadataFilesRemovedSuccess": "{0} 'metadata.{1}' raderades", "ToastMetadataFilesRemovedSuccess": "{0} 'metadata.{1}' raderades",
"ToastNameEmailRequired": "Ett namn och en e-postadress måste anges", "ToastNameEmailRequired": "Ett namn och en e-postadress måste anges",
"ToastNameRequired": "Ett namn måste anges", "ToastNameRequired": "Ett namn måste anges",
"ToastNewEpisodesFound": "Hittade {0} nya avsnitt",
"ToastNewUserCreatedFailed": "Misslyckades med att skapa kontot \"{0}\"", "ToastNewUserCreatedFailed": "Misslyckades med att skapa kontot \"{0}\"",
"ToastNewUserCreatedSuccess": "Ett nytt konto har skapats", "ToastNewUserCreatedSuccess": "Ett nytt konto har skapats",
"ToastNewUserLibraryError": "Minst ett bibliotek måste anges",
"ToastNewUserPasswordError": "Ett lösenord måste anges. Endast användaren 'root' kan vara utan lösenord.",
"ToastNewUserTagError": "Minst en tagg måste läggas till",
"ToastNewUserUsernameError": "Ange ett användarnamn",
"ToastNoNewEpisodesFound": "Inga nya avsnitt kunde hittas",
"ToastNoUpdatesNecessary": "Inga uppdateringar var nödvändiga", "ToastNoUpdatesNecessary": "Inga uppdateringar var nödvändiga",
"ToastNotificationCreateFailed": "Misslyckades med att skapa meddelandet",
"ToastNotificationDeleteFailed": "Misslyckades med att radera meddelandet",
"ToastNotificationUpdateSuccess": "Meddelandet har uppdaterats",
"ToastPlaylistCreateFailed": "Det gick inte att skapa spellistan", "ToastPlaylistCreateFailed": "Det gick inte att skapa spellistan",
"ToastPlaylistCreateSuccess": "Spellistan skapad", "ToastPlaylistCreateSuccess": "Spellistan skapad",
"ToastPlaylistRemoveSuccess": "Spellistan har tagits bort", "ToastPlaylistRemoveSuccess": "Spellistan har tagits bort",
"ToastPlaylistUpdateSuccess": "Spellistan uppdaterad", "ToastPlaylistUpdateSuccess": "Spellistan har uppdaterats",
"ToastPodcastCreateFailed": "Misslyckades med att skapa podcasten", "ToastPodcastCreateFailed": "Misslyckades med att skapa podcasten",
"ToastPodcastCreateSuccess": "Podcasten skapad framgångsrikt", "ToastPodcastCreateSuccess": "Podcasten skapades framgångsrikt",
"ToastPodcastNoEpisodesInFeed": "Inga avsnitt finns i RSS-flödet",
"ToastProviderCreatedFailed": "Misslyckades med att addera en källa", "ToastProviderCreatedFailed": "Misslyckades med att addera en källa",
"ToastProviderCreatedSuccess": "En ny källa har adderats", "ToastProviderCreatedSuccess": "En ny källa har adderats",
"ToastProviderNameAndUrlRequired": "Ett namn och en URL-adress krävs",
"ToastProviderRemoveSuccess": "Källan har tagits bort", "ToastProviderRemoveSuccess": "Källan har tagits bort",
"ToastRSSFeedCloseFailed": "Misslyckades med att stänga RSS-flödet", "ToastRSSFeedCloseFailed": "Misslyckades med att stänga RSS-flödet",
"ToastRSSFeedCloseSuccess": "RSS-flödet stängt", "ToastRSSFeedCloseSuccess": "RSS-flödet stängt",
"ToastRemoveFailed": "Misslyckades med att radera",
"ToastRemoveItemFromCollectionFailed": "Misslyckades med att ta bort objektet från samlingen", "ToastRemoveItemFromCollectionFailed": "Misslyckades med att ta bort objektet från samlingen",
"ToastRemoveItemFromCollectionSuccess": "Objektet borttaget från samlingen", "ToastRemoveItemFromCollectionSuccess": "Objektet borttaget från samlingen",
"ToastRemoveItemsWithIssuesFailed": "Misslyckades med att radera objekt med problem",
"ToastRemoveItemsWithIssuesSuccess": "Raderade objekt med problem",
"ToastRenameFailed": "Misslyckades med att ändra namn",
"ToastRescanFailed": "Skanningen misslyckades för {0}",
"ToastRescanRemoved": "Skanningen har genomförts - objektet har raderats",
"ToastRescanUpToDate": "Skanningen har genomförts - objektet behövde inte uppdateras",
"ToastRescanUpdated": "Skanningen har genomförts - objektet har uppdaterats",
"ToastScanFailed": "Misslyckades med att skanna biblioteket",
"ToastSelectAtLeastOneUser": "Åtminstone en användare måste väljas", "ToastSelectAtLeastOneUser": "Åtminstone en användare måste väljas",
"ToastSendEbookToDeviceFailed": "Misslyckades med att skicka e-boken till enheten", "ToastSendEbookToDeviceFailed": "Misslyckades med att skicka e-boken till enheten",
"ToastSendEbookToDeviceSuccess": "E-boken skickad till enheten \"{0}\"", "ToastSendEbookToDeviceSuccess": "E-boken skickad till enheten \"{0}\"",
@@ -893,5 +979,6 @@
"ToastUserDeleteSuccess": "Användaren borttagen", "ToastUserDeleteSuccess": "Användaren borttagen",
"ToastUserPasswordChangeSuccess": "Lösenordet har ändrats", "ToastUserPasswordChangeSuccess": "Lösenordet har ändrats",
"ToastUserPasswordMismatch": "Lösenorden är inte identiska", "ToastUserPasswordMismatch": "Lösenorden är inte identiska",
"ToastUserPasswordMustChange": "Det nya lösenordet kan inte vara samma som det gamla" "ToastUserPasswordMustChange": "Det nya lösenordet kan inte vara samma som det gamla",
"ToastUserRootRequireName": "Ett användarnamn för 'root' måste anges"
} }
+1
View File
@@ -0,0 +1 @@
{}
+8 -1
View File
@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Зберегти глави", "ButtonApplyChapters": "Зберегти глави",
"ButtonAuthors": "Автори", "ButtonAuthors": "Автори",
"ButtonBack": "Назад", "ButtonBack": "Назад",
"ButtonBatchEditPopulateFromExisting": "Заповнити з наявних",
"ButtonBatchEditPopulateMapDetails": "Заповнити деталі карти",
"ButtonBrowseForFolder": "Огляд тек", "ButtonBrowseForFolder": "Огляд тек",
"ButtonCancel": "Скасувати", "ButtonCancel": "Скасувати",
"ButtonCancelEncode": "Скасувати кодування", "ButtonCancelEncode": "Скасувати кодування",
@@ -432,7 +434,7 @@
"LabelMetadataProvider": "Джерело метаданих", "LabelMetadataProvider": "Джерело метаданих",
"LabelMinute": "Хвилина", "LabelMinute": "Хвилина",
"LabelMinutes": "Хвилини", "LabelMinutes": "Хвилини",
"LabelMissing": "Бракує", "LabelMissing": "Відсутня",
"LabelMissingEbook": "Без електронної книги", "LabelMissingEbook": "Без електронної книги",
"LabelMissingSupplementaryEbook": "Без додаткової електронної книги", "LabelMissingSupplementaryEbook": "Без додаткової електронної книги",
"LabelMobileRedirectURIs": "Дозволені адреси перенаправлення", "LabelMobileRedirectURIs": "Дозволені адреси перенаправлення",
@@ -484,6 +486,7 @@
"LabelPersonalYearReview": "Ваші підсумки року ({0})", "LabelPersonalYearReview": "Ваші підсумки року ({0})",
"LabelPhotoPathURL": "Шлях/URL фото", "LabelPhotoPathURL": "Шлях/URL фото",
"LabelPlayMethod": "Метод відтворення", "LabelPlayMethod": "Метод відтворення",
"LabelPlaybackRateIncrementDecrement": "Величина збільшення/зменшення швидкості відтворення",
"LabelPlayerChapterNumberMarker": "{0} з {1}", "LabelPlayerChapterNumberMarker": "{0} з {1}",
"LabelPlaylists": "Списки відтворення", "LabelPlaylists": "Списки відтворення",
"LabelPodcast": "Подкаст", "LabelPodcast": "Подкаст",
@@ -704,8 +707,11 @@
"MessageBackupsLocationEditNote": "Примітка: оновлення розташування резервної копії не переносить та не змінює існуючих копій", "MessageBackupsLocationEditNote": "Примітка: оновлення розташування резервної копії не переносить та не змінює існуючих копій",
"MessageBackupsLocationNoEditNote": "Примітка: розташування резервної копії встановлюється за допомогою змінної середовища та не може бути змінене тут.", "MessageBackupsLocationNoEditNote": "Примітка: розташування резервної копії встановлюється за допомогою змінної середовища та не може бути змінене тут.",
"MessageBackupsLocationPathEmpty": "Шлях розташування резервної копії не може бути порожнім", "MessageBackupsLocationPathEmpty": "Шлях розташування резервної копії не може бути порожнім",
"MessageBatchEditPopulateMapDetailsAllHelp": "Заповнити увімкнені поля даними з усіх елементів. Поля з кількома значеннями буде об’єднано",
"MessageBatchEditPopulateMapDetailsItemHelp": "Заповніть увімкнені поля деталей карти даними з цього елемента",
"MessageBatchQuickMatchDescription": "Швидкий пошук спробує знайти відсутні обкладинки та метадані обраних елементів. Увімкніть налаштування нижче, аби дозволити заміну наявних обкладинок та/або метаданих під час швидкого пошуку.", "MessageBatchQuickMatchDescription": "Швидкий пошук спробує знайти відсутні обкладинки та метадані обраних елементів. Увімкніть налаштування нижче, аби дозволити заміну наявних обкладинок та/або метаданих під час швидкого пошуку.",
"MessageBookshelfNoCollections": "Ви не створили жодної добірки", "MessageBookshelfNoCollections": "Ви не створили жодної добірки",
"MessageBookshelfNoCollectionsHelp": "Колекції публічні. Їх можуть бачити всі користувачі, які мають доступ до бібліотеки.",
"MessageBookshelfNoRSSFeeds": "Немає відкритих RSS-каналів", "MessageBookshelfNoRSSFeeds": "Немає відкритих RSS-каналів",
"MessageBookshelfNoResultsForFilter": "Немає результатів з фільтром \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "Немає результатів з фільтром \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Немає результатів за запитом", "MessageBookshelfNoResultsForQuery": "Немає результатів за запитом",
@@ -816,6 +822,7 @@
"MessageNoTasksRunning": "Немає активних завдань", "MessageNoTasksRunning": "Немає активних завдань",
"MessageNoUpdatesWereNecessary": "Оновлень не потрібно", "MessageNoUpdatesWereNecessary": "Оновлень не потрібно",
"MessageNoUserPlaylists": "У вас немає списків відтворення", "MessageNoUserPlaylists": "У вас немає списків відтворення",
"MessageNoUserPlaylistsHelp": "Списки відтворення приватні. Лише користувач, який їх створює, може бачити їх.",
"MessageNotYetImplemented": "Ще не реалізовано", "MessageNotYetImplemented": "Ще не реалізовано",
"MessageOpmlPreviewNote": "Примітка: це попередній перегляд OPML-файлу. Актуальна назва подкасту буде завантажена з RSS-каналу.", "MessageOpmlPreviewNote": "Примітка: це попередній перегляд OPML-файлу. Актуальна назва подкасту буде завантажена з RSS-каналу.",
"MessageOr": "або", "MessageOr": "або",
+10 -3
View File
@@ -10,6 +10,8 @@
"ButtonApplyChapters": "应用到章节", "ButtonApplyChapters": "应用到章节",
"ButtonAuthors": "作者", "ButtonAuthors": "作者",
"ButtonBack": "返回", "ButtonBack": "返回",
"ButtonBatchEditPopulateFromExisting": "用现有内容填充",
"ButtonBatchEditPopulateMapDetails": "填充地图详细信息",
"ButtonBrowseForFolder": "浏览文件夹", "ButtonBrowseForFolder": "浏览文件夹",
"ButtonCancel": "取消", "ButtonCancel": "取消",
"ButtonCancelEncode": "取消编码", "ButtonCancelEncode": "取消编码",
@@ -196,7 +198,7 @@
"HeaderSleepTimer": "睡眠计时", "HeaderSleepTimer": "睡眠计时",
"HeaderStatsLargestItems": "最大的项目", "HeaderStatsLargestItems": "最大的项目",
"HeaderStatsLongestItems": "项目时长(小时)", "HeaderStatsLongestItems": "项目时长(小时)",
"HeaderStatsMinutesListeningChart": "收听分钟数(最近7天)", "HeaderStatsMinutesListeningChart": "收听分钟数 (最近7天)",
"HeaderStatsRecentSessions": "历史会话", "HeaderStatsRecentSessions": "历史会话",
"HeaderStatsTop10Authors": "前 10 位作者", "HeaderStatsTop10Authors": "前 10 位作者",
"HeaderStatsTop5Genres": "前 5 种流派", "HeaderStatsTop5Genres": "前 5 种流派",
@@ -432,7 +434,7 @@
"LabelMetadataProvider": "元数据提供商", "LabelMetadataProvider": "元数据提供商",
"LabelMinute": "分钟", "LabelMinute": "分钟",
"LabelMinutes": "分钟", "LabelMinutes": "分钟",
"LabelMissing": "丢失", "LabelMissing": "丢失",
"LabelMissingEbook": "没有电子书", "LabelMissingEbook": "没有电子书",
"LabelMissingSupplementaryEbook": "没有补充电子书", "LabelMissingSupplementaryEbook": "没有补充电子书",
"LabelMobileRedirectURIs": "允许移动应用重定向 URI", "LabelMobileRedirectURIs": "允许移动应用重定向 URI",
@@ -463,7 +465,7 @@
"LabelNotificationsMaxQueueSize": "通知事件的最大队列大小", "LabelNotificationsMaxQueueSize": "通知事件的最大队列大小",
"LabelNotificationsMaxQueueSizeHelp": "通知事件被限制为每秒触发 1 个. 如果队列处于最大大小, 则将忽略事件. 这可以防止通知垃圾邮件.", "LabelNotificationsMaxQueueSizeHelp": "通知事件被限制为每秒触发 1 个. 如果队列处于最大大小, 则将忽略事件. 这可以防止通知垃圾邮件.",
"LabelNumberOfBooks": "图书数量", "LabelNumberOfBooks": "图书数量",
"LabelNumberOfEpisodes": "# 集", "LabelNumberOfEpisodes": "# 集",
"LabelOpenIDAdvancedPermsClaimDescription": "OpenID 声明的名称, 该声明包含应用程序内用户操作的高级权限, 该权限将应用于非管理员角色(<b>如果已配置</b>). 如果响应中缺少声明, 获取 ABS 的权限将被拒绝. 如果缺少单个选项, 它将被视为 <code>禁用</code>. 确保身份提供商的声明与预期结构匹配:", "LabelOpenIDAdvancedPermsClaimDescription": "OpenID 声明的名称, 该声明包含应用程序内用户操作的高级权限, 该权限将应用于非管理员角色(<b>如果已配置</b>). 如果响应中缺少声明, 获取 ABS 的权限将被拒绝. 如果缺少单个选项, 它将被视为 <code>禁用</code>. 确保身份提供商的声明与预期结构匹配:",
"LabelOpenIDClaims": "将以下选项留空以禁用高级组和权限分配, 然后自动分配 'User' 组.", "LabelOpenIDClaims": "将以下选项留空以禁用高级组和权限分配, 然后自动分配 'User' 组.",
"LabelOpenIDGroupClaimDescription": "OpenID 声明的名称, 该声明包含用户组的列表. 通常称为<code>组</code><b>如果已配置</b>, 应用程序将根据用户的组成员身份自动分配角色, 前提是这些组在声明中以不区分大小写的方式命名为 'Admin', 'User' 或 'Guest'. 声明应包含一个列表, 如果用户属于多个组, 则应用程序将分配与最高访问级别相对应的角色. 如果没有组匹配, 访问将被拒绝.", "LabelOpenIDGroupClaimDescription": "OpenID 声明的名称, 该声明包含用户组的列表. 通常称为<code>组</code><b>如果已配置</b>, 应用程序将根据用户的组成员身份自动分配角色, 前提是这些组在声明中以不区分大小写的方式命名为 'Admin', 'User' 或 'Guest'. 声明应包含一个列表, 如果用户属于多个组, 则应用程序将分配与最高访问级别相对应的角色. 如果没有组匹配, 访问将被拒绝.",
@@ -484,6 +486,7 @@
"LabelPersonalYearReview": "你的年度回顾 ({0})", "LabelPersonalYearReview": "你的年度回顾 ({0})",
"LabelPhotoPathURL": "图片路径或 URL", "LabelPhotoPathURL": "图片路径或 URL",
"LabelPlayMethod": "播放方法", "LabelPlayMethod": "播放方法",
"LabelPlaybackRateIncrementDecrement": "播放速率增加/减少量",
"LabelPlayerChapterNumberMarker": "{0} 于 {1}", "LabelPlayerChapterNumberMarker": "{0} 于 {1}",
"LabelPlaylists": "播放列表", "LabelPlaylists": "播放列表",
"LabelPodcast": "播客", "LabelPodcast": "播客",
@@ -704,8 +707,11 @@
"MessageBackupsLocationEditNote": "注意: 更新备份位置不会移动或修改现有备份", "MessageBackupsLocationEditNote": "注意: 更新备份位置不会移动或修改现有备份",
"MessageBackupsLocationNoEditNote": "注意: 备份位置是通过环境变量设置的, 不能在此处更改.", "MessageBackupsLocationNoEditNote": "注意: 备份位置是通过环境变量设置的, 不能在此处更改.",
"MessageBackupsLocationPathEmpty": "备份位置路径不能为空", "MessageBackupsLocationPathEmpty": "备份位置路径不能为空",
"MessageBatchEditPopulateMapDetailsAllHelp": "使用所有项目的数据填充已启用的字段. 具有多个值的字段将被合并",
"MessageBatchEditPopulateMapDetailsItemHelp": "使用此项目的数据填充已启用的地图详细信息字段",
"MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.", "MessageBatchQuickMatchDescription": "快速匹配将尝试为所选项目添加缺少的封面和元数据. 启用以下选项以允许快速匹配覆盖现有封面和或元数据.",
"MessageBookshelfNoCollections": "你尚未进行任何收藏", "MessageBookshelfNoCollections": "你尚未进行任何收藏",
"MessageBookshelfNoCollectionsHelp": "收藏是公开的. 所有有权访问图书馆的用户都可以看到它们.",
"MessageBookshelfNoRSSFeeds": "没有打开的 RSS 源", "MessageBookshelfNoRSSFeeds": "没有打开的 RSS 源",
"MessageBookshelfNoResultsForFilter": "过滤器无结果 \"{0}: {1}\"", "MessageBookshelfNoResultsForFilter": "过滤器无结果 \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "没有可查询的结果", "MessageBookshelfNoResultsForQuery": "没有可查询的结果",
@@ -816,6 +822,7 @@
"MessageNoTasksRunning": "没有正在运行的任务", "MessageNoTasksRunning": "没有正在运行的任务",
"MessageNoUpdatesWereNecessary": "无需更新", "MessageNoUpdatesWereNecessary": "无需更新",
"MessageNoUserPlaylists": "你没有播放列表", "MessageNoUserPlaylists": "你没有播放列表",
"MessageNoUserPlaylistsHelp": "播放列表是私密的. 只有创建播放列表的用户才能看到.",
"MessageNotYetImplemented": "尚未实施", "MessageNotYetImplemented": "尚未实施",
"MessageOpmlPreviewNote": "注意: 这是解析的OPML文件的预览. 实际的播客标题将从 RSS 提要中获取.", "MessageOpmlPreviewNote": "注意: 这是解析的OPML文件的预览. 实际的播客标题将从 RSS 提要中获取.",
"MessageOr": "或", "MessageOr": "或",
+2 -2
View File
@@ -29,7 +29,7 @@ if (isDev) {
if (devEnv.AllowIframe) process.env.ALLOW_IFRAME = '1' if (devEnv.AllowIframe) process.env.ALLOW_IFRAME = '1'
if (devEnv.BackupPath) process.env.BACKUP_PATH = devEnv.BackupPath if (devEnv.BackupPath) process.env.BACKUP_PATH = devEnv.BackupPath
process.env.SOURCE = 'local' process.env.SOURCE = 'local'
process.env.ROUTER_BASE_PATH = devEnv.RouterBasePath || '' process.env.ROUTER_BASE_PATH = devEnv.RouterBasePath ?? '/audiobookshelf'
} }
const inputConfig = options.config ? Path.resolve(options.config) : null const inputConfig = options.config ? Path.resolve(options.config) : null
@@ -41,7 +41,7 @@ const CONFIG_PATH = inputConfig || process.env.CONFIG_PATH || Path.resolve('conf
const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata') const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata')
const SOURCE = options.source || process.env.SOURCE || 'debian' const SOURCE = options.source || process.env.SOURCE || 'debian'
const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH || '/audiobookshelf' const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH ?? '/audiobookshelf'
console.log(`Running in ${process.env.NODE_ENV} mode.`) console.log(`Running in ${process.env.NODE_ENV} mode.`)
console.log(`Options: CONFIG_PATH=${CONFIG_PATH}, METADATA_PATH=${METADATA_PATH}, PORT=${PORT}, HOST=${HOST}, SOURCE=${SOURCE}, ROUTER_BASE_PATH=${ROUTER_BASE_PATH}`) console.log(`Options: CONFIG_PATH=${CONFIG_PATH}, METADATA_PATH=${METADATA_PATH}, PORT=${PORT}, HOST=${HOST}, SOURCE=${SOURCE}, ROUTER_BASE_PATH=${ROUTER_BASE_PATH}`)
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "audiobookshelf", "name": "audiobookshelf",
"version": "2.18.0", "version": "2.19.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "audiobookshelf", "name": "audiobookshelf",
"version": "2.18.0", "version": "2.19.1",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"axios": "^0.27.2", "axios": "^0.27.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "audiobookshelf", "name": "audiobookshelf",
"version": "2.18.0", "version": "2.19.1",
"buildNumber": 1, "buildNumber": 1,
"description": "Self-hosted audiobook and podcast server", "description": "Self-hosted audiobook and podcast server",
"main": "index.js", "main": "index.js",
+1 -1
View File
@@ -25,7 +25,7 @@ const CONFIG_PATH = inputConfig || process.env.CONFIG_PATH || Path.resolve('conf
const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata') const METADATA_PATH = inputMetadata || process.env.METADATA_PATH || Path.resolve('metadata')
const SOURCE = options.source || process.env.SOURCE || 'debian' const SOURCE = options.source || process.env.SOURCE || 'debian'
const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH || '/audiobookshelf' const ROUTER_BASE_PATH = process.env.ROUTER_BASE_PATH ?? '/audiobookshelf'
console.log(process.env.NODE_ENV, 'Config', CONFIG_PATH, METADATA_PATH) console.log(process.env.NODE_ENV, 'Config', CONFIG_PATH, METADATA_PATH)
+11 -2
View File
@@ -47,7 +47,6 @@ Check out the web client demo: https://audiobooks.dev/ (thanks for hosting [@Vit
Username/password: `demo`/`demo` (user account) Username/password: `demo`/`demo` (user account)
### Android App (beta) ### Android App (beta)
Try it out on the [Google Play Store](https://play.google.com/store/apps/details?id=com.audiobookshelf.app) Try it out on the [Google Play Store](https://play.google.com/store/apps/details?id=com.audiobookshelf.app)
@@ -86,7 +85,7 @@ See [install docs](https://www.audiobookshelf.org/docs)
#### Important! Audiobookshelf requires a websocket connection. #### Important! Audiobookshelf requires a websocket connection.
#### Note: Subfolder paths (e.g. /audiobooks) are not supported yet. See [issue](https://github.com/advplyr/audiobookshelf/issues/385) #### Note: Using a subfolder is supported with no additional changes but the path must be `/audiobookshelf` (this is not changeable). See [discussion](https://github.com/advplyr/audiobookshelf/discussions/3535)
### NGINX Proxy Manager ### NGINX Proxy Manager
@@ -165,6 +164,16 @@ For this to work you must enable at least the following mods using `a2enmod`:
</IfModule> </IfModule>
``` ```
If using Apache >= 2.4.47 you can use the following, without having to use any of the `RewriteEngine`, `RewriteCond`, or `RewriteRule` directives. For example:
```xml
<Location /audiobookshelf>
ProxyPreserveHost on
ProxyPass http://localhost:<audiobookshelf_port>/audiobookshelf upgrade=websocket
ProxyPassReverse http://localhost:<audiobookshelf_port>/audiobookshelf
</Location>
```
Some SSL certificates like those signed by Let's Encrypt require ACME validation. To allow Let's Encrypt to write and confirm the ACME challenge, edit your VirtualHost definition to prevent proxying traffic that queries `/.well-known` and instead serve that directly: Some SSL certificates like those signed by Let's Encrypt require ACME validation. To allow Let's Encrypt to write and confirm the ACME challenge, edit your VirtualHost definition to prevent proxying traffic that queries `/.well-known` and instead serve that directly:
```bash ```bash
+7 -2
View File
@@ -10,6 +10,7 @@ const ExtractJwt = require('passport-jwt').ExtractJwt
const OpenIDClient = require('openid-client') const OpenIDClient = require('openid-client')
const Database = require('./Database') const Database = require('./Database')
const Logger = require('./Logger') const Logger = require('./Logger')
const { escapeRegExp } = require('./utils')
/** /**
* @class Class for handling all the authentication related functionality. * @class Class for handling all the authentication related functionality.
@@ -18,7 +19,11 @@ class Auth {
constructor() { constructor() {
// Map of openId sessions indexed by oauth2 state-variable // Map of openId sessions indexed by oauth2 state-variable
this.openIdAuthSession = new Map() this.openIdAuthSession = new Map()
this.ignorePatterns = [/\/api\/items\/[^/]+\/cover/, /\/api\/authors\/[^/]+\/image/] const escapedRouterBasePath = escapeRegExp(global.RouterBasePath)
this.ignorePatterns = [
new RegExp(`^(${escapedRouterBasePath}/api)?/items/[^/]+/cover$`),
new RegExp(`^(${escapedRouterBasePath}/api)?/authors/[^/]+/image$`)
]
} }
/** /**
@@ -28,7 +33,7 @@ class Auth {
* @private * @private
*/ */
authNotNeeded(req) { authNotNeeded(req) {
return req.method === 'GET' && this.ignorePatterns.some((pattern) => pattern.test(req.originalUrl)) return req.method === 'GET' && this.ignorePatterns.some((pattern) => pattern.test(req.path))
} }
ifAuthNeeded(middleware) { ifAuthNeeded(middleware) {
+36
View File
@@ -226,6 +226,28 @@ class Database {
try { try {
await this.sequelize.authenticate() await this.sequelize.authenticate()
// Set SQLite pragmas from environment variables
const allowedPragmas = [
{ name: 'mmap_size', env: 'SQLITE_MMAP_SIZE' },
{ name: 'cache_size', env: 'SQLITE_CACHE_SIZE' },
{ name: 'temp_store', env: 'SQLITE_TEMP_STORE' }
]
for (const pragma of allowedPragmas) {
const value = process.env[pragma.env]
if (value !== undefined) {
try {
Logger.info(`[Database] Running "PRAGMA ${pragma.name} = ${value}"`)
await this.sequelize.query(`PRAGMA ${pragma.name} = ${value}`)
const [result] = await this.sequelize.query(`PRAGMA ${pragma.name}`)
Logger.debug(`[Database] "PRAGMA ${pragma.name}" query result:`, result)
} catch (error) {
Logger.error(`[Database] Failed to set SQLite pragma ${pragma.name}`, error)
}
}
}
if (process.env.NUSQLITE3_PATH) { if (process.env.NUSQLITE3_PATH) {
await this.loadExtension(process.env.NUSQLITE3_PATH) await this.loadExtension(process.env.NUSQLITE3_PATH)
Logger.info(`[Database] Db supports unaccent and unicode foldings`) Logger.info(`[Database] Db supports unaccent and unicode foldings`)
@@ -678,6 +700,7 @@ class Database {
await libraryItem.destroy() await libraryItem.destroy()
} }
// Remove invalid PlaylistMediaItem records
const playlistMediaItemsWithNoMediaItem = await this.playlistMediaItemModel.findAll({ const playlistMediaItemsWithNoMediaItem = await this.playlistMediaItemModel.findAll({
include: [ include: [
{ {
@@ -699,6 +722,19 @@ class Database {
await playlistMediaItem.destroy() await playlistMediaItem.destroy()
} }
// Remove invalid CollectionBook records
const collectionBooksWithNoBook = await this.collectionBookModel.findAll({
include: {
model: this.bookModel,
required: false
},
where: { '$book.id$': null }
})
for (const collectionBook of collectionBooksWithNoBook) {
Logger.warn(`Found collectionBook with no book - removing it`)
await collectionBook.destroy()
}
// Remove empty series // Remove empty series
const emptySeries = await this.seriesModel.findAll({ const emptySeries = await this.seriesModel.findAll({
include: { include: {
+2 -2
View File
@@ -117,7 +117,7 @@ class Logger {
if (level < LogLevel.FATAL && level < this.logLevel) return if (level < LogLevel.FATAL && level < this.logLevel) return
const consoleMethod = Logger.ConsoleMethods[levelName] const consoleMethod = Logger.ConsoleMethods[levelName]
console[consoleMethod](`[${this.timestamp}] ${levelName}:`, ...args) console[consoleMethod](`[${this.timestamp}] ${levelName}:`, ...args)
this.#logToFileAndListeners(level, levelName, args, source) return this.#logToFileAndListeners(level, levelName, args, source)
} }
trace(...args) { trace(...args) {
@@ -141,7 +141,7 @@ class Logger {
} }
fatal(...args) { fatal(...args) {
this.#log('FATAL', this.source, ...args) return this.#log('FATAL', this.source, ...args)
} }
note(...args) { note(...args) {
+3 -1
View File
@@ -251,6 +251,7 @@ class CollectionController {
/** /**
* DELETE: /api/collections/:id/book/:bookId * DELETE: /api/collections/:id/book/:bookId
* Remove a single book from a collection. Re-order books * Remove a single book from a collection. Re-order books
* Users with update permission can remove books from collections
* TODO: bookId is actually libraryItemId. Clients need updating to use bookId * TODO: bookId is actually libraryItemId. Clients need updating to use bookId
* *
* @param {CollectionControllerRequest} req * @param {CollectionControllerRequest} req
@@ -427,7 +428,8 @@ class CollectionController {
req.collection = collection req.collection = collection
} }
if (req.method == 'DELETE' && !req.user.canDelete) { // Users with update permission can remove books from collections
if (req.method == 'DELETE' && !req.params.bookId && !req.user.canDelete) {
Logger.warn(`[CollectionController] User "${req.user.username}" attempted to delete without permission`) Logger.warn(`[CollectionController] User "${req.user.username}" attempted to delete without permission`)
return res.sendStatus(403) return res.sendStatus(403)
} else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) { } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
+7
View File
@@ -8,6 +8,7 @@ const AudiobookCovers = require('../providers/AudiobookCovers')
const CustomProviderAdapter = require('../providers/CustomProviderAdapter') const CustomProviderAdapter = require('../providers/CustomProviderAdapter')
const Logger = require('../Logger') const Logger = require('../Logger')
const { levenshteinDistance, escapeRegExp } = require('../utils/index') const { levenshteinDistance, escapeRegExp } = require('../utils/index')
const htmlSanitizer = require('../utils/htmlSanitizer')
class BookFinder { class BookFinder {
#providerResponseTimeout = 30000 #providerResponseTimeout = 30000
@@ -463,6 +464,12 @@ class BookFinder {
} else { } else {
books = await this.getGoogleBooksResults(title, author) books = await this.getGoogleBooksResults(title, author)
} }
books.forEach((book) => {
if (book.description) {
book.description = htmlSanitizer.sanitize(book.description)
book.descriptionPlain = htmlSanitizer.stripAllTags(book.description)
}
})
return books return books
} }
-112
View File
@@ -7,12 +7,6 @@
*/ */
const htmlparser = require('htmlparser2'); const htmlparser = require('htmlparser2');
// const escapeStringRegexp = require('escape-string-regexp');
// const { isPlainObject } = require('is-plain-object');
// const deepmerge = require('deepmerge');
// const parseSrcset = require('parse-srcset');
// const { parse: postcssParse } = require('postcss');
// Tags that can conceivably represent stand-alone media.
// ABS UPDATE: Packages not necessary // ABS UPDATE: Packages not necessary
// SOURCE: https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js // SOURCE: https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js
@@ -76,17 +70,6 @@ function has(obj, key) {
return ({}).hasOwnProperty.call(obj, key); return ({}).hasOwnProperty.call(obj, key);
} }
// Returns those elements of `a` for which `cb(a)` returns truthy
function filter(a, cb) {
const n = [];
each(a, function (v) {
if (cb(v)) {
n.push(v);
}
});
return n;
}
function isEmptyObject(obj) { function isEmptyObject(obj) {
for (const key in obj) { for (const key in obj) {
if (has(obj, key)) { if (has(obj, key)) {
@@ -96,21 +79,6 @@ function isEmptyObject(obj) {
return true; return true;
} }
function stringifySrcset(parsedSrcset) {
return parsedSrcset.map(function (part) {
if (!part.url) {
throw new Error('URL missing');
}
return (
part.url +
(part.w ? ` ${part.w}w` : '') +
(part.h ? ` ${part.h}h` : '') +
(part.d ? ` ${part.d}x` : '')
);
}).join(', ');
}
module.exports = sanitizeHtml; module.exports = sanitizeHtml;
// A valid attribute name. // A valid attribute name.
@@ -714,86 +682,6 @@ function sanitizeHtml(html, options, _recursing) {
return !options.allowedSchemes || options.allowedSchemes.indexOf(scheme) === -1; return !options.allowedSchemes || options.allowedSchemes.indexOf(scheme) === -1;
} }
/**
* Filters user input css properties by allowlisted regex attributes.
* Modifies the abstractSyntaxTree object.
*
* @param {object} abstractSyntaxTree - Object representation of CSS attributes.
* @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }.
* @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i).
* @return {object} - The modified tree.
*/
// function filterCss(abstractSyntaxTree, allowedStyles) {
// if (!allowedStyles) {
// return abstractSyntaxTree;
// }
// const astRules = abstractSyntaxTree.nodes[0];
// let selectedRule;
// // Merge global and tag-specific styles into new AST.
// if (allowedStyles[astRules.selector] && allowedStyles['*']) {
// selectedRule = deepmerge(
// allowedStyles[astRules.selector],
// allowedStyles['*']
// );
// } else {
// selectedRule = allowedStyles[astRules.selector] || allowedStyles['*'];
// }
// if (selectedRule) {
// abstractSyntaxTree.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []);
// }
// return abstractSyntaxTree;
// }
/**
* Extracts the style attributes from an AbstractSyntaxTree and formats those
* values in the inline style attribute format.
*
* @param {AbstractSyntaxTree} filteredAST
* @return {string} - Example: "color:yellow;text-align:center !important;font-family:helvetica;"
*/
function stringifyStyleAttributes(filteredAST) {
return filteredAST.nodes[0].nodes
.reduce(function (extractedAttributes, attrObject) {
extractedAttributes.push(
`${attrObject.prop}:${attrObject.value}${attrObject.important ? ' !important' : ''}`
);
return extractedAttributes;
}, [])
.join(';');
}
/**
* Filters the existing attributes for the given property. Discards any attributes
* which don't match the allowlist.
*
* @param {object} selectedRule - Example: { color: red, font-family: helvetica }
* @param {array} allowedDeclarationsList - List of declarations which pass the allowlist.
* @param {object} attributeObject - Object representing the current css property.
* @property {string} attributeObject.type - Typically 'declaration'.
* @property {string} attributeObject.prop - The CSS property, i.e 'color'.
* @property {string} attributeObject.value - The corresponding value to the css property, i.e 'red'.
* @return {function} - When used in Array.reduce, will return an array of Declaration objects
*/
function filterDeclarations(selectedRule) {
return function (allowedDeclarationsList, attributeObject) {
// If this property is allowlisted...
if (has(selectedRule, attributeObject.prop)) {
const matchesRegex = selectedRule[attributeObject.prop].some(function (regularExpression) {
return regularExpression.test(attributeObject.value);
});
if (matchesRegex) {
allowedDeclarationsList.push(attributeObject);
}
}
return allowedDeclarationsList;
};
}
function filterClasses(classes, allowed, allowedGlobs) { function filterClasses(classes, allowed, allowedGlobs) {
if (!allowed) { if (!allowed) {
// The class attribute is allowed without filtering on this tag // The class attribute is allowed without filtering on this tag
+9
View File
@@ -246,6 +246,15 @@ class RssFeedManager {
const extname = Path.extname(feed.coverPath).toLowerCase().slice(1) const extname = Path.extname(feed.coverPath).toLowerCase().slice(1)
res.type(`image/${extname}`) res.type(`image/${extname}`)
const readStream = fs.createReadStream(feed.coverPath) const readStream = fs.createReadStream(feed.coverPath)
readStream.on('error', (error) => {
Logger.error(`[RssFeedManager] Error streaming cover image: ${error.message}`)
// Only send error if headers haven't been sent yet
if (!res.headersSent) {
res.sendStatus(404)
}
})
readStream.pipe(res) readStream.pipe(res)
} }
+1
View File
@@ -13,3 +13,4 @@ Please add a record of every database migration that you create to this file. Th
| v2.17.5 | v2.17.5-remove-host-from-feed-urls | removes the host (serverAddress) from URL columns in the feeds and feedEpisodes tables | | v2.17.5 | v2.17.5-remove-host-from-feed-urls | removes the host (serverAddress) from URL columns in the feeds and feedEpisodes tables |
| v2.17.6 | v2.17.6-share-add-isdownloadable | Adds the isDownloadable column to the mediaItemShares table | | v2.17.6 | v2.17.6-share-add-isdownloadable | Adds the isDownloadable column to the mediaItemShares table |
| v2.17.7 | v2.17.7-add-indices | Adds indices to the libraryItems and books tables to reduce query times | | v2.17.7 | v2.17.7-add-indices | Adds indices to the libraryItems and books tables to reduce query times |
| v2.19.1 | v2.19.1-copy-title-to-library-items | Copies title and titleIgnorePrefix to the libraryItems table, creates update triggers and indices |
@@ -0,0 +1,164 @@
const util = require('util')
/**
* @typedef MigrationContext
* @property {import('sequelize').QueryInterface} queryInterface - a suquelize QueryInterface object.
* @property {import('../Logger')} logger - a Logger object.
*
* @typedef MigrationOptions
* @property {MigrationContext} context - an object containing the migration context.
*/
const migrationVersion = '2.19.1'
const migrationName = `${migrationVersion}-copy-title-to-library-items`
const loggerPrefix = `[${migrationVersion} migration]`
/**
* This upward migration adds a title column to the libraryItems table, copies the title from the book to the libraryItem,
* and creates a new index on the title column. In addition it sets a trigger on the books table to update the title column
* in the libraryItems table when a book is updated.
*
* @param {MigrationOptions} options - an object containing the migration context.
* @returns {Promise<void>} - A promise that resolves when the migration is complete.
*/
async function up({ context: { queryInterface, logger } }) {
// Upwards migration script
logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`)
await addColumn(queryInterface, logger, 'libraryItems', 'title', { type: queryInterface.sequelize.Sequelize.STRING, allowNull: true })
await copyColumn(queryInterface, logger, 'books', 'title', 'id', 'libraryItems', 'title', 'mediaId')
await addTrigger(queryInterface, logger, 'books', 'title', 'id', 'libraryItems', 'title', 'mediaId')
await addIndex(queryInterface, logger, 'libraryItems', ['libraryId', 'mediaType', { name: 'title', collate: 'NOCASE' }])
await addColumn(queryInterface, logger, 'libraryItems', 'titleIgnorePrefix', { type: queryInterface.sequelize.Sequelize.STRING, allowNull: true })
await copyColumn(queryInterface, logger, 'books', 'titleIgnorePrefix', 'id', 'libraryItems', 'titleIgnorePrefix', 'mediaId')
await addTrigger(queryInterface, logger, 'books', 'titleIgnorePrefix', 'id', 'libraryItems', 'titleIgnorePrefix', 'mediaId')
await addIndex(queryInterface, logger, 'libraryItems', ['libraryId', 'mediaType', { name: 'titleIgnorePrefix', collate: 'NOCASE' }])
await addIndex(queryInterface, logger, 'libraryItems', ['libraryId', 'mediaType', 'createdAt'])
logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`)
}
/**
* This downward migration script removes the title column from the libraryItems table, removes the trigger on the books table,
* and removes the index on the title column.
*
* @param {MigrationOptions} options - an object containing the migration context.
* @returns {Promise<void>} - A promise that resolves when the migration is complete.
*/
async function down({ context: { queryInterface, logger } }) {
// Downward migration script
logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`)
await removeIndex(queryInterface, logger, 'libraryItems', ['libraryId', 'mediaType', 'title'])
await removeTrigger(queryInterface, logger, 'libraryItems', 'title')
await removeColumn(queryInterface, logger, 'libraryItems', 'title')
await removeIndex(queryInterface, logger, 'libraryItems', ['libraryId', 'mediaType', 'titleIgnorePrefix'])
await removeTrigger(queryInterface, logger, 'libraryItems', 'titleIgnorePrefix')
await removeColumn(queryInterface, logger, 'libraryItems', 'titleIgnorePrefix')
await removeIndex(queryInterface, logger, 'libraryItems', ['libraryId', 'mediaType', 'createdAt'])
logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`)
}
/**
* Utility function to add an index to a table. If the index already z`exists, it logs a message and continues.
*
* @param {import('sequelize').QueryInterface} queryInterface
* @param {import ('../Logger')} logger
* @param {string} tableName
* @param {string[]} columns
*/
async function addIndex(queryInterface, logger, tableName, columns) {
const columnString = columns.map((column) => util.inspect(column)).join(', ')
const indexName = convertToSnakeCase(`${tableName}_${columns.map((column) => (typeof column === 'string' ? column : column.name)).join('_')}`)
try {
logger.info(`${loggerPrefix} adding index on [${columnString}] to table ${tableName}. index name: ${indexName}"`)
await queryInterface.addIndex(tableName, columns)
logger.info(`${loggerPrefix} added index on [${columnString}] to table ${tableName}. index name: ${indexName}"`)
} catch (error) {
if (error.name === 'SequelizeDatabaseError' && error.message.includes('already exists')) {
logger.info(`${loggerPrefix} index [${columnString}] for table "${tableName}" already exists`)
} else {
throw error
}
}
}
/**
* Utility function to remove an index from a table.
* Sequelize implemets it using DROP INDEX IF EXISTS, so it won't throw an error if the index doesn't exist.
*
* @param {import('sequelize').QueryInterface} queryInterface
* @param {import ('../Logger')} logger
* @param {string} tableName
* @param {string[]} columns
*/
async function removeIndex(queryInterface, logger, tableName, columns) {
logger.info(`${loggerPrefix} removing index [${columns.join(', ')}] from table "${tableName}"`)
await queryInterface.removeIndex(tableName, columns)
logger.info(`${loggerPrefix} removed index [${columns.join(', ')}] from table "${tableName}"`)
}
async function addColumn(queryInterface, logger, table, column, options) {
logger.info(`${loggerPrefix} adding column "${column}" to table "${table}"`)
const tableDescription = await queryInterface.describeTable(table)
if (!tableDescription[column]) {
await queryInterface.addColumn(table, column, options)
logger.info(`${loggerPrefix} added column "${column}" to table "${table}"`)
} else {
logger.info(`${loggerPrefix} column "${column}" already exists in table "${table}"`)
}
}
async function removeColumn(queryInterface, logger, table, column) {
logger.info(`${loggerPrefix} removing column "${column}" from table "${table}"`)
await queryInterface.removeColumn(table, column)
logger.info(`${loggerPrefix} removed column "${column}" from table "${table}"`)
}
async function copyColumn(queryInterface, logger, sourceTable, sourceColumn, sourceIdColumn, targetTable, targetColumn, targetIdColumn) {
logger.info(`${loggerPrefix} copying column "${sourceColumn}" from table "${sourceTable}" to table "${targetTable}"`)
await queryInterface.sequelize.query(`
UPDATE ${targetTable}
SET ${targetColumn} = ${sourceTable}.${sourceColumn}
FROM ${sourceTable}
WHERE ${targetTable}.${targetIdColumn} = ${sourceTable}.${sourceIdColumn}
`)
logger.info(`${loggerPrefix} copied column "${sourceColumn}" from table "${sourceTable}" to table "${targetTable}"`)
}
async function addTrigger(queryInterface, logger, sourceTable, sourceColumn, sourceIdColumn, targetTable, targetColumn, targetIdColumn) {
logger.info(`${loggerPrefix} adding trigger to update ${targetTable}.${targetColumn} when ${sourceTable}.${sourceColumn} is updated`)
const triggerName = convertToSnakeCase(`update_${targetTable}_${targetColumn}`)
await queryInterface.sequelize.query(`DROP TRIGGER IF EXISTS ${triggerName}`)
await queryInterface.sequelize.query(`
CREATE TRIGGER ${triggerName}
AFTER UPDATE OF ${sourceColumn} ON ${sourceTable}
FOR EACH ROW
BEGIN
UPDATE ${targetTable}
SET ${targetColumn} = NEW.${sourceColumn}
WHERE ${targetTable}.${targetIdColumn} = NEW.${sourceIdColumn};
END;
`)
logger.info(`${loggerPrefix} added trigger to update ${targetTable}.${targetColumn} when ${sourceTable}.${sourceColumn} is updated`)
}
async function removeTrigger(queryInterface, logger, targetTable, targetColumn) {
logger.info(`${loggerPrefix} removing trigger to update ${targetTable}.${targetColumn}`)
const triggerName = convertToSnakeCase(`update_${targetTable}_${targetColumn}`)
await queryInterface.sequelize.query(`DROP TRIGGER IF EXISTS ${triggerName}`)
logger.info(`${loggerPrefix} removed trigger to update ${targetTable}.${targetColumn}`)
}
function convertToSnakeCase(str) {
return str.replace(/([A-Z])/g, '_$1').toLowerCase()
}
module.exports = { up, down }
+13 -2
View File
@@ -2,6 +2,8 @@ const { DataTypes, Model } = require('sequelize')
const Logger = require('../Logger') const Logger = require('../Logger')
const { getTitlePrefixAtEnd, getTitleIgnorePrefix } = require('../utils') const { getTitlePrefixAtEnd, getTitleIgnorePrefix } = require('../utils')
const parseNameString = require('../utils/parsers/parseNameString') const parseNameString = require('../utils/parsers/parseNameString')
const htmlSanitizer = require('../utils/htmlSanitizer')
const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilters')
/** /**
* @typedef EBookFileObject * @typedef EBookFileObject
@@ -191,6 +193,14 @@ class Book extends Model {
] ]
} }
) )
Book.addHook('afterDestroy', async (instance) => {
libraryItemsBookFilters.clearCountCache('afterDestroy')
})
Book.addHook('afterCreate', async (instance) => {
libraryItemsBookFilters.clearCountCache('afterCreate')
})
} }
/** /**
@@ -285,7 +295,7 @@ class Book extends Model {
const track = structuredClone(af) const track = structuredClone(af)
track.title = af.metadata.filename track.title = af.metadata.filename
track.startOffset = startOffset track.startOffset = startOffset
track.contentUrl = `${global.RouterBasePath}/api/items/${libraryItemId}/file/${track.ino}` track.contentUrl = `/api/items/${libraryItemId}/file/${track.ino}`
startOffset += track.duration startOffset += track.duration
return track return track
}) })
@@ -364,7 +374,7 @@ class Book extends Model {
if (payload.metadata) { if (payload.metadata) {
const metadataStringKeys = ['title', 'subtitle', 'publishedYear', 'publishedDate', 'publisher', 'description', 'isbn', 'asin', 'language'] const metadataStringKeys = ['title', 'subtitle', 'publishedYear', 'publishedDate', 'publisher', 'description', 'isbn', 'asin', 'language']
metadataStringKeys.forEach((key) => { metadataStringKeys.forEach((key) => {
if (typeof payload.metadata[key] === 'string' && this[key] !== payload.metadata[key]) { if ((typeof payload.metadata[key] === 'string' || payload.metadata[key] === null) && this[key] !== payload.metadata[key]) {
this[key] = payload.metadata[key] || null this[key] = payload.metadata[key] || null
if (key === 'title') { if (key === 'title') {
@@ -579,6 +589,7 @@ class Book extends Model {
oldMetadataJSON.authorNameLF = this.authorNameLF oldMetadataJSON.authorNameLF = this.authorNameLF
oldMetadataJSON.narratorName = (this.narrators || []).join(', ') oldMetadataJSON.narratorName = (this.narrators || []).join(', ')
oldMetadataJSON.seriesName = this.seriesName oldMetadataJSON.seriesName = this.seriesName
oldMetadataJSON.descriptionPlain = this.description ? htmlSanitizer.stripAllTags(this.description) : null
return oldMetadataJSON return oldMetadataJSON
} }
+9 -6
View File
@@ -135,13 +135,14 @@ class FeedEpisode extends Model {
* @param {string} slug * @param {string} slug
* @param {import('./Book').AudioFileObject} audioTrack * @param {import('./Book').AudioFileObject} audioTrack
* @param {boolean} useChapterTitles * @param {boolean} useChapterTitles
* @param {number} offsetIndex
* @param {string} [existingEpisodeId] * @param {string} [existingEpisodeId]
*/ */
static getFeedEpisodeObjFromAudiobookTrack(book, pubDateStart, feed, slug, audioTrack, useChapterTitles, existingEpisodeId = null) { static getFeedEpisodeObjFromAudiobookTrack(book, pubDateStart, feed, slug, audioTrack, useChapterTitles, offsetIndex, existingEpisodeId = null) {
// Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate> // Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate>
// Offset pubdate in 1 minute intervals to ensure correct order // Offset pubdate in 1 minute intervals to ensure correct order
let timeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 60000 const timeOffset = offsetIndex * 60000
let episodeId = existingEpisodeId || uuidv4() const episodeId = existingEpisodeId || uuidv4()
// e.g. Track 1 will have a pub date before Track 2 // e.g. Track 1 will have a pub date before Track 2
const audiobookPubDate = date.format(new Date(pubDateStart.valueOf() + timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]') const audiobookPubDate = date.format(new Date(pubDateStart.valueOf() + timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
@@ -191,14 +192,15 @@ class FeedEpisode extends Model {
const feedEpisodeObjs = [] const feedEpisodeObjs = []
let numExisting = 0 let numExisting = 0
for (const track of trackList) { for (let i = 0; i < trackList.length; i++) {
const track = trackList[i]
// Check for existing episode by filepath // Check for existing episode by filepath
const existingEpisode = feed.feedEpisodes?.find((episode) => { const existingEpisode = feed.feedEpisodes?.find((episode) => {
return episode.filePath === track.metadata.path return episode.filePath === track.metadata.path
}) })
numExisting = existingEpisode ? numExisting + 1 : numExisting numExisting = existingEpisode ? numExisting + 1 : numExisting
feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(libraryItemExpanded.media, libraryItemExpanded.createdAt, feed, slug, track, useChapterTitles, existingEpisode?.id)) feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(libraryItemExpanded.media, libraryItemExpanded.createdAt, feed, slug, track, useChapterTitles, i, existingEpisode?.id))
} }
Logger.info(`[FeedEpisode] Upserting ${feedEpisodeObjs.length} episodes for feed ${feed.id} (${numExisting} existing)`) Logger.info(`[FeedEpisode] Upserting ${feedEpisodeObjs.length} episodes for feed ${feed.id} (${numExisting} existing)`)
return this.bulkCreate(feedEpisodeObjs, { transaction, updateOnDuplicate: ['title', 'author', 'description', 'siteURL', 'enclosureURL', 'enclosureType', 'enclosureSize', 'pubDate', 'season', 'episode', 'episodeType', 'duration', 'filePath', 'explicit'] }) return this.bulkCreate(feedEpisodeObjs, { transaction, updateOnDuplicate: ['title', 'author', 'description', 'siteURL', 'enclosureURL', 'enclosureType', 'enclosureSize', 'pubDate', 'season', 'episode', 'episodeType', 'duration', 'filePath', 'explicit'] })
@@ -219,6 +221,7 @@ class FeedEpisode extends Model {
const feedEpisodeObjs = [] const feedEpisodeObjs = []
let numExisting = 0 let numExisting = 0
let offsetIndex = 0
for (const book of books) { for (const book of books) {
const trackList = book.getTracklist(book.libraryItem.id) const trackList = book.getTracklist(book.libraryItem.id)
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(trackList, book) const useChapterTitles = this.checkUseChapterTitlesForEpisodes(trackList, book)
@@ -229,7 +232,7 @@ class FeedEpisode extends Model {
}) })
numExisting = existingEpisode ? numExisting + 1 : numExisting numExisting = existingEpisode ? numExisting + 1 : numExisting
feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(book, earliestLibraryItemCreatedAt, feed, slug, track, useChapterTitles, existingEpisode?.id)) feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(book, earliestLibraryItemCreatedAt, feed, slug, track, useChapterTitles, offsetIndex++, existingEpisode?.id))
} }
} }
Logger.info(`[FeedEpisode] Upserting ${feedEpisodeObjs.length} episodes for feed ${feed.id} (${numExisting} existing)`) Logger.info(`[FeedEpisode] Upserting ${feedEpisodeObjs.length} episodes for feed ${feed.id} (${numExisting} existing)`)
+17 -2
View File
@@ -73,6 +73,10 @@ class LibraryItem extends Model {
/** @type {Book.BookExpanded|Podcast.PodcastExpanded} - only set when expanded */ /** @type {Book.BookExpanded|Podcast.PodcastExpanded} - only set when expanded */
this.media this.media
/** @type {string} */
this.title // Only used for sorting
/** @type {string} */
this.titleIgnorePrefix // Only used for sorting
} }
/** /**
@@ -155,7 +159,7 @@ class LibraryItem extends Model {
{ {
model: this.sequelize.models.series, model: this.sequelize.models.series,
through: { through: {
attributes: ['sequence'] attributes: ['id', 'sequence']
} }
} }
] ]
@@ -677,7 +681,9 @@ class LibraryItem extends Model {
lastScan: DataTypes.DATE, lastScan: DataTypes.DATE,
lastScanVersion: DataTypes.STRING, lastScanVersion: DataTypes.STRING,
libraryFiles: DataTypes.JSON, libraryFiles: DataTypes.JSON,
extraData: DataTypes.JSON extraData: DataTypes.JSON,
title: DataTypes.STRING,
titleIgnorePrefix: DataTypes.STRING
}, },
{ {
sequelize, sequelize,
@@ -695,6 +701,15 @@ class LibraryItem extends Model {
{ {
fields: ['libraryId', 'mediaType', 'size'] fields: ['libraryId', 'mediaType', 'size']
}, },
{
fields: ['libraryId', 'mediaType', 'createdAt']
},
{
fields: ['libraryId', 'mediaType', { name: 'title', collate: 'NOCASE' }]
},
{
fields: ['libraryId', 'mediaType', { name: 'titleIgnorePrefix', collate: 'NOCASE' }]
},
{ {
fields: ['libraryId', 'mediaId', 'mediaType'] fields: ['libraryId', 'mediaId', 'mediaType']
}, },
+3 -2
View File
@@ -202,8 +202,9 @@ class Podcast extends Model {
} else if (key === 'itunesPageUrl') { } else if (key === 'itunesPageUrl') {
newKey = 'itunesPageURL' newKey = 'itunesPageURL'
} }
if (typeof payload.metadata[key] === 'string' && payload.metadata[key] !== this[newKey]) { if ((typeof payload.metadata[key] === 'string' || payload.metadata[key] === null) && payload.metadata[key] !== this[newKey]) {
this[newKey] = payload.metadata[key] this[newKey] = payload.metadata[key] || null
if (key === 'title') { if (key === 'title') {
this.titleIgnorePrefix = getTitleIgnorePrefix(this.title) this.titleIgnorePrefix = getTitleIgnorePrefix(this.title)
} }
+1 -1
View File
@@ -169,7 +169,7 @@ class PodcastEpisode extends Model {
const track = structuredClone(this.audioFile) const track = structuredClone(this.audioFile)
track.startOffset = 0 track.startOffset = 0
track.title = this.audioFile.metadata.filename track.title = this.audioFile.metadata.filename
track.contentUrl = `${global.RouterBasePath}/api/items/${libraryItemId}/file/${track.ino}` track.contentUrl = `/api/items/${libraryItemId}/file/${track.ino}`
return track return track
} }
+1 -1
View File
@@ -74,7 +74,7 @@ class PodcastEpisodeDownload {
return this.rssPodcastEpisode.title return this.rssPodcastEpisode.title
} }
get targetFilename() { get targetFilename() {
const appendage = this.appendRandomId ? ` (${uuidv4()})` : '' const appendage = this.appendRandomId ? ` (${this.id})` : ''
const filename = `${this.rssPodcastEpisode.title}${appendage}.${this.fileExtension}` const filename = `${this.rssPodcastEpisode.title}${appendage}.${this.fileExtension}`
return sanitizeFilename(filename) return sanitizeFilename(filename)
} }
+2 -2
View File
@@ -29,7 +29,7 @@ class AudioTrack {
this.duration = audioFile.duration this.duration = audioFile.duration
this.title = audioFile.metadata.filename || '' this.title = audioFile.metadata.filename || ''
this.contentUrl = `${global.RouterBasePath}/api/items/${itemId}/file/${audioFile.ino}` this.contentUrl = `/api/items/${itemId}/file/${audioFile.ino}`
this.mimeType = audioFile.mimeType this.mimeType = audioFile.mimeType
this.codec = audioFile.codec || null this.codec = audioFile.codec || null
this.metadata = audioFile.metadata.clone() this.metadata = audioFile.metadata.clone()
@@ -44,4 +44,4 @@ class AudioTrack {
this.mimeType = 'application/vnd.apple.mpegurl' this.mimeType = 'application/vnd.apple.mpegurl'
} }
} }
module.exports = AudioTrack module.exports = AudioTrack
+1 -2
View File
@@ -1,5 +1,4 @@
const axios = require('axios').default const axios = require('axios').default
const htmlSanitizer = require('../utils/htmlSanitizer')
const Logger = require('../Logger') const Logger = require('../Logger')
const { isValidASIN } = require('../utils/index') const { isValidASIN } = require('../utils/index')
@@ -68,7 +67,7 @@ class Audible {
narrator: narrators ? narrators.map(({ name }) => name).join(', ') : null, narrator: narrators ? narrators.map(({ name }) => name).join(', ') : null,
publisher: publisherName, publisher: publisherName,
publishedYear: releaseDate ? releaseDate.split('-')[0] : null, publishedYear: releaseDate ? releaseDate.split('-')[0] : null,
description: summary ? htmlSanitizer.stripAllTags(summary) : null, description: summary || null,
cover: image, cover: image,
asin, asin,
genres: genresFiltered.length ? genresFiltered : null, genres: genresFiltered.length ? genresFiltered : null,
+2 -1
View File
@@ -1,6 +1,7 @@
const axios = require('axios').default const axios = require('axios').default
const Database = require('../Database') const Database = require('../Database')
const Logger = require('../Logger') const Logger = require('../Logger')
const htmlSanitizer = require('../utils/htmlSanitizer')
class CustomProviderAdapter { class CustomProviderAdapter {
#responseTimeout = 30000 #responseTimeout = 30000
@@ -74,7 +75,7 @@ class CustomProviderAdapter {
narrator, narrator,
publisher, publisher,
publishedYear, publishedYear,
description, description: htmlSanitizer.sanitize(description),
cover, cover,
isbn, isbn,
asin, asin,
+1 -1
View File
@@ -112,7 +112,7 @@ class iTunes {
artistId: data.artistId, artistId: data.artistId,
title: data.collectionName, title: data.collectionName,
author, author,
description: htmlSanitizer.stripAllTags(data.description || ''), description: data.description || null,
publishedYear: data.releaseDate ? data.releaseDate.split('-')[0] : null, publishedYear: data.releaseDate ? data.releaseDate.split('-')[0] : null,
genres: data.primaryGenreName ? [data.primaryGenreName] : null, genres: data.primaryGenreName ? [data.primaryGenreName] : null,
cover: this.getCoverArtwork(data) cover: this.getCoverArtwork(data)
+2
View File
@@ -521,6 +521,8 @@ class BookScanner {
libraryItemObj.isMissing = false libraryItemObj.isMissing = false
libraryItemObj.isInvalid = false libraryItemObj.isInvalid = false
libraryItemObj.extraData = {} libraryItemObj.extraData = {}
libraryItemObj.title = bookMetadata.title
libraryItemObj.titleIgnorePrefix = getTitleIgnorePrefix(bookMetadata.title)
// Set isSupplementary flag on ebook library files // Set isSupplementary flag on ebook library files
for (const libraryFile of libraryItemObj.libraryFiles) { for (const libraryFile of libraryItemObj.libraryFiles) {
+13
View File
@@ -286,10 +286,23 @@ module.exports.downloadFile = (url, filepath, contentTypeFilter = null) => {
return reject(new Error(`Invalid content type "${response.headers?.['content-type'] || ''}"`)) return reject(new Error(`Invalid content type "${response.headers?.['content-type'] || ''}"`))
} }
const totalSize = parseInt(response.headers['content-length'], 10)
let downloadedSize = 0
// Write to filepath // Write to filepath
const writer = fs.createWriteStream(filepath) const writer = fs.createWriteStream(filepath)
response.data.pipe(writer) response.data.pipe(writer)
let lastProgress = 0
response.data.on('data', (chunk) => {
downloadedSize += chunk.length
const progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
if (progress >= lastProgress + 5) {
Logger.debug(`[fileUtils] File "${Path.basename(filepath)}" download progress: ${progress}% (${downloadedSize}/${totalSize} bytes)`)
lastProgress = progress
}
})
writer.on('finish', resolve) writer.on('finish', resolve)
writer.on('error', reject) writer.on('error', reject)
}) })
+13 -5
View File
@@ -1,11 +1,19 @@
const sanitizeHtml = require('../libs/sanitizeHtml') const sanitizeHtml = require('../libs/sanitizeHtml')
const { entities } = require("./htmlEntities"); const { entities } = require('./htmlEntities')
/**
*
* @param {string} html
* @returns {string}
* @throws {Error} if input is not a string
*/
function sanitize(html) { function sanitize(html) {
if (typeof html !== 'string') {
throw new Error('sanitizeHtml: input must be a string')
}
const sanitizerOptions = { const sanitizerOptions = {
allowedTags: [ allowedTags: ['p', 'ol', 'ul', 'li', 'a', 'strong', 'em', 'del', 'br', 'b', 'i'],
'p', 'ol', 'ul', 'li', 'a', 'strong', 'em', 'del', 'br'
],
disallowedTagsMode: 'discard', disallowedTagsMode: 'discard',
allowedAttributes: { allowedAttributes: {
a: ['href', 'name', 'target'] a: ['href', 'name', 'target']
@@ -34,6 +42,6 @@ function decodeHTMLEntities(strToDecode) {
if (entity in entities) { if (entity in entities) {
return entities[entity] return entities[entity]
} }
return entity; return entity
}) })
} }
+3 -1
View File
@@ -56,7 +56,9 @@ async function extractCoverImage(epubPath, epubImageFilepath, outputCoverPath) {
return false return false
}) })
await zip.close() await zip.close().catch((error) => {
Logger.error(`[parseEpubMetadata] Failed to close zip`, error)
})
return success return success
} }
+14 -6
View File
@@ -35,11 +35,18 @@ module.exports.nameToLastFirst = (firstLast) => {
return `${nameObj.last_name}, ${nameObj.first_name}` return `${nameObj.last_name}, ${nameObj.first_name}`
} }
// Handle any name string /**
* Parses a name string into an array of names
*
* @param {string} nameString - The name string to parse
* @returns {{ names: string[] }} Array of names
*/
module.exports.parse = (nameString) => { module.exports.parse = (nameString) => {
if (!nameString) return null if (!nameString) return null
var splitNames = [] let splitNames = []
const isCommaSeparated = nameString.includes(',')
// Example &LF: Friedman, Milton & Friedman, Rose // Example &LF: Friedman, Milton & Friedman, Rose
if (nameString.includes('&')) { if (nameString.includes('&')) {
nameString.split('&').forEach((asa) => (splitNames = splitNames.concat(asa.split(',')))) nameString.split('&').forEach((asa) => (splitNames = splitNames.concat(asa.split(','))))
@@ -59,17 +66,18 @@ module.exports.parse = (nameString) => {
} }
} }
var names = [] let names = []
// 1 name FIRST LAST // 1 name FIRST LAST
if (splitNames.length === 1) { if (splitNames.length === 1) {
names.push(parseName(nameString)) names.push(parseName(nameString))
} else { } else {
var firstChunkIsALastName = checkIsALastName(splitNames[0]) // Determines whether this is formatted as last, first or first last (only if using comma separator)
var isEvenNum = splitNames.length % 2 === 0 // Example: "Smith; James Jones" -> ["Smith", "James Jones"]
let firstChunkIsALastName = !isCommaSeparated ? false : checkIsALastName(splitNames[0])
let isEvenNum = splitNames.length % 2 === 0
if (!isEvenNum && firstChunkIsALastName) { if (!isEvenNum && firstChunkIsALastName) {
// console.error('Multi-name LAST,FIRST entry has a straggler (could be roman numerals or a suffix), ignore it')
splitNames = splitNames.slice(0, splitNames.length - 1) splitNames = splitNames.slice(0, splitNames.length - 1)
} }
+9 -4
View File
@@ -141,15 +141,19 @@ function extractPodcastMetadata(channel) {
function extractEpisodeData(item) { function extractEpisodeData(item) {
// Episode must have url // Episode must have url
if (!item.enclosure?.[0]?.['$']?.url) { let enclosure
if (item.enclosure?.[0]?.['$']?.url) {
enclosure = item.enclosure[0]['$']
} else if(item['media:content']?.find(c => c?.['$']?.url && (c?.['$']?.type ?? "").startsWith("audio"))) {
enclosure = item['media:content'].find(c => (c['$']?.type ?? "").startsWith("audio"))['$']
} else {
Logger.error(`[podcastUtils] Invalid podcast episode data`) Logger.error(`[podcastUtils] Invalid podcast episode data`)
return null return null
} }
const episode = { const episode = {
enclosure: { enclosure: enclosure,
...item.enclosure[0]['$']
}
} }
episode.enclosure.url = episode.enclosure.url.trim() episode.enclosure.url = episode.enclosure.url.trim()
@@ -307,6 +311,7 @@ module.exports.getPodcastFeed = (feedUrl, excludeEpisodeMetadata = false) => {
responseType: 'arraybuffer', responseType: 'arraybuffer',
headers: { headers: {
Accept: 'application/rss+xml, application/xhtml+xml, application/xml, */*;q=0.8', Accept: 'application/rss+xml, application/xhtml+xml, application/xml, */*;q=0.8',
'Accept-Encoding': 'gzip, compress, deflate',
'User-Agent': userAgent 'User-Agent': userAgent
}, },
httpAgent: global.DisableSsrfRequestFilter?.(feedUrl) ? null : ssrfFilter(feedUrl), httpAgent: global.DisableSsrfRequestFilter?.(feedUrl) ? null : ssrfFilter(feedUrl),
+41
View File
@@ -0,0 +1,41 @@
const { performance, createHistogram } = require('perf_hooks')
const util = require('util')
const Logger = require('../Logger')
const histograms = new Map()
function profile(asyncFunc, isFindQuery = true, funcName = asyncFunc.name) {
if (!histograms.has(funcName)) {
const histogram = createHistogram()
histogram.values = []
histograms.set(funcName, histogram)
}
const histogram = histograms.get(funcName)
return async (...args) => {
if (isFindQuery) {
const findOptions = args[0]
Logger.info(`[${funcName}] findOptions:`, util.inspect(findOptions, { depth: null }))
findOptions.logging = (query, time) => Logger.info(`[${funcName}] ${query} Elapsed time: ${time}ms`)
findOptions.benchmark = true
}
const start = performance.now()
try {
const result = await asyncFunc(...args)
return result
} catch (error) {
Logger.error(`[${funcName}] failed`)
throw error
} finally {
const end = performance.now()
const duration = Math.round(end - start)
histogram.record(duration)
histogram.values.push(duration)
Logger.info(`[${funcName}] duration: ${duration}ms`)
Logger.info(`[${funcName}] histogram values:`, histogram.values)
Logger.info(`[${funcName}] histogram:`, histogram)
}
}
}
module.exports = { profile }
@@ -4,6 +4,9 @@ const Logger = require('../../Logger')
const authorFilters = require('./authorFilters') const authorFilters = require('./authorFilters')
const ShareManager = require('../../managers/ShareManager') const ShareManager = require('../../managers/ShareManager')
const { profile } = require('../profiler')
const countCache = new Map()
module.exports = { module.exports = {
/** /**
@@ -270,9 +273,9 @@ module.exports = {
} }
if (global.ServerSettings.sortingIgnorePrefix) { if (global.ServerSettings.sortingIgnorePrefix) {
return [[Sequelize.literal('titleIgnorePrefix COLLATE NOCASE'), dir]] return [[Sequelize.literal('`libraryItem`.`titleIgnorePrefix` COLLATE NOCASE'), dir]]
} else { } else {
return [[Sequelize.literal('`book`.`title` COLLATE NOCASE'), dir]] return [[Sequelize.literal('`libraryItem`.`title` COLLATE NOCASE'), dir]]
} }
} else if (sortBy === 'sequence') { } else if (sortBy === 'sequence') {
const nullDir = sortDesc ? 'DESC NULLS FIRST' : 'ASC NULLS LAST' const nullDir = sortDesc ? 'DESC NULLS FIRST' : 'ASC NULLS LAST'
@@ -336,6 +339,28 @@ module.exports = {
return { booksToExclude, bookSeriesToInclude } return { booksToExclude, bookSeriesToInclude }
}, },
clearCountCache(hook) {
Logger.debug(`[LibraryItemsBookFilters] book.${hook}: Clearing count cache`)
countCache.clear()
},
async findAndCountAll(findOptions, limit, offset) {
const findOptionsKey = JSON.stringify(findOptions)
Logger.debug(`[LibraryItemsBookFilters] findOptionsKey: ${findOptionsKey}`)
findOptions.limit = limit || null
findOptions.offset = offset
if (countCache.has(findOptionsKey)) {
const rows = await Database.bookModel.findAll(findOptions)
return { rows, count: countCache.get(findOptionsKey) }
} else {
const result = await Database.bookModel.findAndCountAll(findOptions)
countCache.set(findOptionsKey, result.count)
return result
}
},
/** /**
* Get library items for book media type using filter and sort * Get library items for book media type using filter and sort
* @param {string} libraryId * @param {string} libraryId
@@ -411,7 +436,8 @@ module.exports = {
if (includeRSSFeed) { if (includeRSSFeed) {
libraryItemIncludes.push({ libraryItemIncludes.push({
model: Database.feedModel, model: Database.feedModel,
required: filterGroup === 'feed-open' required: filterGroup === 'feed-open',
separate: true
}) })
} }
if (filterGroup === 'feed-open' && !includeRSSFeed) { if (filterGroup === 'feed-open' && !includeRSSFeed) {
@@ -560,7 +586,7 @@ module.exports = {
} }
} }
const { rows: books, count } = await Database.bookModel.findAndCountAll({ const findOptions = {
where: bookWhere, where: bookWhere,
distinct: true, distinct: true,
attributes: bookAttributes, attributes: bookAttributes,
@@ -577,10 +603,11 @@ module.exports = {
...bookIncludes ...bookIncludes
], ],
order: sortOrder, order: sortOrder,
subQuery: false, subQuery: false
limit: limit || null, }
offset
}) const findAndCountAll = process.env.QUERY_PROFILING ? profile(this.findAndCountAll) : this.findAndCountAll
const { rows: books, count } = await findAndCountAll(findOptions, limit, offset)
const libraryItems = books.map((bookExpanded) => { const libraryItems = books.map((bookExpanded) => {
const libraryItem = bookExpanded.libraryItem const libraryItem = bookExpanded.libraryItem
@@ -129,9 +129,9 @@ describe('migration-v2.15.0-series-column-unique', () => {
{ id: series1Id, name: 'Series 1', libraryId: library1Id, createdAt: new Date(), updatedAt: new Date() }, { id: series1Id, name: 'Series 1', libraryId: library1Id, createdAt: new Date(), updatedAt: new Date() },
{ id: series2Id, name: 'Series 2', libraryId: library2Id, createdAt: new Date(), updatedAt: new Date() }, { id: series2Id, name: 'Series 2', libraryId: library2Id, createdAt: new Date(), updatedAt: new Date() },
{ id: series3Id, name: 'Series 3', libraryId: library1Id, createdAt: new Date(), updatedAt: new Date() }, { id: series3Id, name: 'Series 3', libraryId: library1Id, createdAt: new Date(), updatedAt: new Date() },
{ id: series1Id_dup, name: 'Series 1', libraryId: library1Id, createdAt: new Date(), updatedAt: new Date() }, { id: series1Id_dup, name: 'Series 1', libraryId: library1Id, createdAt: new Date(0), updatedAt: new Date(0) },
{ id: series3Id_dup, name: 'Series 3', libraryId: library1Id, createdAt: new Date(), updatedAt: new Date() }, { id: series3Id_dup, name: 'Series 3', libraryId: library1Id, createdAt: new Date(0), updatedAt: new Date(0) },
{ id: series1Id_dup2, name: 'Series 1', libraryId: library1Id, createdAt: new Date(), updatedAt: new Date() } { id: series1Id_dup2, name: 'Series 1', libraryId: library1Id, createdAt: new Date(0), updatedAt: new Date(0) }
]) ])
// Add some entries to the BookSeries table // Add some entries to the BookSeries table
await queryInterface.bulkInsert('BookSeries', [ await queryInterface.bulkInsert('BookSeries', [
@@ -0,0 +1,148 @@
const chai = require('chai')
const sinon = require('sinon')
const { expect } = chai
const { DataTypes, Sequelize } = require('sequelize')
const Logger = require('../../../server/Logger')
const { up, down } = require('../../../server/migrations/v2.19.1-copy-title-to-library-items')
describe('Migration v2.19.1-copy-title-to-library-items', () => {
let sequelize
let queryInterface
let loggerInfoStub
beforeEach(async () => {
sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
queryInterface = sequelize.getQueryInterface()
loggerInfoStub = sinon.stub(Logger, 'info')
await queryInterface.createTable('books', {
id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, unique: true },
title: { type: DataTypes.STRING, allowNull: true },
titleIgnorePrefix: { type: DataTypes.STRING, allowNull: true }
})
await queryInterface.createTable('libraryItems', {
id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true, unique: true },
libraryId: { type: DataTypes.INTEGER, allowNull: false },
mediaType: { type: DataTypes.STRING, allowNull: false },
mediaId: { type: DataTypes.INTEGER, allowNull: false },
createdAt: { type: DataTypes.DATE, allowNull: false }
})
await queryInterface.bulkInsert('books', [
{ id: 1, title: 'The Book 1', titleIgnorePrefix: 'Book 1, The' },
{ id: 2, title: 'Book 2', titleIgnorePrefix: 'Book 2' }
])
await queryInterface.bulkInsert('libraryItems', [
{ id: 1, libraryId: 1, mediaType: 'book', mediaId: 1, createdAt: '2025-01-01 00:00:00.000 +00:00' },
{ id: 2, libraryId: 2, mediaType: 'book', mediaId: 2, createdAt: '2025-01-02 00:00:00.000 +00:00' }
])
})
afterEach(() => {
sinon.restore()
})
describe('up', () => {
it('should copy title and titleIgnorePrefix to libraryItems', async () => {
await up({ context: { queryInterface, logger: Logger } })
const [libraryItems] = await queryInterface.sequelize.query('SELECT * FROM libraryItems')
expect(libraryItems).to.deep.equal([
{ id: 1, libraryId: 1, mediaType: 'book', mediaId: 1, title: 'The Book 1', titleIgnorePrefix: 'Book 1, The', createdAt: '2025-01-01 00:00:00.000 +00:00' },
{ id: 2, libraryId: 2, mediaType: 'book', mediaId: 2, title: 'Book 2', titleIgnorePrefix: 'Book 2', createdAt: '2025-01-02 00:00:00.000 +00:00' }
])
})
it('should add index on title to libraryItems', async () => {
await up({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_id_media_type_title_ignore_prefix'`)
expect(count).to.equal(1)
})
it('should add trigger to books.title to update libraryItems.title', async () => {
await up({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='trigger' AND name='update_library_items_title'`)
expect(count).to.equal(1)
})
it('should add index on titleIgnorePrefix to libraryItems', async () => {
await up({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_id_media_type_title_ignore_prefix'`)
expect(count).to.equal(1)
})
it('should add trigger to books.titleIgnorePrefix to update libraryItems.titleIgnorePrefix', async () => {
await up({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='trigger' AND name='update_library_items_title_ignore_prefix'`)
expect(count).to.equal(1)
})
it('should add index on createdAt to libraryItems', async () => {
await up({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_id_media_type_created_at'`)
expect(count).to.equal(1)
})
})
describe('down', () => {
it('should remove title and titleIgnorePrefix from libraryItems', async () => {
await up({ context: { queryInterface, logger: Logger } })
await down({ context: { queryInterface, logger: Logger } })
const [libraryItems] = await queryInterface.sequelize.query('SELECT * FROM libraryItems')
expect(libraryItems).to.deep.equal([
{ id: 1, libraryId: 1, mediaType: 'book', mediaId: 1, createdAt: '2025-01-01 00:00:00.000 +00:00' },
{ id: 2, libraryId: 2, mediaType: 'book', mediaId: 2, createdAt: '2025-01-02 00:00:00.000 +00:00' }
])
})
it('should remove title trigger from books', async () => {
await up({ context: { queryInterface, logger: Logger } })
await down({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='trigger' AND name='update_library_items_title'`)
expect(count).to.equal(0)
})
it('should remove titleIgnorePrefix trigger from books', async () => {
await up({ context: { queryInterface, logger: Logger } })
await down({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='trigger' AND name='update_library_items_title_ignore_prefix'`)
expect(count).to.equal(0)
})
it('should remove index on titleIgnorePrefix from libraryItems', async () => {
await up({ context: { queryInterface, logger: Logger } })
await down({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_id_media_type_title_ignore_prefix'`)
expect(count).to.equal(0)
})
it('should remove index on title from libraryItems', async () => {
await up({ context: { queryInterface, logger: Logger } })
await down({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_id_media_type_title'`)
expect(count).to.equal(0)
})
it('should remove index on createdAt from libraryItems', async () => {
await up({ context: { queryInterface, logger: Logger } })
await down({ context: { queryInterface, logger: Logger } })
const [[{ count }]] = await queryInterface.sequelize.query(`SELECT COUNT(*) as count FROM sqlite_master WHERE type='index' AND name='library_items_library_id_media_type_created_at'`)
expect(count).to.equal(0)
})
})
})
@@ -0,0 +1,99 @@
const chai = require('chai')
const expect = chai.expect
const { parse, nameToLastFirst } = require('../../../../server/utils/parsers/parseNameString')
describe('parseNameString', () => {
describe('parse', () => {
it('returns null if nameString is empty', () => {
const result = parse('')
expect(result).to.be.null
})
it('parses single name in First Last format', () => {
const result = parse('John Smith')
expect(result.names).to.deep.equal(['John Smith'])
})
it('parses single name in Last, First format', () => {
const result = parse('Smith, John')
expect(result.names).to.deep.equal(['John Smith'])
})
it('parses multiple names separated by &', () => {
const result = parse('John Smith & Jane Doe')
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe'])
})
it('parses multiple names separated by "and"', () => {
const result = parse('John Smith and Jane Doe')
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe'])
})
it('parses multiple names separated by comma and "and"', () => {
const result = parse('John Smith, Jane Doe and John Doe')
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe', 'John Doe'])
})
it('parses multiple names separated by semicolon', () => {
const result = parse('John Smith; Jane Doe')
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe'])
})
it('parses multiple names in Last, First format', () => {
const result = parse('Smith, John, Doe, Jane')
expect(result.names).to.deep.equal(['John Smith', 'Jane Doe'])
})
it('parses multiple names with single word name', () => {
const result = parse('John Smith, Jones, James Doe, Ludwig von Mises')
expect(result.names).to.deep.equal(['John Smith', 'Jones', 'James Doe', 'Ludwig von Mises'])
})
it('parses multiple names with single word name listed first (semicolon separator)', () => {
const result = parse('Jones; John Smith; James Doe; Ludwig von Mises')
expect(result.names).to.deep.equal(['Jones', 'John Smith', 'James Doe', 'Ludwig von Mises'])
})
it('handles names with suffixes', () => {
const result = parse('Smith, John Jr.')
expect(result.names).to.deep.equal(['John Jr. Smith'])
})
it('handles compound last names', () => {
const result = parse('von Mises, Ludwig')
expect(result.names).to.deep.equal(['Ludwig von Mises'])
})
it('handles Chinese/Japanese/Korean names', () => {
const result = parse('张三, 李四')
expect(result.names).to.deep.equal(['张三', '李四'])
})
it('removes duplicate names', () => {
const result = parse('John Smith & John Smith')
expect(result.names).to.deep.equal(['John Smith'])
})
it('filters out empty names', () => {
const result = parse('John Smith,')
expect(result.names).to.deep.equal(['John Smith'])
})
})
describe('nameToLastFirst', () => {
it('converts First Last to Last, First format', () => {
const result = nameToLastFirst('John Smith')
expect(result).to.equal('Smith, John')
})
it('returns last name only when no first name', () => {
const result = nameToLastFirst('Smith')
expect(result).to.equal('Smith')
})
it('handles names with middle names', () => {
const result = nameToLastFirst('John Middle Smith')
expect(result).to.equal('Smith, John Middle')
})
})
})