Compare commits

...

219 Commits

Author SHA1 Message Date
advplyr e70e4b9d40 Fix typo on onTest notification body 2026-05-30 15:43:50 -05:00
advplyr aacdcc47ec Version bump v2.35.1 2026-05-28 15:22:55 -05:00
advplyr 499b52b4dd Update Sequelize where query for User username/email case insensitive 2026-05-28 14:49:49 -05:00
advplyr 1bad2d9072 Cleanup abmetadata file parsing & fix server crash #5268 #4287 #5142 2026-05-27 17:33:14 -05:00
advplyr c009db9f28 Merge pull request #5256 from nichwall/fix-bookauthor-collision-on-rename
Fix duplicate bookAuthor creation when renaming authors
2026-05-22 15:43:13 -05:00
advplyr 325469c5a5 Merge pull request #5255 from nichwall/refresh-token-uniqueness
Add unique UUID to access and refresh tokens
2026-05-22 15:39:01 -05:00
Nicholas Wallace c97b36e11c Add ignoreDuplicates for bookAuthor when renaming to respect unique index 2026-05-21 21:06:17 -07:00
Nicholas Wallace e944b2a2f5 Add unique UUID to access and refresh tokens 2026-05-21 17:08:39 -07:00
advplyr 2d0a5462d2 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2026-05-17 14:31:45 -05:00
advplyr 72dc75482f Version bump v2.35.0 2026-05-17 14:31:41 -05:00
advplyr cac74f3477 Merge pull request #5004 from nichwall/token_refresh_race_condition
Access token refresh grace period
2026-05-17 14:16:58 -05:00
advplyr 1ad11b2b9e Merge pull request #5216 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2026-05-17 14:16:24 -05:00
Pavel Miniutka 50eeca2e0f Translated using Weblate (Belarusian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2026-05-15 18:13:27 +00:00
EteranlK 4f21fc023c Translated using Weblate (Arabic)
Currently translated at 96.3% (1120 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2026-05-15 18:13:26 +00:00
advplyr 52a485d135 Added translation using Weblate (Latvian) 2026-05-15 18:13:25 +00:00
d0nizam 3b025076e8 Translated using Weblate (Bulgarian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bg/
2026-05-15 18:13:23 +00:00
Mateusz Lesiak 6d5d89429d Translated using Weblate (Polish)
Currently translated at 99.8% (1161 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2026-05-15 18:13:22 +00:00
advplyr c010f0e1eb Fix android device sdkVersion not handling it using number type, causing android session device names to show as iOS 2026-05-15 13:13:14 -05:00
advplyr eee377e081 Cleanup TokenManager logs 2026-05-13 16:23:26 -05:00
advplyr b0aaa24660 Update socket events to check client is admin & validate log level 2026-05-12 16:57:28 -05:00
advplyr 47ea6b5092 Update book/podcast scanner to sanitize description pulled from metadata 2026-05-05 17:18:49 -05:00
advplyr 4b060febc2 Merge pull request #5221 from brandonfhall/fix/rss-feed-m4b-content-type
Fix: RSS feed serves m4b files with correct Content-Type: audio/mp4
2026-05-03 14:40:43 -05:00
Brandon 40869bcf39 fix: set correct Content-Type for RSS feed audio files
Express's mime package does not recognize .m4b, causing it to fall back
to application/octet-stream. This reuses the existing
getAudioMimeTypeFromExtname utility (already applied to the download
endpoint) to set the correct audio/mp4 header before sendFile.

Fixes #5041
2026-05-02 22:13:35 -04:00
advplyr 3942805129 Cleanup rotateTokensForSession 2026-04-30 16:25:43 -05:00
advplyr dc446862c1 Rename migration to v2.35.0 & merge master 2026-04-30 16:08:24 -05:00
advplyr 379f6c716a Merge branch 'master' into token_refresh_race_condition 2026-04-30 15:59:22 -05:00
advplyr 47457ee1e7 Version bump v2.34.0 2026-04-27 16:51:34 -05:00
advplyr cb6ff9eedf Merge pull request #5204 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2026-04-27 16:45:57 -05:00
LvanAlphen 5dc01261c1 Translated using Weblate (Dutch)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2026-04-26 21:51:49 +00:00
Naoto Ishikawa cbc103cf05 Translated using Weblate (Japanese)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/
2026-04-26 21:51:49 +00:00
Pavel Miniutka e79256d0fb Translated using Weblate (Belarusian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2026-04-26 21:51:48 +00:00
ugyes f8ef56c6bc Translated using Weblate (Hungarian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2026-04-26 21:51:48 +00:00
advplyr 62d7097e23 Add ApiCacheManager test for should remove recent-episodes cache entries 2026-04-26 16:51:39 -05:00
advplyr 92df92ec99 Fix recent episodes endpoint cache not being cleared when updating media progress #5159 2026-04-26 16:51:08 -05:00
advplyr 1c229e0627 Merge pull request #5211 from na3shkw/add-i18n-japanese
Add Japanese (ja) language and Japan podcast search region
2026-04-26 16:19:37 -05:00
advplyr f8a71cc514 Merge pull request #5089 from meek2100/pass_managers
feat: add autocomplete attributes for password manager support
2026-04-26 16:16:42 -05:00
Naoto Ishikawa 63de5bb2d5 Merge branch 'advplyr:master' into add-i18n-japanese 2026-04-26 15:23:22 +09:00
advplyr 2c3108a1fa Merge pull request #5163 from pjkottke/master
The timestamp in the share URL should override the saved position for the user.
2026-04-25 17:15:23 -05:00
advplyr 928051744a ShareController check ?t param is less than duration, revert frontend mounted usage of param 2026-04-25 17:13:22 -05:00
advplyr 3ccdcaec1a Implement SSRF filter for podcast episode downloads 2026-04-25 16:46:54 -05:00
na3shkw f47bbc7886 Add Japanese language and Japan podcast search region 2026-04-25 15:56:16 +00:00
advplyr 7c0ca44727 Update podcast create/update endpoints to validate autoDownloadSchedule cron expression, validate cron expression before starting in CronManager 2026-04-24 16:55:42 -05:00
advplyr d6a2e5596b Fix undefined variable in error log for when podcast cron is invalid 2026-04-24 16:18:56 -05:00
advplyr a5362de9cc Update podcast createFromRequest to sanitize html description 2026-04-23 14:34:59 -05:00
advplyr 9ab35ef418 Update playlist endpoints to check user still has library access 2026-04-22 16:42:58 -05:00
advplyr 79cc9765cf Update collection endpoints to check user library access 2026-04-22 16:29:47 -05:00
advplyr 5b2a788cfc Update LibraryItemController test with 403 tests 2026-04-21 17:13:52 -05:00
advplyr 80b39abaa2 Update library item batch api endpoints check users per-item access & return 403 2026-04-21 17:13:06 -05:00
advplyr b41db23994 Version bump v2.33.2 2026-04-19 16:46:10 -05:00
advplyr 125f265f55 Merge pull request #5141 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2026-04-19 16:21:40 -05:00
Gernomaly aa4a191567 Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2026-04-19 21:20:46 +00:00
Jan e431ea0472 Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2026-04-19 21:20:45 +00:00
Laurin Sorgend e3388d4446 Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2026-04-19 21:20:45 +00:00
Mario 88879f1409 Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2026-04-19 21:20:44 +00:00
Pavel Miniutka 3e0099e8d9 Translated using Weblate (Belarusian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2026-04-19 21:20:44 +00:00
tizio04 f558182d94 Translated using Weblate (Italian)
Currently translated at 99.9% (1162 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2026-04-19 21:20:43 +00:00
Владимир Макеев a30fe15b10 Translated using Weblate (Russian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2026-04-19 21:20:43 +00:00
A L 0bbf8bde5c Translated using Weblate (Bulgarian)
Currently translated at 91.2% (1061 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bg/
2026-04-19 21:20:42 +00:00
Alex 0e2cdde731 Translated using Weblate (Slovak)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2026-04-19 21:20:41 +00:00
tfr tint bc6bfbe804 Translated using Weblate (Italian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2026-04-19 21:20:41 +00:00
Vadzim Kurdzesau 2755204168 Translated using Weblate (Russian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2026-04-19 21:20:40 +00:00
Francisco Serrador 2d4df273f0 Translated using Weblate (Spanish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2026-04-19 21:20:40 +00:00
Pavel Miniutka d73b64a19c Translated using Weblate (Belarusian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2026-04-19 21:20:39 +00:00
advplyr b7e8a0474a Update bulk download endpoint ensure items are from the same library requested 2026-04-19 16:20:31 -05:00
advplyr 39adefb632 Update backup load & upload to remove tempfile on failed backups, validate details filesize & close zip 2026-04-18 17:03:37 -05:00
advplyr 24cab79c66 Update filesystem/pathexists endpoint to use existing isSameOrSubPath func 2026-04-18 16:24:48 -05:00
advplyr b27f21fd95 Update podcastUtils to sanitize episode subtitle from rss feed 2026-04-17 16:59:22 -05:00
advplyr 09fa0b38f5 Update podcast create path validation & fix relPath 2026-04-17 16:51:22 -05:00
advplyr 455e605162 Update author & library item image endpoints to clamp width/height query params 2026-04-17 16:30:08 -05:00
advplyr 88667d00a1 Merge pull request #5115 from rktjmp/fix-mka-opus-desktop-streaming
Force AAC transcode when streaming mka+opus to desktop client
2026-04-10 16:54:18 -05:00
advplyr 94c426bd97 Update comments on matroska 2026-04-10 16:42:39 -05:00
Oliver Marriott 522b9735e2 Add audio/(x-)matroska to client player MIME types to avoid transcode
Firefox, at least, supports playing `matroska/audio` containers natively but
the client was not checking for support. Clients  that do not support
playing `matroska/audio` containers will fallback to transcoding.
2026-04-09 21:07:14 +10:00
peter.kottke 5a6b3d8e61 updates to allow share t argument to over-ride server stored position 2026-04-01 21:05:48 -04:00
advplyr 64cbf59609 Merge pull request #5160 from mikiher/fix-item-removed-payload
Fix item_removed payload to include libraryId
2026-03-31 16:43:51 -05:00
mikiher fda1a6ea9b Fix item_removed payload to include libraryId 2026-03-31 22:02:52 +03:00
advplyr c4c8b8d0f2 Merge pull request #5158 from mikiher/book-update-author-events
Emit proper author_updated/added events when updating book media
2026-03-30 16:26:47 -05:00
advplyr ab3bd6f4a1 Update JS docs 2026-03-30 16:22:27 -05:00
mikiher 093124aac6 Emit proper author_updated/added events when updating book media 2026-03-30 22:02:56 +03:00
advplyr 5de92d08f9 Fix share playback session not including coverAspectRatio 2026-03-29 15:36:07 -05:00
advplyr 8b89b27654 Version bump v2.33.1 2026-03-19 17:09:14 -05:00
advplyr 3faa6f3e7d Update playlist create/update endpoint to strip all html tags 2026-03-19 16:57:22 -05:00
advplyr 9821c31f8e Update collection create/update endpoints to strip html tags from collection name 2026-03-19 16:53:21 -05:00
advplyr efe2a22674 Merge pull request #5122 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2026-03-19 16:41:19 -05:00
Francisco Serrador 9634c46bc5 Translated using Weblate (Spanish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2026-03-18 23:01:31 +01:00
Francisco Serrador 5f8db24b96 Translated using Weblate (Spanish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2026-03-18 23:01:30 +01:00
Francisco Serrador e781ff5eae Translated using Weblate (Spanish)
Currently translated at 99.7% (1160 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2026-03-18 23:01:30 +01:00
Francisco Serrador 32a17c0044 Translated using Weblate (Spanish)
Currently translated at 99.7% (1160 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2026-03-18 23:01:29 +01:00
Fabian Jülich f84831d6f1 Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2026-03-18 23:01:29 +01:00
Pavel Miniutka dc54d42dcf Translated using Weblate (Belarusian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2026-03-18 23:01:28 +01:00
Pavel Miniutka 15af7407ff Translated using Weblate (Belarusian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2026-03-18 23:01:27 +01:00
Charlie 5d9682410a Translated using Weblate (French)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2026-03-18 23:01:27 +01:00
advplyr 4bdd76d94c Update podcast episode update endpoint to sanitize subtitle 2026-03-18 17:01:19 -05:00
advplyr 7c0d9efe91 Update Confirm component to support allowHtml prompt option 2026-03-18 16:51:51 -05:00
advplyr 874e9e1856 Update API Key jwtAuthCheck to check user active status 2026-03-18 16:17:45 -05:00
advplyr 6d3773a0b8 Version bump v2.33.0 2026-03-12 17:01:54 -05:00
advplyr a47c869d0b Update migration file to v2.33.0 2026-03-12 16:45:08 -05:00
advplyr eb0383d37a Merge pull request #5073 from kevingatera/perf/minimal-upstream-patchset
Improve personalized/discover query performance and cache invalidation behavior
2026-03-12 16:38:26 -05:00
advplyr e66ffb9c23 Add indexes to MediaProgress and BookSeries models 2026-03-12 16:37:59 -05:00
advplyr 972193b193 Update server settings authLoginCustomMessage to sanitize on save and load 2026-03-11 17:18:05 -05:00
advplyr 690a7e0da9 Update session DeviceInfo with sanitize on clientDeviceInfo 2026-03-11 17:03:07 -05:00
Oliver Marriott d9355ac3aa Force AAC transcode when streaming mka+opus to desktop client
Matroska audio containers (aka mka files) with Opus codec streams inside
were unplayable on the desktop client because hls.js was unable to
decode the stream, resulting in an infinitely "spinning" play button.

When configuring a stream, we now check for the opus codec and force AAC
transcoding.

Matroska containers support other codecs besides Opus, eg: mp3, which do
not require transcoding and work fine before this patch, which is why we
check for opus in codecsToForceAAC instead of AudioMimeType.MKA in
mimeTypesToForceAAC.

The AudioMimeType.OPUS mimetype is already marked as requiring
transcoding but since its inside a container this check does not
evaluate to true, we must check the codec explicitly.
2026-03-11 00:35:12 +11:00
advplyr fbe1d1eed6 Merge pull request #5030 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2026-03-08 17:30:54 -05:00
Grzegorz Orlowski e83aca572e Translated using Weblate (Polish)
Currently translated at 95.7% (1113 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2026-03-08 23:26:10 +01:00
Artur 367826ce64 Translated using Weblate (Polish)
Currently translated at 95.7% (1113 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2026-03-08 23:26:10 +01:00
Torstein Eide 6e6c43c53c Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2026-03-08 23:26:10 +01:00
weblate.user.1274 6479cdb66d Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2026-03-08 23:26:10 +01:00
Øystein S. Hegnander 635e132325 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2026-03-08 23:26:10 +01:00
thehijacker 45dd843ce1 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2026-03-08 23:26:10 +01:00
Torstein Eide 7c956b1582 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2026-03-08 23:26:10 +01:00
Pavel Miniutka 9afa39e29e Translated using Weblate (Belarusian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2026-03-08 23:26:10 +01:00
Pavel Miniutka 5f8450602e Translated using Weblate (Belarusian)
Currently translated at 92.1% (1072 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2026-03-08 23:26:10 +01:00
Pavel Miniutka fa6dae1a53 Translated using Weblate (Belarusian)
Currently translated at 79.8% (929 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2026-03-08 23:26:10 +01:00
lambolighting a1d439b8d5 Translated using Weblate (Greek)
Currently translated at 27.6% (322 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/el/
2026-03-08 23:26:10 +01:00
peter cerny 2f32673991 Translated using Weblate (Slovak)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2026-03-08 23:26:10 +01:00
herny ucet c1a6b51d78 Translated using Weblate (Slovak)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2026-03-08 23:26:09 +01:00
Gneb 6c2e13fb4e Translated using Weblate (Slovak)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2026-03-08 23:26:09 +01:00
Plazec 210fa55b6a Translated using Weblate (Czech)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2026-03-08 23:26:09 +01:00
Silviu Bajenaru fb8ca043ad Translated using Weblate (Romanian)
Currently translated at 55.1% (641 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ro/
2026-03-08 23:26:09 +01:00
Karl Bernstål 69d7c399b8 Translated using Weblate (Swedish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2026-03-08 23:26:09 +01:00
Igor Dobrača a35ba05600 Translated using Weblate (Croatian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2026-03-08 23:26:09 +01:00
B0rax 40f42b2ab6 Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2026-03-08 23:26:09 +01:00
Maxklos cf4b9e938d Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2026-03-08 23:26:09 +01:00
ugyes 31120ad111 Translated using Weblate (Hungarian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2026-03-08 23:26:09 +01:00
Yusuke Sakai cd8640f00e Translated using Weblate (Japanese)
Currently translated at 23.1% (269 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/
2026-03-08 23:26:09 +01:00
Luis Landeiro d0ba455ed6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pt_BR/
2026-03-08 23:26:09 +01:00
advplyr e5b7aea46c Merge pull request #5063 from mandreko/idor-fixes
IDOR fixes
2026-03-08 17:26:03 -05:00
advplyr 2f2d026b06 Auto format 2026-03-08 17:25:52 -05:00
advplyr c156b063f5 Merge pull request #5036 from kctdfh/Slightly-better-subtitle-parsing-logic
Improved subtitle parsing to account for bare colon in title
2026-03-08 17:12:55 -05:00
advplyr e6d49a2d53 Fix home page check current user for hide from continue listening 2026-03-04 16:50:36 -06:00
advplyr 6d3404272c Fix Match click to use current label string 2026-02-28 11:32:28 -06:00
meek2100 a9e12657f5 Add autocomplete attributes to login and setup fields for password manager support 2026-02-26 14:29:28 -08:00
Kevin Gatera c0319ebbac Adjust discover/search changes for API compatibility 2026-02-23 19:22:45 -05:00
advplyr 1d0b7e383a Merge pull request #5071 from pavel-miniutka/master
Add Belarusian language option & Belarus podcast region
2026-02-22 16:19:19 -06:00
advplyr 9f5d8386f3 Merge pull request #5077 from belpe/add-slovak-language
Add Slovak (sk) language to language selector
2026-02-22 16:15:55 -06:00
advplyr 6e0da3bf7a Fix updating author name merging with same name authors in a different library #4628 2026-02-21 16:00:38 -06:00
Peter Belák ee6016f70e Add Slovak (sk) language to the language selector
The Slovak translation file (client/strings/sk.json) already exists
with a complete translation but was missing from the languageCodeMap
in i18n.js, making it inaccessible from the language dropdown.

Also adds Slovakia to the podcast search region map.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 09:11:50 +01:00
Kevin Gatera f1a2e56054 Add database indexes for discover query performance 2026-02-19 20:11:49 -05:00
Kevin Gatera d2915e689f Speed up personalized shelves and reduce search payload size 2026-02-19 20:11:38 -05:00
Kevin Gatera 05d9ab81f9 Improve API cache invalidation for high-churn models 2026-02-19 20:11:24 -05:00
pavel-miniutka 75eed9d09a Add Belarusian language option & Belarus podcast region 2026-02-19 10:36:28 +03:00
Matt Andreko e5af2f336b Move file to correct folder... 2026-02-15 22:06:20 -05:00
Matt Andreko ade1752e97 Fix IDOR bugs 2026-02-15 22:01:36 -05:00
advplyr fa5fa7b788 Fix server crash on /me/progress/:libraryItemId/:episodeId? when episodeId is not passed in for a podcast library item #5058 2026-02-14 17:17:12 -06:00
advplyr b01facc034 Merge pull request #5042 from openam/open-api-spec-fixes
Fix OpenAPI spec description
2026-02-08 17:23:18 -06:00
Michael Tuttle dd4fc09909 Fix OpenAPI spec description 2026-02-08 00:19:23 -07:00
Khashayar Toodehfallah c15cb48def Improved subtitle parsing to account for bare colon in title 2026-02-06 14:31:09 -05:00
advplyr fe13456a2b Merge pull request #4936 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2026-02-03 17:26:42 -06:00
Lluís Forns 2ee893062f Translated using Weblate (Catalan)
Currently translated at 92.2% (1073 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2026-02-03 20:10:06 +01:00
dapitch666 31630f50a5 Translated using Weblate (French)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2026-02-03 20:10:06 +01:00
Delta Umhöfer edfce46058 Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2026-02-02 08:02:28 +00:00
Jan-Eric Myhrgren cc5244c596 Translated using Weblate (Swedish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2026-02-02 08:02:27 +00:00
enosh b8942c5931 Translated using Weblate (Hebrew)
Currently translated at 81.4% (947 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/he/
2026-02-02 08:02:26 +00:00
FiendFEARing 6e5feee78a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-02-02 08:02:25 +00:00
Dawid Kuźnicki e7cb0466e6 Translated using Weblate (Polish)
Currently translated at 94.5% (1100 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2026-02-02 08:02:24 +00:00
dv4yGY2U 6c7221d37d Translated using Weblate (Turkish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/tr/
2026-02-02 08:02:24 +00:00
Samuel Guerrero 1f3fa80ddd Translated using Weblate (Spanish)
Currently translated at 97.5% (1134 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2026-02-02 08:02:23 +00:00
Mantas 87f3766299 Translated using Weblate (Lithuanian)
Currently translated at 59.9% (697 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/lt/
2026-02-02 08:02:22 +00:00
Bartłomiej d08cef11ed Translated using Weblate (Polish)
Currently translated at 94.5% (1100 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2026-02-02 08:02:21 +00:00
Kabika82 7201cced42 Translated using Weblate (Hungarian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2026-02-02 08:02:20 +00:00
N Visi 4f8fbbc979 Translated using Weblate (Japanese)
Currently translated at 22.8% (266 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/
2026-02-02 08:02:19 +00:00
Henrik Lynge e55fed4a33 Translated using Weblate (Danish)
Currently translated at 99.9% (1162 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2026-02-02 08:02:19 +00:00
herny ucet dcbeecff7a Translated using Weblate (Slovak)
Currently translated at 99.7% (1160 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2026-02-02 08:02:18 +00:00
xxzp3 32276aacd9 Translated using Weblate (Danish)
Currently translated at 99.8% (1161 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2026-02-02 08:02:17 +00:00
J. Lavoie b921a08809 Translated using Weblate (French)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2026-02-02 08:02:16 +00:00
J. Lavoie c089336e41 Translated using Weblate (Italian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2026-02-02 08:02:15 +00:00
Charlie 5107b0307c Translated using Weblate (French)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2026-02-02 08:02:14 +00:00
advplyr 8498378bca Merge pull request #4952 from sir-wilhelm/use-localized-text-for-selected-filter
Display localized/styled text for selected filter.
2026-01-31 17:56:07 -06:00
advplyr b61e2c30f2 Merge pull request #4973 from KiwiHour/jump-backwards-label-fix
Fix screen reader compatability issue for the "jump backwards" button for media controls
2026-01-31 17:50:41 -06:00
Nicholas Wallace cfeb6bd502 Fix: grace period enable statement 2026-01-24 18:57:40 -07:00
Nicholas Wallace 077b523bd6 Fix JS Doc deletion 2026-01-24 18:42:50 -07:00
Nicholas Wallace b8a2d113f0 Allow rotation without grace period for invalidating all user sessions 2026-01-24 18:26:11 -07:00
Nicholas Wallace e1ae4f2d31 Fix: race condition in rotation 2026-01-24 18:10:38 -07:00
Nicholas Wallace 7aa2f84daa Revert default token expiry 2026-01-24 17:00:07 -07:00
Nicholas Wallace da0a64daed Add: 10 second grace period to access token cycle 2026-01-24 16:57:25 -07:00
KiwiHour 3e4225bced Fix aria-label for jumpBackward button 2026-01-09 14:26:56 +00:00
sir-wilhelm e6d99d07f0 Display localized/styled text for selected filter.
The selected filter was using the id before.
2025-12-28 11:28:36 -06:00
advplyr 122fc34a75 Fix server crash filtering by decade with collapsed series 2025-12-24 17:07:05 -06:00
advplyr e5c0a9d22c Version bump v2.32.1 2025-12-23 16:51:54 -06:00
advplyr 3bf136a20b Merge pull request #4933 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-12-23 16:49:04 -06:00
Marcin b387d9484a Translated using Weblate (Polish)
Currently translated at 89.2% (1038 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-12-23 23:44:39 +01:00
bittin1ddc447d824349b2 e8668d9f22 Translated using Weblate (Swedish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-12-23 23:44:38 +01:00
Petri Hämäläinen f3e90bd420 Translated using Weblate (Finnish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2025-12-23 23:44:37 +01:00
Ivan Smoliakov 4bf15bbffd Translated using Weblate (Russian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-12-23 23:44:37 +01:00
advplyr 04eb3bc437 Fix server crash on audible match #4931 2025-12-23 16:44:29 -06:00
advplyr 81e96df9c5 Version bump v2.32.0 2025-12-21 15:54:07 -06:00
advplyr 44aff23e1b Merge pull request #4921 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-12-21 15:44:28 -06:00
lambolighting cc48d9f26d Translated using Weblate (Greek)
Currently translated at 26.9% (313 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/el/
2025-12-21 21:38:43 +00:00
Ahetek ac08e897ee Translated using Weblate (Polish)
Currently translated at 89.2% (1038 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-12-21 21:38:43 +00:00
FiendFEARing 3c2eec8279 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-12-21 21:38:42 +00:00
advplyr 7b37c98e88 Book tags genres dedupe (#4927)
* Update Audible provider dedupe genres/tags and return tags as array

* Update custom metadata provider to dedupe tags/genres and return tags as array
2025-12-21 15:38:34 -06:00
advplyr 088353ae26 Merge pull request #4649 from votex001/multi-select-item-fix
[fix] prevent duplicates in multi-selects
2025-12-21 14:58:04 -06:00
advplyr e003544edd Merge pull request #4766 from TN-SKYC/Authors-bug
Bug in matching author of a book when this author already exists in the db.
2025-12-21 14:49:37 -06:00
advplyr 076ece6fe7 Auto-formatting 2025-12-21 14:45:04 -06:00
advplyr 14f72ab7d4 Merge pull request #4740 from Yetangitu/fix_debian_user_exists
fix #1617 (useradd: user 'audiobookshelf' already exists)
2025-12-21 14:08:10 -06:00
advplyr ebcb122eb8 Merge pull request #4906 from sir-wilhelm/playlist-sorted
When adding items to playlist, sort the playlist sections alphabetically.
2025-12-21 14:01:08 -06:00
advplyr 626596b192 Merge pull request #4890 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-12-14 16:35:16 -06:00
lambolighting 10a4777ddf Translated using Weblate (Greek)
Currently translated at 9.0% (105 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/el/
2025-12-14 23:00:22 +01:00
MODI NAVON 0ecbb1c3f4 Translated using Weblate (Hebrew)
Currently translated at 73.0% (850 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/he/
2025-12-12 23:24:22 +00:00
Lucas Jaksys dc2398a072 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pt_BR/
2025-12-12 23:24:21 +00:00
lambolighting c1e21d31ee Translated using Weblate (Greek)
Currently translated at 1.1% (13 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/el/
2025-12-12 23:24:21 +00:00
Mario 70e6efc3d0 Translated using Weblate (German)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-12-12 23:24:20 +00:00
Alberto Coronado 092c504eb1 Translated using Weblate (Spanish)
Currently translated at 97.5% (1134 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-12-12 23:24:19 +00:00
A L f7d7c9a4f5 Translated using Weblate (Bulgarian)
Currently translated at 88.2% (1026 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bg/
2025-12-12 23:24:19 +00:00
kfctatertot 8bdcabf973 Translated using Weblate (Spanish)
Currently translated at 96.8% (1126 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-12-12 23:24:18 +00:00
kfctatertot 646c861bcc Translated using Weblate (Arabic)
Currently translated at 95.9% (1116 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-12-12 23:24:17 +00:00
zard Kim ee60169995 Translated using Weblate (Korean)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ko/
2025-12-12 23:24:17 +00:00
advplyr afb4108c30 Added translation using Weblate (Greek) 2025-12-12 23:24:16 +00:00
Igor Dobrača 2e2d857ce0 Translated using Weblate (Croatian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-12-12 23:24:15 +00:00
Alessandro Burzio ed5766b4ab Translated using Weblate (Italian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-12-12 23:24:14 +00:00
Petri Hämäläinen a33e87db99 Translated using Weblate (Finnish)
Currently translated at 97.6% (1136 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2025-12-12 23:24:14 +00:00
Napitauki 5de942aefb Translated using Weblate (Finnish)
Currently translated at 97.6% (1136 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2025-12-12 23:24:13 +00:00
thehijacker bcfe1e9647 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-12-12 23:24:12 +00:00
advplyr 503f4611b2 Update tooltip with plaintext prop 2025-12-12 17:24:01 -06:00
sir-wilhelm 648983708e Sort the playlist sections alphabetically. 2025-12-12 04:18:29 +00:00
Tomasz N. 961d066bdd Wrong branch. 2025-10-31 15:39:12 +01:00
Tomasz N. 372c9a5322 Increasing the timeout for bookfinder - some metadata providers heavily throttle the requests, original 10s is not enough. 2025-10-31 15:36:47 +01:00
Tomasz N. a5750deaaf The key change: Move the Database.bookAuthorModel.create() block outside the if (!author) check,
so it runs whether the author was just created OR already existed in the database.

This bug was visible when using "Match Books" for a library and the outcome was books had no author(s) assigned
despite the custom providers correctly providing those values.
2025-10-22 23:02:49 +02:00
Frank de Lange 797dba2448 fix #1617 (useradd: user 'audiobookshelf' already exists)
This change fixes the problem of failing upgrades on dpkg-based systems
by reworking the check for whether the `audiobookshelf` user/group already
exists.
2025-10-10 22:30:38 +02:00
votex001 fbe9971a8b [fix] prevent duplicates in multi-selects 2025-09-03 18:19:52 +03:00
115 changed files with 5281 additions and 860 deletions
+2 -2
View File
@@ -22,7 +22,7 @@ add_user() {
declare -r descr="${4:-No description}"
declare -r shell="${5:-/bin/false}"
if ! getent passwd | grep -q "^$user:"; then
if ! getent passwd "$user" 2>&1 >/dev/null; then
echo "Creating system user: $user in $group with $descr and shell $shell"
useradd $uid_flags --gid $group --no-create-home --system --shell $shell -c "$descr" $user
fi
@@ -39,7 +39,7 @@ add_group() {
declare -r gid_flags="--gid $gid"
fi
if ! getent group | grep -q "^$group:" ; then
if ! getent group "$group" 2>&1 >/dev/null; then
echo "Creating system group: $group"
groupadd $gid_flags --system $group
fi
+1
View File
@@ -196,6 +196,7 @@ export default {
requestBatchQuickEmbed() {
const payload = {
message: this.$strings.MessageConfirmQuickEmbed,
allowHtml: true,
callback: (confirmed) => {
if (confirmed) {
this.$axios
@@ -300,6 +300,8 @@ export default {
})
},
userUpdated(user) {
if (user.id !== this.$store.state.user.user.id) return
if (user.seriesHideFromContinueListening && user.seriesHideFromContinueListening.length) {
this.removeAllSeriesFromContinueSeries(user.seriesHideFromContinueListening)
}
+3 -3
View File
@@ -78,7 +78,7 @@
</div>
<!-- Error widget -->
<ui-tooltip cy-id="ErrorTooltip" v-if="showError" :text="errorText" class="absolute bottom-4e left-0 z-10">
<ui-tooltip cy-id="ErrorTooltip" v-if="showError" :text="errorText" plaintext class="absolute bottom-4e left-0 z-10">
<div :style="{ height: 1.5 + 'em', width: 2.5 + 'em' }" class="bg-error rounded-r-full shadow-md flex items-center justify-end border-r border-b border-red-300">
<span class="material-symbols text-red-100 pr-1e" :style="{ fontSize: 0.875 + 'em' }">priority_high</span>
</div>
@@ -121,12 +121,12 @@
<!-- Alternative bookshelf title/author/sort -->
<div cy-id="detailBottom" :id="`description-area-${index}`" v-if="isAlternativeBookshelfView || isAuthorBookshelfView" dir="auto" class="relative mt-2e mb-2e left-0 z-50 w-full">
<div :style="{ fontSize: 0.9 + 'em' }">
<ui-tooltip v-if="displayTitle" :text="displayTitle" :disabled="!displayTitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
<ui-tooltip v-if="displayTitle" :text="displayTitle" plaintext :disabled="!displayTitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
<p cy-id="title" ref="displayTitle" class="truncate">{{ displayTitle }}</p>
<widgets-explicit-indicator cy-id="explicitIndicator" v-if="isExplicit" />
</ui-tooltip>
</div>
<ui-tooltip v-if="showSubtitles" :text="displaySubtitle" :disabled="!displaySubtitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
<ui-tooltip v-if="showSubtitles" :text="displaySubtitle" plaintext :disabled="!displaySubtitleTruncated" direction="bottom" :delayOnShow="500" class="flex items-center">
<p cy-id="subtitle" class="truncate" ref="displaySubtitle" :style="{ fontSize: 0.6 + 'em' }">{{ displaySubtitle }}</p>
</ui-tooltip>
<p cy-id="line2" class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ displayLineTwo || '&nbsp;' }}</p>
@@ -338,6 +338,18 @@ export default {
const series = this.series.find((se) => se.id == decoded)
if (series) filterValue = series.name
}
} else if (parts[0] === 'progress') {
const item = this.progress.find((p) => p.id == decoded)
if (item) filterValue = item.name
} else if (parts[0] === 'tracks') {
const item = this.tracks.find((t) => t.id == decoded)
if (item) filterValue = item.name
} else if (parts[0] === 'ebooks') {
const item = this.ebooks.find((e) => e.id == decoded)
if (item) filterValue = item.name
} else if (parts[0] === 'missing') {
const item = this.missing.find((m) => m.id == decoded)
if (item) filterValue = item.name
} else {
filterValue = decoded
}
@@ -227,7 +227,7 @@ export default {
.catch((error) => {
console.error('Failed to create collection', error)
var errMsg = error.response ? error.response.data || '' : ''
this.$toast.error(this.$strings.ToastCollectionCreateFailed + ': ' + errMsg)
this.$toast.error(errMsg)
this.processing = false
})
}
+5 -5
View File
@@ -78,7 +78,7 @@
<div class="grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.author" :disabled="!selectedMatchUsage.author" :label="$strings.LabelAuthor" />
<p v-if="mediaMetadata.authorName || (isPodcast && mediaMetadata.author)" class="text-xs ml-1 text-white/60">
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('author', isPodcast ? mediaMetadata.author : mediaMetadata.authorName)">{{ isPodcast ? mediaMetadata.author : mediaMetadata.authorName }}</a>
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('author', isPodcast ? mediaMetadata.author : mediaMetadata.authorName)">{{ isPodcast ? mediaMetadata.author : mediaMetadata.authorName }}</a>
</p>
</div>
</div>
@@ -87,7 +87,7 @@
<div class="grow ml-4">
<ui-multi-select v-model="selectedMatch.narrator" :items="narrators" :disabled="!selectedMatchUsage.narrator" :label="$strings.LabelNarrators" />
<p v-if="mediaMetadata.narratorName" class="text-xs ml-1 text-white/60">
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('narrator', mediaMetadata.narrators)">{{ mediaMetadata.narratorName }}</a>
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('narrator', mediaMetadata.narrators)">{{ mediaMetadata.narratorName }}</a>
</p>
</div>
</div>
@@ -96,7 +96,7 @@
<div class="grow ml-4">
<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/60">
{{ $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>
{{ $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>
</div>
</div>
@@ -105,7 +105,7 @@
<div class="grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.publisher" :disabled="!selectedMatchUsage.publisher" :label="$strings.LabelPublisher" />
<p v-if="mediaMetadata.publisher" class="text-xs ml-1 text-white/60">
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publisher', mediaMetadata.publisher)">{{ mediaMetadata.publisher }}</a>
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publisher', mediaMetadata.publisher)">{{ mediaMetadata.publisher }}</a>
</p>
</div>
</div>
@@ -114,7 +114,7 @@
<div class="grow ml-4">
<ui-text-input-with-label v-model="selectedMatch.publishedYear" :disabled="!selectedMatchUsage.publishedYear" :label="$strings.LabelPublishYear" />
<p v-if="mediaMetadata.publishedYear" class="text-xs ml-1 text-white/60">
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publishedYear', mediaMetadata.publishedYear)">{{ mediaMetadata.publishedYear }}</a>
{{ $strings.LabelCurrently }} <a :title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('publishedYear', mediaMetadata.publishedYear)">{{ mediaMetadata.publishedYear }}</a>
</p>
</div>
</div>
@@ -158,6 +158,8 @@ export default {
this.isProcessing = true
var updateResult = await this.$axios.$patch(`/api/items/${this.libraryItemId}/media`, updatePayload).catch((error) => {
console.error('Failed to update', error)
const errorMessage = typeof error?.response?.data === 'string' ? error?.response?.data : null
this.$toast.error(errorMessage || this.$strings.ToastFailedToUpdate)
return false
})
this.isProcessing = false
@@ -107,6 +107,7 @@ export default {
quickEmbed() {
const payload = {
message: this.$strings.MessageConfirmQuickEmbed,
allowHtml: true,
callback: (confirmed) => {
if (confirmed) {
this.$axios
@@ -97,7 +97,10 @@ export default {
...playlist
}
})
.sort((a, b) => (a.isItemIncluded ? -1 : 1))
.sort((a, b) => {
if (a.isItemIncluded !== b.isItemIncluded) return a.isItemIncluded ? -1 : 1
return a.name.localeCompare(b.name)
})
},
isBatch() {
return this.selectedPlaylistItems.length > 1
@@ -8,7 +8,7 @@
</button>
</ui-tooltip>
<ui-tooltip direction="top" :text="jumpBackwardText">
<button :aria-label="jumpForwardText" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpBackward">
<button :aria-label="jumpBackwardText" class="text-gray-300" @mousedown.prevent @mouseup.prevent @click.stop="jumpBackward">
<span class="material-symbols text-2xl sm:text-3xl">replay</span>
</button>
</ui-tooltip>
+21 -1
View File
@@ -3,7 +3,8 @@
<div class="absolute top-0 left-0 right-0 w-full h-36 bg-linear-to-t from-transparent via-black-500 to-black-700 opacity-90 pointer-events-none" />
<div ref="content" class="relative text-white" :style="{ height: modalHeight, width: modalWidth }" v-click-outside="clickedOutside">
<div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300">
<p id="confirm-prompt-message" class="text-lg mb-6 mt-2 px-1" v-html="message" />
<p v-if="allowHtmlMessage" id="confirm-prompt-message" class="text-lg mb-6 mt-2 px-1" v-html="sanitizedMessage" />
<p v-else id="confirm-prompt-message" class="text-lg mb-6 mt-2 px-1">{{ message }}</p>
<ui-checkbox v-if="checkboxLabel" v-model="checkboxValue" checkbox-bg="bg" :label="checkboxLabel" label-class="pl-2 text-base" class="mb-6 px-1" />
@@ -52,6 +53,17 @@ export default {
message() {
return this.confirmPromptOptions.message || ''
},
allowHtmlMessage() {
return !!this.confirmPromptOptions.allowHtml
},
sanitizedMessage() {
if (!this.allowHtmlMessage) return this.message
return this.escapeHtml(this.message)
.replace(/&lt;br\s*\/?&gt;/gi, '<br>')
.replace(/&lt;code&gt;/gi, '<code>')
.replace(/&lt;\/code&gt;/gi, '</code>')
},
callback() {
return this.confirmPromptOptions.callback
},
@@ -103,6 +115,14 @@ export default {
if (this.callback) this.callback(true, this.checkboxValue)
this.show = false
},
escapeHtml(value) {
return String(value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
},
setShow() {
this.checkboxValue = this.checkboxDefaultValue
this.$eventBus.$emit('showing-prompt', true)
@@ -14,7 +14,7 @@
<div :key="n" class="absolute pointer-events-none left-0 h-px bg-white/10" :style="{ top: n * lineSpacing - lineSpacing / 2 + 'px', width: '360px', marginLeft: '24px' }" />
<div :key="`dot-${n}`" class="absolute z-10" :style="{ left: points[n - 1].x + 'px', bottom: points[n - 1].y + 'px' }">
<ui-tooltip :text="last7DaysOfListening[n - 1].minutesListening" direction="top">
<ui-tooltip :text="last7DaysOfListening[n - 1].minutesListening" plaintext direction="top">
<div class="h-2 w-2 bg-yellow-400 hover:bg-yellow-300 rounded-full transform duration-150 transition-transform hover:scale-125" />
</ui-tooltip>
</div>
+1 -1
View File
@@ -278,7 +278,7 @@ export default {
})
},
insertNewItem(item) {
this.selected.push(item)
if (!this.selected.includes(item)) this.selected.push(item)
this.$emit('input', this.selected)
this.$emit('newItem', item)
this.textInput = null
@@ -287,7 +287,7 @@ export default {
})
},
insertNewItem(item) {
this.selected.push(item)
if (!this.selected.find((i) => i.name === item.name)) this.selected.push(item)
this.$emit('input', this.selected)
this.$emit('newItem', item)
this.textInput = null
+3 -2
View File
@@ -1,6 +1,6 @@
<template>
<div ref="wrapper" class="relative">
<input :id="inputId" :name="inputName" ref="input" v-model="inputValue" :type="actualType" :step="step" :min="min" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" dir="auto" class="rounded-sm bg-primary text-gray-200 focus:bg-bg focus:outline-hidden border h-full w-full" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
<input :id="inputId" :name="inputName" ref="input" v-model="inputValue" :type="actualType" :step="step" :min="min" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" :autocomplete="autocomplete" dir="auto" class="rounded-sm bg-primary text-gray-200 focus:bg-bg focus:outline-hidden border h-full w-full" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
<div v-if="clearable && inputValue" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center">
<span class="material-symbols text-gray-300 cursor-pointer" style="font-size: 1.1rem" @click.stop.prevent="clear">close</span>
</div>
@@ -41,7 +41,8 @@ export default {
step: [String, Number],
min: [String, Number],
customInputClass: String,
trimWhitespace: Boolean
trimWhitespace: Boolean,
autocomplete: String
},
data() {
return {
+3 -2
View File
@@ -6,7 +6,7 @@
<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em>
</label>
</slot>
<ui-text-input :placeholder="placeholder || label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" :min="min" :show-copy="showCopy" class="w-full" :class="inputClass" :trim-whitespace="trimWhitespace" @blur="inputBlurred" />
<ui-text-input :placeholder="placeholder || label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" :min="min" :show-copy="showCopy" :autocomplete="autocomplete" class="w-full" :class="inputClass" :trim-whitespace="trimWhitespace" @blur="inputBlurred" />
</div>
</template>
@@ -26,7 +26,8 @@ export default {
disabled: Boolean,
inputClass: String,
showCopy: Boolean,
trimWhitespace: Boolean
trimWhitespace: Boolean,
autocomplete: String
},
data() {
return {}
+12 -3
View File
@@ -22,7 +22,8 @@ export default {
type: Number,
default: 0
},
disabled: Boolean
disabled: Boolean,
plaintext: Boolean
},
data() {
return {
@@ -46,7 +47,11 @@ export default {
methods: {
updateText() {
if (this.tooltip) {
this.tooltip.innerHTML = this.text
if (this.plaintext) {
this.tooltip.textContent = this.text
} else {
this.tooltip.innerHTML = this.text
}
this.setTooltipPosition(this.tooltip)
}
},
@@ -58,7 +63,11 @@ export default {
tooltip.className = 'tooltip-wrapper absolute px-2 py-1 text-white text-xs rounded-sm shadow-lg max-w-xs text-center hidden sm:block'
tooltip.style.zIndex = 100
tooltip.style.backgroundColor = 'rgba(0,0,0,0.85)'
tooltip.innerHTML = this.text
if (this.plaintext) {
tooltip.textContent = this.text
} else {
tooltip.innerHTML = this.text
}
tooltip.addEventListener('mouseover', this.cancelHide)
tooltip.addEventListener('mouseleave', this.hideTooltip)
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "audiobookshelf-client",
"version": "2.31.0",
"version": "2.35.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf-client",
"version": "2.31.0",
"version": "2.35.1",
"license": "ISC",
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
"version": "2.31.0",
"version": "2.35.1",
"buildNumber": 1,
"description": "Self-hosted audiobook and podcast client",
"main": "index.js",
+1 -1
View File
@@ -126,7 +126,7 @@
<ui-tooltip v-if="selectedChapterId === chapter.id && (isPlayingChapter || isLoadingChapter)" :text="$strings.TooltipAdjustChapterStart" direction="bottom">
<div class="ml-2 text-xs text-gray-300 font-mono min-w-10 cursor-pointer hover:text-white transition-colors duration-150" @click="adjustChapterStartTime(chapter)">{{ elapsedTime }}s</div>
</ui-tooltip>
<ui-tooltip v-if="chapter.error" :text="chapter.error" direction="left">
<ui-tooltip v-if="chapter.error" :text="chapter.error" plaintext direction="left">
<button class="w-7 h-7 rounded-full flex items-center justify-center text-error">
<span class="material-symbols text-lg">error_outline</span>
</button>
+1 -1
View File
@@ -390,8 +390,8 @@ export default {
},
purgeItemsCache() {
const payload = {
// message: `This will delete the entire folder at <code>/metadata/cache/items</code>.<br />Are you sure you want to purge items cache?`,
message: this.$strings.MessageConfirmPurgeItemsCache,
allowHtml: true,
callback: (confirmed) => {
if (confirmed) {
this.sendPurgeItemsCache()
@@ -90,9 +90,9 @@ export default {
let message = this.$getString('MessageConfirmRenameGenre', [this.editingGenre, this.newGenreName])
if (genreNameExists) {
message += `<br><span class="text-sm">${this.$strings.MessageConfirmRenameGenreMergeNote}</span>`
message += ` ${this.$strings.MessageConfirmRenameGenreMergeNote}`
} else if (genreNameExistsOfDifferentCase) {
message += `<br><span class="text-warning text-sm">${this.$getString('MessageConfirmRenameGenreWarning', [genreNameExistsOfDifferentCase])}</span>`
message += ` ${this.$getString('MessageConfirmRenameGenreWarning', [genreNameExistsOfDifferentCase])}`
}
const payload = {
@@ -86,9 +86,9 @@ export default {
let message = this.$getString('MessageConfirmRenameTag', [this.editingTag, this.newTagName])
if (tagNameExists) {
message += `<br><span class="text-sm">${this.$strings.MessageConfirmRenameTagMergeNote}</span>`
message += ` ${this.$strings.MessageConfirmRenameTagMergeNote}`
} else if (tagNameExistsOfDifferentCase) {
message += `<br><span class="text-warning text-sm">${this.$getString('MessageConfirmRenameTagWarning', [tagNameExistsOfDifferentCase])}</span>`
message += ` ${this.$getString('MessageConfirmRenameTagWarning', [tagNameExistsOfDifferentCase])}`
}
const payload = {
+19 -7
View File
@@ -66,7 +66,11 @@
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
</td>
<td class="hidden sm:table-cell max-w-32 min-w-32">
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
<p class="text-xs truncate">
<template v-for="(line, index) in getDeviceInfoLines(session.deviceInfo)">
<br v-if="index > 0" :key="'br-' + index" />{{ line }}
</template>
</p>
</td>
<td class="text-center w-24 min-w-24 sm:w-32 sm:min-w-32">
<p class="text-xs font-mono">{{ $elapsedPrettyLocalized(session.timeListening) }}</p>
@@ -130,7 +134,11 @@
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
</td>
<td class="hidden sm:table-cell max-w-32 min-w-32">
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
<p class="text-xs truncate">
<template v-for="(line, index) in getDeviceInfoLines(session.deviceInfo)">
<br v-if="index > 0" :key="'br-' + index" />{{ line }}
</template>
</p>
</td>
<td class="text-center">
<p class="text-xs font-mono">{{ $elapsedPretty(session.timeListening) }}</p>
@@ -172,7 +180,11 @@
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
</td>
<td class="hidden sm:table-cell max-w-32 min-w-32">
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
<p class="text-xs truncate">
<template v-for="(line, index) in getDeviceInfoLines(session.deviceInfo)">
<br v-if="index > 0" :key="'br-' + index" />{{ line }}
</template>
</p>
</td>
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
@@ -433,16 +445,16 @@ export default {
this.selectedSession = session
this.showSessionModal = true
},
getDeviceInfoString(deviceInfo) {
if (!deviceInfo) return ''
var lines = []
getDeviceInfoLines(deviceInfo) {
if (!deviceInfo) return []
const lines = []
if (deviceInfo.clientName) lines.push(`${deviceInfo.clientName} ${deviceInfo.clientVersion || ''}`)
if (deviceInfo.osName) lines.push(`${deviceInfo.osName} ${deviceInfo.osVersion}`)
if (deviceInfo.browserName) lines.push(deviceInfo.browserName)
if (deviceInfo.manufacturer && deviceInfo.model) lines.push(`${deviceInfo.manufacturer} ${deviceInfo.model}`)
if (deviceInfo.sdkVersion) lines.push(`SDK Version: ${deviceInfo.sdkVersion}`)
return lines.join('<br>')
return lines
},
getPlayMethodName(playMethod) {
if (playMethod === this.$constants.PlayMethod.DIRECTPLAY) return 'Direct Play'
+10 -6
View File
@@ -38,8 +38,12 @@
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
</td>
<td class="hidden sm:table-cell min-w-32 max-w-32">
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
</td>
<p class="text-xs truncate">
<template v-for="(line, index) in getDeviceInfoLines(session.deviceInfo)">
<br v-if="index > 0" :key="'br-' + index" />{{ line }}
</template>
</p>
</td>
<td class="text-center">
<p class="text-xs font-mono">{{ $elapsedPrettyLocalized(session.timeListening) }}</p>
</td>
@@ -193,16 +197,16 @@ export default {
this.selectedSession = session
this.showSessionModal = true
},
getDeviceInfoString(deviceInfo) {
if (!deviceInfo) return ''
var lines = []
getDeviceInfoLines(deviceInfo) {
if (!deviceInfo) return []
const lines = []
if (deviceInfo.clientName) lines.push(`${deviceInfo.clientName} ${deviceInfo.clientVersion || ''}`)
if (deviceInfo.osName) lines.push(`${deviceInfo.osName} ${deviceInfo.osVersion}`)
if (deviceInfo.browserName) lines.push(deviceInfo.browserName)
if (deviceInfo.manufacturer && deviceInfo.model) lines.push(`${deviceInfo.manufacturer} ${deviceInfo.model}`)
if (deviceInfo.sdkVersion) lines.push(`SDK Version: ${deviceInfo.sdkVersion}`)
return lines.join('<br>')
return lines
},
getPlayMethodName(playMethod) {
if (playMethod === this.$constants.PlayMethod.DIRECTPLAY) return 'Direct Play'
+5 -5
View File
@@ -17,9 +17,9 @@
<form @submit.prevent="submitServerSetup">
<p class="text-lg font-semibold mb-2 pl-1 text-center">Create Root User</p>
<ui-text-input-with-label v-model.trim="newRoot.username" label="Username" :disabled="processing" class="w-full mb-3 text-sm" />
<ui-text-input-with-label v-model="newRoot.password" label="Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
<ui-text-input-with-label v-model="confirmPassword" label="Confirm Password" type="password" :disabled="processing" class="w-full mb-3 text-sm" />
<ui-text-input-with-label v-model.trim="newRoot.username" label="Username" autocomplete="username" :disabled="processing" class="w-full mb-3 text-sm" />
<ui-text-input-with-label v-model="newRoot.password" label="Password" type="password" autocomplete="new-password" :disabled="processing" class="w-full mb-3 text-sm" />
<ui-text-input-with-label v-model="confirmPassword" label="Confirm Password" type="password" autocomplete="new-password" :disabled="processing" class="w-full mb-3 text-sm" />
<p class="text-lg font-semibold mt-6 mb-2 pl-1 text-center">Directory Paths</p>
<ui-text-input-with-label v-model="ConfigPath" label="Config Path" disabled class="w-full mb-3 text-sm" />
@@ -51,10 +51,10 @@
<form v-show="login_local" @submit.prevent="submitForm">
<label class="text-xs text-gray-300 uppercase">{{ $strings.LabelUsername }}</label>
<ui-text-input v-model.trim="username" :disabled="processing" class="mb-3 w-full" inputName="username" />
<ui-text-input v-model.trim="username" autocomplete="username" :disabled="processing" class="mb-3 w-full" inputName="username" />
<label class="text-xs text-gray-300 uppercase">{{ $strings.LabelPassword }}</label>
<ui-text-input v-model.trim="password" type="password" :disabled="processing" class="w-full mb-3" inputName="password" />
<ui-text-input v-model.trim="password" type="password" autocomplete="current-password" :disabled="processing" class="w-full mb-3" inputName="password" />
<div class="w-full flex justify-end py-3">
<ui-btn type="submit" :disabled="processing" color="bg-primary" class="leading-none">{{ processing ? 'Checking...' : $strings.ButtonSubmit }}</ui-btn>
</div>
+1
View File
@@ -364,6 +364,7 @@ export default {
}
const startTime = this.playbackSession.currentTime || 0
this.localAudioPlayer.set(null, this.audioTracks, false, startTime, false)
this.localAudioPlayer.on('stateChange', this.playerStateChange.bind(this))
this.localAudioPlayer.on('timeupdate', this.playerTimeUpdate.bind(this))
+14 -1
View File
@@ -46,7 +46,20 @@ export default class LocalAudioPlayer extends EventEmitter {
this.player.addEventListener('loadedmetadata', this.evtLoadedMetadata.bind(this))
this.player.addEventListener('timeupdate', this.evtTimeupdate.bind(this))
var mimeTypes = ['audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/aac', 'audio/x-ms-wma', 'audio/x-aiff', 'audio/webm']
var mimeTypes = [
'audio/flac',
'audio/mpeg',
'audio/mp4',
'audio/ogg',
'audio/aac',
'audio/x-ms-wma',
'audio/x-aiff',
'audio/webm',
// `audio/matroska` is the correct mimetype, but the server still uses `audio/x-matroska`
// ref: https://www.iana.org/assignments/media-types/media-types.xhtml
'audio/matroska',
'audio/x-matroska'
]
var mimeTypeCanPlayMap = {}
mimeTypes.forEach((mt) => {
var canPlay = this.player.canPlayType(mt)
+6
View File
@@ -6,6 +6,7 @@ const defaultCode = 'en-us'
const languageCodeMap = {
ar: { label: 'عربي', dateFnsLocale: 'ar' },
be: { label: 'Беларуская', dateFnsLocale: 'be' },
bg: { label: 'Български', dateFnsLocale: 'bg' },
bn: { label: 'বাংলা', dateFnsLocale: 'bn' },
ca: { label: 'Català', dateFnsLocale: 'ca' },
@@ -20,6 +21,7 @@ const languageCodeMap = {
he: { label: 'עברית', dateFnsLocale: 'he' },
hr: { label: 'Hrvatski', dateFnsLocale: 'hr' },
it: { label: 'Italiano', dateFnsLocale: 'it' },
ja: { label: '日本語', dateFnsLocale: 'ja' },
lt: { label: 'Lietuvių', dateFnsLocale: 'lt' },
hu: { label: 'Magyar', dateFnsLocale: 'hu' },
ko: { label: '한국어', dateFnsLocale: 'ko' },
@@ -28,6 +30,7 @@ const languageCodeMap = {
pl: { label: 'Polski', dateFnsLocale: 'pl' },
'pt-br': { label: 'Português (Brasil)', dateFnsLocale: 'ptBR' },
ru: { label: 'Русский', dateFnsLocale: 'ru' },
sk: { label: 'Slovenčina', dateFnsLocale: 'sk' },
sl: { label: 'Slovenščina', dateFnsLocale: 'sl' },
sv: { label: 'Svenska', dateFnsLocale: 'sv' },
tr: { label: 'Türkçe', dateFnsLocale: 'tr' },
@@ -48,6 +51,7 @@ const podcastSearchRegionMap = {
au: { label: 'Australia' },
br: { label: 'Brasil' },
be: { label: 'België / Belgique / Belgien' },
by: { label: 'Беларусь' },
cz: { label: 'Česko' },
dk: { label: 'Danmark' },
de: { label: 'Deutschland' },
@@ -57,6 +61,7 @@ const podcastSearchRegionMap = {
hr: { label: 'Hrvatska' },
il: { label: 'ישראל / إسرائيل' },
it: { label: 'Italia' },
jp: { label: '日本' },
lu: { label: 'Luxembourg / Luxemburg / Lëtezebuerg' },
hu: { label: 'Magyarország' },
nl: { label: 'Nederland' },
@@ -67,6 +72,7 @@ const podcastSearchRegionMap = {
pt: { label: 'Portugal' },
ru: { label: 'Россия' },
ch: { label: 'Schweiz / Suisse / Svizzera' },
sk: { label: 'Slovensko' },
se: { label: 'Sverige' },
vn: { label: 'Việt Nam' },
ua: { label: 'Україна' },
+10 -4
View File
@@ -244,6 +244,8 @@
"LabelAlreadyInYourLibrary": "موجود بالفعل في مكتبتك",
"LabelApiKeyCreated": "تم إنشاء مفتاح API \"{0}\" بنجاح.",
"LabelApiKeyCreatedDescription": "تأكد من نسخ مفتاح API الآن، لن تتمكن من رؤيته مرة أخرى.",
"LabelApiKeyUser": "التصرف بالنيابة عن مستخدم",
"LabelApiKeyUserDescription": "مفتاح API سيمتلك نفس صلاحيات المستخدم الذي ينوب عنه ، سيظهر بالسجلات وكأن المستخدم قام بالطلب.",
"LabelApiToken": "رمز API",
"LabelAppend": "إلحاق",
"LabelAudioBitrate": "معدل بت الصوت (على سبيل المثال 128 كيلو بايت)",
@@ -293,6 +295,7 @@
"LabelContinueListening": "استمرار الاستماع",
"LabelContinueReading": "استمرار القراءة",
"LabelContinueSeries": "استمرار المسلسلات",
"LabelCorsAllowed": "CORS Origins مسموح",
"LabelCover": "الغلاف",
"LabelCoverImageURL": "رابط صورة الغلاف",
"LabelCoverProvider": "مزود الغلاف",
@@ -355,7 +358,7 @@
"LabelExample": "مثال",
"LabelExpandSeries": "توسيع السلاسل",
"LabelExpandSubSeries": "توسيع السلاسل الفرعية",
"LabelExplicit": "صريح",
"LabelExplicit": "محتوى صريح",
"LabelExplicitChecked": "صريح (محدد)",
"LabelExplicitUnchecked": "غير صريح (غير محدد)",
"LabelExportOPML": "تصدير OPML",
@@ -374,7 +377,7 @@
"LabelFolders": "مجلدات",
"LabelFontBold": "عريض",
"LabelFontBoldness": "تعريض الخط",
"LabelFontFamily": "عائلة الخط",
"LabelFontFamily": "عائلة الخطوط",
"LabelFontItalic": "مائل",
"LabelFontScale": "نطاق الخط",
"LabelFontStrikethrough": "يتوسطه خط",
@@ -426,6 +429,9 @@
"LabelLibraryFilterSublistEmpty": "لا يوجد {0}",
"LabelLibraryItem": "عنصر المكتبة",
"LabelLibraryName": "اسم المكتبة",
"LabelLibrarySortByProgress": "المرحلة: الأحدث",
"LabelLibrarySortByProgressFinished": "المرحلة: تم الانتهاء",
"LabelLibrarySortByProgressStarted": "المرحلة: تم البدء",
"LabelLimit": "حد",
"LabelLineSpacing": "تباعد الأسطر",
"LabelListenAgain": "الاستماع مجدداً",
@@ -570,7 +576,7 @@
"LabelSettingsBookshelfViewHelp": "تصميم يحاكي الواقع مع رفوف خشبية",
"LabelSettingsChromecastSupport": "دعم Chromecast",
"LabelSettingsDateFormat": "تنسيق التاريخ",
"LabelSettingsEnableWatcher": "فحص المكتبات تلقائيًا بحثًا عن تغييرات",
"LabelSettingsEnableWatcher": "مراقبة المكتبات تلقائياً بحثاً عن تغييرات",
"LabelSettingsEnableWatcherForLibrary": "فحص المكتبة تلقائيًا بحثًا عن تغييرات",
"LabelSettingsEnableWatcherHelp": "يمكّن الإضافة/التحديث التلقائي للعناصر عند اكتشاف تغييرات في الملفات. *يتطلب إعادة تشغيل الخادم",
"LabelSettingsEpubsAllowScriptedContent": "السماح بالمحتوى النصي في ملفات epub",
@@ -861,7 +867,7 @@
"MessageResetChaptersConfirm": "هل أنت متأكد أنك تريد إعادة تعيين الفصول والتراجع عن التغييرات التي أجريتها؟",
"MessageRestoreBackupConfirm": "هل أنت متأكد أنك تريد استعادة النسخ الاحتياطي الذي تم إنشاؤه في",
"MessageRestoreBackupWarning": "ستؤدي استعادة النسخ الاحتياطي إلى الكتابة فوق قاعدة البيانات بأكملها الموجودة في /config وصور الأغلفة في /metadata/items و /metadata/authors.<br /><br /> لا تعدل النسخ الاحتياطية أي ملفات في مجلدات مكتبتك. إذا قمت بتمكين إعدادات الخادم لتخزين صور الأغلفة والبيانات الوصفية في مجلدات مكتبتك، فلن يتم نسخها احتياطيًا أو الكتابة فوقها.<br /><br /> سيتم تحديث جميع العملاء الذين يستخدمون الخادم الخاص بك تلقائيًا.",
"MessageScheduleLibraryScanNote": "بالنسبة لمعظم المستخدمين، يوصى بترك هذه الميزة معطلة وإبقاء إعداد مراقب المجلدات ممكّنًا. سيكتشف مراقب المجلدات تلقائيًا التغييرات في مجلدات مكتبتك. لا يعمل مراقب المجلدات مع كل نظام ملفات (مثل NFS)، لذا يمكن استخدام عمليات فحص المكتبة المجدولة بدلاً من ذلك.",
"MessageScheduleLibraryScanNote": "لمعظم المستخدمين، موصى بترك هذه الميزة معطلة وإبقاء ممكّنة الأعداد، ”قم بمراقبة المكتبة تلقائاً للتغييرات“. سوف يقم بالكشف التلقائي عن تغييرات في مجلدات مكتبتك. لو لم يعمل الإعداد، \"قم بمراقبة المكتبة تلقائاً للتغييرات،“مع نظمة ملفاتك المستخدمة (مثل NFS على سبيل المثال)، فأمكِن هذه الميزة.",
"MessageScheduleRunEveryWeekdayAtTime": "تشغيل كل {0} في الساعة {1}",
"MessageSearchResultsFor": "نتائج البحث عن",
"MessageSelected": "تم تحديد {0}",
+631 -225
View File
File diff suppressed because it is too large Load Diff
+158 -5
View File
@@ -378,6 +378,7 @@
"LabelFilterByUser": "Филтриране по Потребител",
"LabelFindEpisodes": "Намери Епизоди",
"LabelFinished": "Дата на приключване",
"LabelFinishedDate": "Приключено на {0}",
"LabelFolder": "Папка",
"LabelFolders": "Папки",
"LabelFontBold": "Получерно",
@@ -435,7 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Не {0}",
"LabelLibraryItem": "Елемент на Библиотека",
"LabelLibraryName": "Име на Библиотека",
"LabelLibrarySortByProgress": "Прогресът е обновен",
"LabelLibrarySortByProgress": "Прогрес: Последно обновление",
"LabelLibrarySortByProgressFinished": "Прогрес: Приключено",
"LabelLibrarySortByProgressStarted": "Прогрес: Започнато",
"LabelLimit": "Лимит",
"LabelLineSpacing": "Междуредие",
"LabelListenAgain": "Слушай отново",
@@ -585,8 +588,8 @@
"LabelSettingsBookshelfViewHelp": "Скеуморфен дизайн с дървени рафтове",
"LabelSettingsChromecastSupport": "Chromecast поддръжка",
"LabelSettingsDateFormat": "Формат на Дата",
"LabelSettingsEnableWatcher": "Автоматично сканиране на библиотеките за промени",
"LabelSettingsEnableWatcherForLibrary": "Автоматично сканиране на библиотеката за промени",
"LabelSettingsEnableWatcher": "Автоматично преглеждане на библиотеките за промени",
"LabelSettingsEnableWatcherForLibrary": "Автоматично преглеждане на библиотеката за промени",
"LabelSettingsEnableWatcherHelp": "Включва автоматичното добавяне/обновяване на елементи, когато се открият промени във файловете. *Изисква рестарт на сървъра",
"LabelSettingsEpubsAllowScriptedContent": "Позволи скриптово съдържание в epub-и",
"LabelSettingsEpubsAllowScriptedContentHelp": "Позволи epub файловете да изпълняват скриптове. Препоръчително е да бъде изключено освен ако не се доверявате на източника на epub файловете.",
@@ -635,6 +638,7 @@
"LabelStartTime": "Начално Време",
"LabelStarted": "Стартирано",
"LabelStartedAt": "Стартирано на",
"LabelStartedDate": "Започнато {0}",
"LabelStatsAudioTracks": "Аудио Канали",
"LabelStatsAuthors": "Автори",
"LabelStatsBestDay": "Най-добър ден",
@@ -748,7 +752,7 @@
"MessageBookshelfNoRSSFeeds": "Няма отворени RSS feed-ове",
"MessageBookshelfNoResultsForFilter": "Няма резултат за филтер \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Няма резултати от заявката",
"MessageBookshelfNoSeries": "Нямаш сеЗЙ",
"MessageBookshelfNoSeries": "Нямате поредица",
"MessageBulkChapterPattern": "Колко глави искате да добавите, използвайки тази схема за номериране?",
"MessageChapterEndIsAfter": "Краят на главата е след края на вашата аудиокнига",
"MessageChapterErrorFirstNotZero": "Първата глава трябва да започва от 0",
@@ -780,6 +784,7 @@
"MessageConfirmPurgeCache": "Изчистването на кеша ще изтрие цялата директория в <code>/metadata/cache</code>. <br /><br />Сигурни ли сте, че искате да премахнете директорията на кеша?",
"MessageConfirmPurgeItemsCache": "Изчистването на кеша на елементите ще изтрие цялата директория в <code>/metadata/cache/items</code>. <br />Сигурни ли сте?",
"MessageConfirmQuickEmbed": "Внимание! Бързото вграждане няма да архивира вашите аудио файлове. Уверете се, че имате резервно копие на вашите аудио файлове. <br><br>Искате ли да продължите?",
"MessageConfirmQuickMatchEpisodes": "Бързото сравняване на епизоди ще презапише детайлите, ако се намери съвпадение. Само не съвпаднали епизоди ще бъдат обновени. Сигурни ли сте?",
"MessageConfirmReScanLibraryItems": "Сигурни ли сте, че искате да сканирате отново {0} елемента?",
"MessageConfirmRemoveAllChapters": "Сигурни ли сте, че искате да премахнете всички глави?",
"MessageConfirmRemoveAuthor": "Сигурни ли сте, че искате да премахнете автор \"{0}\"?",
@@ -788,6 +793,7 @@
"MessageConfirmRemoveEpisodeNote": "Забележка: Това няма да доведе до изтриване на аудио файла, освен ако не активирате опцията \"Твърдо изтриване на файла\"",
"MessageConfirmRemoveEpisodes": "Сигурни ли сте, че искате да премахнете {0} епизода?",
"MessageConfirmRemoveListeningSessions": "Сигурни ли сте, че искате да премахнете {0} слушателски сесии?",
"MessageConfirmRemoveMetadataFiles": "Сигурни ли сте, че искате да премахнете всичките метаданни. {0} файлове във папките на Вашата библиотека?",
"MessageConfirmRemoveNarrator": "Сигурни ли сте, че искате да премахнете разказвач \"{0}\"?",
"MessageConfirmRemovePlaylist": "Сигурни ли сте, че искате да премахнете плейлиста \"{0}\"?",
"MessageConfirmRenameGenre": "Сигурни ли сте, че искате да преименувате жанра \"{0}\" на \"{1}\" за всички елементи?",
@@ -862,6 +868,7 @@
"MessageNoUserPlaylists": "Нямате създадени плейлисти",
"MessageNoUserPlaylistsHelp": "Плейлистите за частни. Само създалият ги потребител ще може да ги вижда.",
"MessageNotYetImplemented": "Още не е изпълнено",
"MessageOpmlPreviewNote": "Забележка: Това е преглед на анализирания OPML файл. Действителното заглавие на подкаста ще бъде взето от RSS фийда.",
"MessageOr": "или",
"MessagePauseChapter": "Пауза на глава",
"MessagePlayChapter": "Пусни налчалото на глава",
@@ -871,6 +878,7 @@
"MessagePodcastSearchField": "Въведи какво да търся или RSS емисия адрес",
"MessageQuickEmbedInProgress": "Бързото вграждане е в процес на изпълнение",
"MessageQuickEmbedQueue": "Поставено в опашката за бързо вграждане ({0} в опашката)",
"MessageQuickMatchAllEpisodes": "Бързо Сравняване на Всички Епизоди",
"MessageQuickMatchDescription": "Попълни празните детайли и корици с първия резултат от '{0}'. Не презаписва детайлите, освен ако не е активирана настройката 'Предпочети съвпадащи метаданни' на сървъра.",
"MessageRemoveChapter": "Премахни глава",
"MessageRemoveEpisodes": "Премахни {0} епизод(и)",
@@ -880,16 +888,23 @@
"MessageResetChaptersConfirm": "Сигурни ли сте, че искате да нулирате главите и да отмените промените, които сте направили?",
"MessageRestoreBackupConfirm": "Сигурни ли сте, че искате да възстановите архива създаден на",
"MessageRestoreBackupWarning": "Възстановяването на архив ще презапише цялата база данни, намираща се в /config и кориците в /metadata/items & /metadata/authors.<br /><br />Архивите не променят файловете в папките на вашата библиотека. Ако сте активирали настройките на сървъра за съхранение на корици и метаданни в папките на вашата библиотека, те няма да бъдат архивирани или презаписани.<br /><br />Всички клиенти, използващи вашия сървър, ще бъдат автоматично обновени.",
"MessageScheduleLibraryScanNote": "За повече потребители се препоръчва да оставят този фийчър изключен и да оставят настройката \"Автоматично преглеждане за промени в библиотеката\" включена - тя автоматично ще засече промени в папките на вашата библиотека. Включете тази настройка ако \"Автоматично преглеждане за промени в библиотеката\" не рабови на вашата файлова система (например NFS).",
"MessageScheduleRunEveryWeekdayAtTime": "Изпълни всеки {0} в {1}",
"MessageSearchResultsFor": "Резултати от търсенето за",
"MessageSelected": "{0} избрани",
"MessageSeriesSequenceCannotContainSpaces": "Подредбата в серия не може да съдържа шпации",
"MessageServerCouldNotBeReached": "Сървърът не може да бъде достигнат",
"MessageSetChaptersFromTracksDescription": "Задайте глави, като използвате всеки аудио файл като глава и заглавие на главата като име на аудио файла",
"MessageShareExpirationWillBe": "Изтичането ще бъде на <strong>{0}</strong>",
"MessageShareExpiresIn": "Изтича след {0}",
"MessageShareURLWillBe": "URL за споделяне ще бъде <strong>{0}</strong>",
"MessageStartPlaybackAtTime": "Започни възпроизвеждане на \"{0}\" в {1}?",
"MessageTaskAudioFileNotWritable": "На Аудио файл \"{0}\" не може да се записва",
"MessageTaskCanceledByUser": "Задачата е отказана от потребител",
"MessageTaskDownloadingEpisodeDescription": "Изтегляне на епизод \"{0}\"",
"MessageTaskEmbeddingMetadata": "Вграждане на метаданни",
"MessageTaskEmbeddingMetadataDescription": "Вграждане на метаданни в аудиокнига \"{0}\"",
"MessageTaskEncodingM4b": "Кодиране M4B",
"MessageTaskEncodingM4bDescription": "Кодиране на аудиокнига \"{0}\" в единичен m4b файл",
"MessageTaskFailed": "Неуспешно",
"MessageTaskFailedToBackupAudioFile": "Неуспешно създаване на разервно копие на аудио файл \"{0}\"",
@@ -902,7 +917,9 @@
"MessageTaskNoFilesToScan": "Няма файлове за сканиране",
"MessageTaskOpmlImport": "OPML импортиране",
"MessageTaskOpmlImportDescription": "Създаване на подкасти от {0} RSS хранилки",
"MessageTaskOpmlImportFeed": "OPML импортиран фийд",
"MessageTaskOpmlImportFeedDescription": "Импортиране на RSS хранилка \"{0}\"",
"MessageTaskOpmlImportFeedFailed": "Неуспешно взимане на подкаст фийд",
"MessageTaskOpmlImportFeedPodcastDescription": "Създаване на подкаст \"{0}\"",
"MessageTaskOpmlImportFeedPodcastExists": "На този път вече съществува подкаст",
"MessageTaskOpmlImportFeedPodcastFailed": "Неуспешно създаване на подкаст",
@@ -938,6 +955,9 @@
"NotificationOnBackupFailedDescription": "Изпълнява се при неуспешено създаване на резервно копие",
"NotificationOnEpisodeDownloadedDescription": "Изпълнява се при автоматично изтегляне на подкаст епизод",
"NotificationOnRSSFeedDisabledDescription": "Изпълнява се, когато автоматичното изтегляне на епизодите е деактивирано, поради твърде много неуспешни опити",
"NotificationOnRSSFeedFailedDescription": "Пуска се когато заявката за RSS фийд е неуспешна за автоматично сваляне на епизод",
"NotificationOnTestDescription": "Event за тестване на системата за нотификации",
"PlaceholderBulkChapterInput": "Въведете име на глава или използвайте номериране (прим. 'Епизод 1', 'Глава 10', '1.')",
"PlaceholderNewCollection": "Ново име на колекцията",
"PlaceholderNewFolderPath": "Нов път на папката",
"PlaceholderNewPlaylist": "Ново име на плейлиста",
@@ -945,39 +965,103 @@
"PlaceholderSearchEpisode": "Търсене на Епизоди...",
"StatsAuthorsAdded": "добаврени автори",
"StatsBooksAdded": "добавени книги",
"StatsBooksAdditional": "Някой от вкючените добавки…",
"StatsBooksFinished": "завършени книги",
"StatsBooksFinishedThisYear": "Някой от книгите приключени тази година…",
"StatsBooksListenedTo": "слушани книги",
"StatsCollectionGrewTo": "Твоята книжна колекция израсна до…",
"StatsSessions": "сесии",
"StatsSpentListening": "прекарано в слушане",
"StatsTopAuthor": "ТОП АВТОР",
"StatsTopAuthors": "ТОП АВТОРИ",
"StatsTopGenre": "ТОП ЖАНР",
"StatsTopGenres": "ТОП ЖАНРА",
"StatsTopMonth": "ТОП МЕСЕЦ",
"StatsTopNarrator": "ТОП РАЗКАЗВАЧ",
"StatsTopNarrators": "ТОП РАЗКАЗВАЧИ",
"StatsTotalDuration": "С пълно времетраене…",
"StatsYearInReview": "ГОДИНАТА В ПРЕГЛЕД",
"ToastAccountUpdateSuccess": "Успешно обновяване на акаунта",
"ToastAppriseUrlRequired": "Трябва да въведете Apprise URL",
"ToastAsinRequired": "ASIN-а е задължителен",
"ToastAuthorImageRemoveSuccess": "Авторската снимка е премахната",
"ToastAuthorNotFound": "Автор \"{0}\" не е намерен",
"ToastAuthorRemoveSuccess": "Арторът е премахнат",
"ToastAuthorSearchNotFound": "Авторът не е намерен",
"ToastAuthorUpdateMerged": "Обновяване на автора сливано",
"ToastAuthorUpdateSuccess": "Автора обновен",
"ToastAuthorUpdateSuccessNoImageFound": "Автор обновен (не е намерена снимка)",
"ToastBackupAppliedSuccess": "Архивът е приложен",
"ToastBackupCreateFailed": "Неуспешно създаване на архив",
"ToastBackupCreateSuccess": "Архивът е създаден",
"ToastBackupDeleteFailed": "Неуспешно изтриване на архив",
"ToastBackupDeleteSuccess": "Архивът е изтрит",
"ToastBackupInvalidMaxKeep": "Невалиден брой за архиви за запазване",
"ToastBackupInvalidMaxSize": "Невалиден максимален рамер на архив",
"ToastBackupRestoreFailed": "Неуспешно възстановяване на архив",
"ToastBackupUploadFailed": "Неуспешно качване на архив",
"ToastBackupUploadSuccess": "Архивът е качен",
"ToastBatchApplyDetailsToItemsSuccess": "Детайли приложени на предмети",
"ToastBatchDeleteFailed": "Груповото изтриване се провали",
"ToastBatchDeleteSuccess": "Успешно групово изтриване",
"ToastBatchQuickMatchFailed": "Груповото Бързо Съвпадение се провали!",
"ToastBatchQuickMatchStarted": "Груповото Бързо Съвпадение на {0} книги започна!",
"ToastBatchUpdateFailed": "Неуспешно групово актуализиране",
"ToastBatchUpdateSuccess": "Успешно групово актуализиране",
"ToastBookmarkCreateFailed": "Неуспешно създаване на отметка",
"ToastBookmarkCreateSuccess": "Отметката е създадена",
"ToastBookmarkRemoveSuccess": "Отметката е премахната",
"ToastBulkChapterInvalidCount": "Въведете число между 1 и 150",
"ToastCachePurgeFailed": "Неуспешно изчистване на кеша",
"ToastCachePurgeSuccess": "Успешно изчистване на кеша",
"ToastChapterLocked": "Главата е заключена.",
"ToastChapterStartTimeAdjusted": "Начално време на главате е настоено с {0} секунди",
"ToastChaptersAllLocked": "Всички глави са заключени. Оключете някой глави за да преместите техните времена.",
"ToastChaptersHaveErrors": "Главите имат грешки",
"ToastChaptersInvalidShiftAmountLast": "Невалидно време за преместване. Началният час на последната глава ще превиши общата продължителност на аудиокнигата.",
"ToastChaptersInvalidShiftAmountStart": "Невалидно време за преместване. Първата глава ще има нулева или отрицателна дължина и ще бъде презаписана от втората глава. Увеличете началното време на втората глава.",
"ToastChaptersMustHaveTitles": "Главите трябва да имат заглавия",
"ToastChaptersRemoved": "Главите са премахнати",
"ToastChaptersUpdated": "Главите са актуализирани",
"ToastCollectionItemsAddFailed": "Неуспешно добавяне на елемент(и) към колекцията",
"ToastCollectionRemoveSuccess": "Колекцията е премахната",
"ToastCollectionUpdateSuccess": "Колекцията е обновена",
"ToastConnectionNotAvailable": "Няма връзка. Моля, опитайте отново по-късно",
"ToastCoverSearchFailed": "Търсенето на корица е неуспешно",
"ToastCoverUpdateFailed": "Обновяването на корицата е неуспешно",
"ToastDateTimeInvalidOrIncomplete": "Датата и часът са невалидни или непълни",
"ToastDeleteFileFailed": "Неуспешно изтриване на файла",
"ToastDeleteFileSuccess": "Успешно изтриване на файла",
"ToastDeviceAddFailed": "Неуспешно добавяне на устройство",
"ToastDeviceNameAlreadyExists": "Вече съществува четец с това име",
"ToastDeviceTestEmailFailed": "Неуспешно изпращане на тестов имейл",
"ToastDeviceTestEmailSuccess": "Тестовият имейл е изпратен",
"ToastEmailSettingsUpdateSuccess": "Имейл настройките са актуализирани",
"ToastEncodeCancelFailed": "Неуспешно отменяне на кодирането",
"ToastEncodeCancelSucces": "Кодирането е отменено",
"ToastEpisodeDownloadQueueClearFailed": "Неуспешно изчистване на опашката",
"ToastEpisodeDownloadQueueClearSuccess": "Опашката за изтегляне на епизоди е изчистена",
"ToastEpisodeUpdateSuccess": "{0} епизода са актуализирани",
"ToastErrorCannotShare": "Не може да се споделя директно от това устройство",
"ToastFailedToCreate": "Неуспешно създаване",
"ToastFailedToDelete": "Неуспешно изтриване",
"ToastFailedToLoadData": "Неуспешно зареждане на данни",
"ToastFailedToMatch": "Неуспешно съвпадение",
"ToastFailedToShare": "Неуспешно споделяне",
"ToastFailedToUpdate": "Неуспешно актуализиране",
"ToastInvalidImageUrl": "Невалиден URL адрес на изображение",
"ToastInvalidMaxEpisodesToDownload": "Невалиден максимален брой епизоди за изтегляне",
"ToastInvalidUrl": "Невалиден URL адрес",
"ToastInvalidUrls": "Един или повече URL адреси са невалидни",
"ToastItemCoverUpdateSuccess": "Корицата на елемента е обновена",
"ToastItemDeletedFailed": "Неуспешно изтриване на елемента",
"ToastItemDeletedSuccess": "Елементът е изтрит",
"ToastItemDetailsUpdateSuccess": "Детайлите на елемента са обновени",
"ToastItemMarkedAsFinishedFailed": "Неуспешно маркиране като Завършено",
"ToastItemMarkedAsFinishedSuccess": "Елементът е маркиран като завършен",
"ToastItemMarkedAsNotFinishedFailed": "Неуспешно маркиране като Незавършено",
"ToastItemMarkedAsNotFinishedSuccess": "Елементът е маркиран като незавършен",
"ToastItemUpdateSuccess": "Елементът е актуализиран",
"ToastLibraryCreateFailed": "Неуспешно създаване на библиотека",
"ToastLibraryCreateSuccess": "Библиотеката \"{0}\" е създадена",
"ToastLibraryDeleteFailed": "Неуспешно изтриване на библиотека",
@@ -985,28 +1069,97 @@
"ToastLibraryScanFailedToStart": "Неуспешно стартиране на сканиране",
"ToastLibraryScanStarted": "Сканирането на библиотеката е стартирано",
"ToastLibraryUpdateSuccess": "Библиотеката \"{0}\" е обновена",
"ToastMatchAllAuthorsFailed": "Неуспешно съвпадение на всички автори",
"ToastMetadataFilesRemovedError": "Грешка при премахване на metadata.{0} файлове",
"ToastMetadataFilesRemovedNoneFound": "Не са намерени metadata.{0} файлове в библиотеката",
"ToastMetadataFilesRemovedNoneRemoved": "Не са премахнати metadata.{0} файлове",
"ToastMetadataFilesRemovedSuccess": "Премахнати са {0} файла metadata.{1}",
"ToastMustHaveAtLeastOnePath": "Трябва да има поне един път",
"ToastNameEmailRequired": "Изискват се име и имейл",
"ToastNameRequired": "Изисква се име",
"ToastNewApiKeyUserError": "Трябва да изберете потребител",
"ToastNewEpisodesFound": "Намерени са {0} нови епизода",
"ToastNewUserCreatedFailed": "Неуспешно създаване на акаунт: „{0}“",
"ToastNewUserCreatedSuccess": "Създаден е нов акаунт",
"ToastNewUserLibraryError": "Трябва да изберете поне една библиотека",
"ToastNewUserPasswordError": "Трябва да има парола; само root потребителят може да бъде с празна парола",
"ToastNewUserTagError": "Трябва да изберете поне един етикет",
"ToastNewUserUsernameError": "Въведете потребителско име",
"ToastNoNewEpisodesFound": "Не са намерени нови епизоди",
"ToastNoRSSFeed": "Подкастът няма RSS емисия",
"ToastNoUpdatesNecessary": "Не са необходими актуализации",
"ToastNotificationCreateFailed": "Неуспешно създаване на известие",
"ToastNotificationDeleteFailed": "Неуспешно изтриване на известието",
"ToastNotificationFailedMaximum": "Максималният брой неуспешни опити трябва да бъде >= 0",
"ToastNotificationQueueMaximum": "Максималната опашка за известия трябва да бъде >= 0",
"ToastNotificationSettingsUpdateSuccess": "Настройките за известия са актуализирани",
"ToastNotificationTestTriggerFailed": "Неуспешно задействане на тестово известие",
"ToastNotificationTestTriggerSuccess": "Тестовото известие е задействано",
"ToastNotificationUpdateSuccess": "Известието е актуализирано",
"ToastPlaylistCreateFailed": "Неуспешно създаване на плейлист",
"ToastPlaylistCreateSuccess": "Плейлистът е създаден",
"ToastPlaylistRemoveSuccess": "Плейлистът е премахнат",
"ToastPlaylistUpdateSuccess": "Плейлистът е обновен",
"ToastPodcastCreateFailed": "Неуспешно създаване на подкаст",
"ToastPodcastCreateSuccess": "Подкаст успешно създаден",
"ToastPodcastEpisodeUpdated": "Епизодът е актуализиран",
"ToastPodcastGetFeedFailed": "Неуспешно извличане на емисията на подкаста",
"ToastPodcastNoEpisodesInFeed": "Не са намерени епизоди в RSS емисията",
"ToastPodcastNoRssFeed": "Подкастът няма RSS емисия",
"ToastProgressIsNotBeingSynced": "Напредъкът не се синхронизира, рестартирайте възпроизвеждането",
"ToastProviderCreatedFailed": "Неуспешно добавяне на доставчик",
"ToastProviderCreatedSuccess": "Добавен е нов доставчик",
"ToastProviderNameAndUrlRequired": "Изискват се име и URL адрес",
"ToastProviderRemoveSuccess": "Доставчикът е премахнат",
"ToastRSSFeedCloseFailed": "Неуспешно затваряне на RSS емисията",
"ToastRSSFeedCloseSuccess": "RSS емисията е затворена",
"ToastRemoveFailed": "Неуспешно премахване",
"ToastRemoveItemFromCollectionFailed": "Неуспешно премахване на елемент от колекция",
"ToastRemoveItemFromCollectionSuccess": "Елементът е премахнат от колекция",
"ToastRemoveItemsWithIssuesFailed": "Неуспешно премахване на елементите от библиотеката с проблеми",
"ToastRemoveItemsWithIssuesSuccess": "Елементите от библиотеката с проблеми са премахнати",
"ToastRenameFailed": "Неуспешно преименуване",
"ToastRescanFailed": "Повторното сканиране е неуспешно за {0}",
"ToastRescanRemoved": "Повторното сканиране завърши: елементът е премахнат",
"ToastRescanUpToDate": "Повторното сканиране завърши: елементът вече е актуален",
"ToastRescanUpdated": "Повторното сканиране завърши: елементът е актуализиран",
"ToastScanFailed": "Неуспешно сканиране на елемент от библиотеката",
"ToastSelectAtLeastOneUser": "Изберете поне един потребител",
"ToastSendEbookToDeviceFailed": "Неуспешно изпращане на електронна книга до устройство",
"ToastSendEbookToDeviceSuccess": "Електронната книга е изпратена до устройство \"{0}\"",
"ToastSeriesSubmitFailedSameName": "Не могат да бъдат добавени два сериала с едно и също име",
"ToastSeriesUpdateFailed": "Неуспешно обновяване на серия",
"ToastSeriesUpdateSuccess": "Серията е обновена",
"ToastServerSettingsUpdateSuccess": "Настройките на сървъра са актуализирани",
"ToastSessionCloseFailed": "Неуспешно затваряне на сесията",
"ToastSessionDeleteFailed": "Неуспешно изтриване на сесия",
"ToastSessionDeleteSuccess": "Сесията е изтрита",
"ToastSleepTimerDone": "Таймерът за заспиване приключи... zZzzZz",
"ToastSlugMustChange": "Краткият URL (slug) съдържа невалидни символи",
"ToastSlugRequired": "Изисква се кратък URL (slug)",
"ToastSocketConnected": "Свързан сокет",
"ToastSocketDisconnected": "Сокетът е прекъснат",
"ToastSocketFailedToConnect": "Неуспешно свързване на сокет",
"ToastSortingPrefixesEmptyError": "Трябва да има поне 1 префикс за сортиране",
"ToastSortingPrefixesUpdateSuccess": "Префиксите за сортиране са актуализирани ({0} елемента)",
"ToastTitleRequired": "Изисква се заглавие",
"ToastUnknownError": "Неизвестна грешка",
"ToastUnlinkOpenIdFailed": "Неуспешно прекъсване на връзката на потребителя с OpenID",
"ToastUnlinkOpenIdSuccess": "Връзката на потребителя с OpenID е прекъсната",
"ToastUploaderFilepathExistsError": "Файловият път „{0}“ вече съществува на сървъра",
"ToastUploaderItemExistsInSubdirectoryError": "Елементът „{0}“ използва поддиректория на пътя за качване.",
"ToastUserDeleteFailed": "Неуспешно изтриване на потребител",
"ToastUserDeleteSuccess": "Потребителят е изтрит"
"ToastUserDeleteSuccess": "Потребителят е изтрит",
"ToastUserPasswordChangeSuccess": "Паролата е променена успешно",
"ToastUserPasswordMismatch": "Паролите не съвпадат",
"ToastUserPasswordMustChange": "Новата парола не може да бъде същата като старата",
"ToastUserRootRequireName": "Трябва да въведете root потребителско име",
"TooltipAddChapters": "Добавяне на глава(и)",
"TooltipAddOneSecond": "Добавяне на 1 секунда",
"TooltipAdjustChapterStart": "Кликнете за коригиране на началния час",
"TooltipLockAllChapters": "Заключване на всички глави",
"TooltipLockChapter": "Заключване на глава (Shift+клик за диапазон)",
"TooltipSubtractOneSecond": "Изваждане на 1 секунда",
"TooltipUnlockAllChapters": "Отключване на всички глави",
"TooltipUnlockChapter": "Отключване на глава (Shift+клик за диапазон)"
}
+6 -1
View File
@@ -166,6 +166,7 @@
"HeaderMetadataOrderOfPrecedence": "Ordre de Precedència de Metadades",
"HeaderMetadataToEmbed": "Metadades a Inserir",
"HeaderNewAccount": "Nou Compte",
"HeaderNewApiKey": "Nova clau API",
"HeaderNewLibrary": "Nova Biblioteca",
"HeaderNotificationCreate": "Crea Notificació",
"HeaderNotificationUpdate": "Actualització de Notificació",
@@ -199,6 +200,7 @@
"HeaderSettingsExperimental": "Funcionalitats experimentals",
"HeaderSettingsGeneral": "Generals",
"HeaderSettingsScanner": "Escàner",
"HeaderSettingsSecurity": "Seguretat",
"HeaderSettingsWebClient": "Client web",
"HeaderSleepTimer": "Temporitzador de son",
"HeaderStatsLargestItems": "Elements més grans",
@@ -421,6 +423,9 @@
"LabelLibraryFilterSublistEmpty": "Sense {0}",
"LabelLibraryItem": "Element de Biblioteca",
"LabelLibraryName": "Nom de Biblioteca",
"LabelLibrarySortByProgress": "Progrés: Última actualització",
"LabelLibrarySortByProgressFinished": "Progrés: Finalitzat",
"LabelLibrarySortByProgressStarted": "Progrés: Començat",
"LabelLimit": "Límits",
"LabelLineSpacing": "Interlineat",
"LabelListenAgain": "Escoltar de nou",
@@ -443,7 +448,7 @@
"LabelMetadataProvider": "Proveïdor de metadades",
"LabelMinute": "Minut",
"LabelMinutes": "Minuts",
"LabelMissing": "Absent",
"LabelMissing": "Falta",
"LabelMissingEbook": "No té llibre electrònic",
"LabelMissingSupplementaryEbook": "No té ebook complementari",
"LabelMobileRedirectURIs": "URI de redirecció mòbil permeses",
+1 -1
View File
@@ -385,7 +385,7 @@
"LabelFontBoldness": "Výraznost písma",
"LabelFontFamily": "Rodina písem",
"LabelFontItalic": "Kurzíva",
"LabelFontScale": "Měřítko písma",
"LabelFontScale": "Velikost písma",
"LabelFontStrikethrough": "Přeškrtnutí",
"LabelFormat": "Formát",
"LabelFull": "Plné",
+42 -6
View File
@@ -127,6 +127,7 @@
"HeaderAudiobookTools": "Audiobog Filhåndteringsværktøjer",
"HeaderAuthentication": "Autentificering",
"HeaderBackups": "Sikkerhedskopier",
"HeaderBulkChapterModal": "Tilføj flere kapitler",
"HeaderChangePassword": "Skift Adgangskode",
"HeaderChapters": "Kapitler",
"HeaderChooseAFolder": "Vælg en Mappe",
@@ -199,6 +200,7 @@
"HeaderSettingsExperimental": "Eksperimentelle Funktioner",
"HeaderSettingsGeneral": "Generelt",
"HeaderSettingsScanner": "Scanner",
"HeaderSettingsSecurity": "Sikkerhed",
"HeaderSettingsWebClient": "Webklient",
"HeaderSleepTimer": "Søvntimer",
"HeaderStatsLargestItems": "Største Elementer",
@@ -273,7 +275,7 @@
"LabelBonus": "Bonus",
"LabelBooks": "Bøger",
"LabelButtonText": "Knap tekst",
"LabelByAuthor": "af {0}",
"LabelByAuthor": "Efter Forfatter",
"LabelChangePassword": "Ændre Adgangskode",
"LabelChannels": "Kanaler",
"LabelChapterCount": "{0} Kapitler",
@@ -293,6 +295,7 @@
"LabelContinueListening": "Fortsæt med at lytte",
"LabelContinueReading": "Fortsæt med at læse",
"LabelContinueSeries": "Fortsæt Serien",
"LabelCorsAllowed": "Tilladte CORS-oprindelser",
"LabelCover": "Omslag",
"LabelCoverImageURL": "Omslagsbillede URL",
"LabelCoverProvider": "Cover billede udbyder",
@@ -306,6 +309,7 @@
"LabelDeleteFromFileSystemCheckbox": "Slet fra filsystem (afmarker kun for at fjerne fra databasen)",
"LabelDescription": "Beskrivelse",
"LabelDeselectAll": "Fravælg Alle",
"LabelDetectedPattern": "Identificeret mønster:",
"LabelDevice": "Enheds",
"LabelDeviceInfo": "Enhedsinformation",
"LabelDeviceIsAvailableTo": "Enhed er tilgængelig for...",
@@ -374,11 +378,12 @@
"LabelFilterByUser": "Filtrér efter bruger",
"LabelFindEpisodes": "Find episoder",
"LabelFinished": "Færdig",
"LabelFinishedDate": "Færdig {0}",
"LabelFolder": "Mappe",
"LabelFolders": "Mapper",
"LabelFontBold": "Fed",
"LabelFontBoldness": "Skrift tykkelse",
"LabelFontFamily": "Fontfamilie",
"LabelFontFamily": "Skrifttypefamilie",
"LabelFontItalic": "Kursiv",
"LabelFontScale": "Skriftstørrelse",
"LabelFontStrikethrough": "Gennemstreget",
@@ -418,6 +423,7 @@
"LabelLanguages": "Sprog",
"LabelLastBookAdded": "Senest tilføjede bog",
"LabelLastBookUpdated": "Senest opdaterede bog",
"LabelLastProgressDate": "Sidste fremgang: {0}",
"LabelLastSeen": "Sidst set",
"LabelLastTime": "Sidste gang",
"LabelLastUpdate": "Seneste opdatering",
@@ -430,6 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Nej {0}",
"LabelLibraryItem": "Bibliotekselement",
"LabelLibraryName": "Biblioteksnavn",
"LabelLibrarySortByProgress": "Fremgang: Sidst opdateret",
"LabelLibrarySortByProgressFinished": "Fremgang: Afsluttet",
"LabelLibrarySortByProgressStarted": "Fremgang: Startet",
"LabelLimit": "Grænse",
"LabelLineSpacing": "Linjeafstand",
"LabelListenAgain": "Lyt igen",
@@ -438,6 +447,7 @@
"LabelLogLevelWarn": "Advarsel",
"LabelLookForNewEpisodesAfterDate": "Søg efter nye episoder efter denne dato",
"LabelLowestPriority": "Laveste prioritet",
"LabelMatchConfidence": "Confidens",
"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.",
@@ -467,6 +477,7 @@
"LabelNewestAuthors": "Nyeste forfattere",
"LabelNewestEpisodes": "Nyeste episoder",
"LabelNextBackupDate": "Næste sikkerhedskopi dato",
"LabelNextChapters": "Næste kapitler vil være:",
"LabelNextScheduledRun": "Næste planlagte kørsel",
"LabelNoApiKeys": "Ingen API-nøgler",
"LabelNoCustomMetadataProviders": "Ingen brugerdefinerede metadata udbydere",
@@ -484,6 +495,7 @@
"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.",
"LabelNumberOfBooks": "Antal bøger",
"LabelNumberOfChapters": "Antal kapitler:",
"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.",
@@ -576,8 +588,8 @@
"LabelSettingsBookshelfViewHelp": "Skeumorfisk design med træhylder",
"LabelSettingsChromecastSupport": "Chromecast-understøttelse",
"LabelSettingsDateFormat": "Datoformat",
"LabelSettingsEnableWatcher": "Scan automatisk bibliotek for ændringer",
"LabelSettingsEnableWatcherForLibrary": "Scan automatisk bibliotek for ændringer",
"LabelSettingsEnableWatcher": "Automatisk biblioteksovervåger",
"LabelSettingsEnableWatcherForLibrary": "Automatisk biblioteksovervåger",
"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.",
@@ -626,6 +638,7 @@
"LabelStartTime": "Starttid",
"LabelStarted": "Startet",
"LabelStartedAt": "Startet klokken",
"LabelStartedDate": "Startet {0}",
"LabelStatsAudioTracks": "Lydspor",
"LabelStatsAuthors": "Forfattere",
"LabelStatsBestDay": "Bedste dag",
@@ -655,6 +668,7 @@
"LabelTheme": "Tema",
"LabelThemeDark": "Mørk",
"LabelThemeLight": "Lys",
"LabelThemeSepia": "Sepia",
"LabelTimeBase": "Tidsbase",
"LabelTimeDurationXHours": "{0} timer",
"LabelTimeDurationXMinutes": "{0} minutter",
@@ -739,6 +753,7 @@
"MessageBookshelfNoResultsForFilter": "Ingen resultater for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Intet resultat for query",
"MessageBookshelfNoSeries": "Du har ingen serier",
"MessageBulkChapterPattern": "Hvor mange kapitler vil du tilføje med dette nummereringsmønster?",
"MessageChapterEndIsAfter": "Kapitelslutningen er efter slutningen af din lydbog",
"MessageChapterErrorFirstNotZero": "Første kapitel skal starte ved 0",
"MessageChapterErrorStartGteDuration": "Ugyldig starttid skal være mindre end lydbogens varighed",
@@ -775,6 +790,7 @@
"MessageConfirmRemoveAuthor": "Er du sikker på, at du vil fjerne forfatteren \"{0}\"?",
"MessageConfirmRemoveCollection": "Er du sikker på, at du vil fjerne samlingen \"{0}\"?",
"MessageConfirmRemoveEpisode": "Er du sikker på, at du vil fjerne episoden \"{0}\"?",
"MessageConfirmRemoveEpisodeNote": "Obs: Dette sletter ikke lydfilen medmindre \"Permanent sletning af fil\" er aktiveret",
"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?",
@@ -800,6 +816,8 @@
"MessageFeedURLWillBe": "Feed-URL vil være {0}",
"MessageFetching": "Henter...",
"MessageForceReScanDescription": "vil scanne alle filer igen som en frisk scanning. Lydfilens ID3-tags, OPF-filer og tekstfiler scannes som nye.",
"MessageHeatmapListeningTimeTooltip": "<strong>{0} lytter</strong> på {1}",
"MessageHeatmapNoListeningSessions": "Ingen lyttesessioner på {0}",
"MessageImportantNotice": "Vigtig besked!",
"MessageInsertChapterBelow": "Indsæt kapitel nedenfor",
"MessageInvalidAsin": "Ugyldig ASIN",
@@ -870,7 +888,7 @@
"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",
"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.",
"MessageScheduleLibraryScanNote": "For de fleste brugere er det anbefalet, at efterlade denne funktion deaktiveret, og lade biblioteksovervågeren være aktiveret - den vil automatisk opdage ændringer i dine biblioteksmapper. Aktiver denne funktion, hvis biblioteksovervågeren ikke virker med dit filsystem (f. eks. NFS).",
"MessageScheduleRunEveryWeekdayAtTime": "Kør hvert {0} af {1}",
"MessageSearchResultsFor": "Søgeresultater for",
"MessageSelected": "{0} valgt",
@@ -939,6 +957,7 @@
"NotificationOnRSSFeedDisabledDescription": "Aktiveret når automatiske episode-downloads er slået fra, på grund af for mange forsøg",
"NotificationOnRSSFeedFailedDescription": "Aktiveret når anmodning om RSS-feedet fejler for en automatisk episode-download",
"NotificationOnTestDescription": "Event for test af notifikationssystemet",
"PlaceholderBulkChapterInput": "Indtast kapiteltitel eller brug nummerering (f.eks. 'Episode 1', 'Kapitel 10', '1.')",
"PlaceholderNewCollection": "Nyt samlingnavn",
"PlaceholderNewFolderPath": "Ny mappes sti",
"PlaceholderNewPlaylist": "Nyt afspilningslistnavn",
@@ -992,9 +1011,15 @@
"ToastBookmarkCreateFailed": "Mislykkedes oprettelse af bogmærke",
"ToastBookmarkCreateSuccess": "Bogmærke tilføjet",
"ToastBookmarkRemoveSuccess": "Bogmærke fjernet",
"ToastBulkChapterInvalidCount": "Indtast et tal mellem 1 og 150",
"ToastCachePurgeFailed": "Fejlede at opryde cache",
"ToastCachePurgeSuccess": "Cache ryddet op i succesfuldt",
"ToastChapterLocked": "Kapitel er låst.",
"ToastChapterStartTimeAdjusted": "Kapitelstarttid justeret med {0} sekunder",
"ToastChaptersAllLocked": "Alle kapitler er låst. Lås op for nogle kapitler for at ændre deres tider.",
"ToastChaptersHaveErrors": "Kapitler har fejl",
"ToastChaptersInvalidShiftAmountLast": "Ugyldig ændring. Det sidste kapitels starttid ville fortsætte længere end varigheden på denne lydbog.",
"ToastChaptersInvalidShiftAmountStart": "Ugyldig ændring. Første kapitel ville have en længde på nul eller negativt og ville blive overskrevet af andet kapitel. Udvid startvarigheden på andet kapitel.",
"ToastChaptersMustHaveTitles": "Kapitler skal have titler",
"ToastChaptersRemoved": "Kapitler fjernet",
"ToastChaptersUpdated": "Kapitler opdateret",
@@ -1002,6 +1027,7 @@
"ToastCollectionRemoveSuccess": "Samling fjernet",
"ToastCollectionUpdateSuccess": "Samling opdateret",
"ToastConnectionNotAvailable": "Forbindelse mislykkedes. Prøv igen senere",
"ToastCoverSearchFailed": "Cover-søgning mislykkedes",
"ToastCoverUpdateFailed": "Cover opdatering fejlede",
"ToastDateTimeInvalidOrIncomplete": "Dato og tid er ugyldig eller ufærdig",
"ToastDeleteFileFailed": "Sletning af fil fejlede",
@@ -1051,6 +1077,7 @@
"ToastMustHaveAtLeastOnePath": "Skal have mindst en sti",
"ToastNameEmailRequired": "Navn og email påkrævet",
"ToastNameRequired": "Navn påkrævet",
"ToastNewApiKeyUserError": "En bruger skal vælges",
"ToastNewEpisodesFound": "{0} nye afsnit fundet",
"ToastNewUserCreatedFailed": "Fejlede at oprette konto: \"{0}\"",
"ToastNewUserCreatedSuccess": "Ny konto oprettet",
@@ -1075,6 +1102,7 @@
"ToastPlaylistUpdateSuccess": "Afspilningsliste opdateret",
"ToastPodcastCreateFailed": "Mislykkedes oprettelse af podcast",
"ToastPodcastCreateSuccess": "Podcast oprettet med succes",
"ToastPodcastEpisodeUpdated": "Episode opdateret",
"ToastPodcastGetFeedFailed": "Fejlede at hente podcast feed",
"ToastPodcastNoEpisodesInFeed": "Ingen nye afsnit fundet i RSS feed",
"ToastPodcastNoRssFeed": "Podcast har ingen RSS feed",
@@ -1125,5 +1153,13 @@
"ToastUserPasswordChangeSuccess": "Password ændret",
"ToastUserPasswordMismatch": "Passwords passer ikke sammen",
"ToastUserPasswordMustChange": "Nyt password må ikke være det gamle",
"ToastUserRootRequireName": "Skal indholde et root brugernavn"
"ToastUserRootRequireName": "Skal indholde et root brugernavn",
"TooltipAddChapters": "Tilføj kapitler",
"TooltipAddOneSecond": "Tilføj 1 sekund",
"TooltipAdjustChapterStart": "Klik for at ændre starttiden",
"TooltipLockAllChapters": "Lås alle kapitler",
"TooltipLockChapter": "Lås kapitel (Shift+click for at markere flere)",
"TooltipSubtractOneSecond": "Fratag 1 sekund",
"TooltipUnlockAllChapters": "Lås alle kapitaler op",
"TooltipUnlockChapter": "Lås kapitel op (Shift+click for at markere flere)"
}
+10 -10
View File
@@ -13,7 +13,7 @@
"ButtonBack": "Zurück",
"ButtonBatchEditPopulateFromExisting": "Auffüllen aus vorhandenem",
"ButtonBatchEditPopulateMapDetails": "Kartendetails auffüllen",
"ButtonBrowseForFolder": "Ordnersuche",
"ButtonBrowseForFolder": "Ordner auswählen",
"ButtonCancel": "Abbrechen",
"ButtonCancelEncode": "Konvertierung abbrechen",
"ButtonChangeRootPassword": "Hauptpasswort ändern",
@@ -116,7 +116,7 @@
"ButtonViewAll": "Alles anzeigen",
"ButtonYes": "Ja",
"ErrorUploadFetchMetadataAPI": "Fehler beim Abrufen der Metadaten",
"ErrorUploadFetchMetadataNoResults": "Metadaten konnten nicht abgerufen werden. Versuche den Titel und/oder den Autor zu aktualisieren.",
"ErrorUploadFetchMetadataNoResults": "Metadaten konnten nicht abgerufen werden - versuche den Titel und/oder den Autor zu aktualisieren",
"ErrorUploadLacksTitle": "Es muss ein Titel eingegeben werden",
"HeaderAccount": "Konto",
"HeaderAddCustomMetadataProvider": "Benutzerdefinierten Metadatenanbieter hinzufügen",
@@ -331,7 +331,7 @@
"LabelEmail": "E-Mail",
"LabelEmailSettingsFromAddress": "Sender",
"LabelEmailSettingsRejectUnauthorized": "Nicht autorisierte Zertifikate ablehnen",
"LabelEmailSettingsRejectUnauthorizedHelp": "Durch das Deaktivieren der SSL-Zertifikatsüberprüfung kann deine Verbindung Sicherheitsrisiken wie Man-in-the-Middle-Angriffen ausgesetzt sein. Deaktiviere diese Option nur, wenn due die Auswirkungen verstehst und dem E-Mail-Server vertraust, mit dem eine Verbindung hergestellt wird.",
"LabelEmailSettingsRejectUnauthorizedHelp": "Durch das Deaktivieren der SSL-Zertifikatsüberprüfung kann deine Verbindung Sicherheitsrisiken wie Man-in-the-Middle-Angriffen ausgesetzt sein. Deaktiviere diese Option nur, wenn du die Auswirkungen verstehst und dem E-Mail-Server vertraust, mit dem eine Verbindung hergestellt wird.",
"LabelEmailSettingsSecure": "Sicher",
"LabelEmailSettingsSecureHelp": "Wenn an, verwendet die Verbindung TLS, wenn du eine Verbindung zum Server herstellst. Bei „aus“ wird TLS verwendet, wenn der Server die STARTTLS-Erweiterung unterstützt. In den meisten Fällen solltest du diesen Wert auf „an“ schalten, wenn du eine Verbindung zu Port 465 herstellst. Für Port 587 oder 25 behalte den Wert „aus“ bei. (von nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test-Adresse",
@@ -436,7 +436,7 @@
"LabelLibraryFilterSublistEmpty": "Keine {0}",
"LabelLibraryItem": "Bibliothekseintrag",
"LabelLibraryName": "Bibliotheksname",
"LabelLibrarySortByProgress": "Fortschritt: Zuletzt aktualisiert",
"LabelLibrarySortByProgress": "Fortschritt: Letzte Aktualisierung",
"LabelLibrarySortByProgressFinished": "Fortschritt: Beendet",
"LabelLibrarySortByProgressStarted": "Fortschritt: Gestartet",
"LabelLimit": "Begrenzung",
@@ -622,7 +622,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Standardmäßig werden die Metadaten in /metadata/items gespeichert. Wenn diese Option aktiviert ist, werden die Metadaten als OPF-Datei (Textdatei) in dem gleichen Ordner gespeichert in welchem sich auch das Medium befindet",
"LabelSettingsTimeFormat": "Zeitformat",
"LabelShare": "Freigeben",
"LabelShareDownloadableHelp": "Erlaubt es einem Nutzer, mit dem Link, die Dateien des Mediums als ZIP herunterzuladen.",
"LabelShareDownloadableHelp": "Erlaubt es einem Nutzer, mit dem Link die Dateien des Mediums als ZIP herunterzuladen.",
"LabelShareOpen": "Freigeben",
"LabelShareURL": "Freigabe URL",
"LabelShowAll": "Alles anzeigen",
@@ -710,7 +710,7 @@
"LabelUploaderDragAndDropFilesOnly": "Dateien per Drag & Drop hierher ziehen",
"LabelUploaderDropFiles": "Dateien löschen",
"LabelUploaderItemFetchMetadataHelp": "Automatisches Aktualisieren von Titel, Autor und Serie",
"LabelUseAdvancedOptions": "Nutze Erweiterte Optionen",
"LabelUseAdvancedOptions": "Erweiterte Optionen verwenden",
"LabelUseChapterTrack": "Kapiteldatei verwenden",
"LabelUseFullTrack": "Gesamte Datei verwenden",
"LabelUseZeroForUnlimited": "0 für unbegrenzt",
@@ -737,7 +737,7 @@
"MessageAddToPlayerQueue": "Zur Abspielwarteliste hinzufügen",
"MessageAppriseDescription": "Um diese Funktion nutzen zu können, musst du eine Instanz von <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> laufen haben oder eine API verwenden welche dieselbe Anfragen bearbeiten kann. <br />Die Apprise API Url muss der vollständige URL-Pfad sein, an den die Benachrichtigung gesendet werden soll, z.B. wenn Ihre API-Instanz unter <code>http://192.168.1.1:8337</code> läuft, würdest du <code>http://192.168.1.1:8337/notify</code> eingeben.",
"MessageAsinCheck": "Stelle sicher, dass die ASIN aus der richtigen Audible Region verwendet wird, nicht Amazon.",
"MessageAuthenticationLegacyTokenWarning": "Alte API tokens werden in Zukunft entfernt. Benutze stattdessen <a href=\"/config/api-keys\">API Keys</a>.",
"MessageAuthenticationLegacyTokenWarning": "Nicht mehr unterstützte API tokens werden in der Zukunft entfernt. Nutze stattdessen <a href=\"/config/api-keys\">API Schlüssel</a>.",
"MessageAuthenticationOIDCChangesRestart": "Nach dem Speichern muss der Server neugestartet werden um die OIDC Änderungen zu übernehmen.",
"MessageAuthenticationSecurityMessage": "Die Anmeldung wurde abgesichert. Benutzersitzungen werden getrennt, alle Benutzer müssen sich erneut anmelden.",
"MessageBackupsDescription": "In einer Sicherung werden Benutzer, Benutzerfortschritte, Details zu den Bibliotheksobjekten, Servereinstellungen und Bilder welche in <code>/metadata/items</code> & <code>/metadata/authors</code> gespeichert sind gespeichert. Sicherungen enthalten keine Dateien welche in den einzelnen Bibliotheksordnern (Medien-Ordnern) gespeichert sind.",
@@ -816,7 +816,7 @@
"MessageFeedURLWillBe": "Feed-URL wird {0} sein",
"MessageFetching": "Wird abgerufen …",
"MessageForceReScanDescription": "Durchsucht alle Dateien erneut, wie bei einem frischen Scan. ID3-Tags von Audiodateien, OPF-Dateien und Textdateien werden neu durchsucht.",
"MessageHeatmapListeningTimeTooltip": "<strong>{0} </strong> auf {1} gehört",
"MessageHeatmapListeningTimeTooltip": "<strong>{0} gehört</strong> auf {1}",
"MessageHeatmapNoListeningSessions": "Keine Hörsitzungen am {0}",
"MessageImportantNotice": "Wichtiger Hinweis!",
"MessageInsertChapterBelow": "Kapitel unten einfügen",
@@ -1103,11 +1103,11 @@
"ToastPodcastCreateFailed": "Podcast konnte nicht erstellt werden",
"ToastPodcastCreateSuccess": "Podcast erstellt",
"ToastPodcastEpisodeUpdated": "Podcast-Folge aktualisiert",
"ToastPodcastGetFeedFailed": "Fehler beim abrufen des Podcast Feeds",
"ToastPodcastGetFeedFailed": "Fehler beim Abrufen des Podcast Feeds",
"ToastPodcastNoEpisodesInFeed": "Keine Episoden in RSS Feed gefunden",
"ToastPodcastNoRssFeed": "Podcast enthält keinen RSS Feed",
"ToastProgressIsNotBeingSynced": "Fortschritt wird nicht synchronisiert, Wiedergabe wird neu gestartet",
"ToastProviderCreatedFailed": "Fehler beim hinzufügen des Anbieters",
"ToastProviderCreatedFailed": "Fehler beim Hinzufügen des Anbieters",
"ToastProviderCreatedSuccess": "Neuer Anbieter hinzugefügt",
"ToastProviderNameAndUrlRequired": "Name und URL notwendig",
"ToastProviderRemoveSuccess": "Anbieter entfernt",
+324
View File
@@ -0,0 +1,324 @@
{
"ButtonAdd": "Προσθήκη",
"ButtonAddApiKey": "Προσθήκη Κλειδιού API",
"ButtonAddChapters": "Προσθήκη Κεφαλαίων",
"ButtonAddDevice": "Προσθήκη Συσκευής",
"ButtonAddLibrary": "Προσθήκη Βιβλιοθήκης",
"ButtonAddPodcasts": "Προσθήκη Podcasts",
"ButtonAddUser": "Προσθήκη Χρήστη",
"ButtonAddYourFirstLibrary": "Πρόσθεσε την πρώτη σου βιβλιοθήκη",
"ButtonApply": "Εφαρμογή",
"ButtonApplyChapters": "Εφαρμογή Κεφαλαίων",
"ButtonAuthors": "Συγγραφείς",
"ButtonBack": "Πίσω",
"ButtonBatchEditPopulateFromExisting": "Συμπλήρωση από υπάρχοντα",
"ButtonBatchEditPopulateMapDetails": "Συμπλήρωση λεπτομερειών χάρτη",
"ButtonBrowseForFolder": "Περιήγηση για Φάκελο",
"ButtonCancel": "Ακύρωση",
"ButtonCancelEncode": "Ακύρωση Κωδικοποίησης",
"ButtonChangeRootPassword": "Αλλαγή Κωδικού Πρόσβασης Root",
"ButtonCheckAndDownloadNewEpisodes": "Έλεγχος και Κατέβασμα Νέων Επεισοδίων",
"ButtonChooseAFolder": "Επιλογή φακέλου",
"ButtonChooseFiles": "Επιλογή αρχείων",
"ButtonClearFilter": "Διαγραφή Φίλτρου",
"ButtonClose": "Κλείσιμο",
"ButtonCloseFeed": "Κλείσιμο Τροφοδοσίας",
"ButtonCloseSession": "Κλείσιμο Ανοιχτής Συνεδρίας",
"ButtonCollections": "Συλλογές",
"ButtonConfigureScanner": "Ρύθμιση Παραμέτρων Σαρωτή",
"ButtonCreate": "Δημιουργία",
"ButtonCreateBackup": "Δημιουργία Αντιγράφου Ασφαλείας",
"ButtonDelete": "Διαγραφή",
"ButtonDownloadQueue": "Ουρά",
"ButtonEdit": "Επεξεργασία",
"ButtonEditChapters": "Επεξεργασία Κεφαλαίων",
"ButtonEditPodcast": "Επεξεργασία Podcast",
"ButtonEnable": "Ενεργοποίηση",
"ButtonForceReScan": "Αναγκαστική Επανάληψη Σάρωσης",
"ButtonFullPath": "Πλήρης Διαδρομή",
"ButtonHide": "Απόκρυψη",
"ButtonHome": "Αρχική",
"ButtonIssues": "Θέματα",
"ButtonJumpBackward": "Μεταπήδηση Πίσω",
"ButtonJumpForward": "Μεταπήδηση Μπροστά",
"ButtonLatest": "Τελευταία",
"ButtonLibrary": "Βιβλιοθήκη",
"ButtonLogout": "Αποσύνδεση",
"ButtonLookup": "Εύρεση",
"ButtonManageTracks": "Διαχείριση Κομματιών",
"ButtonMapChapterTitles": "Χαρτογράφηση Τίτλων Κεφαλαίων",
"ButtonMatchAllAuthors": "Αντιστοίχιση Όλων των Συγγραφέων",
"ButtonMatchBooks": "Αντιστοίχιση Βιβλίων",
"ButtonNevermind": "Άστο",
"ButtonNext": "Επόμενο",
"ButtonNextChapter": "Επόμενο Κεφάλαιο",
"ButtonNextItemInQueue": "Επόμενο Αντικείμενο στην Ουρά",
"ButtonOk": "Εντάξει",
"ButtonOpenFeed": "Άνοιγμα Τροφοδοσίας",
"ButtonOpenManager": "Άνοιγμα Διαχειριστή",
"ButtonPause": "Παύση",
"ButtonPlay": "Αναπαραγωγή",
"ButtonPlayAll": "Αναπαραγωγή Όλων",
"ButtonPlaying": "Αναπαράγεται",
"ButtonPlaylists": "Λίστες Αναπαραγωγής",
"ButtonPrevious": "Προηγούμενο",
"ButtonPreviousChapter": "Προηγούμενο Κεφάλαιο",
"ButtonProbeAudioFile": "Ανάλυση Αρχείου Ήχου",
"ButtonPurgeAllCache": "Εκκαθάριση Όλης της Προσωρινής Μνήμης",
"ButtonPurgeItemsCache": "Εκκαθάριση της Μνήμης Αντικειμένων",
"ButtonQueueAddItem": "Προσθήκη στην ουρά",
"ButtonQueueRemoveItem": "Αφαίρεση απ'την ουρά",
"ButtonQuickEmbed": "Γρήγορη Ενσωμάτωση",
"ButtonQuickEmbedMetadata": "Γρήγορη Ενσωμάτωση Μεταδεδομένων",
"ButtonQuickMatch": "Γρήγορη Αντιστοίχηση",
"ButtonReScan": "Επανασάρωση",
"ButtonRead": "Ανάγνωση",
"ButtonReadLess": "Ανάγνωση λιγότερων",
"ButtonReadMore": "Διάβασε περισσότερα",
"ButtonRefresh": "Ανανέωση",
"ButtonRemove": "Αφαίρεση",
"ButtonRemoveAll": "Αφαίρεση Όλων",
"ButtonRemoveAllLibraryItems": "Αφαίρεση Όλων των Αντικειμέων Βιβλιοθήκης",
"ButtonRemoveFromContinueListening": "Αφαίρεση από τη Συνέχεια Ακρόασης",
"ButtonRemoveFromContinueReading": "Αφαίρεση από τη Συνέχεια Ανάγνωσης",
"ButtonRemoveSeriesFromContinueSeries": "Αφαίρεση Σειράς από τη Συνέχεια Σειράς",
"ButtonReset": "Επαναφορά",
"ButtonResetToDefault": "Επαναφορά στις προεπιλογές",
"ButtonRestore": "Επαναφορά",
"ButtonSave": "Αποθήκευση",
"ButtonSaveAndClose": "Αποθήκευση και Κλείσιμο",
"ButtonSaveTracklist": "Αποθήκευση Λίστας Κομματιών",
"ButtonScan": "Σάρψση",
"ButtonScanLibrary": "Σάρωση Βιβλιοθήκης",
"ButtonScrollLeft": "Κύλιση Αριστερά",
"ButtonScrollRight": "Κύλιση Δεξιά",
"ButtonSearch": "Αναζήτηση",
"ButtonSelectFolderPath": "Επιλογή Διαδρομής Φακέλου",
"ButtonSeries": "Σειρά",
"ButtonSetChaptersFromTracks": "Ορισμός κεφαλαίων από τα κομμάτια",
"ButtonShare": "Κοινοποίηση",
"ButtonShiftTimes": "Χρόνοι Μετακίνησης",
"ButtonShow": "Εμφάνιση",
"ButtonStartM4BEncode": "Έναρξη Κωδικοποίησης M4B",
"ButtonStartMetadataEmbed": "Έναρξη Ενσωμάτωσης Μεταδεδομένων",
"ButtonStats": "Στατιστικά",
"ButtonSubmit": "Υποβολή",
"ButtonTest": "Δοκιμή",
"ButtonUnlinkOpenId": "Αποσύνδεση OpenID",
"ButtonUpload": "Μεταφόρτωση",
"ButtonUploadBackup": "Μεταφόρτωση Αντιγράφου Ασφαλείας",
"ButtonUploadCover": "Μεταφόρτωση Εξωφύλλου",
"ButtonUploadOPMLFile": "Μεταφόρτωση Αρχείου OPML",
"ButtonUserDelete": "Διαγραφή Χρήστη {0}",
"ButtonUserEdit": "Επεξεργασίας χρήστη {0}",
"ButtonViewAll": "Εμφάνιση Όλων",
"ButtonYes": "Ναι",
"ErrorUploadFetchMetadataAPI": "Σφάλμα κατά την ανάκτηση μεταδεδομένων",
"ErrorUploadFetchMetadataNoResults": "Δεν ήταν δυνατή η ανάκτηση των μεταδεδομένων - δοκιμάστε να ενημερώσετε τον τίτλο και/ή τον συγγραφέα",
"ErrorUploadLacksTitle": "Πρέπει να έχει τίτλο",
"HeaderAccount": "Λογαριασμός",
"HeaderAddCustomMetadataProvider": "Προσθήκη Προσαρμοσμένου Παρόχου Μεταδεδομένων",
"HeaderAdvanced": "Για Προχωρημένους",
"HeaderApiKeys": "Κλειδιά API",
"HeaderAppriseNotificationSettings": "Ρυθμίσεις Ειδοποιήσεων Apprise",
"HeaderAudioTracks": "Κομμάτια Ήχου",
"HeaderAudiobookTools": "Εργαλεία Διαχείρισης Αρχείων Audiobooks",
"HeaderAuthentication": "Αυθεντικοποίηση",
"HeaderBackups": "Αντίγραφα Ασφαλείας",
"HeaderBulkChapterModal": "Προσθήκη Πολλαπλών Κεφαλαίων",
"HeaderChangePassword": "Αλλαγή Κωδικού Πρόσβασης",
"HeaderChapters": "Κεφάλαια",
"HeaderChooseAFolder": "Επιλογή Φακέλου",
"HeaderCollection": "Συλλογή",
"HeaderCollectionItems": "Αντικείμενα Συλλογής",
"HeaderCover": "Εξώφυλλο",
"HeaderCurrentDownloads": "Τρέχουσες Λήψεις",
"HeaderDetails": "Λεπτομέρειες",
"HeaderDownloadQueue": "Ουρά Λήψης",
"HeaderEbookFiles": "Αρχεία Ebook",
"HeaderEmail": "Ηλεκτρονικό Ταχυδρομίο",
"HeaderEmailSettings": "Ρυθμίσεις Ηλεκτρονικού Ταχυδρομίου",
"HeaderEpisodes": "Επεισόδια",
"HeaderEreaderSettings": "Ρυθμίσεις Ereader",
"HeaderFiles": "Αρχεία",
"HeaderFindChapters": "Εύρεση Κεφαλαίων",
"HeaderItemFiles": "Αρχεία Αντικειμένων",
"HeaderLastListeningSession": "Τελευταία Συνεδρία Ακρόασης",
"HeaderLatestEpisodes": "Τελευταία Επεισόδια",
"HeaderLibraries": "Βιβλιοθήκες",
"HeaderLibraryFiles": "Αρχεία Βιβλιοθήκης",
"HeaderLibraryStats": "Στατιστικά Βιβλιοθήκης",
"HeaderListeningSessions": "Συνεδρίες Ακρόασης",
"HeaderListeningStats": "Στατιστικά Ακρόασης",
"HeaderMatch": "Ταύτιση",
"HeaderNewAccount": "Νέος Λογαριασμός",
"HeaderNewApiKey": "Νέο Κλειδί API",
"HeaderNewLibrary": "Νέα Βιβλιοθήκη",
"HeaderNotificationCreate": "Δημιουργία Ειδοποίησης",
"HeaderNotificationUpdate": "Ενημέρωση Ειδοποίησης",
"HeaderNotifications": "Ειδοποιήσεις",
"HeaderOpenRSSFeed": "Άνοιγμα Τροφοδοσίας RSS",
"HeaderOtherFiles": "Άλλα Αρχεία",
"HeaderPermissions": "Δικαιώματα",
"HeaderPlayerSettings": "Ρυθμίσεις Αναπαραγωγής",
"HeaderPlaylist": "Λίστα Αναπαραγωγής",
"HeaderPlaylistItems": "Αντικείμενα Λίστας Αναπαραγωγής",
"HeaderPresets": "Προεπιλογές",
"HeaderRSSFeedGeneral": "Λεπτομέρειες RSS",
"HeaderRSSFeedIsOpen": "Η Τροφοδοσία RSS είναι Ανοιχτή",
"HeaderRemoveEpisode": "Αφαίρεση Επεισοδίου",
"HeaderSession": "Συνεδρία",
"HeaderSetBackupSchedule": "Ορισμός Προγράμματος Αντιγράφων Ασφαλείας",
"HeaderSettings": "Ρυθμίσεις",
"HeaderSettingsDisplay": "Προβολή",
"HeaderSettingsGeneral": "Γενικά",
"HeaderSettingsSecurity": "Ασφάλεια",
"HeaderSleepTimer": "Χρονοδιακόπτης Ύπνου",
"HeaderStatsLargestItems": "Μεγαλύτερα Αντικείμενα",
"HeaderStatsLongestItems": "Μεγαλύτερα Αντικείμενα (ώρες)",
"HeaderStatsMinutesListeningChart": "Λεπτά Ακρόασης (τελευταίες 7 ημέρες)",
"HeaderStatsRecentSessions": "Πρόσφατες Συνεδρίες",
"HeaderStatsTop10Authors": "10 Κορυφαίου Συγγραφείς",
"HeaderStatsTop5Genres": "5 Κορυφαία Είδη",
"HeaderTableOfContents": "Πίνακας Περιεχομένων",
"HeaderTools": "Εργαλεία",
"HeaderUpdateAccount": "Ενημέρωση Λογαριασμού",
"HeaderUpdateApiKey": "Ενημέρωση Κλειδιού API",
"HeaderUpdateAuthor": "Ενημέρωση Συγγραφέα",
"HeaderUpdateDetails": "Ενημέρωση Λεπτομερειεών",
"HeaderUpdateLibrary": "Ενημέρωση Βιβλιοθήκης",
"HeaderUsers": "Χρήστες",
"HeaderYourStats": "Τα Στατιστικά Σας",
"LabelAbridged": "Συνοπτικό",
"LabelAccessibleBy": "Προσβάσιμο από",
"LabelAccountType": "Τύπος Λογαριασμού",
"LabelAccountTypeAdmin": "Διαχειριστής",
"LabelAccountTypeGuest": "Επισκέπτης",
"LabelAccountTypeUser": "Χρήστης",
"LabelAddToCollection": "Προσθήκη σε Συλλογή",
"LabelAddToCollectionBatch": "Προσθήκη {0} Βιβλίων στην Συλλογή",
"LabelAddToPlaylist": "Προσθήκη στην Λίστα Αναπαραγωγής",
"LabelAddedAt": "Προστέθηκε Στις",
"LabelAddedDate": "Προστέθηκε {0}",
"LabelAll": "Όλα",
"LabelAllEpisodesDownloaded": "Όλα τα επεισόδια λήφθηκαν",
"LabelAllUsers": "Όλοι οι Χρήστες",
"LabelAlreadyInYourLibrary": "Υπάρχει ήδη στην βιβλιοθήκη",
"LabelAudioChannels": "Κανάλια Ήχου (1 ή 2)",
"LabelAuthor": "Συγγραφέας",
"LabelAuthorFirstLast": "Συγγραφέας (Όνομα Επώνυμο)",
"LabelAuthorLastFirst": "Συγγραφέας (Επώνυμο, Όνομα)",
"LabelAuthors": "Συγγραφείς",
"LabelAutoDownloadEpisodes": "Αυτόματο Κατέβασμα Επεισοδίων",
"LabelAutoLaunch": "Αυτόματη Εκκίνηση",
"LabelBackupLocation": "Τοποθεσία Αντιγράφου Ασφαλείας",
"LabelBackupsEnableAutomaticBackups": "Αυτόματα αντίγραφα ασφαλείας",
"LabelBackupsNumberToKeep": "Αριθμός αντιγράφων ασφαλείας προς διατήρηση",
"LabelBooks": "Βιβλία",
"LabelButtonText": "Κείμενο Κουμπιού",
"LabelByAuthor": "κατά {0}",
"LabelChangePassword": "Αλλαγή Κωδικού Πρόσβασης",
"LabelChannels": "Κανάλια",
"LabelChapterCount": "{0} Κεφάλαια",
"LabelChapterTitle": "Τίτλος Κεφαλαίου",
"LabelChapters": "Κεφάλαια",
"LabelChaptersFound": "κεφάλαια βρέθηκαν",
"LabelClosePlayer": "Κλείσιμο αναπαραγωγής",
"LabelCollapseSeries": "Σύμπτυξη Σειράς",
"LabelCollection": "Συλλογή",
"LabelCollections": "Συλλογές",
"LabelComplete": "Ολοκλήρωση",
"LabelConfirmPassword": "Επιβεβαίωση Κωδικού Πρόσβασης",
"LabelContinueListening": "Συνέχεια Ακρόασης",
"LabelContinueReading": "Συνέχεια Ανάγνωσης",
"LabelContinueSeries": "Συνέχεια Σειράς",
"LabelCover": "Εξώφυλλο",
"LabelCoverImageURL": "URL Εικόνας Εξωφύλλου",
"LabelCoverProvider": "Πάροχος Εξωφύλλου",
"LabelCreatedAt": "Δημιουρήθηκε Στις",
"LabelCurrent": "Τρέχων",
"LabelCurrently": "Τρέχων:",
"LabelDays": "Ημέρες",
"LabelDescription": "Περιγραφή",
"LabelDevice": "Συσκευή",
"LabelDeviceInfo": "Πληροφορίες Συσκευής",
"LabelDownload": "Λήψη",
"LabelDownloadNEpisodes": "Λήψη {0} επεισοδίων",
"LabelDuration": "Διάρκεια",
"LabelDurationComparisonExactMatch": "(ακριβής ταύτιση)",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
"LabelEdit": "Επεξεργασία",
"LabelEmail": "Ηλεκτρονικό Ταχυδρομίο",
"LabelEmailSettingsFromAddress": "Από Διεύθυνση",
"LabelEmailSettingsSecure": "Ασφαλές",
"LabelEmailSettingsTestAddress": "Δοκιμή Διεύθυνσης",
"LabelEmbeddedCover": "Ενσωματωμένο Εξώφυλλο",
"LabelEnable": "Ενεργοποίηση",
"LabelEnd": "Τέλος",
"LabelEndOfChapter": "Τέλος Κεφαλαίου",
"LabelEpisode": "Επεισόδιο",
"LabelFile": "Αρχείο",
"LabelFilename": "Όνομα Αρχείου",
"LabelFinished": "Ολοκληρώθηκε",
"LabelFolder": "Φάκελος",
"LabelFontFamily": "Οικογένεια Γραμματοσειράς",
"LabelGenre": "Είδος",
"LabelGenres": "Είδη",
"LabelHost": "Διακομιστής",
"LabelInProgress": "Σε Εξέλιξη",
"LabelLanguage": "Γλώσσα",
"LabelLayoutSinglePage": "Μονή Σελίδα",
"LabelListenAgain": "Επανάληψη Ακρόασης",
"LabelMediaType": "Τύπος Πολυμέσων",
"LabelMore": "Περισσότερα",
"LabelMoreInfo": "Περισσότερες Πληροφορίες",
"LabelName": "Όνομα",
"LabelNarrator": "Αφηγητής",
"LabelNarrators": "Αφηγητές",
"LabelNewestAuthors": "Πρόσφατοι Συγγραφείς",
"LabelNewestEpisodes": "Πρόσφατα Επεισόδια",
"LabelNotStarted": "Δεν Έχει Ξεκινήσει",
"LabelNumberOfEpisodes": "# Επεισοδίων",
"LabelPassword": "Κωδικός Πρόσβασης",
"LabelPath": "Διαδρομή",
"LabelProgress": "Πρόοδος",
"LabelPublishYear": "Χρονολογία Έκδοσης",
"LabelPublishedDate": "Εκδόθηκε {0}",
"LabelRandomly": "Τυχαία",
"LabelRead": "Ανάγνωση",
"LabelReadAgain": "Ανάγνωση Ξανά",
"LabelRecentSeries": "Πρόσφατη Σειρά",
"LabelRecentlyAdded": "Προστέθηκαν Πρόσφατα",
"LabelSeries": "Σειρά",
"LabelSetEbookAsPrimary": "Ορισμός ως πρωτεύων",
"LabelShowAll": "Εμφάνιση Όλων",
"LabelSize": "Μέγεθος",
"LabelSleepTimer": "Χρονοδιακόπτης Ύπνου",
"LabelStart": "Έναρξη",
"LabelStatsBestDay": "Καλύτερη Ημέρα",
"LabelStatsDailyAverage": "Ημερήσιος Μέσος Όρος",
"LabelStatsDays": "Ημέρες",
"LabelStatsDaysListened": "Ημέρες Ακρόασης",
"LabelStatsInARow": "Σε σειρά",
"LabelStatsItemsFinished": "Ολοκληρωμένα Αντικείμενα",
"LabelStatsMinutes": "λεπτά",
"LabelStatsMinutesListening": "Λεπτά Ακρόασης",
"LabelStatsWeekListening": "Εβδομαδιαία Ακρόαση",
"LabelTheme": "Θέμα",
"LabelThemeDark": "Σκοτεινό",
"LabelThemeLight": "Φωτεινό",
"LabelTimeRemaining": "{0} απομένουν",
"LabelTitle": "Τίτλος",
"LabelTracks": "Κομμάτια",
"LabelType": "Τύπος",
"LabelUnknown": "Άγνωστο",
"LabelUser": "Χρήστης",
"LabelUsername": "Όνομα Χρήστη",
"LabelYourProgress": "Η Πρόοδος Σας",
"MessageDownloadingEpisode": "Λήψη επεισοδίου",
"MessageLoading": "Φόρτωση...",
"MessageMarkAsFinished": "Σήμανση ως Ολοκληρωμένο",
"MessageNoItemsFound": "Δεν βρέθηκαν αντικείμενα",
"MessageNoUserPlaylists": "Δεν έχετε λίστες αναπαραγωγής"
}
+134 -97
View File
@@ -20,10 +20,10 @@
"ButtonCheckAndDownloadNewEpisodes": "Comprobar y descargar episodios nuevos",
"ButtonChooseAFolder": "Elegir una carpeta",
"ButtonChooseFiles": "Elegir archivos",
"ButtonClearFilter": "Quitar filtros",
"ButtonClearFilter": "Vaciar filtro",
"ButtonClose": "Cerrar",
"ButtonCloseFeed": "Cerrar suministro",
"ButtonCloseSession": "Cerrar la sesión abierta",
"ButtonCloseSession": "Cerrar sesión abierta",
"ButtonCollections": "Colecciones",
"ButtonConfigureScanner": "Configurar Escáner",
"ButtonCreate": "Crear",
@@ -33,27 +33,27 @@
"ButtonEdit": "Editar",
"ButtonEditChapters": "Editar capítulos",
"ButtonEditPodcast": "Editar pódcast",
"ButtonEnable": "Permitir",
"ButtonEnable": "Habilitar",
"ButtonFireAndFail": "Ejecutado y fallido",
"ButtonFireOnTest": "Activar evento de prueba",
"ButtonForceReScan": "Forzar Re-Escaneo",
"ButtonFullPath": "Ruta completa",
"ButtonHide": "Ocultar",
"ButtonHome": "Inicio",
"ButtonIssues": "Problemas",
"ButtonIssues": "Incidencias",
"ButtonJumpBackward": "Retroceder",
"ButtonJumpForward": "Adelantar",
"ButtonLatest": "Más recientes",
"ButtonLibrary": "Biblioteca",
"ButtonLogout": "Salir",
"ButtonLookup": "Buscar",
"ButtonLogout": "Cerrar Sesión",
"ButtonLookup": "Averiguar",
"ButtonManageTracks": "Gestionar pistas",
"ButtonMapChapterTitles": "Asignar Títulos a Capítulos",
"ButtonMatchAllAuthors": "Encontrar Todos los Autores",
"ButtonMatchBooks": "Encontrar Libros",
"ButtonMatchBooks": "Cotejar Libros",
"ButtonNevermind": "Olvidar",
"ButtonNext": "Siguiente",
"ButtonNextChapter": "Siguiente Capítulo",
"ButtonNextChapter": "Siguiente capítulo",
"ButtonNextItemInQueue": "El siguiente elemento en cola",
"ButtonOk": "Aceptar",
"ButtonOpenFeed": "Abrir suministro",
@@ -64,26 +64,26 @@
"ButtonPlaying": "Reproduciendo",
"ButtonPlaylists": "Listas de reproducción",
"ButtonPrevious": "Anterior",
"ButtonPreviousChapter": "Capítulo Anterior",
"ButtonProbeAudioFile": "Examinar archivo de audio",
"ButtonPurgeAllCache": "Purgar toda la antememoria",
"ButtonPurgeItemsCache": "Purgar antememoria de elementos",
"ButtonQueueAddItem": "Añadir a la cola",
"ButtonQueueRemoveItem": "Quitar de la cola",
"ButtonPreviousChapter": "Capítulo anterior",
"ButtonProbeAudioFile": "Sonda del archivo de audio",
"ButtonPurgeAllCache": "Purgar toda la caché",
"ButtonPurgeItemsCache": "Purgar caché de elementos",
"ButtonQueueAddItem": "Añadir a cola",
"ButtonQueueRemoveItem": "Quitar de cola",
"ButtonQuickEmbed": "Inserción rápida",
"ButtonQuickEmbedMetadata": "Agregue metadatos rápidamente",
"ButtonQuickMatch": "Encontrar Rápido",
"ButtonQuickEmbedMetadata": "Empotrar metadatos rápidamente",
"ButtonQuickMatch": "Cotejo Rápido",
"ButtonReScan": "Re-Escanear",
"ButtonRead": "Leer",
"ButtonReadLess": "Leer menos",
"ButtonReadMore": "Leer más",
"ButtonRefresh": "Actualizar",
"ButtonRefresh": "Recargar",
"ButtonRemove": "Quitar",
"ButtonRemoveAll": "Quitar todo",
"ButtonRemoveAllLibraryItems": "Quitar todos los elementos de la biblioteca",
"ButtonRemoveFromContinueListening": "Quitar de Continuar escuchando",
"ButtonRemoveFromContinueReading": "Quitar de Continuar leyendo",
"ButtonRemoveSeriesFromContinueSeries": "Quitar serie de Continuar serie",
"ButtonRemoveFromContinueListening": "Quitar desde Escucha Continua",
"ButtonRemoveFromContinueReading": "Quitar desde Continuar Leyendo",
"ButtonRemoveSeriesFromContinueSeries": "Quitar Series desde Series Continuas",
"ButtonReset": "Restablecer",
"ButtonResetToDefault": "Restaurar valores predeterminados",
"ButtonRestore": "Restaurar",
@@ -92,47 +92,47 @@
"ButtonSaveTracklist": "Guardar lista de pistas",
"ButtonScan": "Escanear",
"ButtonScanLibrary": "Escanear biblioteca",
"ButtonScrollLeft": "Desplazarse hacia la izquierda",
"ButtonScrollRight": "Desplazarse hacia la derecha",
"ButtonScrollLeft": "Desplazarse a la izquierda",
"ButtonScrollRight": "Desplazarse a la derecha",
"ButtonSearch": "Buscar",
"ButtonSelectFolderPath": "Seleccionar ruta de carpeta",
"ButtonSeries": "Series",
"ButtonSetChaptersFromTracks": "Seleccionar Capítulos Según las Pistas",
"ButtonSetChaptersFromTracks": "Establecer capítulos según las pistas",
"ButtonShare": "Compartir",
"ButtonShiftTimes": "Desplazar Tiempos",
"ButtonShiftTimes": "Veces de Desplazo",
"ButtonShow": "Mostrar",
"ButtonStartM4BEncode": "Iniciar Codificación M4B",
"ButtonStartMetadataEmbed": "Iniciar la Inserción de Metadata",
"ButtonStartMetadataEmbed": "Iniciar Inserción de Metadatos",
"ButtonStats": "Estadísticas",
"ButtonSubmit": "Enviar",
"ButtonSubmit": "Entregar",
"ButtonTest": "Prueba",
"ButtonUnlinkOpenId": "Desvincular OpenID",
"ButtonUpload": "Cargar",
"ButtonUploadBackup": "Cargar respaldo",
"ButtonUploadCover": "Cargar cubierta",
"ButtonUploadOPMLFile": "Cargar archivo OPML",
"ButtonUnlinkOpenId": "Desenlazar OpenID",
"ButtonUpload": "Subir",
"ButtonUploadBackup": "Subir Respaldo",
"ButtonUploadCover": "Subir Cubierta",
"ButtonUploadOPMLFile": "Subir archivo OPML",
"ButtonUserDelete": "Eliminar usuario {0}",
"ButtonUserEdit": "Editar usuario {0}",
"ButtonViewAll": "Ver todo",
"ButtonYes": "Sí",
"ErrorUploadFetchMetadataAPI": "Error al recuperar los metadatos",
"ErrorUploadFetchMetadataNoResults": "No se pudieron recuperar los metadatos; pruebe a actualizar el título o autor",
"ErrorUploadLacksTitle": "Debe tener título",
"ErrorUploadFetchMetadataNoResults": "No se pudieron recuperar los metadatos; pruebe a actualizar el título y/o autor",
"ErrorUploadLacksTitle": "Debe tener un título",
"HeaderAccount": "Cuenta",
"HeaderAddCustomMetadataProvider": "Añadir proveedor de metadatos personalizado",
"HeaderAdvanced": "Avanzado",
"HeaderApiKeys": "Claves API",
"HeaderAppriseNotificationSettings": "Configuración de notificaciones de Apprise",
"HeaderAudioTracks": "Pistas de audio",
"HeaderAppriseNotificationSettings": "Ajustes de notificaciones de Apprise",
"HeaderAudioTracks": "Pistas de Audio",
"HeaderAudiobookTools": "Herramientas de Gestión de Archivos de Audiolibro",
"HeaderAuthentication": "Autenticación",
"HeaderBackups": "Respaldos",
"HeaderBulkChapterModal": "Añadir Múltiples Capítulos",
"HeaderChangePassword": "Cambiar contraseña",
"HeaderChangePassword": "Cambiar Contraseña",
"HeaderChapters": "Capítulos",
"HeaderChooseAFolder": "Escoger una Carpeta",
"HeaderCollection": "Colección",
"HeaderCollectionItems": "Elementos en la colección",
"HeaderCollectionItems": "Elementos de colección",
"HeaderCover": "Cubierta",
"HeaderCurrentDownloads": "Descargas actuales",
"HeaderCustomMessageOnLogin": "Mensaje personalizado al acceder",
@@ -140,49 +140,49 @@
"HeaderDetails": "Detalles",
"HeaderDownloadQueue": "Cola de descargas",
"HeaderEbookFiles": "Archivos de libros digitales",
"HeaderEmail": "Correo electrónico",
"HeaderEmailSettings": "Configuración de correo electrónico",
"HeaderEmail": "Correo-e",
"HeaderEmailSettings": "Ajustes de correo-e",
"HeaderEpisodes": "Episodios",
"HeaderEreaderDevices": "Dispositivos Ereader",
"HeaderEreaderSettings": "Configuración del lector",
"HeaderEreaderDevices": "Dispositivos Lector-e",
"HeaderEreaderSettings": "Ajustes del Lector-e",
"HeaderFiles": "Archivos",
"HeaderFindChapters": "Buscar capítulos",
"HeaderIgnoredFiles": "Archivos ignorados",
"HeaderItemFiles": "Archivos de elementos",
"HeaderItemMetadataUtils": "Utilidades de metadatos de elementos",
"HeaderItemFiles": "Archivos del elemento",
"HeaderItemMetadataUtils": "Utilidades de metadatos del elemento",
"HeaderLastListeningSession": "Última sesión de escucha",
"HeaderLatestEpisodes": "Episodios más recientes",
"HeaderLibraries": "Bibliotecas",
"HeaderLibraryFiles": "Archivos de biblioteca",
"HeaderLibraryStats": "Estadísticas de biblioteca",
"HeaderListeningSessions": "Sesión",
"HeaderListeningSessions": "Sesiones Listadas",
"HeaderListeningStats": "Estadísticas de Tiempo Escuchado",
"HeaderLogin": "Acceder",
"HeaderLogs": "Registros",
"HeaderLogin": "Inicio de Sesión",
"HeaderLogs": "Bitácoras",
"HeaderManageGenres": "Gestionar géneros",
"HeaderManageTags": "Gestionar etiquetas",
"HeaderMapDetails": "Asignar Detalles",
"HeaderMatch": "Encontrar",
"HeaderMatch": "Coincidir",
"HeaderMetadataOrderOfPrecedence": "Orden de precedencia de metadatos",
"HeaderMetadataToEmbed": "Metadatos para Insertar",
"HeaderNewAccount": "Cuenta nueva",
"HeaderMetadataToEmbed": "Metadatos para empotrar",
"HeaderNewAccount": "Crear Cuenta",
"HeaderNewApiKey": "Nueva clave API",
"HeaderNewLibrary": "Biblioteca nueva",
"HeaderNotificationCreate": "Crear notificación",
"HeaderNotificationUpdate": "Notificación de actualización",
"HeaderNotificationCreate": "Crear Notificación",
"HeaderNotificationUpdate": "Notificación de Actualización",
"HeaderNotifications": "Notificaciones",
"HeaderOpenIDConnectAuthentication": "Autenticación OpenID Connect",
"HeaderOpenListeningSessions": "Sesiones públicas de escucha",
"HeaderOpenListeningSessions": "Abrir escucha de sesiones",
"HeaderOpenRSSFeed": "Abrir suministro RSS",
"HeaderOtherFiles": "Otros archivos",
"HeaderPasswordAuthentication": "Autenticación por contraseña",
"HeaderPermissions": "Permisos",
"HeaderPlayerQueue": "Cola del reproductor",
"HeaderPlayerSettings": "Configuración del reproductor",
"HeaderPlayerSettings": "Ajustes del reproductor",
"HeaderPlaylist": "Lista de reproducción",
"HeaderPlaylistItems": "Elementos de lista de reproducción",
"HeaderPodcastsToAdd": "Pódcast para añadir",
"HeaderPresets": "Preconfiguraciones",
"HeaderPresets": "Preajustes",
"HeaderPreviewCover": "Previsualizar cubierta",
"HeaderRSSFeedGeneral": "Detalles de RSS",
"HeaderRSSFeedIsOpen": "El suministro RSS está abierto",
@@ -191,18 +191,18 @@
"HeaderRemoveEpisodes": "Quitar {0} episodios",
"HeaderSavedMediaProgress": "Guardar Progreso de Multimedia",
"HeaderSchedule": "Horario",
"HeaderScheduleEpisodeDownloads": "Programar descargas automáticas de episodios",
"HeaderScheduleLibraryScans": "Programar Escaneo Automático de Biblioteca",
"HeaderScheduleEpisodeDownloads": "Planificador de autodescargas de episodios",
"HeaderScheduleLibraryScans": "Planificar AutoEscaneo de Biblioteca",
"HeaderSession": "Sesión",
"HeaderSetBackupSchedule": "Programar Respaldo",
"HeaderSettings": "Configuración",
"HeaderSetBackupSchedule": "Establecer Planificación de Respaldo",
"HeaderSettings": "Ajustes",
"HeaderSettingsDisplay": "Interfaz",
"HeaderSettingsExperimental": "Funcionalidades experimentales",
"HeaderSettingsExperimental": "Características experimentales",
"HeaderSettingsGeneral": "Generales",
"HeaderSettingsScanner": "Escáner",
"HeaderSettingsSecurity": "Seguridad",
"HeaderSettingsWebClient": "Cliente web",
"HeaderSleepTimer": "Temporizador de apagado",
"HeaderSleepTimer": "Cronómetro de dormida",
"HeaderStatsLargestItems": "Elementos más grandes",
"HeaderStatsLongestItems": "Elementos más extensos (h)",
"HeaderStatsMinutesListeningChart": "Minutos escuchando (últimos 7 días)",
@@ -233,25 +233,29 @@
"LabelAddToCollectionBatch": "Añadir {0} libros a colección",
"LabelAddToPlaylist": "Añadir a lista de reproducción",
"LabelAddToPlaylistBatch": "Añadir {0} elementos a lista de reproducción",
"LabelAddedAt": "Añadido",
"LabelAddedDate": "{0} Añadido",
"LabelAddedAt": "Añadido en",
"LabelAddedDate": "Añadido {0}",
"LabelAdminUsersOnly": "Solamente usuarios administradores",
"LabelAll": "Todos",
"LabelAllEpisodesDownloaded": "Todos los episodios descargados",
"LabelAllUsers": "Todos los usuarios",
"LabelAllUsersExcludingGuests": "Todos los usuarios excepto invitados",
"LabelAllUsersIncludingGuests": "Todos los usuarios e invitados",
"LabelAlreadyInYourLibrary": "Ya existe en la Biblioteca",
"LabelApiToken": "Token de la API",
"LabelAlreadyInYourLibrary": "Ya dentro de tu biblioteca",
"LabelApiKeyCreated": "La clave de API “{0}” se ha creado correctamente.",
"LabelApiKeyCreatedDescription": "Asegúrate de copiar la clave de API ahora, no la volverás a ver otra vez.",
"LabelApiKeyUser": "Actuar en nombre del usuario",
"LabelApiKeyUserDescription": "Esta clave de API tendrá los mismos permisos que el usuario al que representa. En los registros se verá como si la solicitud la hubiera hecho el usuario directamente.",
"LabelApiToken": "Vale del API",
"LabelAppend": "Adjuntar",
"LabelAudioBitrate": "Tasa de bits del audio (por ejemplo, 128k)",
"LabelAudioBitrate": "Tasa de bit del audio (p.ej., 128k)",
"LabelAudioChannels": "Canales de audio (1 o 2)",
"LabelAudioCodec": "Códec de audio",
"LabelAuthor": "Autor",
"LabelAuthorFirstLast": "Autor (Nombre Apellido)",
"LabelAuthorLastFirst": "Autor (Apellido, Nombre)",
"LabelAuthors": "Autores",
"LabelAutoDownloadEpisodes": "Descargar episodios automáticamente",
"LabelAutoDownloadEpisodes": "AutoDescargar episodios",
"LabelAutoFetchMetadata": "Recuperar metadatos automáticamente",
"LabelAutoFetchMetadataHelp": "Obtiene metadatos de título, autor y serie para agilizar la carga. Es posible que haya que cotejar metadatos adicionales después de la carga.",
"LabelAutoLaunch": "Lanzamiento automático",
@@ -282,15 +286,16 @@
"LabelClickToUseCurrentValue": "Pulse para utilizar el valor actual",
"LabelClosePlayer": "Cerrar reproductor",
"LabelCodec": "Codec",
"LabelCollapseSeries": "Colapsar serie",
"LabelCollapseSeries": "Colapsar Series",
"LabelCollapseSubSeries": "Contraer la subserie",
"LabelCollection": "Colección",
"LabelCollections": "Colecciones",
"LabelComplete": "Completo",
"LabelConfirmPassword": "Confirmar contraseña",
"LabelContinueListening": "Seguir escuchando",
"LabelContinueListening": "Seguir Escuchando",
"LabelContinueReading": "Continuar leyendo",
"LabelContinueSeries": "Continuar series",
"LabelCorsAllowed": "Orígenes CORS Permitidos",
"LabelCover": "Cubierta",
"LabelCoverImageURL": "URL de imagen de cubierta",
"LabelCoverProvider": "Proveedor de cubiertas",
@@ -320,14 +325,14 @@
"LabelDurationComparisonLonger": "({0} más largo)",
"LabelDurationComparisonShorter": "({0} más corto)",
"LabelDurationFound": "Duración Comprobada:",
"LabelEbook": "Libro electrónico",
"LabelEbooks": "Libros electrónicos",
"LabelEbook": "Libro-e",
"LabelEbooks": "Libros-e",
"LabelEdit": "Editar",
"LabelEmail": "Correo electrónico",
"LabelEmailSettingsFromAddress": "Remitente",
"LabelEmailSettingsRejectUnauthorized": "Rechazar certificados no autorizados",
"LabelEmailSettingsRejectUnauthorizedHelp": "Desactivar la validación de certificados SSL puede exponer su conexión a riesgos de seguridad, como los ataques por intermediario. Desactive esta opción solo si conoce las implicaciones y confía en el servidor de correo al que se conecta.",
"LabelEmailSettingsSecure": "Seguridad",
"LabelEmailSettingsSecure": "Seguro",
"LabelEmailSettingsSecureHelp": "Si está activado, se usará TLS para conectarse al servidor. Si está apagado, se usará TLS si su servidor tiene soporte para la extensión STARTTLS. En la mayoría de los casos, puede dejar esta opción activada si se está conectando al puerto 465. Apáguela en el caso de usar los puertos 587 o 25. (de nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Probar dirección",
"LabelEmbeddedCover": "Cubierta incrustada",
@@ -354,6 +359,10 @@
"LabelExample": "Ejemplo",
"LabelExpandSeries": "Ampliar serie",
"LabelExpandSubSeries": "Expandir la subserie",
"LabelExpired": "Expirado",
"LabelExpiresAt": "Expira El",
"LabelExpiresInSeconds": "Expira en (segundos)",
"LabelExpiresNever": "Nunca",
"LabelExplicit": "Explícito",
"LabelExplicitChecked": "Explícito (marcado)",
"LabelExplicitUnchecked": "No Explícito (sin marcar)",
@@ -368,11 +377,12 @@
"LabelFilename": "Nombre del archivo",
"LabelFilterByUser": "Filtrar por Usuario",
"LabelFindEpisodes": "Buscar Episodio",
"LabelFinished": "Terminado",
"LabelFinished": "Finalizado",
"LabelFinishedDate": "Finalizado {0}",
"LabelFolder": "Carpeta",
"LabelFolders": "Carpetas",
"LabelFontBold": "Negrilla",
"LabelFontBoldness": "Peso tipográfico",
"LabelFontBoldness": "Tipográfico sin Negrita",
"LabelFontFamily": "Familia tipográfica",
"LabelFontItalic": "Itálica",
"LabelFontScale": "Escala de letra",
@@ -382,8 +392,8 @@
"LabelGenre": "Género",
"LabelGenres": "Géneros",
"LabelHardDeleteFile": "Eliminar Definitivamente",
"LabelHasEbook": "Tiene un libro",
"LabelHasSupplementaryEbook": "Tiene un libro complementario",
"LabelHasEbook": "Tiene libro-e",
"LabelHasSupplementaryEbook": "Tiene un libro-e suplementario",
"LabelHideSubtitles": "Ocultar subtítulos",
"LabelHighestPriority": "Mayor prioridad",
"LabelHost": "Anfitrión",
@@ -413,6 +423,7 @@
"LabelLanguages": "Idiomas",
"LabelLastBookAdded": "Último libro añadido",
"LabelLastBookUpdated": "Último libro actualizado",
"LabelLastProgressDate": "Último progreso: {0}",
"LabelLastSeen": "Última Vez Visto",
"LabelLastTime": "Última Vez",
"LabelLastUpdate": "Última Actualización",
@@ -425,6 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Sin {0}",
"LabelLibraryItem": "Elemento de Biblioteca",
"LabelLibraryName": "Nombre de Biblioteca",
"LabelLibrarySortByProgress": "Progreso: Último actualizado",
"LabelLibrarySortByProgressFinished": "Progreso: Finalizado",
"LabelLibrarySortByProgressStarted": "Progreso: Iniciado",
"LabelLimit": "Limites",
"LabelLineSpacing": "Interlineado",
"LabelListenAgain": "Volver a escuchar",
@@ -433,6 +447,7 @@
"LabelLogLevelWarn": "Advertencia",
"LabelLookForNewEpisodesAfterDate": "Buscar Nuevos Episodios a partir de esta Fecha",
"LabelLowestPriority": "Menor prioridad",
"LabelMatchConfidence": "Confidencia",
"LabelMatchExistingUsersBy": "Emparejar a los usuarios existentes por",
"LabelMatchExistingUsersByDescription": "Se utiliza para conectar usuarios existentes. Una vez conectados, los usuarios serán emparejados por un identificador único de su proveedor de SSO",
"LabelMaxEpisodesToDownload": "Número máximo # de episodios para descargar. Usa 0 para descargar una cantidad ilimitada.",
@@ -451,7 +466,7 @@
"LabelMissingEbook": "No tiene libro electrónico",
"LabelMissingSupplementaryEbook": "No tiene libro electrónico suplementario",
"LabelMobileRedirectURIs": "URIs de redirección a móviles permitidos",
"LabelMobileRedirectURIsDescription": "Esta es una lista blanca de URI de redireccionamiento válidos para aplicaciones móviles. El predeterminado es <code> audiobookshelf</code> , que puede eliminar o complementar con URI adicionales para la integración de aplicaciones de terceros. Usando un asterisco (<code> *</code> ) como única entrada que permite cualquier URI.",
"LabelMobileRedirectURIsDescription": "Esta es una lista en blanco de las URI de redireccionamiento válidos para aplicaciones móviles. El predeterminado es <code>audiobookshelf</code> , que puede retirar o sustituir con las URI adicionales para la integración de aplicaciones de terceros. Usando un asterisco (<code>*</code> ) como única entrada que permite cualquier URI.",
"LabelMore": "Más",
"LabelMoreInfo": "Más información",
"LabelName": "Nombre",
@@ -464,9 +479,10 @@
"LabelNextBackupDate": "Fecha del siguiente respaldo",
"LabelNextChapters": "Los próximos capítulos serán:",
"LabelNextScheduledRun": "Próxima ejecución programada",
"LabelNoApiKeys": "Sin claves API",
"LabelNoCustomMetadataProviders": "Sin proveedores de metadatos personalizados",
"LabelNoEpisodesSelected": "Ningún Episodio Seleccionado",
"LabelNotFinished": "No terminado",
"LabelNotFinished": "No finalizado",
"LabelNotStarted": "Sin iniciar",
"LabelNotes": "Notas",
"LabelNotificationAppriseURL": "URL(s) de Apprise",
@@ -480,7 +496,7 @@
"LabelNotificationsMaxQueueSizeHelp": "Las notificaciones están limitadas a 1 por segundo. Las notificaciones serán ignoradas si llegan al numero máximo de cola para prevenir spam de eventos.",
"LabelNumberOfBooks": "Número de libros",
"LabelNumberOfChapters": "Número de capítulos:",
"LabelNumberOfEpisodes": "N.º de episodios",
"LabelNumberOfEpisodes": "Nº de episodios",
"LabelOpenIDAdvancedPermsClaimDescription": "Nombre de la notificación de OpenID que contiene permisos avanzados para acciones de usuario dentro de la aplicación que se aplicarán a roles que no sean de administrador (<b>si están configurados</b>). Si el reclamo no aparece en la respuesta, se denegará el acceso a ABS. Si falta una sola opción, se tratará como <code>falsa</code>. Asegúrese de que la notificación del proveedor de identidades coincida con la estructura esperada:",
"LabelOpenIDClaims": "Deje las siguientes opciones vacías para desactivar la asignación avanzada de grupos y permisos, lo que asignaría de manera automática al grupo «Usuario».",
"LabelOpenIDGroupClaimDescription": "Nombre de la declaración OpenID que contiene una lista de grupos del usuario. Comúnmente conocidos como <code>grupos</code>. <b>Si se configura</b>, la aplicación asignará automáticamente roles en función de la pertenencia a grupos del usuario, siempre que estos grupos se denominen \"admin\", \"user\" o \"guest\" en la notificación. La solicitud debe contener una lista, y si un usuario pertenece a varios grupos, la aplicación asignará el rol correspondiente al mayor nivel de acceso. Si ningún grupo coincide, se denegará el acceso.",
@@ -510,7 +526,7 @@
"LabelPodcasts": "Pódcast",
"LabelPort": "Puerto",
"LabelPrefixesToIgnore": "Prefijos para ignorar (no distingue entre mayúsculas y minúsculas)",
"LabelPreventIndexing": "Evite que los directorios de pódcast de iTunes y Google indicen su suministro",
"LabelPreventIndexing": "Evite que los directorios de pódcast de iTunes y Google indexen su suministro",
"LabelPrimaryEbook": "Libro electrónico principal",
"LabelProgress": "Progreso",
"LabelProvider": "Proveedor",
@@ -522,11 +538,11 @@
"LabelPublishedDecades": "Décadas publicadas",
"LabelPublisher": "Editor",
"LabelPublishers": "Editores",
"LabelRSSFeedCustomOwnerEmail": "Correo electrónico de dueño personalizado",
"LabelRSSFeedCustomOwnerName": "Nombre de dueño personalizado",
"LabelRSSFeedOpen": "Suministro RSS abierto",
"LabelRSSFeedCustomOwnerEmail": "Correo-e de propietario personalizado",
"LabelRSSFeedCustomOwnerName": "Nombre de propietario personalizado",
"LabelRSSFeedOpen": "Fuente RSS Abierta",
"LabelRSSFeedPreventIndexing": "Evitar indización",
"LabelRSSFeedSlug": "«Slug» de suministro RSS",
"LabelRSSFeedSlug": "Ficha de suministro RSS",
"LabelRSSFeedURL": "URL de suministro RSS",
"LabelRandomly": "Aleatorio",
"LabelReAddSeriesToContinueListening": "Volver a agregar la serie para continuar escuchándola",
@@ -554,11 +570,12 @@
"LabelSelectAll": "Seleccionar todo",
"LabelSelectAllEpisodes": "Seleccionar todos los episodios",
"LabelSelectEpisodesShowing": "Seleccionar los {0} episodios visibles",
"LabelSelectUser": "Seleccionar usuario",
"LabelSelectUsers": "Seleccionar usuarios",
"LabelSendEbookToDevice": "Enviar libro electrónico a...",
"LabelSequence": "Secuencia",
"LabelSerial": "En serie",
"LabelSeries": "Serie",
"LabelSeries": "Series",
"LabelSeriesName": "Nombre de la serie",
"LabelSeriesProgress": "Progreso de la serie",
"LabelServerLogLevel": "Nivel de registro del servidor",
@@ -571,8 +588,8 @@
"LabelSettingsBookshelfViewHelp": "Diseño Esqueuomorfo con Estantes de Madera",
"LabelSettingsChromecastSupport": "Compatibilidad con Chromecast",
"LabelSettingsDateFormat": "Formato de Fecha",
"LabelSettingsEnableWatcher": "Buscar cambios automáticamente en las bibliotecas",
"LabelSettingsEnableWatcherForLibrary": "Buscar cambios automáticamente en la biblioteca",
"LabelSettingsEnableWatcher": "Vigilar automáticamente los cambios en bibliotecas",
"LabelSettingsEnableWatcherForLibrary": "Vigilar automáticamente los cambios de biblioteca",
"LabelSettingsEnableWatcherHelp": "Permite agregar/actualizar elementos automáticamente cuando se detectan cambios en los archivos. *Requiere reiniciar el servidor",
"LabelSettingsEpubsAllowScriptedContent": "Permitir scripts en epubs",
"LabelSettingsEpubsAllowScriptedContentHelp": "Permitir que los archivos epub ejecuten scripts. Se recomienda mantener esta opción desactivada a menos que confíe en el origen de los archivos epub.",
@@ -605,14 +622,14 @@
"LabelSettingsStoreMetadataWithItemHelp": "Por defecto, los archivos de metadatos se almacenan en /metadata/items. Si habilita esta opción, los archivos de metadatos se guardarán en la carpeta de elementos de su biblioteca",
"LabelSettingsTimeFormat": "Formato de Tiempo",
"LabelShare": "Compartir",
"LabelShareDownloadableHelp": "Permite a quienes posean el enlace de compartición descargar un archivo ZIP del elemento de la biblioteca.",
"LabelShareDownloadableHelp": "Permite a quienes posean el enlace de compartición descargar un archivo zip del elemento de la biblioteca.",
"LabelShareOpen": "abrir un recurso compartido",
"LabelShareURL": "Compartir la URL",
"LabelShowAll": "Mostrar todo",
"LabelShowSeconds": "Mostrar segundos",
"LabelShowSubtitles": "Mostrar subtítulos",
"LabelSize": "Tamaño",
"LabelSleepTimer": "Temporizador de apagado",
"LabelSleepTimer": "Temporizador de dormida",
"LabelSlug": "Slug",
"LabelSortAscending": "Ascendente",
"LabelSortDescending": "Descendente",
@@ -621,6 +638,7 @@
"LabelStartTime": "Tiempo de Inicio",
"LabelStarted": "Iniciado",
"LabelStartedAt": "Iniciado En",
"LabelStartedDate": "Iniciado {0}",
"LabelStatsAudioTracks": "Pistas de Audio",
"LabelStatsAuthors": "Autores",
"LabelStatsBestDay": "Mejor día",
@@ -650,6 +668,7 @@
"LabelTheme": "Tema",
"LabelThemeDark": "Oscuro",
"LabelThemeLight": "Claro",
"LabelThemeSepia": "Sepia",
"LabelTimeBase": "Tiempo Base",
"LabelTimeDurationXHours": "{0} horas",
"LabelTimeDurationXMinutes": "{0} minutos",
@@ -718,8 +737,10 @@
"MessageAddToPlayerQueue": "Agregar a fila del Reproductor",
"MessageAppriseDescription": "Para usar esta función deberás tener <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">la API de Apprise</a> corriendo o una API que maneje los mismos resultados. <br/>La URL de la API de Apprise debe tener la misma ruta de archivos que donde se envían las notificaciones. Por ejemplo: si su API esta en <code>http://192.168.1.1:8337</code> entonces pondría <code>http://192.168.1.1:8337/notify</code>.",
"MessageAsinCheck": "Cerciórese de usar el ASIN de la región correcta de Audible, no de Amazon.",
"MessageAuthenticationLegacyTokenWarning": "Los vales de API heredados serán retirados en el futuro. Utilice las <a href=\"/config/api-keys\">claves de API</a> en su lugar.",
"MessageAuthenticationOIDCChangesRestart": "Reinicie el servidor tras el guardado para aplicar los cambios de OIDC.",
"MessageBackupsDescription": "Los respaldos incluyen: usuarios, el progreso del los usuarios, los detalles de los elementos de la biblioteca, la configuración del servidor y las imágenes en <code>/metadata/items</code> y <code>/metadata/authors</code>. Los Respaldos <strong>NO</strong> incluyen ningún archivo guardado en la carpeta de tu biblioteca.",
"MessageAuthenticationSecurityMessage": "La autenticación ha sido mejorada para seguridad. Todos los usuarios requieren reiniciar sesión.",
"MessageBackupsDescription": "Los respaldos incluyen: usuarios, el progreso del los usuarios, los detalles de los elementos de la biblioteca, la configuración del servidor y las imágenes en <code>/metadata/items</code> y <code>/metadata/authors</code>. Los Respaldos <strong>no</strong> incluyen ningún archivo guardado en la carpeta de tu biblioteca.",
"MessageBackupsLocationEditNote": "Nota: actualizar la ubicación de la copia de respaldo no moverá ni modificará los respaldos existentes",
"MessageBackupsLocationNoEditNote": "Nota: la ubicación de la copia de respaldo se establece a través de una variable de entorno y no se puede cambiar aquí.",
"MessageBackupsLocationPathEmpty": "La ruta de la copia de seguridad no puede estar vacía",
@@ -741,6 +762,7 @@
"MessageChaptersNotFound": "Capítulos no encontrados",
"MessageCheckingCron": "Revisando cron...",
"MessageConfirmCloseFeed": "¿Confirma que quiere cerrar este suministro?",
"MessageConfirmDeleteApiKey": "¿Está seguro que desea eliminar la clave API «{0}»?",
"MessageConfirmDeleteBackup": "¿Confirma que quiere eliminar el respaldo de {0}?",
"MessageConfirmDeleteDevice": "¿Confirma que quiere eliminar el lector electrónico «{0}»?",
"MessageConfirmDeleteFile": "Esto eliminará el archivo del sistema de archivos. ¿Quiere continuar?",
@@ -759,8 +781,8 @@
"MessageConfirmMarkSeriesFinished": "¿Confirma que quiere marcar todos los libros de esta serie como terminados?",
"MessageConfirmMarkSeriesNotFinished": "¿Confirma que quiere marcar todos los libros de esta serie como no terminados?",
"MessageConfirmNotificationTestTrigger": "¿Activar esta notificación con datos de prueba?",
"MessageConfirmPurgeCache": "Purgar la antememoria eliminará el directorio completo ubicado en <code>/metadata/cache</code>. <br /><br />¿Confirma que quiere eliminar el directorio de antememoria?",
"MessageConfirmPurgeItemsCache": "Purgar la antememoria de elementos eliminará el directorio completo ubicado en <code>/metadata/cache/items</code>.<br />¿Lo confirma?",
"MessageConfirmPurgeCache": "La purga del caché eliminará el directorio completo en <code>/metadata/cache</code>. <br /><br />¿Confirma que desea quitar el directorio de caché?",
"MessageConfirmPurgeItemsCache": "Purgar el caché de elementos eliminará el directorio completo ubicado en <code>/metadata/cache/items</code>.<br />¿Lo confirma?",
"MessageConfirmQuickEmbed": "Atención: la incrustación rápida no realiza copias de respaldo a ninguno de sus archivos de audio. Cerciórese de haber realizado una copia de los mismos previamente. <br><br>¿Quiere continuar?",
"MessageConfirmQuickMatchEpisodes": "El reconocimiento rápido de extensiones sobrescribirá los detalles si se encuentra una coincidencia. Se actualizarán las extensiones no reconocidas. ¿Quiere continuar?",
"MessageConfirmReScanLibraryItems": "¿Confirma que quiere volver a analizar {0} elementos?",
@@ -768,6 +790,7 @@
"MessageConfirmRemoveAuthor": "¿Confirma que quiere quitar el autor «{0}»?",
"MessageConfirmRemoveCollection": "¿Confirma que quiere quitar la colección «{0}»?",
"MessageConfirmRemoveEpisode": "¿Confirma que quiere quitar el episodio «{0}»?",
"MessageConfirmRemoveEpisodeNote": "Nota: Esto no borra el archivo de audio a menos que se active la opción \"Borrado definitivo del archivo\"",
"MessageConfirmRemoveEpisodes": "¿Confirma que quiere quitar {0} episodios?",
"MessageConfirmRemoveListeningSessions": "¿Confirma que quiere quitar {0} sesiones de escucha?",
"MessageConfirmRemoveMetadataFiles": "¿Confirma que quiere quitar todos los archivos metadata.{0} en las carpetas de elementos de su biblioteca?",
@@ -785,14 +808,16 @@
"MessageDaysListenedInTheLastYear": "{0} días escuchados el año pasado",
"MessageDownloadingEpisode": "Descargando episodio",
"MessageDragFilesIntoTrackOrder": "Arrastre los archivos al orden correcto de las pistas",
"MessageEmbedFailed": "Falló la incrustación.",
"MessageEmbedFinished": "Finalizó la incrustación.",
"MessageEmbedFailed": "Incorporación incorrecta.",
"MessageEmbedFinished": "Incorporación finalizada.",
"MessageEmbedQueue": "En cola para incrustar metadatos ({0} en cola)",
"MessageEpisodesQueuedForDownload": "{0} episodio(s) en cola para descargar",
"MessageEreaderDevices": "Para garantizar la entrega de libros electrónicos, es posible que tenga que agregar la dirección de correo electrónico anterior como remitente válido para cada dispositivo enumerado a continuación.",
"MessageFeedURLWillBe": "El URL del suministro será {0}",
"MessageFetching": "Recuperando...",
"MessageForceReScanDescription": "Escaneará todos los archivos como un nuevo escaneo. Archivos de audio con etiquetas ID3, archivos OPF y archivos de texto serán escaneados como nuevos.",
"MessageHeatmapListeningTimeTooltip": "<strong>{0} escuchando</strong> en {1}",
"MessageHeatmapNoListeningSessions": "No enumera sesiones en {0}",
"MessageImportantNotice": "¡Notificación importante!",
"MessageInsertChapterBelow": "Insertar capítulo debajo",
"MessageInvalidAsin": "ASIN no válido",
@@ -825,7 +850,7 @@
"MessageNoEpisodes": "Ningún episodio",
"MessageNoFoldersAvailable": "Ninguna carpeta disponible",
"MessageNoGenres": "Ningún género",
"MessageNoIssues": "Ningún número",
"MessageNoIssues": "Sin incidencias",
"MessageNoItems": "Ningún elemento",
"MessageNoItemsFound": "Ningún elemento encontrado",
"MessageNoListeningSessions": "Ninguna sesión de escucha",
@@ -863,7 +888,7 @@
"MessageResetChaptersConfirm": "¿Confirma que quiere deshacer los cambios y restablecer los capítulos a su estado original?",
"MessageRestoreBackupConfirm": "¿Confirma que quiere restaurar el respaldo creado el",
"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.",
"MessageScheduleLibraryScanNote": "Para muchos usuarios, es recomendado dejar esta característica inhabilitada y mantener habilitados los ajustes de la «Vigía automática de cambio de biblioteca»: detectará automáticamente los cambios en sus carpetas de bibliotecas. Habilitar esta características si «Vigía automática de cambio de biblioteca» no funciona en su sistema de archivo (como NFS).",
"MessageScheduleRunEveryWeekdayAtTime": "Ejecutar cada {0} a las {1}",
"MessageSearchResultsFor": "Resultados de la búsqueda de",
"MessageSelected": "{0} seleccionado(s)",
@@ -929,6 +954,8 @@
"NotificationOnBackupCompletedDescription": "Se activa cuando se completa una copia de seguridad",
"NotificationOnBackupFailedDescription": "Se activa cuando falla una copia de seguridad",
"NotificationOnEpisodeDownloadedDescription": "Se activa cuando se descarga automáticamente un episodio de un podcast",
"NotificationOnRSSFeedDisabledDescription": "Se activa cuando las descargas automáticas de episodios se desactivan debido a varios intentos fallidos",
"NotificationOnRSSFeedFailedDescription": "Se activa cuando la solicitud a la fuente RSS falla durante una descarga automática de episodio",
"NotificationOnTestDescription": "Evento para probar el sistema de notificaciones",
"PlaceholderBulkChapterInput": "Ingrese título de capítulo o use numeración (ej. 'Episodio 1', 'Capítulo 10', '1.')",
"PlaceholderNewCollection": "Nuevo nombre de la colección",
@@ -987,6 +1014,8 @@
"ToastBulkChapterInvalidCount": "Por favor ingrese un número válido entre 1 y 150",
"ToastCachePurgeFailed": "No se pudo purgar la antememoria",
"ToastCachePurgeSuccess": "Se purgó la antememoria correctamente",
"ToastChapterLocked": "El capítulo está bloqueado.",
"ToastChapterStartTimeAdjusted": "El capítulo inicia el tiempo ajustado en {0} segundos",
"ToastChaptersAllLocked": "Todos los capítulos están bloqueados. Desbloquee algunos capítulos para cambiar sus tiempos.",
"ToastChaptersHaveErrors": "Los capítulos tienen errores",
"ToastChaptersInvalidShiftAmountLast": "Cantidad de desplazamiento no válida. La hora de inicio del último capítulo se extendería más allá de la duración de este audiolibro.",
@@ -997,6 +1026,8 @@
"ToastCollectionItemsAddFailed": "Artículo(s) añadido(s) a la colección fallido(s)",
"ToastCollectionRemoveSuccess": "Colección quitada",
"ToastCollectionUpdateSuccess": "Colección actualizada",
"ToastConnectionNotAvailable": "Conexión no disponible. Intenta de nuevo más tarde",
"ToastCoverSearchFailed": "Cobertura de búsqueda incorrecta",
"ToastCoverUpdateFailed": "Error al actualizar la cubierta",
"ToastDateTimeInvalidOrIncomplete": "Fecha y hora no válidas o incompletas",
"ToastDeleteFileFailed": "Falló la eliminación del archivo",
@@ -1012,6 +1043,8 @@
"ToastEpisodeDownloadQueueClearSuccess": "Se borró la cola de descargas de los episodios",
"ToastEpisodeUpdateSuccess": "{0} episodio(s) actualizado(s)",
"ToastErrorCannotShare": "No se puede compartir de forma nativa en este dispositivo",
"ToastFailedToCreate": "Ha fallado al crear",
"ToastFailedToDelete": "Ha fallado al eliminar",
"ToastFailedToLoadData": "Error al cargar data",
"ToastFailedToMatch": "Error al emparejar",
"ToastFailedToShare": "Error al compartir",
@@ -1019,6 +1052,7 @@
"ToastInvalidImageUrl": "URL de la imagen no válida",
"ToastInvalidMaxEpisodesToDownload": "Número máximo de episodios para descargar no válidos",
"ToastInvalidUrl": "URL no válida",
"ToastInvalidUrls": "Una o más URL son inválidas",
"ToastItemCoverUpdateSuccess": "Cubierta del elemento actualizada",
"ToastItemDeletedFailed": "Error al eliminar el elemento",
"ToastItemDeletedSuccess": "Elemento borrado",
@@ -1043,6 +1077,7 @@
"ToastMustHaveAtLeastOnePath": "Debe tener al menos una ruta",
"ToastNameEmailRequired": "Son obligatorios el nombre y el correo electrónico",
"ToastNameRequired": "Nombre obligatorio",
"ToastNewApiKeyUserError": "Debe seleccionar un usuario",
"ToastNewEpisodesFound": "{0} nuevo(s) episodio(s) encontrado(s)",
"ToastNewUserCreatedFailed": "No se pudo crear la cuenta: «{0}»",
"ToastNewUserCreatedSuccess": "Nueva cuenta creada",
@@ -1067,6 +1102,7 @@
"ToastPlaylistUpdateSuccess": "Lista de reproducción actualizada",
"ToastPodcastCreateFailed": "No se pudo crear el pódcast",
"ToastPodcastCreateSuccess": "Se creó el pódcast correctamente",
"ToastPodcastEpisodeUpdated": "Episodio actualizado",
"ToastPodcastGetFeedFailed": "No se puede obtener el podcast",
"ToastPodcastNoEpisodesInFeed": "No se han encontrado episodios en el feed del RSS",
"ToastPodcastNoRssFeed": "El pódcast no tiene suministro RSS",
@@ -1080,8 +1116,8 @@
"ToastRemoveFailed": "Error al eliminar",
"ToastRemoveItemFromCollectionFailed": "Error al eliminar el elemento de la colección",
"ToastRemoveItemFromCollectionSuccess": "Elemento eliminado de la colección",
"ToastRemoveItemsWithIssuesFailed": "Error en la eliminación de artículos de biblioteca incorrectos",
"ToastRemoveItemsWithIssuesSuccess": "Se eliminaron artículos de biblioteca incorrectos",
"ToastRemoveItemsWithIssuesFailed": "Error en la eliminación de artículos de biblioteca con incidencias",
"ToastRemoveItemsWithIssuesSuccess": "Se eliminaron artículos de biblioteca con incidencias",
"ToastRenameFailed": "Error al cambiar el nombre",
"ToastRescanFailed": "Error al volver a escanear para {0}",
"ToastRescanRemoved": "Se eliminó el elemento reescaneado",
@@ -1120,6 +1156,7 @@
"ToastUserRootRequireName": "Debe introducir un nombre de usuario administrativo",
"TooltipAddChapters": "Añadir capítulo(s)",
"TooltipAddOneSecond": "Añadir 1 segundo",
"TooltipAdjustChapterStart": "Pulse para ajustar la hora de inicio",
"TooltipLockAllChapters": "Bloquear todos los capítulos",
"TooltipLockChapter": "Bloquear capítulo (Mayús+clic para rango)",
"TooltipSubtractOneSecond": "Restar 1 segundo",
+32 -4
View File
@@ -275,7 +275,7 @@
"LabelBonus": "Bonus",
"LabelBooks": "Kirjat",
"LabelButtonText": "Painikkeen teksti",
"LabelByAuthor": "tekijältä {0}",
"LabelByAuthor": "Tekijältä: {0}",
"LabelChangePassword": "Vaihda salasana",
"LabelChannels": "Kanavat",
"LabelChapterCount": "{0} lukua",
@@ -790,6 +790,7 @@
"MessageConfirmRemoveAuthor": "Oletko varma, että haluat poistaa tekijän \"{0}\"?",
"MessageConfirmRemoveCollection": "Oletko varma, että haluat poistaa kokoelman \"{0}\"?",
"MessageConfirmRemoveEpisode": "Oletko varma, että haluat poistaa jakson \"{0}\"?",
"MessageConfirmRemoveEpisodeNote": "Huomioi: Tämä ei poista äänitiedostoa, ellei \"Poista tiedosto pysyvästi\" -asetusta ole valittuna",
"MessageConfirmRemoveEpisodes": "Oletko varma, että haluat poistaa {0} jaksoa?",
"MessageConfirmRemoveListeningSessions": "Oletko varma, että haluat poistaa {0} kuuntelukertaa?",
"MessageConfirmRemoveMetadataFiles": "Oletko varma, että haluat poistaa kaikki metadata.{0}-tiedostot kirjaston kohdekansioista?",
@@ -815,6 +816,8 @@
"MessageFeedURLWillBe": "Syötteen URL tulee olemaan {0}",
"MessageFetching": "Haetaan...",
"MessageForceReScanDescription": "skannaa kaikki tiedostot uudelleen kuten uusi tarkistus. Äänitiedoston ID3-tunnisteet, OPF-tiedostot ja tekstitiedostot skannataan uusina.",
"MessageHeatmapListeningTimeTooltip": "<strong>{0} kuunnellaan</strong> on {1}",
"MessageHeatmapNoListeningSessions": "Ei kuuntelujaksoja {0}",
"MessageImportantNotice": "Tärkeä huomautus!",
"MessageInsertChapterBelow": "Syötä luku alle",
"MessageInvalidAsin": "Virheellinen ASIN",
@@ -885,10 +888,11 @@
"MessageResetChaptersConfirm": "Oletko varma, että haluat nollata luvut ja kumota tekemäsi muutokset?",
"MessageRestoreBackupConfirm": "Oletko varma, että haluat palauttaa varmuuskopion, joka on luotu",
"MessageRestoreBackupWarning": "Varmuuskopion palauttaminen korvaa koko /config:ssa sijaitsevan tietokannan, ja kansikuvat /metadata/items & /metadata/authors:ssa.<br /><br />Varmuuskopiot eivät muuta kirjastokansioissasi olevia tiedostoja. Jos olet ottanut käyttöön palvelinasetuksissa kansikuvien ja metatietojen tallentamisen kirjaston kansioihin, niitä ei varmuuskopioida tai korvata.<br /><br />Kaikki palvelintasi käyttävät asiakkaat virkistetään automaattisesti.",
"MessageScheduleLibraryScanNote": "Suurimmalle osaa käyttäjistä on suositeltavaa jättää tämä ominaisuus pois päältä ja säilyttää kansiotarkkailu päällä. Kansiotarkkailu havaitsee automaattisesti tiedostomuutokset kirjaston kansioissa. Kansiotarkkailu ei toimi kaikille tiedostojärjestelmille (kuten NFS), jolloin voidaan käyttää ajastettuja kirjastoskannauksia.",
"MessageScheduleLibraryScanNote": "Suurimmalle osaa käyttäjistä on suositeltavaa jättää tämä ominaisuus pois päältä ja \"Tarkkaile kirjaston muutoksia automaattisesti\" -asetus pidetään käytössä - se havaitsee muutokset kirjastokansioissasi automaattisesti. Ota tämä ominaisuus käyttöön, jos \"Tarkkaile kirjaston muutoksia automaattisesti\" ei toimi tiedostojärjestelmässäsi (kuten NFS).",
"MessageScheduleRunEveryWeekdayAtTime": "Suorita joka {0} klo {1}",
"MessageSearchResultsFor": "Hakutulokset haulle",
"MessageSelected": "{0} valittuna",
"MessageSeriesSequenceCannotContainSpaces": "Sarjan sekvenssi ei voi sisältää välilyöntejä",
"MessageServerCouldNotBeReached": "Palvelimelle ei saatu yhteyttä",
"MessageSetChaptersFromTracksDescription": "Aseta luvut käyttämällä kutakin äänitiedostoa lukuna ja luvun otsikkoa äänitiedoston nimenä",
"MessageShareExpirationWillBe": "Umpeutuminen on <strong>{0}</strong>",
@@ -930,7 +934,7 @@
"MessageTaskScanningFileChanges": "Tarkastetaan tiedoston muutoksia \"{0}\":sta",
"MessageTaskScanningLibrary": "Tarkastetaan kirjastoa \"{0}\"",
"MessageTaskTargetDirectoryNotWritable": "Kohdehakemisto ei ole kirjoitettava",
"MessageThinking": "Ajattellaan...",
"MessageThinking": "Ajatellaan...",
"MessageUploaderItemFailed": "Lataaminen ulospäin epäonnistui",
"MessageUploaderItemSuccess": "Onnistuneesti ladattu! ulospäin!",
"MessageUploading": "Ladataan! ulospäin...",
@@ -950,7 +954,10 @@
"NotificationOnBackupCompletedDescription": "Laukaistu, kun varmuuskopiointi on valmis",
"NotificationOnBackupFailedDescription": "Laukaistu, kun varmuuskopiointi epäonnistuu",
"NotificationOnEpisodeDownloadedDescription": "Laukaistu, kun podcast-jakso ladataan automaattisesti",
"NotificationOnRSSFeedDisabledDescription": "Laukaistaan, kun automaattiset jaksolataukset poistetaan käytöstä liian monen epäonnistuneen yrityksen vuoksi",
"NotificationOnRSSFeedFailedDescription": "Laukaistaan, kun RRS-syötteen pyyntö epäonnistuu automaattisessa jaksolatauksessa",
"NotificationOnTestDescription": "Tapahtuma ilmoitusjärjestelmän testaamista varten",
"PlaceholderBulkChapterInput": "Syötä luvun otsikko tai käytä numerointia (esim. 'Episodi 1', 'Luku 10', '1.')",
"PlaceholderNewCollection": "Uusi kokoelman nimi",
"PlaceholderNewFolderPath": "Uusi kansion polku",
"PlaceholderNewPlaylist": "Uusi soittolistan nimi",
@@ -1004,15 +1011,23 @@
"ToastBookmarkCreateFailed": "Kirjanmerkin luominen epäonnistui",
"ToastBookmarkCreateSuccess": "Kirjanmerkki lisätty",
"ToastBookmarkRemoveSuccess": "Kirjanmerkki poistettu",
"ToastBulkChapterInvalidCount": "Syötä numero 1 ja 150 välillä",
"ToastCachePurgeFailed": "Välimuistin tyhjentäminen epäonnistui",
"ToastCachePurgeSuccess": "Välimuisti tyhjennetty onnistuneesti",
"ToastChapterLocked": "Luku on lukittu.",
"ToastChapterStartTimeAdjusted": "Luvun aloitusaikaa on säädetty {0} sekunnilla",
"ToastChaptersAllLocked": "Kaikki luvut ovat lukittuina. Avaa lukuja vaihtaaksesi niiden aikoja.",
"ToastChaptersHaveErrors": "Luvuissa on virheitä",
"ToastChaptersInvalidShiftAmountLast": "Virheellinen siirtomäärä. Viimeisen luvun aloitusaika ylittäisi tämän äänikirjan keston.",
"ToastChaptersInvalidShiftAmountStart": "Virheellinen siirtomäärä. Ensimmäisen luvun pituudeksi tulisi nolla tai negatiivinen arvo, ja toinen luku kirjoittaisi sen päälle. Kasvata toisen luvun aloitusaikaa.",
"ToastChaptersMustHaveTitles": "Lukuilla on oltava otsikot",
"ToastChaptersRemoved": "Luvut poistettu",
"ToastChaptersUpdated": "Luvut päivitetty",
"ToastCollectionItemsAddFailed": "Kohteen/kohteiden lisääminen kokoelmaan epäonnistui",
"ToastCollectionRemoveSuccess": "Kokoelma poistettu",
"ToastCollectionUpdateSuccess": "Kokoelma päivitetty",
"ToastConnectionNotAvailable": "Verkkoyhteyttä ei saatavilla. Yritä hetken päästä uudelleen",
"ToastCoverSearchFailed": "Kansikuvan haku epäonnistui",
"ToastCoverUpdateFailed": "Kansikuvan päivitys epäonnistui",
"ToastDateTimeInvalidOrIncomplete": "Päivämäärä ja aika ovat epäkelvolliset tai puutteelliset",
"ToastDeleteFileFailed": "Tiedoston poistaminen epäonnistui",
@@ -1028,6 +1043,8 @@
"ToastEpisodeDownloadQueueClearSuccess": "Jakson latausjono tyhjennetty",
"ToastEpisodeUpdateSuccess": "{0} jaksoa päivitetty",
"ToastErrorCannotShare": "Ei voi jakaa alkuperäisesti tällä laitteella",
"ToastFailedToCreate": "Luonti epäonnistui",
"ToastFailedToDelete": "Poisto epäonnistui",
"ToastFailedToLoadData": "Tietojen lataaminen epäonnistui",
"ToastFailedToMatch": "Vastaaminen epäonnistui",
"ToastFailedToShare": "Jakaminen epäonnistui",
@@ -1035,6 +1052,7 @@
"ToastInvalidImageUrl": "Epäkelvollinen kuvan URL-osoite",
"ToastInvalidMaxEpisodesToDownload": "Ladattavien jaksojen enimmäismäärä on epäkelvollinen",
"ToastInvalidUrl": "Epäkelvollinen URL-osoite",
"ToastInvalidUrls": "Yksi tai useampi URL on virheellinen",
"ToastItemCoverUpdateSuccess": "Kohteen kansikuva päivitetty",
"ToastItemDeletedFailed": "Kohteen poistaminen epäonnistui",
"ToastItemDeletedSuccess": "Poistettu kohde",
@@ -1059,6 +1077,7 @@
"ToastMustHaveAtLeastOnePath": "On oltava vähintään yksi polku",
"ToastNameEmailRequired": "Nimi ja sähköpostiosoite vaaditaan",
"ToastNameRequired": "Nimi vaaditaan",
"ToastNewApiKeyUserError": "Täytyy valita käyttäjä",
"ToastNewEpisodesFound": "{0} uutta jaksoa löydetty",
"ToastNewUserCreatedFailed": "Tilin \"{0}\" luominen epäonnistui",
"ToastNewUserCreatedSuccess": "Uusi tili luotu",
@@ -1083,6 +1102,7 @@
"ToastPlaylistUpdateSuccess": "Soittolista päivitetty",
"ToastPodcastCreateFailed": "Podcastin luominen epäonnistui",
"ToastPodcastCreateSuccess": "Podcastin luominen onnistui",
"ToastPodcastEpisodeUpdated": "Episodi päivitetty",
"ToastPodcastGetFeedFailed": "Podcast-syötteen saaminen epäonnistui",
"ToastPodcastNoEpisodesInFeed": "RSS-syötteestä ei löytynyt jaksoja",
"ToastPodcastNoRssFeed": "Podcastilla ei ole RSS-syötettä",
@@ -1133,5 +1153,13 @@
"ToastUserPasswordChangeSuccess": "Salasana vaihdettu onnistuneesti",
"ToastUserPasswordMismatch": "Salasanat eivät täsmää",
"ToastUserPasswordMustChange": "Uusi salasana ei voi olla sama kuin vanha salasana",
"ToastUserRootRequireName": "Pääkäyttäjän nimi on pakollinen"
"ToastUserRootRequireName": "Pääkäyttäjän nimi on pakollinen",
"TooltipAddChapters": "Lisää luku tai lukuja",
"TooltipAddOneSecond": "Lisää 1 sekunti",
"TooltipAdjustChapterStart": "Napauta säätääksesi aloitusaikaa",
"TooltipLockAllChapters": "Lukitse kaikki luvut",
"TooltipLockChapter": "Lukitse luku (Shift+napauta valitaksesi alueen)",
"TooltipSubtractOneSecond": "Vähennä 1 sekunti",
"TooltipUnlockAllChapters": "Avaa kaikki luvut",
"TooltipUnlockChapter": "Avaa luku (Shift+napauta valitaksesi alueen)"
}
+7 -7
View File
@@ -275,7 +275,7 @@
"LabelBonus": "Bonus",
"LabelBooks": "Livres",
"LabelButtonText": "Texte du bouton",
"LabelByAuthor": "par {0}",
"LabelByAuthor": "de {0}",
"LabelChangePassword": "Modifier le mot de passe",
"LabelChannels": "Canaux",
"LabelChapterCount": "{0} Chapitres",
@@ -436,11 +436,11 @@
"LabelLibraryFilterSublistEmpty": "Aucun {0}",
"LabelLibraryItem": "Élément de bibliothèque",
"LabelLibraryName": "Nom de la bibliothèque",
"LabelLibrarySortByProgress": "Progression : dernière mise à jour",
"LabelLibrarySortByProgress": "Progression : Mise à jour",
"LabelLibrarySortByProgressFinished": "Progression : Terminé",
"LabelLibrarySortByProgressStarted": "Progression : Commencé",
"LabelLibrarySortByProgressStarted": "Progression : En cours",
"LabelLimit": "Limite",
"LabelLineSpacing": "Espacement des lignes",
"LabelLineSpacing": "Interligne",
"LabelListenAgain": "Écouter à nouveau",
"LabelLogLevelDebug": "Débogage",
"LabelLogLevelInfo": "Info",
@@ -622,7 +622,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",
"LabelSettingsTimeFormat": "Format dheure",
"LabelShare": "Partager",
"LabelShareDownloadableHelp": "Permet aux utilisateurs de télécharger un fichier ZIP de l'élément de la bibliothèque.",
"LabelShareDownloadableHelp": "Permet aux utilisateurs disposant du lien de partage de télécharger un fichier zip contenant l'élément de la bibliothèque.",
"LabelShareOpen": "Ouvrir le partage",
"LabelShareURL": "Partager lURL",
"LabelShowAll": "Tout afficher",
@@ -961,8 +961,8 @@
"PlaceholderNewCollection": "Nom de la nouvelle collection",
"PlaceholderNewFolderPath": "Nouveau chemin de dossier",
"PlaceholderNewPlaylist": "Nouveau nom de liste de lecture",
"PlaceholderSearch": "Recherche",
"PlaceholderSearchEpisode": "Rechercher un épisode..",
"PlaceholderSearch": "Recherche...",
"PlaceholderSearchEpisode": "Rechercher un épisode",
"StatsAuthorsAdded": "auteurs ajoutés",
"StatsBooksAdded": "livres ajoutés",
"StatsBooksAdditional": "Les ajouts comprennent…",
+103 -2
View File
@@ -81,7 +81,7 @@
"ButtonRemove": "הסר",
"ButtonRemoveAll": "הסר הכל",
"ButtonRemoveAllLibraryItems": "הסר את כל פריטי הספרייה",
"ButtonRemoveFromContinueListening": "הסר מ- המשך האזנה",
"ButtonRemoveFromContinueListening": "הסר מ״המשך האזנה״",
"ButtonRemoveFromContinueReading": "הסר מ- המשך קריאה",
"ButtonRemoveSeriesFromContinueSeries": "הסר סדרה מ- המשך סדרה",
"ButtonReset": "איפוס",
@@ -121,11 +121,13 @@
"HeaderAccount": "חשבון",
"HeaderAddCustomMetadataProvider": "הוסף ספק מטא-נתונים מותאם אישית",
"HeaderAdvanced": "מתקדם",
"HeaderApiKeys": "מפתחות API",
"HeaderAppriseNotificationSettings": "הגדרות התראות של Apprise",
"HeaderAudioTracks": "רצועות קול",
"HeaderAudiobookTools": "כלים לניהול קבצי ספרים קוליים",
"HeaderAuthentication": "אימות",
"HeaderBackups": "גיבויים",
"HeaderBulkChapterModal": "הוסף מספר פרקים",
"HeaderChangePassword": "שנה סיסמה",
"HeaderChapters": "פרקים",
"HeaderChooseAFolder": "בחר תיקייה",
@@ -164,6 +166,7 @@
"HeaderMetadataOrderOfPrecedence": "סדר העדפת מטא-נתונים",
"HeaderMetadataToEmbed": "מטא-נתונים להטמעה",
"HeaderNewAccount": "חשבון חדש",
"HeaderNewApiKey": "מפתח API חדש",
"HeaderNewLibrary": "ספרייה חדשה",
"HeaderNotificationCreate": "צור התראה",
"HeaderNotificationUpdate": "עדכון התראה",
@@ -197,6 +200,7 @@
"HeaderSettingsExperimental": "תכונות ניסיוניות",
"HeaderSettingsGeneral": "כללי",
"HeaderSettingsScanner": "סורק",
"HeaderSettingsSecurity": "אבטחה",
"HeaderSettingsWebClient": "מערך",
"HeaderSleepTimer": "טיימר שינה",
"HeaderStatsLargestItems": "הפריטים הגדולים ביותר",
@@ -208,6 +212,7 @@
"HeaderTableOfContents": "תוכן עניינים",
"HeaderTools": "כלים",
"HeaderUpdateAccount": "עדכן חשבון",
"HeaderUpdateApiKey": "עדכן מפתח API",
"HeaderUpdateAuthor": "עדכן יוצר",
"HeaderUpdateDetails": "עדכן פרטים",
"HeaderUpdateLibrary": "עדכן ספרייה",
@@ -237,6 +242,10 @@
"LabelAllUsersExcludingGuests": "כל המשתמשים, ללא אורחים",
"LabelAllUsersIncludingGuests": "כל המשתמשים כולל אורחים",
"LabelAlreadyInYourLibrary": "כבר קיים בספרייה שלך",
"LabelApiKeyCreated": "מפתח API ״{0}״ נוצר בהצלחה.",
"LabelApiKeyCreatedDescription": "אנא העתק את מפתח ה־API כעת, לא ניתן יהיה להציגו שוב.",
"LabelApiKeyUser": "פעל בשם המשתמש",
"LabelApiKeyUserDescription": "למפתח ה־API יהיו הרשאות זהות למשתמש שעל שמו הוא פועל. ביומני הרישום (logs), הפעולות יופיעו כאילו בוצעו על ידי המשתמש עצמו.",
"LabelApiToken": "טוקן API",
"LabelAppend": "הוסף לסוף",
"LabelAudioBitrate": "קצב סיביות (לדוגמא 128k)",
@@ -286,6 +295,7 @@
"LabelContinueListening": "המשך האזנה",
"LabelContinueReading": "המשך קריאה",
"LabelContinueSeries": "המשך סדרה",
"LabelCorsAllowed": "מקורות CORS מורשים",
"LabelCover": "כריכה",
"LabelCoverImageURL": "כתובת התמונה ברשת",
"LabelCoverProvider": "ספק כריכה",
@@ -299,6 +309,7 @@
"LabelDeleteFromFileSystemCheckbox": "מחיקה מהמערכת הקבצים (הסר סימון למחיקה רק ממסד הנתונים)",
"LabelDescription": "תיאור",
"LabelDeselectAll": "הסר בחירת כל הפריטים",
"LabelDetectedPattern": "תבנית שזוהתה:",
"LabelDevice": "התקן",
"LabelDeviceInfo": "מידע על התקן",
"LabelDeviceIsAvailableTo": "התקן זמין ל...",
@@ -348,6 +359,10 @@
"LabelExample": "דוגמה",
"LabelExpandSeries": "הרחב סדרה",
"LabelExpandSubSeries": "הרחב תת סדרה",
"LabelExpired": "פג תוקף",
"LabelExpiresAt": "יפוג בתאריך",
"LabelExpiresInSeconds": "יפוג בעוד (שניות)",
"LabelExpiresNever": "ללא הגבלת זמן",
"LabelExplicit": "מפורש",
"LabelExplicitChecked": "בוטה (מסומן)",
"LabelExplicitUnchecked": "לא בוטה (לא מסומן)",
@@ -363,6 +378,7 @@
"LabelFilterByUser": "סינון לפי משתמש",
"LabelFindEpisodes": "מצא פרקים",
"LabelFinished": "הושלם",
"LabelFinishedDate": "הושלם {0}",
"LabelFolder": "תיקייה",
"LabelFolders": "תיקיות",
"LabelFontBold": "מודגש",
@@ -407,6 +423,7 @@
"LabelLanguages": "שפות",
"LabelLastBookAdded": "הספר האחרון שנוסף",
"LabelLastBookUpdated": "הספר האחרון שעודכן",
"LabelLastProgressDate": "התקדמות אחרונה: {0}",
"LabelLastSeen": "נראה לאחרונה",
"LabelLastTime": "הזמן האחרון",
"LabelLastUpdate": "עדכון אחרון",
@@ -419,6 +436,9 @@
"LabelLibraryFilterSublistEmpty": "לא {0}",
"LabelLibraryItem": "פריט ספרייה",
"LabelLibraryName": "שם הספרייה",
"LabelLibrarySortByProgress": "התקדמות: עודכן לאחרונה",
"LabelLibrarySortByProgressFinished": "התקדמות: הושלם",
"LabelLibrarySortByProgressStarted": "התקדמות: הותחל",
"LabelLimit": "מגבלה",
"LabelLineSpacing": "מרווח שורה",
"LabelListenAgain": "האזן שוב",
@@ -427,6 +447,7 @@
"LabelLogLevelWarn": "אזהרה",
"LabelLookForNewEpisodesAfterDate": "חפש פרקים חדשים לאחר תאריך זה",
"LabelLowestPriority": "העדיפות הנמוכה ביותר",
"LabelMatchConfidence": "רמת ודאות",
"LabelMatchExistingUsersBy": "התאם משתמשים קיימים לפי",
"LabelMatchExistingUsersByDescription": "משמש לחיבור משתמשים קיימים. לאחר החיבור, המשתמשים יותאמו לפי זיהוי ייחודי מספק ה-SSO שלך",
"LabelMaxEpisodesToDownload": "מספר פרקים מקסימלי להורדה. 0 - ללא הגבלה.",
@@ -456,7 +477,9 @@
"LabelNewestAuthors": "הסופרים האחרונים",
"LabelNewestEpisodes": "הפרקים החדשים ביותר",
"LabelNextBackupDate": "תאריך הגיבוי הבא",
"LabelNextChapters": "הפרקים הבא יהיו:",
"LabelNextScheduledRun": "הרצה מתוזמנת הבאה",
"LabelNoApiKeys": "אין מפתחות API",
"LabelNoCustomMetadataProviders": "אין ספקי מטא-נתונים מותאמים אישית",
"LabelNoEpisodesSelected": "לא נבחרו פרקים",
"LabelNotFinished": "לא הושלם",
@@ -472,16 +495,21 @@
"LabelNotificationsMaxQueueSize": "גודל התור המרבי לאירועי התראה",
"LabelNotificationsMaxQueueSizeHelp": "האירועים מוגבלים לשליחה אחת לשנייה. האירועים יתעלמו אם התור מלא. הגדרה זו נועדה למנוע ספאם התראות.",
"LabelNumberOfBooks": "מספר הספרים",
"LabelNumberOfChapters": "מספר הפרקים:",
"LabelNumberOfEpisodes": "# פרקים",
"LabelOpenIDAdvancedPermsClaimDescription": "שם OpenID claim המכילה הרשאות מתקדמות לפעולות משתמש בתוך האפליקציה, אשר יחולו על תפקידים שאינם מנהלי מערכת (<b>אם הוגדרה</b>). אם התביעה חסרה בתגובה, הגישה ל-ABS תידחה. אם אפשרות אחת חסרה, היא תטופל כ-<code>false</code> יש לוודא שטענת ספק הזהויות תואמת את המבנה הצפוי:",
"LabelOpenIDClaims": "השאר את האפשרויות הבאות ריקות כדי להשבית הקצאת קבוצות והרשאות מתקדמת, ולאחר מכן להקצות אוטומטית את קבוצת 'משתמש'.",
"LabelOpenIDGroupClaimDescription": "שם ה־OpenID claim המכיל את רשימת הקבוצות של המשתמש. בדרך כלל נקרא <code>groups</code>. <b>אם הוגדרה</b>, האפליקציה תקצה תפקידים באופן אוטומטי על סמך השיוך לקבוצות, בתנאי ששמות הקבוצות ב־claim הם 'admin', 'user' או 'guest' (ללא רגישות לרישיות - Case-insensitive). ה־claim צריך להכיל רשימה; אם המשתמש משויך למספר קבוצות, האפליקציה תקצה את התפקיד בעל רמת הגישה הגבוהה ביותר. במידה ולא נמצאה קבוצה תואמת, הגישה תיחסם.",
"LabelOpenRSSFeed": "פתח ערוץ RSS",
"LabelOverwrite": "לשכפל",
"LabelPaginationPageXOfY": "עמוד {0} מתוך {1}",
"LabelPassword": "סיסמה",
"LabelPath": "נתיב",
"LabelPermanent": "קבוע",
"LabelPermissionsAccessAllLibraries": "ניתן לגשת לכל הספריות",
"LabelPermissionsAccessAllTags": "ניתן לגשת לכל התגיות",
"LabelPermissionsAccessExplicitContent": "ניתן לגשת לתוכן בוטה",
"LabelPermissionsCreateEreader": "ניתן ליצור קורא ספרים דיגיטלי",
"LabelPermissionsDelete": "מותר למחוק",
"LabelPermissionsDownload": "מותר להוריד",
"LabelPermissionsUpdate": "מותר לעדכן",
@@ -489,6 +517,8 @@
"LabelPersonalYearReview": "השנה שלך בסקירה ({0})",
"LabelPhotoPathURL": "נתיב/URL לתמונה",
"LabelPlayMethod": "שיטת הפעלה",
"LabelPlaybackRateIncrementDecrement": "שיעור הגדלה/הפחתה של מהירות ההשמעה",
"LabelPlayerChapterNumberMarker": "{0} מתוך {1}",
"LabelPlaylists": "רשימות השמעה",
"LabelPodcast": "פודקאסט",
"LabelPodcastSearchRegion": "אזור חיפוש פודקאסט",
@@ -500,10 +530,14 @@
"LabelPrimaryEbook": "ספר אלקטרוני ראשי",
"LabelProgress": "התקדמות",
"LabelProvider": "ספק",
"LabelProviderAuthorizationValue": "ערך כותרת האימות (Authorization Header)",
"LabelPubDate": "תאריך פרסום",
"LabelPublishYear": "שנת הפרסום",
"LabelPublishedDate": "פורסם {0}",
"LabelPublishedDecade": "עשור פרסום",
"LabelPublishedDecades": "עשורי פרסום",
"LabelPublisher": "מוציא לאור",
"LabelPublishers": "מוצאים לאור",
"LabelRSSFeedCustomOwnerEmail": "אימייל בעלים מותאם אישית",
"LabelRSSFeedCustomOwnerName": "שם בעלים מותאם אישית",
"LabelRSSFeedOpen": "ערוץ RSS פתוח",
@@ -511,6 +545,7 @@
"LabelRSSFeedSlug": "Slug של ערוץ ה-RSS",
"LabelRSSFeedURL": "כתובת ערוץ ה-RSS",
"LabelRandomly": "באופן אקראי",
"LabelReAddSeriesToContinueListening": "הוסף סדרה בחזרה אל ״המשך האזנה״",
"LabelRead": "קריאה",
"LabelReadAgain": "קרא שוב",
"LabelReadEbookWithoutProgress": "קרא/י ספר אלקטרוני ללא שמירת התקדמות",
@@ -520,29 +555,44 @@
"LabelRedo": "עשה שוב",
"LabelRegion": "אזור",
"LabelReleaseDate": "תאריך הוצאה לאור",
"LabelRemoveAllMetadataAbs": "הסר את כל קבצי metadata.abs",
"LabelRemoveAllMetadataJson": "הסר את כל קבצי metadata.json",
"LabelRemoveAudibleBranding": "הסר פתיח וסיום של Audible מהפרקים",
"LabelRemoveCover": "הסר כריכה",
"LabelRemoveMetadataFile": "הסר קבצי מטא־נתונים מתיקיות הפריטים בספרייה",
"LabelRemoveMetadataFileHelp": "הסר את כל קבצי metadata.json ו־metadata.abs מתיקיות {0}.",
"LabelRowsPerPage": "שורות לעמוד",
"LabelSearchTerm": "מונח חיפוש",
"LabelSearchTitle": "כותרת חיפוש",
"LabelSearchTitleOrASIN": "כותרת חיפוש או ASIN",
"LabelSeason": "עונה",
"LabelSeasonNumber": "עונה #{0}",
"LabelSelectAll": "בחר הכל",
"LabelSelectAllEpisodes": "בחר את כל הפרקים",
"LabelSelectEpisodesShowing": "בחר {0} פרקים המוצגים",
"LabelSelectUser": "בחר משתמש",
"LabelSelectUsers": "בחר משתמשים",
"LabelSendEbookToDevice": "שלח ספר אלקטרוני ל...",
"LabelSequence": "רצף",
"LabelSerial": "מספר סידורי",
"LabelSeries": "סדרה",
"LabelSeriesName": "שם הסדרה",
"LabelSeriesProgress": "התקדמות בסדרה",
"LabelServerLogLevel": "רמת פירוט יומני הרישום",
"LabelServerYearReview": "השנה בסקירה של השרת ({0})",
"LabelSetEbookAsPrimary": "קבע כראשי",
"LabelSetEbookAsSupplementary": "קבע כמשלים",
"LabelSettingsAllowIframe": "אפשר הטמעה בתוך iframe",
"LabelSettingsAudiobooksOnly": "רק ספרי קול",
"LabelSettingsAudiobooksOnlyHelp": "הפעלת ההגדרה הזו תתעלם מקבצי ספרים אלקטרוניים אלא אם כן הם נמצאים בתיקיית ספרי קול, שבמקרה זה יקבעו כספרים אלקטרוניים נלווים",
"LabelSettingsBookshelfViewHelp": "עיצוב סקאומורפי עם מדפי עץ",
"LabelSettingsChromecastSupport": "תמיכה ב-Chromecast",
"LabelSettingsDateFormat": "פורמט תאריך",
"LabelSettingsEnableWatcher": "הפעל מעקב שינויים בספריות",
"LabelSettingsEnableWatcherForLibrary": "הפעל מעקב שינויים בספרייה",
"LabelSettingsEnableWatcherHelp": "מאפשר הוספת/עדכון אוטומטי של פריטים כאשר שינויי קבצים זוהים. *דורש איתחול שרת",
"LabelSettingsEpubsAllowScriptedContent": "אפשור תוכן הכולל סקריפטים ב־ePubs",
"LabelSettingsEpubsAllowScriptedContentHelp": "אפשר לקובצי EPUB להריץ סקריפטים. מומלץ להשאיר את ההגדרה כבויה, אלא אם כן מקור קובצי ה־ePub מהימן.",
"LabelSettingsExperimentalFeatures": "תכונות ניסיוניות",
"LabelSettingsExperimentalFeaturesHelp": "תכונות בפיתוח שדורשות משובך ובדיקה. לחץ לפתיחת דיון ב-GitHub.",
"LabelSettingsFindCovers": "מצא כריכות",
@@ -551,7 +601,8 @@
"LabelSettingsHideSingleBookSeriesHelp": "סדרות הכוללות ספר אחד יוסתרו מדף הסדרות ומדף הבית.",
"LabelSettingsHomePageBookshelfView": "השתמש בתצוגת מדף בדף הבית",
"LabelSettingsLibraryBookshelfView": "השתמש בתצוגת מדף בספרייה",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "דלג על ספרים קודמים ב-המשך סדרה",
"LabelSettingsLibraryMarkAsFinishedWhen": "סמן פריט מדיה כהושלם כאשר",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "דלג על ספרים קודמים ב״המשך סדרה״",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "מדף המשך סדרות מציג את הספר הראשון שלא הושמע בסדרה שיש בה לפחות ספר אחד שהושלם ואין ספרים שכבר באמצע שמיעה. הפעלת הגדרה זו תמשיך סדרות מהספר שהושלם הכי מתקדם בסדרה במקום מהספר הראשון שלא הושמע.",
"LabelSettingsParseSubtitles": "פענח כתוביות",
"LabelSettingsParseSubtitlesHelp": "העתק כותרת משנה משם תיקיית הספר.<br>כותרת המשנה חייבת להיות מופרדת עם התו ״-״<br>לדוגמא, כותרת המשנה לספר ״שם הספר - כותרת משנה״, היא ״כותרת משנה״",
@@ -568,13 +619,22 @@
"LabelSettingsStoreMetadataWithItem": "אחסן מטה-נתונים עם הפריט",
"LabelSettingsStoreMetadataWithItemHelp": "כברירת מחדל, קבצי מטה-נתונים מאוחסנים ב- /metadata/items, הפעלת ההגדרה תאחסן קבצי מטה-נתונים בתיקיית פריט שלך בספרייה",
"LabelSettingsTimeFormat": "פורמט זמן",
"LabelShare": "שתף",
"LabelShareDownloadableHelp": "אפשר למי שיש ברשותו קישור שיתוף להוריד קובץ ZIP של פריט הספרייה.",
"LabelShareURL": "שתף קישור",
"LabelShowAll": "הצג הכל",
"LabelShowSeconds": "הצג שניות",
"LabelShowSubtitles": "הצג כתוביות",
"LabelSize": "גודל",
"LabelSleepTimer": "טיימר שינה",
"LabelSortAscending": "סדר עולה",
"LabelSortDescending": "סדר יורד",
"LabelSortPubDate": "מיין לפי תאריך פרסום",
"LabelStart": "התחל",
"LabelStartTime": "זמן התחלה",
"LabelStarted": "התחיל",
"LabelStartedAt": "התחיל ב",
"LabelStartedDate": "הותחל {0}",
"LabelStatsAudioTracks": "רצועות שמע",
"LabelStatsAuthors": "מחברים",
"LabelStatsBestDay": "היום הטוב ביותר",
@@ -604,7 +664,13 @@
"LabelTheme": "ערכת נושא",
"LabelThemeDark": "כהה",
"LabelThemeLight": "בהיר",
"LabelThemeSepia": "ספיה",
"LabelTimeBase": "בסיס זמן",
"LabelTimeDurationXHours": "{0} שעות",
"LabelTimeDurationXMinutes": "{0} דקות",
"LabelTimeDurationXSeconds": "{0} שניות",
"LabelTimeInMinutes": "זמן בשניות",
"LabelTimeLeft": "נותרו {0}",
"LabelTimeListened": "זמן האזנה",
"LabelTimeListenedToday": "זמן האזנה היום",
"LabelTimeRemaining": "{0} נותרו",
@@ -612,6 +678,7 @@
"LabelTitle": "כותרת",
"LabelToolsEmbedMetadata": "הטמעת מטה-נתונים",
"LabelToolsEmbedMetadataDescription": "הטמעת מטה-נתונים לקבצי שמע כולל תמונות כריכה ופרקים.",
"LabelToolsM4bEncoder": "מקודד M4B",
"LabelToolsMakeM4b": "יצירת קובץ אודיו M4B",
"LabelToolsMakeM4bDescription": "יצירת קובץ אודיו .M4B עם מטה-נתונים מוטמעים, תמונת שער ופרקים.",
"LabelToolsSplitM4b": "פיצול M4B ל-MP3",
@@ -624,29 +691,39 @@
"LabelTracksMultiTrack": "רב-ערוצי",
"LabelTracksNone": "אין ערוצים",
"LabelTracksSingleTrack": "רצועה יחידה",
"LabelTrailer": "קדימון",
"LabelType": "סוג",
"LabelUnabridged": "לא מקוצר",
"LabelUndo": "בטל",
"LabelUnknown": "לא ידוע",
"LabelUnknownPublishDate": "תאריך הוצאה לאור לא ידוע",
"LabelUpdateCover": "עדכן כריכה",
"LabelUpdateCoverHelp": "אפשר החלפה של כריכות קיימות עבור הספרים הנבחרים כאשר נמצאה התאמה",
"LabelUpdateDetails": "עדכון פרטים",
"LabelUpdateDetailsHelp": "אפשר החלפה של פרטים קיימים עבור הספרים הנבחרים כאשר נמצאה התאמה",
"LabelUpdatedAt": "עודכן ב-",
"LabelUploaderDragAndDrop": "גרור ושחרר קבצים או תיקיות",
"LabelUploaderDragAndDropFilesOnly": "גרור ושחרר קבצים",
"LabelUploaderDropFiles": "שחרר קבצים",
"LabelUploaderItemFetchMetadataHelp": "משיכת כותרת, סופר וסדרה באופן אוטומטי",
"LabelUseAdvancedOptions": "השתמש באפשרויות מתקדמות",
"LabelUseChapterTrack": "השתמש ברצועות הפרקים",
"LabelUseFullTrack": "השתמש ברצועה המלאה",
"LabelUseZeroForUnlimited": "השתמש ב־0 מתוך אין־סוף",
"LabelUser": "משתמש",
"LabelUsername": "שם משתמש",
"LabelValue": "ערך",
"LabelVersion": "גרסה",
"LabelViewBookmarks": "הצג סימניות",
"LabelViewChapters": "הצג פרקים",
"LabelViewPlayerSettings": "הצג הגדרות נגן",
"LabelViewQueue": "הצג תור נגן",
"LabelVolume": "עוצמת קול",
"LabelWebRedirectURLsDescription": "יש לאשר את הכתובות הבאות אצל ספק ה־OAuth כדי לאפשר הפניה חזרה לאפליקציית הדפדפן לאחר ההתחברות:",
"LabelWebRedirectURLsSubfolder": "תיקיית משנה לכתובות הפניה",
"LabelWeekdaysToRun": "ימי השבוע להרצה",
"LabelXBooks": "{0} ספרים",
"LabelXItems": "{0} פריטים",
"LabelYearReviewHide": "הסתר סקירת שנה",
"LabelYearReviewShow": "הצג סקירת שנה",
"LabelYourAudiobookDuration": "משך הספר הקולי שלך",
@@ -655,31 +732,55 @@
"LabelYourProgress": "ההתקדמות שלך",
"MessageAddToPlayerQueue": "הוסף לתור הנגן",
"MessageAppriseDescription": "כדי להשתמש בתכונה זו יש לך להריץ מופע של <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">ממשק התכנית האפליקציה</a> או API שיטפל בבקשות אלו. <br /> כתובת URL של ממשק ה-Apprise API צריכה להיות הנתיב המלא לשליחת ההתראה, לדוגמה, אם המופע של ה-API שלך מוצע ב-<code>http://192.168.1.1:8337</code> אז עליך לשים <code>http://192.168.1.1:8337/notify</code>.",
"MessageAsinCheck": "יש לוודא שימוש ב־ASIN מאזור ה־Audible הנכון, ולא מ־Amazon.",
"MessageAuthenticationLegacyTokenWarning": "אסימוני API ישנים יוסרו בעתיד. יש להשתמש ב <a href=\"/config/api-keys\">מפתחות API</a> במקום.",
"MessageAuthenticationOIDCChangesRestart": "יש להפעיל מחדש את השרת לאחר השמירה כדי להחיל את שינויי ה־OIDC.",
"MessageAuthenticationSecurityMessage": "האימות שופר מטעמי אבטחה. כל המשתמשים נדרשים להתחבר מחדש.",
"MessageBackupsDescription": "גיבויים כוללים משתמשים, התקדמות משתמש, פרטי פריטי ספרייה, הגדרות שרת ותמונות השמורות ב-<code>/metadata/items</code> & <code>/metadata/authors</code>. גיבויים <strong>לא</strong> כוללים קבצים שמורים בתיקיות הספרייה שלך.",
"MessageBackupsLocationEditNote": "הערה: שינוי מיקום הגיבוי לא יגרום להעברה או לשינוי של גיבויים קיימים",
"MessageBackupsLocationNoEditNote": "הערה: מיקום הגיבוי מוגדר באמצעות משתנה סביבה ולא ניתן לשנותו כאן.",
"MessageBackupsLocationPathEmpty": "נתיב מיקום הגיבוי אינו יכול להיות ריק",
"MessageBatchEditPopulateMapDetailsAllHelp": "מלא את השדות הפעילים בנתונים מכל הפריטים. שדות בעלי ערכים מרובים ימוזגו",
"MessageBatchEditPopulateMapDetailsItemHelp": "מלא את שדות פרטי המיפוי הפעילים בנתונים מפריט זה",
"MessageBatchQuickMatchDescription": "התאמה מהירה תנסה להוסיף כריכות ומטה-נתונים חסרים עבור הפריטים הנבחרים. הפעל את האפשרויות למטה כדי לאפשר להתאמה מהירה להחליף כריכות קיימות ו/או מטה-נתונים.",
"MessageBookshelfNoCollections": "עדיין לא יצרת אוספים",
"MessageBookshelfNoCollectionsHelp": "האוספים ציבוריים. כל המשתמשים בעלי גישה לספרייה יכולים לראות אותם.",
"MessageBookshelfNoRSSFeeds": "אין ערוצי RSS פתוחים",
"MessageBookshelfNoResultsForFilter": "אין תוצאות עבור סינון \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "אין תוצאות עבור השאילתה",
"MessageBookshelfNoSeries": "אין לך סדרות",
"MessageBulkChapterPattern": "כמה פרקים להוסיף לפי תבנית מספור זו?",
"MessageChapterEndIsAfter": "זמן סיום הפרק אחרי סיום הספר הקולי שלך",
"MessageChapterErrorFirstNotZero": "הפרק הראשון חייב להתחיל ב-0",
"MessageChapterErrorStartGteDuration": "זמן התחלה לא תקין, חייב להיות פחות ממשך הספר הקולי",
"MessageChapterErrorStartLtPrev": "זמן התחלה לא תקין, חייב להיות גדול או שווה לזמן ההתחלה של הפרק הקודם",
"MessageChapterStartIsAfter": "התחלת הפרק אחרי סיום הספר הקולי שלך",
"MessageChaptersNotFound": "לא נמצאו פרקים",
"MessageCheckingCron": "בודק את תזמון העבודה...",
"MessageConfirmCloseFeed": "האם אתה בטוח שאתה רוצה לסגור את הערוץ הזה?",
"MessageConfirmDeleteApiKey": "האם למחוק את מפתח ה־API \"{0}\"?",
"MessageConfirmDeleteBackup": "האם אתה בטוח שברצונך למחוק גיבוי עבור {0}?",
"MessageConfirmDeleteDevice": "האם למחוק את הקורא האלקטרוני \"{0}\"?",
"MessageConfirmDeleteFile": "הקובץ ימחק לצמיתות מהמערכת שלך. האם אתה בטוח?",
"MessageConfirmDeleteLibrary": "האם אתה בטוח שברצונך למחוק לצמיתות את הספרייה \"{0}\"?",
"MessageConfirmDeleteLibraryItem": "פריט הספרייה יימחק לצמיתות ממסד הנתונים ומהמערכת שלך. האם אתה בטוח?",
"MessageConfirmDeleteLibraryItems": "פריטי הספרייה {0} יימחקו ממסד הנתונים ומהמערכת שלך. האם אתה בטוח?",
"MessageConfirmDeleteMetadataProvider": "האם למחוק את ספק המטא־נתונים המותאם \"{0}\"?",
"MessageConfirmDeleteNotification": "האם למחוק התראה זו?",
"MessageConfirmDeleteSession": "האם אתה בטוח שאתה רוצה למחוק את ההפעלה הזו?",
"MessageConfirmEmbedMetadataInAudioFiles": "האם להטמיע מטא־נתונים ב־{0} קובצי שמע?",
"MessageConfirmForceReScan": "האם אתה בטוח שאתה רוצה להכריח סריקה מחדש?",
"MessageConfirmMarkAllEpisodesFinished": "האם אתה בטוח שברצונך לסמן את כל הפרקים כהסתיימו?",
"MessageConfirmMarkAllEpisodesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הפרקים כלא הסתיימו?",
"MessageConfirmMarkItemFinished": "האם לסמן את \"{0}\" כהושלם?",
"MessageConfirmMarkItemNotFinished": "האם לסמן את \"{0}\" כלא הושלם?",
"MessageConfirmMarkSeriesFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כהסתיימו?",
"MessageConfirmMarkSeriesNotFinished": "האם אתה בטוח שברצונך לסמן את כל הספרים בסדרה זו כלא הסתיימו?",
"MessageConfirmNotificationTestTrigger": "האם להפעיל התראה זו עם נתוני בדיקה?",
"MessageConfirmPurgeCache": "ניקוי המטמון ימחק את כל התיקייה ב־<code>/metadata/cache</code>.<br /><br />האם למחוק את תיקיית המטמון?",
"MessageConfirmPurgeItemsCache": "ניקוי מטמון הפריטים ימחק את כל התיקייה ב־<code>metadata/cache/items/</code>.<br />האם למחוק?",
"MessageConfirmQuickEmbed": "אזהרה! הטמעה מהירה לא תגבה גיבוי של קבצי האודיו שלך. וודא שיש לך גיבוי של קבצי האודיו שלך. <br><br>האם ברצונך להמשיך?",
"MessageConfirmQuickMatchEpisodes": "התאמה מהירה תדרוס פרטים עבור פרקים תואמים. רק פרקים ללא התאמה יעודכנו. האם להמשיך?",
"MessageConfirmReScanLibraryItems": "האם אתה בטוח שברצונך לסרוק מחדש {0} פריטים?",
"MessageConfirmRemoveAllChapters": "האם אתה בטוח שברצונך להסיר את כל הפרקים?",
"MessageConfirmRemoveAuthor": "האם אתה בטוח שברצונך להסיר את המחבר \"{0}\"?",
+7 -7
View File
@@ -197,7 +197,7 @@
"HeaderSetBackupSchedule": "Zakazivanje sigurnosne pohrane",
"HeaderSettings": "Postavke",
"HeaderSettingsDisplay": "Prikaz",
"HeaderSettingsExperimental": "Eksperimentalne funkcije",
"HeaderSettingsExperimental": "Eksperimentalne značajke",
"HeaderSettingsGeneral": "Općenito",
"HeaderSettingsScanner": "Skener",
"HeaderSettingsSecurity": "Sigurnost",
@@ -383,7 +383,7 @@
"LabelFolders": "Mape",
"LabelFontBold": "Podebljano",
"LabelFontBoldness": "Debljina slova",
"LabelFontFamily": "Skupina fontova",
"LabelFontFamily": "Skup pisma",
"LabelFontItalic": "Kurziv",
"LabelFontScale": "Veličina slova",
"LabelFontStrikethrough": "Precrtano",
@@ -436,15 +436,15 @@
"LabelLibraryFilterSublistEmpty": "Br {0}",
"LabelLibraryItem": "Stavka knjižnice",
"LabelLibraryName": "Ime knjižnice",
"LabelLibrarySortByProgress": "Napredak: zadnje ažurirano",
"LabelLibrarySortByProgressFinished": "Napredak: završeno",
"LabelLibrarySortByProgressStarted": "Napredak: započeto",
"LabelLibrarySortByProgress": "Napredak: Zadnje ažuriranje",
"LabelLibrarySortByProgressFinished": "Napredak: Završeno",
"LabelLibrarySortByProgressStarted": "Napredak: Započeto",
"LabelLimit": "Ograničenje",
"LabelLineSpacing": "Razmak između redaka",
"LabelListenAgain": "Ponovno poslušaj",
"LabelLogLevelDebug": "Debug",
"LabelLogLevelInfo": "Info",
"LabelLogLevelWarn": "Warn",
"LabelLogLevelWarn": "Upozorenje",
"LabelLookForNewEpisodesAfterDate": "Traži nove nastavke nakon ovog datuma",
"LabelLowestPriority": "Najniži prioritet",
"LabelMatchConfidence": "Pouzdanost",
@@ -452,7 +452,7 @@
"LabelMatchExistingUsersByDescription": "Rabi se za povezivanje postojećih korisnika. Nakon što se spoje, korisnike se prepoznaje temeljem jedinstvene oznake vašeg pružatelja SSO usluga",
"LabelMaxEpisodesToDownload": "Najveći broj nastavaka za preuzimanje. 0 za neograničeno.",
"LabelMaxEpisodesToDownloadPerCheck": "Najveći broj novih nastavaka za preuzimanje po provjeri",
"LabelMaxEpisodesToKeep": "Najveći broj nastavaka za čuvanje",
"LabelMaxEpisodesToKeep": "Najveći # nastavaka za čuvanje",
"LabelMaxEpisodesToKeepHelp": "Ako je vrijednost 0, nema ograničenja broja. Nakon automatskog preuzimanja novog nastavka ova funkcija briše najstariji nastavak ako ih ima više od zadanog broja. Ovo briše samo jedan nastavak po novom preuzetom nastavku.",
"LabelMediaPlayer": "Reproduktor medijskih sadržaja",
"LabelMediaType": "Vrsta medija",
+5 -5
View File
@@ -16,7 +16,7 @@
"ButtonBrowseForFolder": "Mappa keresése",
"ButtonCancel": "Mégse",
"ButtonCancelEncode": "Kódolás megszakítása",
"ButtonChangeRootPassword": "Gyökérjelszó megváltoztatása",
"ButtonChangeRootPassword": "Root jelszó megváltoztatása",
"ButtonCheckAndDownloadNewEpisodes": "Új epizódok ellenőrzése és letöltése",
"ButtonChooseAFolder": "Válassz egy mappát",
"ButtonChooseFiles": "Fájlok kiválasztása",
@@ -205,7 +205,7 @@
"HeaderSleepTimer": "Alvásidőzítő",
"HeaderStatsLargestItems": "Legnagyobb elemek",
"HeaderStatsLongestItems": "Leghosszabb elemek (órában)",
"HeaderStatsMinutesListeningChart": "Hallgatási grafikon percekben (az elmúlt 7 napból)",
"HeaderStatsMinutesListeningChart": "Hallgatási grafikon percben (az elmúlt 7 napból)",
"HeaderStatsRecentSessions": "Legutóbbi munkamenetek",
"HeaderStatsTop10Authors": "Top 10 szerző",
"HeaderStatsTop5Genres": "Top 5 műfaj",
@@ -499,7 +499,7 @@
"LabelNumberOfEpisodes": "Epizódok száma",
"LabelOpenIDAdvancedPermsClaimDescription": "Az OpenID-igény neve, amely a felhasználói műveletekre vonatkozó haladó jogosultságokat tartalmazza az alkalmazáson belül, és amely a nem adminisztrátori szerepkörökre vonatkozik (<b>ha konfigurálva van</b>). Ha az igény hiányzik a válaszból, az ABS-hez való hozzáférés megtagadásra kerül. Ha egyetlen opció hiányzik, azt <code>false</code>-ként fogja kezelni. Győződj meg arról, hogy az identitásszolgáltató igénye megfelel a várt struktúrának:",
"LabelOpenIDClaims": "Hagyd üresen a következő opciókat, hogy letiltsd a haladó csoport- és jogosultság-hozzárendelést, ekkor automatikusan a Felhasználó’ csoport kerül hozzárendelésre.",
"LabelOpenIDGroupClaimDescription": "Az OpenID-igény neve, amely a felhasználó csoportjainak listáját tartalmazza. Általában <code>groups<code> néven hivatkoznak rá. <b>Ha konfigurálva van<b>, az alkalmazás automatikusan hozzárendeli a szerepköröket a felhasználó csoporttagságai alapján, feltéve, hogy ezek a csoportok az igényben kis- és nagybetűkre érzéketlenül admin, user vagy guest néven szerepelnek. Az igénynek egy listát kell tartalmaznia, és ha egy felhasználó több csoport tagja, az alkalmazás a legmagasabb szintű hozzáféréssel rendelkező szerepkört rendeli hozzá. Ha egyetlen csoport sem felel meg, a hozzáférés megtagadásra kerül.",
"LabelOpenIDGroupClaimDescription": "Az OpenID-igény neve, amely a felhasználó csoportjainak listáját tartalmazza. Általában <code>groups</code> néven hivatkoznak rá. <b>Ha konfigurálva van</b>, az alkalmazás automatikusan hozzárendeli a szerepköröket a felhasználó csoporttagságai alapján, feltéve, hogy ezek a csoportok az igényben kis- és nagybetűkre érzéketlenül admin, user vagy guest néven szerepelnek. Az igénynek egy listát kell tartalmaznia, és ha egy felhasználó több csoport tagja, az alkalmazás a legmagasabb szintű hozzáféréssel rendelkező szerepkört rendeli hozzá. Ha egyetlen csoport sem felel meg, a hozzáférés megtagadásra kerül.",
"LabelOpenRSSFeed": "RSS hírcsatorna megnyitása",
"LabelOverwrite": "Felülírás",
"LabelPaginationPageXOfY": "{0} oldal {1}-ból/ből",
@@ -643,8 +643,8 @@
"LabelStatsAuthors": "Szerző",
"LabelStatsBestDay": "Legjobb nap",
"LabelStatsDailyAverage": "Napi átlag",
"LabelStatsDays": "Napok",
"LabelStatsDaysListened": "Hallgatással töltött napok",
"LabelStatsDays": "Nap",
"LabelStatsDaysListened": "Hallgatással töltött nap",
"LabelStatsHours": "Órák",
"LabelStatsInARow": "egymás után",
"LabelStatsItemsFinished": "Befejezett elem",
+17 -17
View File
@@ -34,7 +34,7 @@
"ButtonEditChapters": "Modifica Capitoli",
"ButtonEditPodcast": "Modifica Podcast",
"ButtonEnable": "Abilita",
"ButtonFireAndFail": "Fire and Fail",
"ButtonFireAndFail": "Centro e fallimento",
"ButtonFireOnTest": "Fire onTest event",
"ButtonForceReScan": "Forza Re-Scan",
"ButtonFullPath": "Percorso Completo",
@@ -182,7 +182,7 @@
"HeaderPlaylist": "Playlist",
"HeaderPlaylistItems": "Elementi della playlist",
"HeaderPodcastsToAdd": "Podcasts da Aggiungere",
"HeaderPresets": "Presets",
"HeaderPresets": "Preimpostazioni",
"HeaderPreviewCover": "Anteprima Cover",
"HeaderRSSFeedGeneral": "Dettagli RSS",
"HeaderRSSFeedIsOpen": "RSS Feed è aperto",
@@ -275,7 +275,7 @@
"LabelBonus": "Bonus",
"LabelBooks": "Libri",
"LabelButtonText": "Buttone Testo",
"LabelByAuthor": "da {0}",
"LabelByAuthor": "di {0}",
"LabelChangePassword": "Cambia Password",
"LabelChannels": "Canali",
"LabelChapterCount": "{0} Capitoli",
@@ -306,7 +306,7 @@
"LabelCustomCronExpression": "Espressione Cron personalizzata:",
"LabelDatetime": "Data & Ora",
"LabelDays": "Giorni",
"LabelDeleteFromFileSystemCheckbox": "Elimina dal file system (togli la spunta per eliminarla solo dal DB)",
"LabelDeleteFromFileSystemCheckbox": "Elimina dal file system (despunta per rimuoverla solo dal database)",
"LabelDescription": "Descrizione",
"LabelDeselectAll": "Deseleziona Tutto",
"LabelDetectedPattern": "Trovato pattern:",
@@ -383,7 +383,7 @@
"LabelFolders": "Cartelle",
"LabelFontBold": "Grassetto",
"LabelFontBoldness": "Grassetto",
"LabelFontFamily": "Famiglia di caratteri",
"LabelFontFamily": "Famiglia caratteri",
"LabelFontItalic": "Corsivo",
"LabelFontScale": "Dimensione font",
"LabelFontStrikethrough": "Barrato",
@@ -436,9 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Nessuno {0}",
"LabelLibraryItem": "Elementi della biblioteca",
"LabelLibraryName": "Nome della biblioteca",
"LabelLibrarySortByProgress": "Progressi: Ultimi aggiornamenti",
"LabelLibrarySortByProgressFinished": "Progressi: Completati",
"LabelLibrarySortByProgressStarted": "Progressi: Iniziati",
"LabelLibrarySortByProgress": "Progresso: ultimo aggiornamento",
"LabelLibrarySortByProgressFinished": "Progresso: finito",
"LabelLibrarySortByProgressStarted": "Progresso: iniziato",
"LabelLimit": "Limiti",
"LabelLineSpacing": "Interlinea",
"LabelListenAgain": "Ascolta ancora",
@@ -497,7 +497,7 @@
"LabelNumberOfBooks": "Numero di libri",
"LabelNumberOfChapters": "Numero di capitoli:",
"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 amministrativi (<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>falso</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\".",
"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.",
"LabelOpenRSSFeed": "Apri RSS Feed",
@@ -530,7 +530,7 @@
"LabelPrimaryEbook": "Libro principale",
"LabelProgress": "Cominciati",
"LabelProvider": "Fornitore",
"LabelProviderAuthorizationValue": "Authorization Header Value",
"LabelProviderAuthorizationValue": "Valore intestazione di autorizzazione",
"LabelPubDate": "Data di pubblicazione",
"LabelPublishYear": "Anno di pubblicazione",
"LabelPublishedDate": "Pubblicati {0}",
@@ -588,8 +588,8 @@
"LabelSettingsBookshelfViewHelp": "Design con scaffali in legno",
"LabelSettingsChromecastSupport": "Supporto a Chromecast",
"LabelSettingsDateFormat": "Formato Data",
"LabelSettingsEnableWatcher": "Scansiona le librerie Automaticamente per trovare modifiche",
"LabelSettingsEnableWatcherForLibrary": "Scansiona la libreria Automaticamente per trovare modifiche",
"LabelSettingsEnableWatcher": "Controlla automaticamente le modifiche alle librerie",
"LabelSettingsEnableWatcherForLibrary": "Controlla automaticamente le modifiche alle librerie",
"LabelSettingsEnableWatcherHelp": "Abilita l'aggiunta/aggiornamento automatico degli elementi quando vengono rilevate modifiche ai file. *Richiede il riavvio del Server",
"LabelSettingsEpubsAllowScriptedContent": "Consenti contenuti con script negli epub",
"LabelSettingsEpubsAllowScriptedContentHelp": "Consenti ai file epub di eseguire script. Si consiglia di mantenere questa impostazione disabilitata a meno che non si ritenga attendibile l'origine dei file epub.",
@@ -674,7 +674,7 @@
"LabelTimeDurationXMinutes": "{0} minuti",
"LabelTimeDurationXSeconds": "{0} secondi",
"LabelTimeInMinutes": "Tempo in minuti",
"LabelTimeLeft": "{0} sinistra",
"LabelTimeLeft": "{0} rimasti",
"LabelTimeListened": "Tempo di Ascolto",
"LabelTimeListenedToday": "Tempo di Ascolto Oggi",
"LabelTimeRemaining": "{0} rimanente",
@@ -682,7 +682,7 @@
"LabelTitle": "Titolo",
"LabelToolsEmbedMetadata": "Incorpora Metadata",
"LabelToolsEmbedMetadataDescription": "Incorpora i metadati nei file audio, inclusi l'immagine di copertina e i capitoli.",
"LabelToolsM4bEncoder": "M4B Encoder",
"LabelToolsM4bEncoder": "Codificatore M4B",
"LabelToolsMakeM4b": "Crea un file M4B",
"LabelToolsMakeM4bDescription": "Genera un file audiolibro M4B con metadati incorporati, immagine di copertina e capitoli.",
"LabelToolsSplitM4b": "Converti M4B in MP3",
@@ -854,7 +854,7 @@
"MessageNoItems": "Nessun oggetto",
"MessageNoItemsFound": "Nessun oggetto trovato",
"MessageNoListeningSessions": "Nessuna sessione di ascolto",
"MessageNoLogs": "Nessun Log",
"MessageNoLogs": "Nessun rapporto",
"MessageNoMediaProgress": "Nessun progresso multimediale",
"MessageNoNotifications": "Nessuna notifica",
"MessageNoPodcastFeed": "Podcast non valido: nessun feed",
@@ -888,7 +888,7 @@
"MessageResetChaptersConfirm": "Sei sicuro di voler reimpostare i capitoli e annullare le modifiche ?",
"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.",
"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.",
"MessageScheduleLibraryScanNote": "Per la maggior parte degli utenti, si consiglia di lasciare questa funzione disabilitata e mantenere abilitata l'impostazione “Controlla automaticamente le modifiche nella libreria”: in questo modo verranno rilevate automaticamente le modifiche nelle cartelle della libreria. Abilita questa funzione se “Controlla automaticamente le modifiche nella libreria” non funziona con il tuo file system (come NFS).",
"MessageScheduleRunEveryWeekdayAtTime": "Esegui ogni {0} alle {1}",
"MessageSearchResultsFor": "cerca risultati per",
"MessageSelected": "{0} selezionati",
@@ -1109,7 +1109,7 @@
"ToastProgressIsNotBeingSynced": "L'avanzamento non è sincronizzato, riavviare la riproduzione",
"ToastProviderCreatedFailed": "Impossibile aggiungere il provider",
"ToastProviderCreatedSuccess": "Aggiunto nuovo provider",
"ToastProviderNameAndUrlRequired": "Nome e URL richiesti",
"ToastProviderNameAndUrlRequired": "Nome e Url richiesti",
"ToastProviderRemoveSuccess": "Provider rimosso",
"ToastRSSFeedCloseFailed": "Errore chiusura flusso RSS",
"ToastRSSFeedCloseSuccess": "Flusso RSS chiuso",
+1008 -25
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -57,7 +57,7 @@
"ButtonNextItemInQueue": "대기열의 다음 항목",
"ButtonOk": "확인",
"ButtonOpenFeed": "피드 열기",
"ButtonOpenManager": "오픈 매니저",
"ButtonOpenManager": "매니저 열기",
"ButtonPause": "일시정지",
"ButtonPlay": "재생",
"ButtonPlayAll": "모두 재생",
@@ -301,7 +301,7 @@
"LabelCoverProvider": "커버 제공자",
"LabelCreatedAt": "생성일",
"LabelCronExpression": "Cron Expression",
"LabelCurrent": "현재",
"LabelCurrent": "현재",
"LabelCurrently": "현재:",
"LabelCustomCronExpression": "사용자 정의 Cron 표현식:",
"LabelDatetime": "일시",
@@ -320,7 +320,7 @@
"LabelDownload": "다운로드",
"LabelDownloadNEpisodes": "{0}개 에피소드 다운로드",
"LabelDownloadable": "다운로드 가능",
"LabelDuration": "Duration",
"LabelDuration": "기간",
"LabelDurationComparisonExactMatch": "(정확히 일치)",
"LabelDurationComparisonLonger": "({0} 더 길음)",
"LabelDurationComparisonShorter": "({0} 더 짧음)",
@@ -436,7 +436,7 @@
"LabelLibraryFilterSublistEmpty": "{0} 없음",
"LabelLibraryItem": "라이브러리 항목",
"LabelLibraryName": "라이브러리 이름",
"LabelLibrarySortByProgress": "Progress: 마지막 업데이트",
"LabelLibrarySortByProgress": "진행상황: 마지막 업데이트",
"LabelLibrarySortByProgressFinished": "진행 상황: 완료",
"LabelLibrarySortByProgressStarted": "진행 상황: 시작됨",
"LabelLimit": "한계",
@@ -606,7 +606,7 @@
"LabelSettingsLibraryMarkAsFinishedWhen": "미디어 항목을 완료된 것으로 표시",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Continue Series의 이전 책 건너뛰기",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "시리즈 계속하기 홈페이지 선반에는 시리즈 중 아직 시작하지 않은 첫 번째 책이 표시됩니다. 시리즈 중 최소 한 권은 완료되었고 진행 중인 책은 없습니다. 이 설정을 활성화하면 시작하지 않은 첫 번째 책 대신 가장 늦게 완료된 책부터 시리즈가 이어집니다.",
"LabelSettingsParseSubtitles": "Parse subtitles",
"LabelSettingsParseSubtitles": "자막 파싱",
"LabelSettingsParseSubtitlesHelp": "오디오북 폴더 이름에서 자막을 추출합니다.<br>자막은 \" - \"로 구분해야 합니다.<br>즉, \"책 제목 - 여기에 자막이 있습니다\"에는 \"여기에 자막이 있습니다\"라는 자막이 있습니다.",
"LabelSettingsPreferMatchedMetadata": "일치하는 메타데이터를 선호",
"LabelSettingsPreferMatchedMetadataHelp": "빠른 매칭을 사용하면 매칭된 데이터가 항목 세부 정보보다 우선합니다. 기본적으로 빠른 매칭은 누락된 세부 정보만 채웁니다.",
@@ -616,7 +616,7 @@
"LabelSettingsSortingIgnorePrefixesHelp": "즉, 접두사 \"the\"의 경우 책 제목 \"The Book Title\"은 \"Book Title, The\"로 정렬됩니다.",
"LabelSettingsSquareBookCovers": "정사각형 책 표지를 사용",
"LabelSettingsSquareBookCoversHelp": "표준 1.6:1 책 표지보다 정사각형 표지를 사용하는 것을 선호합니다.",
"LabelSettingsStoreCoversWithItem": "품목과 함께 매장 커버",
"LabelSettingsStoreCoversWithItem": "항목에 있는 커버로 저장",
"LabelSettingsStoreCoversWithItemHelp": "기본적으로 표지는 /metadata/items에 저장됩니다. 이 설정을 활성화하면 표지가 라이브러리 항목 폴더에 저장됩니다. \"cover\"라는 이름의 파일 하나만 저장됩니다.",
"LabelSettingsStoreMetadataWithItem": "항목과 함께 메타데이터 저장",
"LabelSettingsStoreMetadataWithItemHelp": "기본적으로 메타데이터 파일은 /metadata/items에 저장되며 이 설정을 활성화하면 라이브러리 항목 폴더에 메타데이터 파일이 저장됩니다.",
@@ -965,11 +965,11 @@
"PlaceholderSearchEpisode": "에피소드 검색..",
"StatsAuthorsAdded": "작가가 추가되었습니다",
"StatsBooksAdded": "추가된 책",
"StatsBooksAdditional": "추가된 내용은 다음과 같습니다…",
"StatsBooksAdditional": "추가된 내용은 포함…",
"StatsBooksFinished": "책 완성",
"StatsBooksFinishedThisYear": "올해 읽은 책이 몇 권 있어요…",
"StatsBooksListenedTo": "듣는 책",
"StatsCollectionGrewTo": "의 책 컬렉션이 다음과 같이 늘어났습니다…",
"StatsCollectionGrewTo": "의 책 컬렉션이 늘어난…",
"StatsSessions": "세션",
"StatsSpentListening": "보낸 청취시간",
"StatsTopAuthor": "인기 작가",
+1
View File
@@ -1,5 +1,6 @@
{
"ButtonAdd": "Pridėti",
"ButtonAddApiKey": "Pridėti API raktą",
"ButtonAddChapters": "Pridėti skyrius",
"ButtonAddDevice": "Pridėti įrenginį",
"ButtonAddLibrary": "Pridėti Biblioteką",
+1
View File
@@ -0,0 +1 @@
{}
+12 -10
View File
@@ -2,7 +2,7 @@
"ButtonAdd": "Toevoegen",
"ButtonAddApiKey": "API Key toevoegen",
"ButtonAddChapters": "Hoofdstukken toevoegen",
"ButtonAddDevice": "Toestel toevoegen",
"ButtonAddDevice": "Apparaat toevoegen",
"ButtonAddLibrary": "Bibliotheek toevoegen",
"ButtonAddPodcasts": "Podcasts toevoegen",
"ButtonAddUser": "Gebruiker toevoegen",
@@ -139,7 +139,7 @@
"HeaderCustomMetadataProviders": "Aangepaste Metadata Providers",
"HeaderDetails": "Details",
"HeaderDownloadQueue": "Download-wachtrij",
"HeaderEbookFiles": "Ebook bestanden",
"HeaderEbookFiles": "E-book bestanden",
"HeaderEmail": "E-mail",
"HeaderEmailSettings": "E-mail instellingen",
"HeaderEpisodes": "Afleveringen",
@@ -275,7 +275,7 @@
"LabelBonus": "Bonus",
"LabelBooks": "Boeken",
"LabelButtonText": "Knop Tekst",
"LabelByAuthor": "Door {0}",
"LabelByAuthor": "door {0}",
"LabelChangePassword": "Wachtwoord wijzigen",
"LabelChannels": "Kanalen",
"LabelChapterCount": "{0} Hoofdstukken",
@@ -383,7 +383,7 @@
"LabelFolders": "Mappen",
"LabelFontBold": "Vetgedrukt",
"LabelFontBoldness": "Lettertype Dikte",
"LabelFontFamily": "Lettertypefamilie",
"LabelFontFamily": "Letterfamilie",
"LabelFontItalic": "Cursief",
"LabelFontScale": "Lettertype schaal",
"LabelFontStrikethrough": "Doorgestreept",
@@ -436,9 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Nee {0}",
"LabelLibraryItem": "Bibliotheekonderdeel",
"LabelLibraryName": "Bibliotheeknaam",
"LabelLibrarySortByProgress": "Voortuigang geüpdatet",
"LabelLibrarySortByProgressFinished": "Datum voltooid",
"LabelLibrarySortByProgressStarted": "Datum gestart",
"LabelLibrarySortByProgress": "Voortgang: Laatst geüpdatet",
"LabelLibrarySortByProgressFinished": "Voortgang: Voltooid",
"LabelLibrarySortByProgressStarted": "Voortgang: Gestart",
"LabelLimit": "Limiet",
"LabelLineSpacing": "Regelruimte",
"LabelListenAgain": "Opnieuw Beluisteren",
@@ -588,8 +588,8 @@
"LabelSettingsBookshelfViewHelp": "Skeumorphisch design met houten planken",
"LabelSettingsChromecastSupport": "Chromecast ondersteuning",
"LabelSettingsDateFormat": "Datumnotatie",
"LabelSettingsEnableWatcher": "Bibliotheken automatisch scannen op wijzigingen",
"LabelSettingsEnableWatcherForLibrary": "Bibliotheek automatisch scannen op wijzigingen",
"LabelSettingsEnableWatcher": "Bibliotheken automatisch monitoren op wijzigingen",
"LabelSettingsEnableWatcherForLibrary": "Bibliotheek automatisch monitoren op wijzigingen",
"LabelSettingsEnableWatcherHelp": "Zorgt voor het automatisch toevoegen/bijwerken van onderdelen als bestandswijzigingen worden gedetecteerd. *Vereist herstarten van server",
"LabelSettingsEpubsAllowScriptedContent": "Sta scripted content toe in epubs",
"LabelSettingsEpubsAllowScriptedContentHelp": "Sta toe dat epub-bestanden scripts uitvoeren. Het wordt aanbevolen om deze instelling uitgeschakeld te houden, tenzij u de bron van de epub-bestanden vertrouwt.",
@@ -888,7 +888,7 @@
"MessageResetChaptersConfirm": "Weet je zeker dat je de hoofdstukken wil resetten en de wijzigingen die je gemaakt hebt ongedaan wil maken?",
"MessageRestoreBackupConfirm": "Weet je zeker dat je wil herstellen met behulp van de back-up gemaakt op",
"MessageRestoreBackupWarning": "Een back-up herstellen zal de volledige database in /config en de omslagen in /metadata/items & /metadata/authors overschrijven.<br /><br />Back-ups wijzigen geen bestanden in je bibliotheekmappen. Als je de serverinstelling gebruikt om omslagen en metadata in je bibliotheekmappen te bewaren dan worden deze niet geback-upt of overschreven.<br /><br />Alle apparaten die je server gebruiken, worden automatisch ververst.",
"MessageScheduleLibraryScanNote": "Voor de meeste gebruikers is het raadzaam om deze functie uitgeschakeld te laten en de folder watcher-instelling ingeschakeld te houden. De folder watcher detecteert automatisch wijzigingen in uw bibliotheekmappen. De folder watcher werkt niet voor elk bestandssysteem (zoals NFS), dus geplande bibliotheekscans kunnen in plaats daarvan worden gebruikt.",
"MessageScheduleLibraryScanNote": "Voor de meeste gebruikers is het aangeraden om deze functie uitgeschakeld te laten en de \"Bibliotheek automatisch monitoren op wijzigingen\" instelling ingeschakeld te houden - deze detecteert automatisch wijzigingen in uw bibliotheekmappen. Activeer deze instelling als \"Bibliotheek automatisch monitoren op wijzigingen\" niet werkt voor uw bestandssysteem (zoals NFS).",
"MessageScheduleRunEveryWeekdayAtTime": "Elke {0} uitvoeren op {1}",
"MessageSearchResultsFor": "Zoekresultaten voor",
"MessageSelected": "{0} geselecteerd",
@@ -1026,6 +1026,8 @@
"ToastCollectionItemsAddFailed": "Item(s) toegevoegd aan collectie mislukt",
"ToastCollectionRemoveSuccess": "Collectie verwijderd",
"ToastCollectionUpdateSuccess": "Collectie bijgewerkt",
"ToastConnectionNotAvailable": "Verbinding niet beschikbaar. Gelieve later opnieuw te proberen",
"ToastCoverSearchFailed": "Omslag zoeken mislukt",
"ToastCoverUpdateFailed": "Omslag bijwerken mislukt",
"ToastDateTimeInvalidOrIncomplete": "Datum en tijd ongeldig of onvolledig",
"ToastDeleteFileFailed": "Bestand verwijderen mislukt",
+115 -10
View File
@@ -34,7 +34,7 @@
"ButtonEditChapters": "Rediger kapittel",
"ButtonEditPodcast": "Rediger podcast",
"ButtonEnable": "Aktiver",
"ButtonFireAndFail": "Kjør ved feil",
"ButtonFireAndFail": "Utfør og feil",
"ButtonFireOnTest": "Kjør onTest-kommando",
"ButtonForceReScan": "Tving skann",
"ButtonFullPath": "Full sti",
@@ -113,7 +113,7 @@
"ButtonUploadOPMLFile": "Last opp OPML fil",
"ButtonUserDelete": "Slett bruker {0}",
"ButtonUserEdit": "Rediger bruker {0}",
"ButtonViewAll": "Vis alle",
"ButtonViewAll": "Vis alt",
"ButtonYes": "Ja",
"ErrorUploadFetchMetadataAPI": "Feil ved innhenting av metadata",
"ErrorUploadFetchMetadataNoResults": "Kunne ikke hente metadata - forsøk å oppdatere tittel og/eller forfatter",
@@ -309,6 +309,7 @@
"LabelDeleteFromFileSystemCheckbox": "Slett fra filsystemet (fjern haken for kun å ta bort fra databasen)",
"LabelDescription": "Beskrivelse",
"LabelDeselectAll": "Fjern valg",
"LabelDetectedPattern": "Oppdaget mønster:",
"LabelDevice": "Enhet",
"LabelDeviceInfo": "Enhetsinformasjon",
"LabelDeviceIsAvailableTo": "Enheten er tilgjengelig for...",
@@ -377,11 +378,12 @@
"LabelFilterByUser": "Filtrer etter bruker",
"LabelFindEpisodes": "Finn episoder",
"LabelFinished": "Fullført",
"LabelFinishedDate": "Fullført {0}",
"LabelFolder": "Mappe",
"LabelFolders": "Mapper",
"LabelFontBold": "Fet",
"LabelFontBoldness": "Skrifttykkelse",
"LabelFontFamily": "Fontfamilie",
"LabelFontFamily": "Skriftfamilie",
"LabelFontItalic": "Kursiv",
"LabelFontScale": "Font størrelse",
"LabelFontStrikethrough": "Gjennomstreking",
@@ -434,7 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Ingen {0}",
"LabelLibraryItem": "Bibliotek enhet",
"LabelLibraryName": "Bibliotek navn",
"LabelLibrarySortByProgress": "Fremgang: Sist oppdatert",
"LabelLibrarySortByProgress": "Fremdrift: Sist oppdatert",
"LabelLibrarySortByProgressFinished": "Fremdrift: Fullført",
"LabelLibrarySortByProgressStarted": "Fremdrift: Startet",
"LabelLimit": "Begrensning",
"LabelLineSpacing": "Linjemellomrom",
"LabelListenAgain": "Lytt igjen",
@@ -443,8 +447,9 @@
"LabelLogLevelWarn": "Varsel",
"LabelLookForNewEpisodesAfterDate": "Se etter nye episoder etter denne datoen",
"LabelLowestPriority": "Laveste prioritet",
"LabelMatchConfidence": "Konfidens",
"LabelMatchExistingUsersBy": "Knytt sammen eksisterende brukere basert på",
"LabelMatchExistingUsersByDescription": "Brukes for å koble til eksisterende brukere. Når koblingen er i orden vil brukerne bli identifisert med en unik id fra SSO-tilbyderen.",
"LabelMatchExistingUsersByDescription": "Brukes for å koble til eksisterende brukere. Når koblingen er i orden vil brukerne bli identifisert med en unik id fra SSO-tilbyderen",
"LabelMaxEpisodesToDownload": "Maksimalt antall episoder som skal lastes ned. Bruk 0 for ubegrenset.",
"LabelMaxEpisodesToDownloadPerCheck": "Maksimalt antall nye episoder som skal lastes ned per sjekk",
"LabelMaxEpisodesToKeep": "Maksimalt antall episoder som skal beholdes",
@@ -453,7 +458,7 @@
"LabelMediaType": "Medie type",
"LabelMetaTag": "Meta tag",
"LabelMetaTags": "Meta tags",
"LabelMetadataOrderOfPrecedenceDescription": "Høyere prioritert kilder for metadata overstyrer laverer prioriterte kilder for metadata.",
"LabelMetadataOrderOfPrecedenceDescription": "Høyere prioritert kilder for metadata overstyrer laverer prioriterte kilder for metadata",
"LabelMetadataProvider": "Metadata Leverandør",
"LabelMinute": "Minutt",
"LabelMinutes": "Minutter",
@@ -472,7 +477,9 @@
"LabelNewestAuthors": "Nyeste forfattere",
"LabelNewestEpisodes": "Nyeste episoder",
"LabelNextBackupDate": "Neste sikkerhetskopi dato",
"LabelNextChapters": "Neste kapitler blir:",
"LabelNextScheduledRun": "Neste planlagte kjøring",
"LabelNoApiKeys": "Ingen API-nøkler",
"LabelNoCustomMetadataProviders": "Ingen egendefinerte tilbydere for metadata",
"LabelNoEpisodesSelected": "Ingen episoder valgt",
"LabelNotFinished": "Ikke fullført",
@@ -488,10 +495,11 @@
"LabelNotificationsMaxQueueSize": "Maksimalt antall varslinger i kø",
"LabelNotificationsMaxQueueSizeHelp": "Hendelser er begrenset til avfyre én gang per sekund. Hendelser blir ignorert om køen er full. Dette forhindrer overflod av varslinger.",
"LabelNumberOfBooks": "Antall bøker",
"LabelNumberOfChapters": "Antall kapitler:",
"LabelNumberOfEpisodes": "# episoder",
"LabelOpenIDAdvancedPermsClaimDescription": "Navnet på OpenID claim'et som inneholder avanserte tilganger for brukerhandlinger i applikasjonen som vil brukes for ikke-administratorroller (<b>hvis konfigurert</b>). Hvis claim'et mangler fra responsen, nektes tilgang til ABS. Hvis en enkelt opsjon mangler, blir behandlet som <code>false</code>. Påse at identitetstilbyderens claim stemmer overens med den forventede strukturen:",
"LabelOpenIDClaims": "La følge valg være tomme for å slå av avanserte gruppe og tillatelser. Gruppen \"Bruker\" vil da også automatisk legges til.",
"LabelOpenIDGroupClaimDescription": "Navn på OpenID-forespørsel som inneholder en lite over brukerens grupper. Vanligvis kalt <code>grupper</code>. <b>Om konfigurert</b>, vil applikasjonen tildele roller baseret på brukerens gruppemedlemsskaper, gitt disse grupper er navngitt (uten forbehold for store og små bokstaver) 'admin', 'user' eller 'guest' i forespørsel. Forespørselen burde inneholde en liste (og hvis brukeren tilhører flere grupper), applikasjonen vil tildele rolle med høyeste adgangsnivå. Hvis ingen grupper matcher vil adgang bli nektet.",
"LabelOpenIDGroupClaimDescription": "Navn på OpenID-forespørsel som inneholder en lite over brukerens grupper. Vanligvis kalt <code>grupper</code>. <b>Om konfigurert</b>, vil applikasjonen tildele roller baseret på brukerens gruppemedlemsskaper, gitt disse grupper er navngitt (uten forbehold for store og små bokstaver) 'admin', 'user' eller 'guest' i forespørsel. Forespørselen burde inneholde en liste (og hvis brukeren tilhører flere grupper), applikasjonen vil tildele rolle med høyeste adgangsnivå. Hvis ingen grupper matcher vil adgang bli nektet.",
"LabelOpenRSSFeed": "Åpne RSS Feed",
"LabelOverwrite": "Overskriv",
"LabelPaginationPageXOfY": "Side {0} av {1}",
@@ -509,6 +517,7 @@
"LabelPersonalYearReview": "Oppsummering av året ditt ({0})",
"LabelPhotoPathURL": "Bilde sti/URL",
"LabelPlayMethod": "Avspillingsmetode",
"LabelPlaybackRateIncrementDecrement": "Trinnstørrelse for økning/senking av avspillingshastighet",
"LabelPlayerChapterNumberMarker": "{0} av {1}",
"LabelPlaylists": "Spilleliste",
"LabelPodcast": "Podcast",
@@ -561,6 +570,7 @@
"LabelSelectAll": "Velg alt",
"LabelSelectAllEpisodes": "Velg alle episoder",
"LabelSelectEpisodesShowing": "Velg {0} episoder vist",
"LabelSelectUser": "Velg bruker",
"LabelSelectUsers": "Velg brukere",
"LabelSendEbookToDevice": "Send Ebok til...",
"LabelSequence": "Sekvens",
@@ -628,6 +638,7 @@
"LabelStartTime": "Start Tid",
"LabelStarted": "Startet",
"LabelStartedAt": "Startet",
"LabelStartedDate": "Startet {0}",
"LabelStatsAudioTracks": "Lydspor",
"LabelStatsAuthors": "Forfattere",
"LabelStatsBestDay": "Beste dag",
@@ -657,6 +668,7 @@
"LabelTheme": "Tema",
"LabelThemeDark": "Mørk",
"LabelThemeLight": "Lys",
"LabelThemeSepia": "Sepia",
"LabelTimeBase": "Tidsbase",
"LabelTimeDurationXHours": "{0} timer",
"LabelTimeDurationXMinutes": "{0} minutter",
@@ -725,24 +737,32 @@
"MessageAddToPlayerQueue": "Legg til i kø",
"MessageAppriseDescription": "For å bruke denne funksjonen trenger du en instans av <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> kjørende eller et API som håndterer disse forespørslene. <br />Apprise API URL skal være hele URL-en til varslingen, f.eks., hvis din API-instans er på <code>http://192.168.1.1:8337</code> så skal du bruke <code>http://192.168.1.1:8337/notify</code>.",
"MessageAsinCheck": "Påse at du bruker ASIN fra den riktige Audible-regionen, ikke Amazon.",
"MessageAuthenticationLegacyTokenWarning": "Eldre API-tokener vil bli fjernet i fremtiden. Bruk <a href=\"/config/api-keys\">API-nøkler</a> i stedet.",
"MessageAuthenticationOIDCChangesRestart": "Etter å ha lagret, start serveren din på nytt for at OIDC-endringene skal tre i kraft.",
"MessageAuthenticationSecurityMessage": "Autentisering er forbedret av sikkerhetshensyn. Alle brukere må logge inn på nytt.",
"MessageBackupsDescription": "Sikkerhetskopier inkluderer, brukerfremgang, detaljer om bibliotekgjenstander, tjener instillinger og bilder lagret under <code>/metadata/items</code> og <code>/metadata/authors</code>. Sikkerhetskopier <strong>vil ikke</strong> inkludere filer som er lagret i bibliotek mappene.",
"MessageBackupsLocationEditNote": "Viktig: Endring av mappen for sikkerhetskopi hverken endrer eller flytter eksisterende sikkerhetskopier!",
"MessageBackupsLocationNoEditNote": "NB: Mappen for sikkerhetskopi settes i en miljøvariabel og kan ikke endres her.",
"MessageBackupsLocationNoEditNote": "Viktig: Mappen for sikkerhetskopi satt i en miljøvariabel og kan ikke endres her.",
"MessageBackupsLocationPathEmpty": "Mappen for sikkerhetskopiering må angis",
"MessageBatchEditPopulateMapDetailsAllHelp": "Fyll aktiverte felt med data fra alle elementer. Felt med flere verdier blir slått sammen",
"MessageBatchEditPopulateMapDetailsItemHelp": "Fyll aktiverte kartdetaljfelt med data fra dette elementet",
"MessageBatchQuickMatchDescription": "Kjapt søk vil forsøke å legge til manglende omslag og metadata for de valgte gjenstandene. Aktiver dette valget for å tillate Kjapt søk til å overskrive eksisterende omslag og/eller metadata.",
"MessageBookshelfNoCollections": "Du har ikke laget noen samlinger ennå",
"MessageBookshelfNoCollectionsHelp": "Samlinger er offentlige. Alle brukere med tilgang til biblioteket kan se dem.",
"MessageBookshelfNoRSSFeeds": "Ingen RSS feed er åpen",
"MessageBookshelfNoResultsForFilter": "Ingen resultat for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Ingen resultater for søket",
"MessageBookshelfNoSeries": "Du har ingen serier",
"MessageBulkChapterPattern": "Hvor mange kapitler vil du legge til med dette nummereringsmønsteret?",
"MessageChapterEndIsAfter": "Kapittel slutt er etter slutt av lydboken",
"MessageChapterErrorFirstNotZero": "Første kapittel starter på 0",
"MessageChapterErrorStartGteDuration": "Feil start tid, må være mindre enn lengde på lydbok",
"MessageChapterErrorStartLtPrev": "Feil start tid, må være større eller det samme som forrige kapittel start tid",
"MessageChapterStartIsAfter": "Kapittel start er etter slutten av din lydbok",
"MessageChaptersNotFound": "Fant ikke kapitler",
"MessageCheckingCron": "Sjekker cron...",
"MessageConfirmCloseFeed": "Er du sikker på at du vil lukke denne feeden?",
"MessageConfirmDeleteApiKey": "Er du sikker på at du vil slette API-nøkkelen \"{0}\"?",
"MessageConfirmDeleteBackup": "Er du sikker på at du vil slette sikkerhetskopi for {0}?",
"MessageConfirmDeleteDevice": "Er du sikker på at du vil slette e-leser enheten \"{0}\"?",
"MessageConfirmDeleteFile": "Dette vil slette filen fra filsystemet. Er du sikker?",
@@ -761,7 +781,7 @@
"MessageConfirmMarkSeriesFinished": "Er du sikker på at du vil markere alle bøkene i serien som fullført?",
"MessageConfirmMarkSeriesNotFinished": "Er du sikker på at du vil markere alle bøkene i serien som ikke fullført?",
"MessageConfirmNotificationTestTrigger": "Utløs dette varselet med test-data?",
"MessageConfirmPurgeCache": "Tømming av mellomlagring vil slette hele mappen <code>/metadata/cache</code>. <br /><br />Er du sikker på at du du vil slette mappen?",
"MessageConfirmPurgeCache": "Tømming av mellomlagring vil slette hele mappen <code>/metadata/cache</code>. <br /><br />Er du sikker på at du vil slette mappen?",
"MessageConfirmPurgeItemsCache": "(Purge items cache) Dette vil sletter hele mappen <code>/metadata/cache/items</code>.<br />Er du sikker?",
"MessageConfirmQuickEmbed": "Advarsel! Rask innbygging av metadata tar ikke backup av lyd-filene først. Forsikre deg om at du har sikkerhetskopi av filene. <br><br> Fortsett?",
"MessageConfirmQuickMatchEpisodes": "Hurtig gjenkjenning av episoder overskriver detaljene hvis en match blir funnet. Kun episoder som ikke allerede er matchet blir oppdatert. Er du sikker?",
@@ -770,6 +790,7 @@
"MessageConfirmRemoveAuthor": "Er du sikker på at du vil fjerne forfatteren \"{0}\"?",
"MessageConfirmRemoveCollection": "Er du sikker på at du vil fjerne samling\"{0}\"?",
"MessageConfirmRemoveEpisode": "Er du sikker på at du vil fjerne episode \"{0}\"?",
"MessageConfirmRemoveEpisodeNote": "Merk: Dette sletter ikke lydfilen med mindre du slår på \"Hard delete file\"",
"MessageConfirmRemoveEpisodes": "Er du sikker på at du vil fjerne {0} episoder?",
"MessageConfirmRemoveListeningSessions": "Er du sikker på at du vil fjerne {0} lytte-sesjoner?",
"MessageConfirmRemoveMetadataFiles": "Er du sikker på at du vil fjerne alle metadata.{0}-filer i mappene for biblioteks-elementer?",
@@ -795,8 +816,11 @@
"MessageFeedURLWillBe": "Feed URL vil bli {0}",
"MessageFetching": "Henter...",
"MessageForceReScanDescription": "vil skanne alle filene igjen som en ny skann. Lyd fil ID3 tagger, OPF filer og tekstfiler vil bli skannet som nye.",
"MessageHeatmapListeningTimeTooltip": "<strong>{0} lytter</strong> på {1}",
"MessageHeatmapNoListeningSessions": "Ingen lytteøkter på {0}",
"MessageImportantNotice": "Viktig varsel!",
"MessageInsertChapterBelow": "Sett inn kapittel under",
"MessageInvalidAsin": "Ugyldig ASIN",
"MessageItemsSelected": "{0} Gjenstander valgt",
"MessageItemsUpdated": "{0} Gjenstander oppdatert",
"MessageJoinUsOn": "Følg oss nå",
@@ -842,6 +866,7 @@
"MessageNoTasksRunning": "Ingen oppgaver kjører",
"MessageNoUpdatesWereNecessary": "Ingen oppdatering var nødvendig",
"MessageNoUserPlaylists": "Du har ingen spillelister",
"MessageNoUserPlaylistsHelp": "Spillelister er private. Bare brukeren som oppretter dem kan se dem.",
"MessageNotYetImplemented": "Ikke implementert ennå",
"MessageOpmlPreviewNote": "PS: Dette er en forhåndvisning av en OPML-fil. Den faktiske podcast-tittelen hentes direkte fra RSS-feeden.",
"MessageOr": "eller",
@@ -864,8 +889,10 @@
"MessageRestoreBackupConfirm": "Er du sikker på at du vil gjenopprette sikkerhetskopien som var laget",
"MessageRestoreBackupWarning": "gjenoppretting av sikkerhetskopi vil overskrive hele databasen under /config og omslagsbilde under /metadata/items og /metadata/authors.<br /><br />Sikkerhetskopier endrer ikke noen filer under dine bibliotekmapper. Hvis du har aktivert tjenerinstillingen for å lagre omslagsbilder og metadata i bibliotekmapper så vil ikke de filene bli tatt sikkerhetskopi eller overskrevet.<br /><br />Alle klientene som bruker din tjener vil bli fornyet automatisk.",
"MessageScheduleLibraryScanNote": "For de fleste brukere er det anbefalt å la denne funksjonen være slått av, og la mappeovervåkeren stå på. Mappeovervåkeren oppdager automatisk endringer i biblioteksmappene. Mappeovervåkeren fungerer ikke med alle filsystemer (f.eks. NFS) og da kan planlagt skanning av bibliotekene brukes i steden for.",
"MessageScheduleRunEveryWeekdayAtTime": "Kjør hver {0} kl. {1}",
"MessageSearchResultsFor": "Søk resultat for",
"MessageSelected": "{0} valgt",
"MessageSeriesSequenceCannotContainSpaces": "Serienummer kan ikke inneholde mellomrom",
"MessageServerCouldNotBeReached": "Tjener kunne ikke bli nådd",
"MessageSetChaptersFromTracksDescription": "Sett kapitler ved å bruke hver lydfil som kapittel og kapitteltittel som lydfilnavnet",
"MessageShareExpirationWillBe": "Utløp vil være <strong>{0}</strong>",
@@ -886,6 +913,27 @@
"MessageTaskFailedToMergeAudioFiles": "Kunne ikke slå sammen lydfiler",
"MessageTaskFailedToMoveM4bFile": "Kunne ikke flytte M4B-fil",
"MessageTaskFailedToWriteMetadataFile": "Kunne ikke lagre metadata-fil",
"MessageTaskMatchingBooksInLibrary": "Samsvarende bøker i biblioteket \"{0}\"",
"MessageTaskNoFilesToScan": "Ingen filer å skanne",
"MessageTaskOpmlImport": "OPML-import",
"MessageTaskOpmlImportDescription": "Oppretter podkaster fra {0} RSS-feeder",
"MessageTaskOpmlImportFeed": "OPML-importfeed",
"MessageTaskOpmlImportFeedDescription": "Importerer RSS-feed \"{0}\"",
"MessageTaskOpmlImportFeedFailed": "Kunne ikke hente podcast-feed",
"MessageTaskOpmlImportFeedPodcastDescription": "Oppretter podkast \"{0}\"",
"MessageTaskOpmlImportFeedPodcastExists": "Podkast finnes allerede på stien",
"MessageTaskOpmlImportFeedPodcastFailed": "Misslykkes å opprette podcast",
"MessageTaskOpmlImportFinished": "La til {0} podkaster",
"MessageTaskOpmlParseFailed": "Klarte ikke å tolke OPML-fil",
"MessageTaskOpmlParseFastFail": "Ugyldig OPML-fil: <opml>-tagg ble ikke funnet ELLER en <outline>-tagg ble ikke funnet",
"MessageTaskOpmlParseNoneFound": "Fant ingen feeder i OPML-filen",
"MessageTaskScanItemsAdded": "{0} lagt til",
"MessageTaskScanItemsMissing": "{0} mangler",
"MessageTaskScanItemsUpdated": "{0} oppdatert",
"MessageTaskScanNoChangesNeeded": "Ingen endringer nødvendig",
"MessageTaskScanningFileChanges": "Skanner filendringer i \"{0}\"",
"MessageTaskScanningLibrary": "Skanner biblioteket \"{0}\"",
"MessageTaskTargetDirectoryNotWritable": "Målkatalogen er ikke skrivbar",
"MessageThinking": "Tenker...",
"MessageUploaderItemFailed": "Opplastning mislykkes",
"MessageUploaderItemSuccess": "Opplastning fullført!",
@@ -903,13 +951,43 @@
"NoteUploaderFoldersWithMediaFiles": "Mapper med mediefiler vil bli behandlet som separate bibliotekgjenstander.",
"NoteUploaderOnlyAudioFiles": "Om man laster opp kun lydfiler så vil hver lydfil bli behandlet som en separat lydbok.",
"NoteUploaderUnsupportedFiles": "Filer som ikke er støttet vil bli ignorert. Når man velger eller slipper en mappe, filer som ikke er en mappe vil bli ignorert.",
"NotificationOnBackupCompletedDescription": "Utløses når en sikkerhetskopi er fullført",
"NotificationOnBackupFailedDescription": "Utløses når en sikkerhetskopi mislykkes",
"NotificationOnEpisodeDownloadedDescription": "Utløses når en podkastepisode lastes ned automatisk",
"NotificationOnRSSFeedDisabledDescription": "Utløses når automatiske episodenedlastinger deaktiveres på grunn av for mange mislykkede forsøk",
"NotificationOnRSSFeedFailedDescription": "Utløses når RSS-feedforespørselen mislykkes for en automatisk episodenedlasting",
"NotificationOnTestDescription": "Hendelse for testing av varslingssystemet",
"PlaceholderBulkChapterInput": "Skriv inn kapitteltittel eller bruk nummerering (f.eks. 'Episode 1', 'Kapittel 10', '1.')",
"PlaceholderNewCollection": "Ny samlingsnavn",
"PlaceholderNewFolderPath": "Ny mappesti",
"PlaceholderNewPlaylist": "Ny spillelistenavn",
"PlaceholderSearch": "Søk..",
"PlaceholderSearchEpisode": "Søk episode..",
"StatsAuthorsAdded": "forfattere lagt til",
"StatsBooksAdded": "bøker lagt til",
"StatsBooksAdditional": "Noen av tilleggene inkluderer…",
"StatsBooksFinished": "bøker fullført",
"StatsBooksFinishedThisYear": "Noen bøker fullført i år…",
"StatsBooksListenedTo": "bøker lyttet til",
"StatsCollectionGrewTo": "Boksamlingen din vokste til…",
"StatsSessions": "økter",
"StatsSpentListening": "brukt på lytting",
"StatsTopAuthor": "BESTE FORFATTER",
"StatsTopAuthors": "BESTE FORFATTERE",
"StatsTopGenre": "BESTE SJANGER",
"StatsTopGenres": "BESTE SJANGRE",
"StatsTopMonth": "BESTE MÅNED",
"StatsTopNarrator": "BESTE FORTELLER",
"StatsTopNarrators": "BESTE FORTELLERE",
"StatsTotalDuration": "Med en total varighet på…",
"StatsYearInReview": "ÅRET SOM GIKK",
"ToastAccountUpdateSuccess": "Konto oppdatert",
"ToastAppriseUrlRequired": "Du må angi en Apprise-URL",
"ToastAsinRequired": "ASIN er påkrevd",
"ToastAuthorImageRemoveSuccess": "Forfatter bilde fjernet",
"ToastAuthorNotFound": "Fant ikke forfatter \"{0}\"",
"ToastAuthorRemoveSuccess": "Forfatter fjernet",
"ToastAuthorSearchNotFound": "Fant ikke forfatter",
"ToastAuthorUpdateMerged": "Forfatter slått sammen",
"ToastAuthorUpdateSuccess": "Forfatter oppdatert",
"ToastAuthorUpdateSuccessNoImageFound": "Forfatter oppdater (ingen bilde funnet)",
@@ -923,6 +1001,7 @@
"ToastBackupRestoreFailed": "Misslykkes å gjenopprette sikkerhetskopi",
"ToastBackupUploadFailed": "Misslykkes å laste opp sikkerhetskopi",
"ToastBackupUploadSuccess": "Sikkerhetskopi lastet opp",
"ToastBatchApplyDetailsToItemsSuccess": "Detaljer brukt på elementene",
"ToastBatchDeleteFailed": "Sletting feilet på utvalget",
"ToastBatchDeleteSuccess": "Sletting av samling utført",
"ToastBatchQuickMatchFailed": "Feil ved rask integrering av metadata!",
@@ -932,17 +1011,25 @@
"ToastBookmarkCreateFailed": "Misslykkes å opprette bokmerke",
"ToastBookmarkCreateSuccess": "Bokmerke lagt til",
"ToastBookmarkRemoveSuccess": "Bokmerke fjernet",
"ToastBulkChapterInvalidCount": "Skriv inn et tall mellom 1 og 150",
"ToastCachePurgeFailed": "Kunne ikke å slette mellomlager",
"ToastCachePurgeSuccess": "Mellomlager slettet",
"ToastChapterLocked": "Kapittelet er låst.",
"ToastChapterStartTimeAdjusted": "Kapittelstart ble justert med {0} sekunder",
"ToastChaptersAllLocked": "Alle kapitler er låst. Lås opp noen kapitler for å flytte tidene.",
"ToastChaptersHaveErrors": "Kapittel har feil",
"ToastChaptersInvalidShiftAmountLast": "Ugyldig forskyvningsverdi. Starttid for siste kapittel vil gå utover varigheten til denne lydboken.",
"ToastChaptersInvalidShiftAmountStart": "Ugyldig forskyvningsverdi. Det første kapitlet ville fått null eller negativ lengde og blitt overskrevet av det andre kapitlet. Øk starttiden til det andre kapitlet.",
"ToastChaptersMustHaveTitles": "Kapittel må ha titler",
"ToastChaptersRemoved": "Kapitler fjernet",
"ToastChaptersUpdated": "Kapitler oppdatert",
"ToastCollectionItemsAddFailed": "Feil med å legge til element(er)",
"ToastCollectionRemoveSuccess": "Samling fjernet",
"ToastCollectionUpdateSuccess": "samlingupdated",
"ToastConnectionNotAvailable": "Tilkobling er ikke tilgjengelig. Prøv igjen senere",
"ToastCoverSearchFailed": "Finner ikke bokomslag",
"ToastCoverUpdateFailed": "Oppdatering av bilde feilet",
"ToastDateTimeInvalidOrIncomplete": "Dato og klokkeslett er ugyldig eller ufullstendig",
"ToastDeleteFileFailed": "Kunne ikke slette fil",
"ToastDeleteFileSuccess": "Fil slettet",
"ToastDeviceAddFailed": "Kunne ikke legge til enhet",
@@ -955,6 +1042,9 @@
"ToastEpisodeDownloadQueueClearFailed": "Kunne ikke tømme køen",
"ToastEpisodeDownloadQueueClearSuccess": "Nedlastingskø for eposider tømt",
"ToastEpisodeUpdateSuccess": "{0} episoder oppdatert",
"ToastErrorCannotShare": "Kan ikke dele direkte på denne enheten",
"ToastFailedToCreate": "Kunne ikke opprette",
"ToastFailedToDelete": "Kunne ikke slette",
"ToastFailedToLoadData": "Kunne ikke laste inn data",
"ToastFailedToMatch": "Kunne ikke matche",
"ToastFailedToShare": "Deling feilet",
@@ -962,6 +1052,7 @@
"ToastInvalidImageUrl": "Ugyldig URL for bilde",
"ToastInvalidMaxEpisodesToDownload": "Ugyldig maksimalt antall for nedlasting av episoder",
"ToastInvalidUrl": "Ugyldig URL",
"ToastInvalidUrls": "Én eller flere URL-er er ugyldige",
"ToastItemCoverUpdateSuccess": "Omslag oppdatert",
"ToastItemDeletedFailed": "Kunne ikke slette element",
"ToastItemDeletedSuccess": "Element slettet",
@@ -986,6 +1077,7 @@
"ToastMustHaveAtLeastOnePath": "Påkrevd med minst én mappe",
"ToastNameEmailRequired": "Navn og e-post påkrevd",
"ToastNameRequired": "Navn er påkrevd",
"ToastNewApiKeyUserError": "Du må velge en bruker",
"ToastNewEpisodesFound": "{0} nye episoder funnet",
"ToastNewUserCreatedFailed": "Kunne ikke opprette konto: \"{0}\"",
"ToastNewUserCreatedSuccess": "Ny konto opprettet",
@@ -994,6 +1086,7 @@
"ToastNewUserTagError": "Velg minst en tag",
"ToastNewUserUsernameError": "Skriv inn brukernavn",
"ToastNoNewEpisodesFound": "Ingen nye episoder funnet",
"ToastNoRSSFeed": "Podkasten har ikke en RSS-feed",
"ToastNoUpdatesNecessary": "Ingen oppdateringer nødvendig",
"ToastNotificationCreateFailed": "Kunne ikke opprette varsling",
"ToastNotificationDeleteFailed": "Kunne ikke slette varsling",
@@ -1009,6 +1102,7 @@
"ToastPlaylistUpdateSuccess": "Spilleliste oppdatert",
"ToastPodcastCreateFailed": "Misslykkes å opprette podcast",
"ToastPodcastCreateSuccess": "Podcast opprettet",
"ToastPodcastEpisodeUpdated": "Episode oppdatert",
"ToastPodcastGetFeedFailed": "Kunne ikke hente podcast-feed",
"ToastPodcastNoEpisodesInFeed": "Ingen episoder funnet i RSS-feed",
"ToastPodcastNoRssFeed": "Podcast har ingen RSS-feed",
@@ -1033,6 +1127,7 @@
"ToastSelectAtLeastOneUser": "Velg minst én bruker",
"ToastSendEbookToDeviceFailed": "Misslykkes å sende ebok",
"ToastSendEbookToDeviceSuccess": "Ebok sendt til \"{0}\"",
"ToastSeriesSubmitFailedSameName": "Kan ikke legge til to serier med samme navn",
"ToastSeriesUpdateFailed": "Misslykkes å oppdatere serie",
"ToastSeriesUpdateSuccess": "Serie oppdatert",
"ToastServerSettingsUpdateSuccess": "Server-innstillinger oppdatert",
@@ -1051,10 +1146,20 @@
"ToastUnknownError": "Ukjent feil",
"ToastUnlinkOpenIdFailed": "Kunne ikke koble bruker fra OpenID",
"ToastUnlinkOpenIdSuccess": "Bruker koblet fra OpenID",
"ToastUploaderFilepathExistsError": "Filstien \"{0}\" finnes allerede på serveren",
"ToastUploaderItemExistsInSubdirectoryError": "Elementet \"{0}\" bruker en underkatalog av opplastingsstien.",
"ToastUserDeleteFailed": "Misslykkes å slette bruker",
"ToastUserDeleteSuccess": "Bruker slettet",
"ToastUserPasswordChangeSuccess": "Passord ble endret",
"ToastUserPasswordMismatch": "Passord må stemme overens",
"ToastUserPasswordMustChange": "Nytt passord kan ikke være identisk med gammelt passord",
"ToastUserRootRequireName": "Root-brukernavn er påkrevd"
"ToastUserRootRequireName": "Root-brukernavn er påkrevd",
"TooltipAddChapters": "Legg til kapittel(er)",
"TooltipAddOneSecond": "Legg til 1 sekund",
"TooltipAdjustChapterStart": "Klikk for å justere starttid",
"TooltipLockAllChapters": "Lås alle kapitler",
"TooltipLockChapter": "Lås kapittel (Shift+klikk for område)",
"TooltipSubtractOneSecond": "Trekk fra 1 sekund",
"TooltipUnlockAllChapters": "Lås opp alle kapitler",
"TooltipUnlockChapter": "Lås opp kapittel (Shift+klikk for område)"
}
+153 -30
View File
@@ -11,7 +11,7 @@
"ButtonApplyChapters": "Zatwierdź rozdziały",
"ButtonAuthors": "Autorzy",
"ButtonBack": "Wstecz",
"ButtonBatchEditPopulateFromExisting": "Powiel z poprzednich",
"ButtonBatchEditPopulateFromExisting": "Uzupełnij na podstawie istniejących",
"ButtonBatchEditPopulateMapDetails": "Powiel szczegóły mapy",
"ButtonBrowseForFolder": "Wyszukaj folder",
"ButtonCancel": "Anuluj",
@@ -55,7 +55,7 @@
"ButtonNext": "Następny",
"ButtonNextChapter": "Następny rozdział",
"ButtonNextItemInQueue": "Następny element w kolejce",
"ButtonOk": "Ok",
"ButtonOk": "OK",
"ButtonOpenFeed": "Otwórz feed",
"ButtonOpenManager": "Otwórz menadżera",
"ButtonPause": "Wstrzymaj",
@@ -96,7 +96,7 @@
"ButtonScrollRight": "Przewiń w prawo",
"ButtonSearch": "Szukaj",
"ButtonSelectFolderPath": "Wybierz ścieżkę folderu",
"ButtonSeries": "Serial",
"ButtonSeries": "Serie",
"ButtonSetChaptersFromTracks": "Ustawiaj rozdziały na podstawie utworów",
"ButtonShare": "Udostępnij",
"ButtonShiftTimes": "Przesunięcie czasowe",
@@ -127,7 +127,7 @@
"HeaderAudiobookTools": "Narzędzia do zarządzania audiobookami",
"HeaderAuthentication": "Uwierzytelnianie",
"HeaderBackups": "Kopie zapasowe",
"HeaderBulkChapterModal": "Dodaj wiele rozdziałów",
"HeaderBulkChapterModal": "Dodaj kilka rozdziałów",
"HeaderChangePassword": "Zmień hasło",
"HeaderChapters": "Rozdziały",
"HeaderChooseAFolder": "Wybierz folder",
@@ -233,8 +233,8 @@
"LabelAddToCollectionBatch": "Dodaj {0} książki do kolekcji",
"LabelAddToPlaylist": "Dodaj do playlisty",
"LabelAddToPlaylistBatch": "Dodaj {0} pozycji do playlisty",
"LabelAddedAt": "Dodano w",
"LabelAddedDate": "Dodano",
"LabelAddedAt": "Dodano",
"LabelAddedDate": "Dodano {0}",
"LabelAdminUsersOnly": "Tylko użytkownicy administracyjni",
"LabelAll": "Wszystkie",
"LabelAllEpisodesDownloaded": "Wszystkie odcinki pobrane",
@@ -275,7 +275,7 @@
"LabelBonus": "Bonus",
"LabelBooks": "Książki",
"LabelButtonText": "Tekst przycisku",
"LabelByAuthor": "autorstwa {0}",
"LabelByAuthor": "Autor {0}",
"LabelChangePassword": "Zmień hasło",
"LabelChannels": "Kanały",
"LabelChapterCount": "{0} rozdziałów",
@@ -286,7 +286,7 @@
"LabelClickToUseCurrentValue": "Kliknij by zastosować aktualną wartość",
"LabelClosePlayer": "Zamknij odtwarzacz",
"LabelCodec": "Kodek",
"LabelCollapseSeries": "Podsumuj serię",
"LabelCollapseSeries": "Zwiń serię",
"LabelCollapseSubSeries": "Zwiń podserie",
"LabelCollection": "Kolekcja",
"LabelCollections": "Kolekcje",
@@ -300,7 +300,7 @@
"LabelCoverImageURL": "URL okładki",
"LabelCoverProvider": "Dostawca okładki",
"LabelCreatedAt": "Utworzone",
"LabelCronExpression": "Wyrażenie CRON",
"LabelCronExpression": "Wyrażenie harmonogramowania zadań cron",
"LabelCurrent": "Aktualny",
"LabelCurrently": "Obecnie:",
"LabelCustomCronExpression": "Niestandardowe wyrażenie Cron:",
@@ -339,11 +339,11 @@
"LabelEnable": "Włącz",
"LabelEncodingBackupLocation": "Kopia zapasowa twoich oryginalnych plików audio będzie się znajdować w:",
"LabelEncodingChaptersNotEmbedded": "W audiobookach wielościeżkowych rozdziały nie są osadzone.",
"LabelEncodingClearItemCache": "Pamiętaj o okresowym czyszczeniu pamięci podręcznej elementów.",
"LabelEncodingClearItemCache": "Pamiętaj, aby okresowo czyścić pamięć podręczną elementów.",
"LabelEncodingFinishedM4B": "Ukończony plik M4B zostanie umieszczony w folderze audiobooka pod adresem:",
"LabelEncodingInfoEmbedded": "Metadane zostaną osadzone w ścieżkach audio w folderze z audiobookiem.",
"LabelEncodingStartedNavigation": "Po uruchomieniu zadania możesz opuścić tę stronę.",
"LabelEncodingTimeWarning": "Konwersja może potrwać do 30 minut.",
"LabelEncodingTimeWarning": "Kodowanie może potrwać do 30 minut.",
"LabelEncodingWarningAdvancedSettings": "Ostrzeżenie: Nie aktualizuj tych ustawień, jeśli nie jesteś zaznajomiony ze sposobem działania ffmpeg i opcji konwersji.",
"LabelEncodingWatcherDisabled": "Jeśli monitorowanie folderów jest wyłączone, należy ponownie przeskanować audiobooka.",
"LabelEnd": "Zakończ",
@@ -359,8 +359,8 @@
"LabelExample": "Przykład",
"LabelExpandSeries": "Rozwiń serie",
"LabelExpandSubSeries": "Rozwiń podserie",
"LabelExpired": "Przeterminowane",
"LabelExpiresAt": "Wygasa w",
"LabelExpired": "Wygasły",
"LabelExpiresAt": "Wygasa o",
"LabelExpiresInSeconds": "Wygasa za (sekund)",
"LabelExpiresNever": "Nigdy",
"LabelExplicit": "18+",
@@ -436,9 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Brak {0}",
"LabelLibraryItem": "Element biblioteki",
"LabelLibraryName": "Nazwa biblioteki",
"LabelLibrarySortByProgress": "Postęp: Ostatnio zaktualizowane",
"LabelLibrarySortByProgressFinished": "Postęp: Ukończone",
"LabelLibrarySortByProgressStarted": "Postęp: Rozpoczęte",
"LabelLibrarySortByProgress": "Postęp: Ostatnia aktualizacja",
"LabelLibrarySortByProgressFinished": "Postęp: Ukończony",
"LabelLibrarySortByProgressStarted": "Postęp: Rozpoczęty",
"LabelLimit": "Limit",
"LabelLineSpacing": "Odstęp między wierszami",
"LabelListenAgain": "Słuchaj ponownie",
@@ -450,7 +450,7 @@
"LabelMatchConfidence": "Zaufanie",
"LabelMatchExistingUsersBy": "Dopasuje istniejących użytkowników poprzez",
"LabelMatchExistingUsersByDescription": "Służy do łączenia istniejących użytkowników. Po połączeniu użytkownicy zostaną dopasowani za pomocą unikalnego identyfikatora od dostawcy SSO",
"LabelMaxEpisodesToDownload": "Maksymalna liczba odcinków do pobrania. Użyj 0, aby wyłączyć ograniczenie.",
"LabelMaxEpisodesToDownload": "Maksymalna liczba odcinków do pobrania. Użyj 0, aby uzyskać nieograniczoną liczbę.",
"LabelMaxEpisodesToDownloadPerCheck": "Maksymalna liczba nowych odcinków do pobrania na jedno sprawdzenie",
"LabelMaxEpisodesToKeep": "Maksymalna liczba odcinków do zachowania",
"LabelMaxEpisodesToKeepHelp": "Wartość 0 wyłącza maksymalny limit. Po automatycznym pobraniu nowego odcinka, najstarszy odcinek zostanie usunięty, jeśli masz ich więcej niż X. Spowoduje to usunięcie tylko 1 odcinka na nowe pobieranie.",
@@ -498,7 +498,8 @@
"LabelNumberOfChapters": "Liczba rozdziałów:",
"LabelNumberOfEpisodes": "# Odcinków",
"LabelOpenIDAdvancedPermsClaimDescription": "Nazwa deklaracji OpenID zawierającej zaawansowane uprawnienia do działań użytkownika w aplikacji, które będą miały zastosowanie do ról innych niż administracyjne (<b>jeśli skonfigurowano</b>). Jeśli deklaracja nie zostanie uwzględniona w odpowiedzi, dostęp do ABS zostanie zablokowany. Brak jednej opcji zostanie uznany za <code>fałsz</code>. Upewnij się, że deklaracja dostawcy tożsamości jest zgodna z oczekiwaną strukturą:",
"LabelOpenIDClaims": "Pozostaw poniższe opcje puste, aby wyłączyć zaawansowane przypisywanie grup i uprawnień. Automatycznie zostanie przypisana grupa „Użytkownik”.",
"LabelOpenIDClaims": "Pozostaw poniższe opcje puste, aby wyłączyć zaawansowane przypisywanie grup i uprawnień. Wówczas automatycznie zostanie przypisana grupa „Użytkownik”.",
"LabelOpenIDGroupClaimDescription": "Nazwa roszczenia OpenID zawierającego listę grup użytkownika. Powszechnie nazywane <code>grupami</code>. <b>Jeśli skonfigurowano</b>, aplikacja automatycznie przypisze role na podstawie przynależności użytkownika do grup, pod warunkiem, że te grupy są nazwane bez uwzględniania wielkości liter „admin”, „user” lub „guest” w roszczeniu. Roszczenie powinno zawierać listę, a jeśli użytkownik należy do wielu grup, aplikacja przypisze rolę odpowiadającą najwyższemu poziomowi dostępu. Jeśli żadna grupa nie będzie pasować, dostęp zostanie odrzucony.",
"LabelOpenRSSFeed": "Otwórz kanał RSS",
"LabelOverwrite": "Nadpisz",
"LabelPaginationPageXOfY": "Strona {0} z {1}",
@@ -573,11 +574,11 @@
"LabelSelectUsers": "Wybór użytkowników",
"LabelSendEbookToDevice": "Wyślij ebook do...",
"LabelSequence": "Kolejność",
"LabelSerial": "Seria",
"LabelSerial": "Numer serii",
"LabelSeries": "Serie",
"LabelSeriesName": "Nazwy serii",
"LabelSeriesProgress": "Postęp w serii",
"LabelServerLogLevel": "Poziom logowania servera",
"LabelServerLogLevel": "Poziom logów servera",
"LabelServerYearReview": "Podsumowanie serwera w roku ({0})",
"LabelSetEbookAsPrimary": "Ustaw jako pierwszy",
"LabelSetEbookAsSupplementary": "Ustaw jako dodatkowy",
@@ -621,7 +622,7 @@
"LabelSettingsStoreMetadataWithItemHelp": "Domyślnie metadane są przechowywane w folderze /metadata/items, włączenie tej opcji spowoduje, że okładka będzie przechowywana w folderze ksiązki. Tylko jedna okładka o nazwie pliku \"cover\" będzie przechowywana",
"LabelSettingsTimeFormat": "Format czasu",
"LabelShare": "Udostępnij",
"LabelShareDownloadableHelp": "Użytkownicy mogą przy pomocy linka ściągnąć archiwum ZIP pozycji biblioteki",
"LabelShareDownloadableHelp": "Zezwala użytkownikom z linkiem udostępniania na pobranie pliku zip elementu biblioteki.",
"LabelShareOpen": "Otwórz udział",
"LabelShareURL": "Link do udziału",
"LabelShowAll": "Pokaż wszystko",
@@ -660,11 +661,11 @@
"LabelTagsAccessibleToUser": "Tagi dostępne dla użytkownika",
"LabelTagsNotAccessibleToUser": "Znaczniki niedostępne dla użytkownika",
"LabelTasks": "Uruchomione zadania",
"LabelTextEditorBulletedList": "Lista punktowana",
"LabelTextEditorBulletedList": "Lista wypunktowana",
"LabelTextEditorLink": "Link",
"LabelTextEditorNumberedList": "Lista numerowana",
"LabelTextEditorUnlink": "Usuń link",
"LabelTheme": "Kompozycja",
"LabelTheme": "Motyw",
"LabelThemeDark": "Ciemny",
"LabelThemeLight": "Jasny",
"LabelThemeSepia": "Sepia",
@@ -686,13 +687,13 @@
"LabelToolsMakeM4bDescription": "Tworzy plik w formacie .M4B, który zawiera metadane, okładkę oraz rozdziały.",
"LabelToolsSplitM4b": "Podziel plik .M4B na pliki .MP3",
"LabelToolsSplitM4bDescription": "Podziel plik .M4B na pliki .MP3 na rozdziały z załączonymi metadanymi oraz okładką.",
"LabelTotalDuration": "TCałkowita długość",
"LabelTotalDuration": "Całkowita długość",
"LabelTotalTimeListened": "Całkowity czas odtwarzania",
"LabelTrackFromFilename": "Ścieżka z nazwy pliku",
"LabelTrackFromMetadata": "Ścieżka z metadanych",
"LabelTracks": "Ścieżki",
"LabelTracksMultiTrack": "Wielościeżkowy",
"LabelTracksNone": "Brak ścieżek",
"LabelTracksNone": "Brak utworów",
"LabelTracksSingleTrack": "Pojedyncza ścieżka",
"LabelTrailer": "Zwiastun",
"LabelType": "Typ",
@@ -709,7 +710,7 @@
"LabelUploaderDragAndDropFilesOnly": "Przeciągnij i upuść pliki",
"LabelUploaderDropFiles": "Puść pliki",
"LabelUploaderItemFetchMetadataHelp": "Automatycznie pobierz tytuł, autora i serie",
"LabelUseAdvancedOptions": "Opcje zaawansowane",
"LabelUseAdvancedOptions": "Użyj ustawień zaawansowanych",
"LabelUseChapterTrack": "Użyj ścieżki rozdziału",
"LabelUseFullTrack": "Użycie ścieżki rozdziału",
"LabelUseZeroForUnlimited": "Użyj 0, aby wyłączyć ograniczenia",
@@ -723,6 +724,7 @@
"LabelViewQueue": "Wyświetlaj kolejkę odtwarzania",
"LabelVolume": "Głośność",
"LabelWebRedirectURLsDescription": "Zezwól na te adresy URL w swoim dostawcy OAuth, aby umożliwić przekierowanie z powrotem do aplikacji internetowej po zalogowaniu:",
"LabelWebRedirectURLsSubfolder": "Podfolder dla adresów URL przekierowań",
"LabelWeekdaysToRun": "Dni tygodnia",
"LabelXBooks": "{0} książek",
"LabelXItems": "{0} elementów",
@@ -743,7 +745,7 @@
"MessageBackupsLocationNoEditNote": "Uwaga: Lokalizacja kopii zapasowej jest ustawiona poprzez zmienną środowiskową i nie może być tutaj zmieniona.",
"MessageBackupsLocationPathEmpty": "Ścieżka do kopii zapasowej nie może być pusta",
"MessageBatchEditPopulateMapDetailsAllHelp": "Wypełnij włączone pola danymi ze wszystkich elementów. Pola z wieloma wartościami zostaną scalone.",
"MessageBatchEditPopulateMapDetailsItemHelp": "Wypełnij pola szczegółów mapy włączonej danymi z tego elementu",
"MessageBatchEditPopulateMapDetailsItemHelp": "Wypełnia włączone pola szczegółów mapy danymi z tego elementu",
"MessageBatchQuickMatchDescription": "Quick Match będzie próbował dodać brakujące okładki i metadane dla wybranych elementów. Włącz poniższe opcje, aby umożliwić Quick Match nadpisanie istniejących okładek i/lub metadanych.",
"MessageBookshelfNoCollections": "Nie posiadasz jeszcze żadnych kolekcji",
"MessageBookshelfNoCollectionsHelp": "Kolekcje są publiczne. Wszyscy użytkownicy mający dostęp do biblioteki mogą je zobaczyć.",
@@ -915,10 +917,22 @@
"MessageTaskNoFilesToScan": "Brak plików do skanowania",
"MessageTaskOpmlImport": "Importuj OPML",
"MessageTaskOpmlImportDescription": "Tworzenie {0} podcastów z kanałów RSS",
"MessageTaskOpmlImportFeed": "Importuje plik OPML",
"MessageTaskOpmlImportFeedDescription": "Importowanie kanału RSS „{0}”",
"MessageTaskOpmlImportFeedFailed": "Nie udało się pobrać kanału podcastowego",
"MessageTaskOpmlImportFeedPodcastDescription": "Tworzenie podcastu \"{0}\"",
"MessageTaskOpmlImportFeedPodcastExists": "Podcast już istnieje pod podaną ścieżką",
"MessageTaskOpmlImportFeedPodcastFailed": "Nie udało się utworzyć podcastu",
"MessageTaskOpmlImportFinished": "Dodano {0} podcastów",
"MessageTaskOpmlParseFailed": "Błąd parsowania pliku OPML",
"MessageTaskOpmlParseFastFail": "Nieprawidłowy plik OPML. Nie znaleziono tagu <opml> LUB nie znaleziono tagu <outline>.",
"MessageTaskOpmlParseNoneFound": "Nie znaleziono kanałów w pliku OPML",
"MessageTaskScanItemsAdded": "Dodano {0}",
"MessageTaskScanItemsMissing": "Brakuje {0}",
"MessageTaskScanItemsUpdated": "Zaktualizowano {0}",
"MessageTaskScanNoChangesNeeded": "Brak zmian",
"MessageTaskScanningFileChanges": "Skanowanie zmian w plikach w „{0}”",
"MessageTaskScanningLibrary": "Skanowanie biblioteki \"{0}\"",
"MessageTaskTargetDirectoryNotWritable": "Brak prawa zapisu do folderu docelowego",
"MessageThinking": "Myślę...",
"MessageUploaderItemFailed": "Nie udało się przesłać",
@@ -937,6 +951,13 @@
"NoteUploaderFoldersWithMediaFiles": "Foldery z plikami multimedialnymi będą traktowane jako osobne elementy w bibliotece.",
"NoteUploaderOnlyAudioFiles": "Jeśli przesyłasz tylko pliki audio, każdy plik audio będzie traktowany jako osobny audiobook.",
"NoteUploaderUnsupportedFiles": "Nieobsługiwane pliki są ignorowane. Podczas dodawania folderu, inne pliki, które nie znajdują się w folderze elementu, są ignorowane.",
"NotificationOnBackupCompletedDescription": "Wyzwalane po zakończeniu tworzenia kopii zapasowej",
"NotificationOnBackupFailedDescription": "Wyzwalane w przypadku gdy stworzenie kopii zapasowej rzuci błąd",
"NotificationOnEpisodeDownloadedDescription": "Wyzwalane, gdy odcinek podcastu zostanie automatycznie pobrany",
"NotificationOnRSSFeedDisabledDescription": "Wyzwalane, gdy automatyczne pobieranie odcinków jest wyłączone z powodu zbyt wielu nieudanych prób",
"NotificationOnRSSFeedFailedDescription": "Wyzwalane, gdy żądanie kanału RSS dotyczące automatycznego pobrania odcinka nie powiedzie się",
"NotificationOnTestDescription": "Zdarzenie używane do testowania systemu powiadomień",
"PlaceholderBulkChapterInput": "Wpisz tytuł rozdziału lub użyj numeracji (np. „Odcinek 1”, „Rozdział 10”, „1.”)",
"PlaceholderNewCollection": "Nowa nazwa kolekcji",
"PlaceholderNewFolderPath": "Nowa ścieżka folderu",
"PlaceholderNewPlaylist": "Nowa nazwa playlisty",
@@ -944,6 +965,7 @@
"PlaceholderSearchEpisode": "Szukanie odcinka..",
"StatsAuthorsAdded": "dodano autorów",
"StatsBooksAdded": "dodano książki",
"StatsBooksAdditional": "Niektóre dodatki obejmują…",
"StatsBooksFinished": "ukończone książki",
"StatsBooksFinishedThisYear": "Wybrane książki ukończone w tym roku…",
"StatsBooksListenedTo": "książki wysłuchane",
@@ -957,26 +979,54 @@
"StatsTopMonth": "TOPOWY MIESIĄC",
"StatsTopNarrator": "TOPOWY NARRATOR",
"StatsTopNarrators": "TOPOWI NARRATORZY",
"StatsTotalDuration": "O sumarycznej długości…",
"StatsYearInReview": "PRZEGLĄD ROKU",
"ToastAccountUpdateSuccess": "Zaktualizowano konto",
"ToastAppriseUrlRequired": "Należy wprowadzić adres URL Apprise",
"ToastAsinRequired": "ASIN jest wymagany",
"ToastAuthorImageRemoveSuccess": "Zdjęcie autora usunięte",
"ToastAuthorNotFound": "Autor \"{0}\" nie został znaleziony",
"ToastAuthorRemoveSuccess": "Autor usunięty",
"ToastAuthorSearchNotFound": "Autor nie odnaleziony",
"ToastAuthorUpdateMerged": "Autor scalony",
"ToastAuthorUpdateSuccess": "Autor zaktualizowany",
"ToastAuthorUpdateSuccessNoImageFound": "Autor zaktualizowany (nie znaleziono obrazu)",
"ToastBackupAppliedSuccess": "Kopia zapasowa została przywrócona",
"ToastBackupCreateFailed": "Nie udało się utworzyć kopii zapasowej",
"ToastBackupCreateSuccess": "Utworzono kopię zapasową",
"ToastBackupDeleteFailed": "Nie udało się usunąć kopii zapasowej",
"ToastBackupDeleteSuccess": "Udało się usunąć kopie zapasowej",
"ToastBackupInvalidMaxKeep": "Nieprawidłowa ilość kopii zapasowych do przechowania",
"ToastBackupInvalidMaxSize": "Nieprawidłowy rozmiar maksymalny kopii zapasowej",
"ToastBackupRestoreFailed": "Nie udało się przywrócić kopii zapasowej",
"ToastBackupUploadFailed": "Nie udało się przesłać kopii zapasowej",
"ToastBackupUploadSuccess": "Kopia zapasowa została przesłana",
"ToastBatchUpdateFailed": "Aktualizacja wsadowa nie powiodła się",
"ToastBatchUpdateSuccess": "Aktualizacja wsadowa powiodła się",
"ToastBatchApplyDetailsToItemsSuccess": "Szczegóły zastosowane do elementów",
"ToastBatchDeleteFailed": "Usuwanie zbiorcze nie powiodło się",
"ToastBatchDeleteSuccess": "Usuwanie zbiorcze powiodło się",
"ToastBatchQuickMatchFailed": "Szybkie dopasowanie partii nie powiodło się!",
"ToastBatchQuickMatchStarted": "Rozpoczęto partię szybkiego dopasowania {0} książek!",
"ToastBatchUpdateFailed": "Aktualizacja zbiorcza nie powiodła się",
"ToastBatchUpdateSuccess": "Aktualizacja zbiorcza powiodła się",
"ToastBookmarkCreateFailed": "Nie udało się utworzyć zakładki",
"ToastBookmarkCreateSuccess": "Dodano zakładkę",
"ToastBookmarkRemoveSuccess": "Zakładka została usunięta",
"ToastBulkChapterInvalidCount": "Wprowadź liczbę z przedziału od 1 do 150",
"ToastCachePurgeFailed": "Nie udało się wyczyścić pamięci cache",
"ToastCachePurgeSuccess": "Wyczyszczono pamięć cache",
"ToastChapterLocked": "Rozdział jest zablokowany.",
"ToastChapterStartTimeAdjusted": "Czas rozpoczęcia rozdziału przesunięty o \"{0}\" sekund",
"ToastChaptersAllLocked": "Wszystkie rozdziały są zablokowane. Odblokuj edycję, aby użyć przesunięcia czasowego.",
"ToastChaptersHaveErrors": "Rozdziały posiadają błędy",
"ToastChaptersInvalidShiftAmountLast": "Niepoprawna wartość przesunięcia. Czas rozpoczęcia ostatniego rozdziału wykroczyłby poza długość tego audiobooka.",
"ToastChaptersInvalidShiftAmountStart": "Niepoprawna wartość przesunięcia. Pierwszy rozdział miałby długość mniejszą lub równą zeru oraz on zostałby nadpisany przez rozdział drugi. Ustaw późniejszy czas rozpoczęcia drugiego rozdziału.",
"ToastChaptersMustHaveTitles": "Rozdziały muszą posiadać tytuł",
"ToastChaptersRemoved": "Rozdziały usunięte",
"ToastChaptersUpdated": "Rozdziały zaktualizowane",
"ToastCollectionItemsAddFailed": "Dodanie elementów do kolekcji nie powiodło się",
"ToastCollectionRemoveSuccess": "Kolekcja usunięta",
"ToastCollectionUpdateSuccess": "Zaktualizowano kolekcję",
"ToastConnectionNotAvailable": "Brak połączenia. Spróbuj ponownie później",
"ToastCoverSearchFailed": "Nieudane wyszukiwanie okładki",
"ToastCoverUpdateFailed": "Nieudana aktualizacja okładki",
"ToastDateTimeInvalidOrIncomplete": "Niepoprawna data i czas",
@@ -987,17 +1037,31 @@
"ToastDeviceTestEmailFailed": "NIeudana próba wysłania testowego maila",
"ToastDeviceTestEmailSuccess": "Testowy email został wysłany",
"ToastEmailSettingsUpdateSuccess": "Ustawienia email zaktualizowane",
"ToastEncodeCancelFailed": "Nie udało się anulować kodowania",
"ToastEncodeCancelSucces": "Kodowanie anulowane",
"ToastEpisodeDownloadQueueClearFailed": "Nie udało się wyczyścić kolejki",
"ToastEpisodeDownloadQueueClearSuccess": "Wyczyszczono kolejkę epizodów do ściągnięcia",
"ToastEpisodeUpdateSuccess": "Zaktualizowano {0} odcinków",
"ToastErrorCannotShare": "Nie można udostępniać natywnie na tym urządzeniu.",
"ToastFailedToCreate": "Nie udało się utworzyć",
"ToastFailedToDelete": "Nie udało się usunąć",
"ToastFailedToLoadData": "Nie udało się załadować danych",
"ToastFailedToMatch": "Nie udało się dopasować",
"ToastFailedToShare": "Nie udało się udostępnić",
"ToastFailedToUpdate": "Nie udało się zaktualizować",
"ToastInvalidImageUrl": "Nieprawidłowy URL obrazu",
"ToastInvalidMaxEpisodesToDownload": "Nieprawidłowa maksymalna liczba odcinków do pobrania",
"ToastInvalidUrl": "Nieprawidłowy URL",
"ToastInvalidUrls": "Jeden lub więcej URL-i są nieprawidłowe",
"ToastItemCoverUpdateSuccess": "Zaktualizowano okładkę",
"ToastItemDeletedFailed": "Nie udało się usunąć elementu",
"ToastItemDeletedSuccess": "Element usunięty",
"ToastItemDetailsUpdateSuccess": "Zaktualizowano szczegóły",
"ToastItemMarkedAsFinishedFailed": "Nie udało się oznaczyć jako ukończone",
"ToastItemMarkedAsFinishedSuccess": "Pozycja oznaczona jako ukończona",
"ToastItemMarkedAsNotFinishedFailed": "Oznaczenie pozycji jako ukończonej nie powiodło się",
"ToastItemMarkedAsNotFinishedSuccess": "Pozycja oznaczona jako nieukończona",
"ToastItemUpdateSuccess": "Element zaktualizowany",
"ToastLibraryCreateFailed": "Nie udało się utworzyć biblioteki",
"ToastLibraryCreateSuccess": "Biblioteka \"{0}\" stworzona",
"ToastLibraryDeleteFailed": "Nie udało się usunąć biblioteki",
@@ -1006,6 +1070,10 @@
"ToastLibraryScanStarted": "Rozpoczęto skanowanie biblioteki",
"ToastLibraryUpdateSuccess": "Zaktualizowano \"{0}\" pozycji",
"ToastMatchAllAuthorsFailed": "Nie udało się dopasować wszystkich autorów",
"ToastMetadataFilesRemovedError": "Błąd podczas usuwania metadata.{0} plików",
"ToastMetadataFilesRemovedNoneFound": "Nie znaleziono metadata.{0} plików w bibliotece",
"ToastMetadataFilesRemovedNoneRemoved": "Nie usunięto żadnego metadata.{0} pliku",
"ToastMetadataFilesRemovedSuccess": "{0} metadata.{0} plików usunięto",
"ToastMustHaveAtLeastOnePath": "Musi mieć przynajmniej jedną ścieżkę",
"ToastNameEmailRequired": "Nazwa i email są wymagane",
"ToastNameRequired": "Imię jest wymagane",
@@ -1019,7 +1087,15 @@
"ToastNewUserUsernameError": "Wprowadź nazwę użytkownika",
"ToastNoNewEpisodesFound": "Nie znaleziono nowych odcinków",
"ToastNoRSSFeed": "Podcast nie posiada RSS Feed",
"ToastNoUpdatesNecessary": "Brak konieczności aktualizacji",
"ToastNotificationCreateFailed": "Nie udało się utworzyć powiadomienia",
"ToastNotificationDeleteFailed": "Nie udało się usunąć powiadomienia",
"ToastNotificationFailedMaximum": "Maks. ilość nieudanych prób musi być >= 0",
"ToastNotificationQueueMaximum": "Maksymalna liczba powiadomień w kolejce musi być >= 0",
"ToastNotificationSettingsUpdateSuccess": "Zaktualizowano ustawienia powiadomień",
"ToastNotificationTestTriggerFailed": "Nie udało się wywołać powiadomienia testowego",
"ToastNotificationTestTriggerSuccess": "Wyzwolono powiadomienie testowe",
"ToastNotificationUpdateSuccess": "Powiadomienie zaktualizowane",
"ToastPlaylistCreateFailed": "Nie udało się utworzyć playlisty",
"ToastPlaylistCreateSuccess": "Playlista utworzona",
"ToastPlaylistRemoveSuccess": "Playlista usunięta",
@@ -1027,16 +1103,63 @@
"ToastPodcastCreateFailed": "Nie udało się utworzyć podcastu",
"ToastPodcastCreateSuccess": "Podcast został pomyślnie utworzony",
"ToastPodcastEpisodeUpdated": "Zaktualizowano odcinki",
"ToastPodcastGetFeedFailed": "Nie udało się pobrać kanału podcastu",
"ToastPodcastNoEpisodesInFeed": "Nie znaleziono żadnych odcinków w kanale RSS",
"ToastPodcastNoRssFeed": "Podcast nie ma kanału RSS",
"ToastProgressIsNotBeingSynced": "Postęp nie jest synchronizowany, uruchom ponownie odtwarzanie",
"ToastProviderCreatedFailed": "Nie udało się dodać dostawcy",
"ToastProviderCreatedSuccess": "Dodano nowego dostawcę",
"ToastProviderNameAndUrlRequired": "Wymagane jest podanie nazwy i adresu URL",
"ToastProviderRemoveSuccess": "Dostawca usunięty",
"ToastRSSFeedCloseFailed": "Zamknięcie kanału RSS nie powiodło się",
"ToastRSSFeedCloseSuccess": "Zamknięcie kanału RSS powiodło się",
"ToastRemoveFailed": "Nie udało się usunąć",
"ToastRemoveItemFromCollectionFailed": "Nie udało się usunąć elementu z kolekcji",
"ToastRemoveItemFromCollectionSuccess": "Pozycja usunięta z kolekcji",
"ToastRemoveItemsWithIssuesFailed": "Nie udało się usunąć wadliwych elementów z biblioteki",
"ToastRemoveItemsWithIssuesSuccess": "Usunięto wadliwe elementy z biblioteki",
"ToastRenameFailed": "Nie udało się zmienić nazwy",
"ToastRescanFailed": "Ponowne skanowanie nie powiodło się dla {0}",
"ToastRescanRemoved": "Ponowne skanowanie powiodło się element został usunięty",
"ToastRescanUpToDate": "Ponowne skanowanie powiodło się element był aktualny",
"ToastRescanUpdated": "Ponowne skanowanie powiodło się element został zaktualizowany",
"ToastScanFailed": "Nie powiódł się skan elementu biblioteki",
"ToastSelectAtLeastOneUser": "Zaznacz co najmniej jednego użytkownika",
"ToastSendEbookToDeviceFailed": "Failed to Send Ebook to device",
"ToastSendEbookToDeviceSuccess": "Ebook wysłany na urządzenie \"{0}\"",
"ToastSeriesSubmitFailedSameName": "Nie można dodać dwóch serii pod tą samą nazwą",
"ToastSeriesUpdateFailed": "Aktualizacja serii nie powiodła się",
"ToastSeriesUpdateSuccess": "Aktualizacja serii powiodła się",
"ToastServerSettingsUpdateSuccess": "Zaktualizowano ustawienia serwera",
"ToastSessionCloseFailed": "Nie udało się zamknąć sesji",
"ToastSessionDeleteFailed": "Nie udało się usunąć sesji",
"ToastSessionDeleteSuccess": "Sesja usunięta",
"ToastSleepTimerDone": "Słodkich snów... zZzzZz",
"ToastSlugMustChange": "Slug zawiera nieprawidłowe znaki",
"ToastSlugRequired": "Slug jest wymagany",
"ToastSocketConnected": "Nawiązano połączenie z serwerem",
"ToastSocketDisconnected": "Połączenie z serwerem zostało zamknięte",
"ToastSocketFailedToConnect": "Poączenie z serwerem nie powiodło się",
"ToastSortingPrefixesEmptyError": "Musi mieć co najmniej 1 prefiks sortowania",
"ToastSortingPrefixesUpdateSuccess": "Zaktualizowano prefiksy sortowania ({0} elementów)",
"ToastTitleRequired": "Tytuł jest wymagany",
"ToastUnknownError": "Nieznany błąd",
"ToastUnlinkOpenIdFailed": "Nie udało się odpiąć użytkownika z OpenID",
"ToastUnlinkOpenIdSuccess": "Użytkownik odpięty z OpenID",
"ToastUploaderFilepathExistsError": "Ścieżka \"{0}\" już istnieje na serwerze",
"ToastUploaderItemExistsInSubdirectoryError": "Element \"{0}\" używa podkatalogu ścieżki przesyłania.",
"ToastUserDeleteFailed": "Nie udało się usunąć użytkownika",
"ToastUserDeleteSuccess": "Użytkownik usunięty"
"ToastUserDeleteSuccess": "Użytkownik usunięty",
"ToastUserPasswordChangeSuccess": "Hasło zostało pomyślnie zmienione",
"ToastUserPasswordMismatch": "Hasła nie są zgodne",
"ToastUserPasswordMustChange": "Nowe hasło nie może być takie samo jak stare hasło",
"ToastUserRootRequireName": "Należy wprowadzić nazwę użytkownika root",
"TooltipAddChapters": "Dodaj rozdział(y)",
"TooltipAddOneSecond": "Dodaj sekundę",
"TooltipAdjustChapterStart": "Kliknij, aby skorygować czas początkowy",
"TooltipLockAllChapters": "Zablokuj wszystkie rozdziały",
"TooltipLockChapter": "Zablokuj rozdział (przytrzymaj Shift i kliknij, aby zaznaczyć zakres)",
"TooltipSubtractOneSecond": "Odejmij sekundę",
"TooltipUnlockAllChapters": "Odblokuj wszystkie rozdziały",
"TooltipUnlockChapter": "Odblokuj rozdział (przytrzymaj Shift i kliknij, aby zaznaczyć zakres)"
}
+3 -3
View File
@@ -383,7 +383,7 @@
"LabelFolders": "Pastas",
"LabelFontBold": "Negrito",
"LabelFontBoldness": "Intensidade do negrito",
"LabelFontFamily": "Família de fonte",
"LabelFontFamily": "Família de fontes",
"LabelFontItalic": "Itálico",
"LabelFontScale": "Escala de fonte",
"LabelFontStrikethrough": "Tachado",
@@ -436,8 +436,8 @@
"LabelLibraryFilterSublistEmpty": "Sem {0}",
"LabelLibraryItem": "Item da Biblioteca",
"LabelLibraryName": "Nome da Biblioteca",
"LabelLibrarySortByProgress": "Última Atualização",
"LabelLibrarySortByProgressFinished": "Concluído",
"LabelLibrarySortByProgress": "Progresso: Ultima Atualização",
"LabelLibrarySortByProgressFinished": "Progresso: Terminado",
"LabelLibrarySortByProgressStarted": "Progresso: Iniciado",
"LabelLimit": "Limite",
"LabelLineSpacing": "Espaçamento entre linhas",
+444 -3
View File
@@ -11,140 +11,581 @@
"ButtonApplyChapters": "Aplică Capitole",
"ButtonAuthors": "Autori",
"ButtonBack": "Înapoi",
"ButtonBatchEditPopulateFromExisting": "Populează din existente",
"ButtonBatchEditPopulateMapDetails": "Populează detaliile hărții",
"ButtonBrowseForFolder": "Caută un dosar",
"ButtonCancel": "Anulează",
"ButtonCancelEncode": "Anulare codificare",
"ButtonChangeRootPassword": "Schimbare parolă de root",
"ButtonCheckAndDownloadNewEpisodes": "Verifică și descarcă episoade noi",
"ButtonChooseAFolder": "Alege un dosar",
"ButtonChooseFiles": "Alege fișiere",
"ButtonClearFilter": "Șterge filtrul",
"ButtonClose": "Închide",
"ButtonCloseFeed": "Închide sursa",
"ButtonCloseSession": "Închide Sesiunea Curentă",
"ButtonCollections": "Colecții",
"ButtonConfigureScanner": "Configurare scaner",
"ButtonCreate": "Creează",
"ButtonCreateBackup": "Creează backup",
"ButtonDelete": "Șterge",
"ButtonDownloadQueue": "Coadă",
"ButtonEdit": "Editare",
"ButtonEditChapters": "Editare capitole",
"ButtonEditPodcast": "Editare podcast",
"ButtonEnable": "Activează",
"ButtonForceReScan": "Forțează rescanare",
"ButtonFullPath": "Calea completă",
"ButtonHide": "Ascunde",
"ButtonHome": "Acasă",
"ButtonIssues": "Erori",
"ButtonIssues": "Probleme",
"ButtonJumpBackward": "Sari înapoi",
"ButtonJumpForward": "Sari înainte",
"ButtonLatest": "Noutăți",
"ButtonLibrary": "Bibliotecă",
"ButtonLogout": "Deconectare",
"ButtonLookup": "Căutare",
"ButtonManageTracks": "Gestionează pista",
"ButtonMapChapterTitles": "Maparea titlurilor capitolelor",
"ButtonMatchAllAuthors": "Potriviește toți autorii",
"ButtonMatchBooks": "Potrivește Cărți",
"ButtonNevermind": "Anulează",
"ButtonNext": "Următorul",
"ButtonNextChapter": "Următorul Capitol",
"ButtonNextItemInQueue": "Următorul Articol în Coadă",
"ButtonOk": "OK",
"ButtonOpenFeed": "Vezi noutățile",
"ButtonOpenManager": "Deschide Managerul",
"ButtonPause": "Pauză",
"ButtonPlay": "Redă",
"ButtonPlayAll": "Redă tot",
"ButtonPlaying": "Redare",
"ButtonPlaylists": "Liste",
"ButtonPrevious": "Anterior",
"ButtonPreviousChapter": "Capitolul Anterior",
"ButtonProbeAudioFile": "Analizare Fișier Audio",
"ButtonPurgeAllCache": "Golire Cache Completă",
"ButtonPurgeItemsCache": "Golire Cache Articole",
"ButtonQueueAddItem": "Adaugă la Coadă",
"ButtonQueueRemoveItem": "Sterge din Coadă",
"ButtonQuickEmbed": "Încorporare Rapidă",
"ButtonQuickEmbedMetadata": "Metadate pentru Încorporare Rapidă",
"ButtonQuickMatch": "Potrivire Rapidă",
"ButtonReScan": "Rescanare",
"ButtonRead": "Citește",
"ButtonReadLess": "Mai puțin",
"ButtonReadLess": "Citește Mai Puțin",
"ButtonReadMore": "Afișează mai mult",
"ButtonRefresh": "Reîmprospătare",
"ButtonRemove": "Elimină",
"ButtonRemoveAll": "Eliminați Tot",
"ButtonRemoveAllLibraryItems": "Ștergerea tuturor Articolelor din Librărie",
"ButtonRemoveFromContinueListening": "Ștergere din \"Continuă să Asculți\"",
"ButtonRemoveFromContinueReading": "Ștergere din \"Continuă să citești\"",
"ButtonRemoveSeriesFromContinueSeries": "Ștergere Serie din \"Continuă Seria\"",
"ButtonReset": "Resetează",
"ButtonResetToDefault": "Resetează la valorile implicite",
"ButtonRestore": "Restaurare",
"ButtonSave": "Salvează",
"ButtonSaveAndClose": "Salvează și Închide",
"ButtonSaveTracklist": "Salvare Pistă",
"ButtonScan": "Scanează",
"ButtonScanLibrary": "Scanează Librăria",
"ButtonScrollLeft": "Derulează spre stânga",
"ButtonScrollRight": "Derulează spre Dreapta",
"ButtonSearch": "Caută",
"ButtonSelectFolderPath": "Selectează Calea către Dosar",
"ButtonSeries": "Serii",
"ButtonSetChaptersFromTracks": "Setează capitole din piste",
"ButtonShare": "Distribuie",
"ButtonShiftTimes": "Aliniează timpi",
"ButtonShow": "Arată",
"ButtonStartM4BEncode": "Începe Codarea M4B",
"ButtonStartMetadataEmbed": "Începe Încorporarea Metadatelor",
"ButtonStats": "Statistici",
"ButtonSubmit": "Trimite",
"ButtonTest": "Testează",
"ButtonUnlinkOpenId": "Deconectare OpenID",
"ButtonUpload": "Încarcă",
"ButtonUploadBackup": "Încarcă Backup",
"ButtonUploadCover": "Încarcă Copertă",
"ButtonUploadOPMLFile": "Încarcă Fișier OPML",
"ButtonUserDelete": "Șterge userul {0}",
"ButtonUserEdit": "Editează userul {0}",
"ButtonViewAll": "Vizualizează tot",
"ButtonYes": "Da",
"ErrorUploadFetchMetadataAPI": "Eroare în descărcarea metadatelor",
"ErrorUploadFetchMetadataNoResults": "Nu s-au putut prelua metadatele - încearcă să editezi titlul și/sau autorul",
"ErrorUploadLacksTitle": "Trebuie să aibă un titlu",
"HeaderAccount": "Cont",
"HeaderAddCustomMetadataProvider": "Adaugă Furnizor de Metadate Personalizat",
"HeaderAdvanced": "Avansat",
"HeaderApiKeys": "Chei API",
"HeaderAppriseNotificationSettings": "Setări Notificări Apprise",
"HeaderAudioTracks": "Înregistrări audio",
"HeaderAudiobookTools": "Instrumente pentru Gestionarea Fișierelor Audiobook",
"HeaderAuthentication": "Autentificare",
"HeaderBackups": "Copii de siguranță",
"HeaderBulkChapterModal": "Adaugă Multiple Capitole",
"HeaderChangePassword": "Schimbă Parola",
"HeaderChapters": "Capitole",
"HeaderChooseAFolder": "Alege Dosar",
"HeaderCollection": "Colecție",
"HeaderCollectionItems": "Conținutul colecției",
"HeaderCover": "Copertă",
"HeaderCurrentDownloads": "Descărcări Curente",
"HeaderCustomMessageOnLogin": "Mesaj Personalizat la Autentificare",
"HeaderCustomMetadataProviders": "Furnizor de Metadate Personalizat",
"HeaderDetails": "Detalii",
"HeaderDownloadQueue": "Coadă de Descărcare",
"HeaderEbookFiles": "Ebook-uri",
"HeaderEmail": "Email",
"HeaderEmailSettings": "Setări Email",
"HeaderEpisodes": "Episoade",
"HeaderEreaderDevices": "Dispozitive eReader",
"HeaderEreaderSettings": "Setări eReader",
"HeaderFiles": "Fișiere",
"HeaderFindChapters": "Caută Capitol",
"HeaderIgnoredFiles": "Fișiere Ignorate",
"HeaderItemFiles": "Fișiere Articol",
"HeaderLastListeningSession": "Ultima Sesiune de Ascultare",
"HeaderLatestEpisodes": "Episoade recente",
"HeaderLibraries": "Biblioteci",
"HeaderLibraryFiles": "Fișiere in Librărie",
"HeaderLibraryStats": "Statistici Librărie",
"HeaderListeningSessions": "Sesiuni de Ascultare",
"HeaderListeningStats": "Statistici Ascultare",
"HeaderLogin": "Autentifică",
"HeaderLogs": "Loguri",
"HeaderManageGenres": "Gestionează Genuri",
"HeaderManageTags": "Gestionează Etichete",
"HeaderMapDetails": "Detaliile Hărții",
"HeaderMatch": "Potrivește",
"HeaderMetadataOrderOfPrecedence": "Prioritatea Metadatelor",
"HeaderMetadataToEmbed": "Metadate pentru Încorporare",
"HeaderNewAccount": "Cont nou",
"HeaderNewApiKey": "Cheie API Nouă",
"HeaderNewLibrary": "Librărie Nouă",
"HeaderNotificationCreate": "Creează Notificare",
"HeaderNotificationUpdate": "Actualizare Notificare",
"HeaderNotifications": "Notificări",
"HeaderOpenIDConnectAuthentication": "Autentificare prin OpenID",
"HeaderOpenListeningSessions": "Deschide Sesiuni de Ascultare",
"HeaderOpenRSSFeed": "Deschide flux RSS",
"HeaderOtherFiles": "Alte Fișiere",
"HeaderPasswordAuthentication": "Autentificare cu Parolă",
"HeaderPermissions": "Permisiuni",
"HeaderPlayerQueue": "Coadă Player",
"HeaderPlayerSettings": "Setări Player",
"HeaderPlaylist": "Listă de redare",
"HeaderPlaylistItems": "Conținut listă",
"HeaderPodcastsToAdd": "Podcast de Adăugat",
"HeaderPresets": "Presetări",
"HeaderPreviewCover": "Previzualizare Copertă",
"HeaderRSSFeedGeneral": "Date RSS",
"HeaderRSSFeedIsOpen": "RSS activ",
"HeaderRSSFeeds": "Fluxuri RSS",
"HeaderRemoveEpisode": "Elimină Episod",
"HeaderRemoveEpisodes": "Elimină {0} Episoade",
"HeaderSavedMediaProgress": "Progres Media Salvat",
"HeaderSchedule": "Planifică",
"HeaderScheduleEpisodeDownloads": "Planifică Descărcare Automată a Episoadelor",
"HeaderScheduleLibraryScans": "Planifică Scanarea Automată a Librăriei",
"HeaderSession": "Sesiuni",
"HeaderSetBackupSchedule": "Planifică Backup",
"HeaderSettings": "Setări",
"HeaderSettingsDisplay": "Afișaj",
"HeaderSettingsExperimental": "Caracteristici Experimentale",
"HeaderSettingsGeneral": "General",
"HeaderSettingsScanner": "Scaner",
"HeaderSettingsSecurity": "Securitate",
"HeaderSettingsWebClient": "Client Web",
"HeaderSleepTimer": "Timer de somn",
"HeaderStatsLargestItems": "Cele mai mari articole",
"HeaderStatsLongestItems": "Cele mai lungi articole (ore)",
"HeaderStatsMinutesListeningChart": "Minute ascultate (ultimele 7 zile)",
"HeaderStatsRecentSessions": "Sesiuni recente",
"HeaderStatsTop10Authors": "Top 10 Autori",
"HeaderStatsTop5Genres": "Top 5 Genuri",
"HeaderTableOfContents": "Cuprins",
"HeaderTools": "Unelte",
"HeaderUpdateAccount": "Actualizare Cont",
"HeaderUpdateApiKey": "Actualizare Cheie API",
"HeaderUpdateAuthor": "Actualizare Autor",
"HeaderUpdateDetails": "Actualizare Detalii",
"HeaderUpdateLibrary": "Actualizare Librărie",
"HeaderUsers": "Utilizatori",
"HeaderYearReview": "Trecere în revistă a anului {0}",
"HeaderYourStats": "Progresul tău",
"LabelAbridged": "Abreviat",
"LabelAbridgedChecked": "Abreviat (verificat)",
"LabelAbridgedUnchecked": "Neprescurtat (neverificat)",
"LabelAccessibleBy": "Accesibil prin",
"LabelAccountType": "Tip de Cont",
"LabelAccountTypeAdmin": "Administrator",
"LabelAccountTypeGuest": "Oaspete",
"LabelAccountTypeUser": "Utilizator",
"LabelActivities": "Activități",
"LabelActivity": "Activitate",
"LabelAddToCollection": "Adaugă la Colecție",
"LabelAddToCollectionBatch": "Adaugare {0} Cărți la Colecție",
"LabelAddToPlaylist": "Adaugă în listă",
"LabelAddToPlaylistBatch": "Adaugare {0} Articole la Listă",
"LabelAddedAt": "Adăugat la",
"LabelAddedDate": "Adăugat {0}",
"LabelAdminUsersOnly": "Doar Administratori",
"LabelAll": "Toate",
"LabelAllEpisodesDownloaded": "Toate episoadele descărcate",
"LabelAllUsers": "Toți Utilizatorii",
"LabelAllUsersExcludingGuests": "Toți utilizatorii cu excepția oaspeților",
"LabelAllUsersIncludingGuests": "Toți utilizatorii inclusiv oaspeții",
"LabelAlreadyInYourLibrary": "Deja în bibliotecă",
"LabelApiKeyCreated": "Cheia API \"{0}\" creată cu succes.",
"LabelApiKeyCreatedDescription": "Copiază cheia API acum deoarece nu va mai fi disponibilă pentru vizualizare.",
"LabelApiKeyUser": "Acționează în numele utilizatorului",
"LabelApiKeyUserDescription": "Această cheie API va avea aceleași permisiuni ca utilizatorul în numele căruia acționează. In loguri va părea că utilizatorul lansa cererile.",
"LabelApiToken": "Token API",
"LabelAppend": "Atașează",
"LabelAudioBitrate": "Rata de Biți Audio (e.g. 128k)",
"LabelAudioChannels": "Canale Audio (1 sau 2)",
"LabelAudioCodec": "Codec Audio",
"LabelAuthor": "Autor",
"LabelAuthorFirstLast": "Autor (Prenume Nume)",
"LabelAuthorLastFirst": "Autor (Nume, Prenume)",
"LabelAuthors": "Autori",
"LabelAutoDownloadEpisodes": "Descarcă automat episoadele",
"LabelAutoFetchMetadata": "Descarcă Automat Metadate",
"LabelAutoFetchMetadataHelp": "Descarcă metadate pentru titlu, autor si serii pentru eficientizarea încărcării. Metadatele suplimentare s-ar putea să trebuiască potrivite după încărcare.",
"LabelAutoLaunch": "Lansare automată",
"LabelAutoLaunchDescription": "Redirecționează automat către furnizorul de autentificare când navighez la pagina de autentificare (cale de suprascriere manuală <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Înregistrare Automată",
"LabelAutoRegisterDescription": "Creează utilizatori automat dupa autentificare",
"LabelBackToUser": "Înapoi la Utilizator",
"LabelBackupAudioFiles": "Copii de rezervă a Fișierelor Audio",
"LabelBackupLocation": "Locația Copiilor de Rezervă",
"LabelBackupsEnableAutomaticBackups": "Copii de Rezervă Automate",
"LabelBackupsEnableAutomaticBackupsHelp": "Copiile de Rezervă au fost salvate în /metadata/backups",
"LabelBackupsMaxBackupSize": "Dimensiunea maximă a copiilor de rezervă (în GB) (0 pentru nelimitat)",
"LabelBackupsMaxBackupSizeHelp": "Ca protecție împotriva configurațiilor greșite, backup-ul va eșua dacă trece de limita de dimensiune configurată.",
"LabelBackupsNumberToKeep": "Numărul copiilor de siguranță de păstrat",
"LabelBackupsNumberToKeepHelp": "Doar 1 copie de siguranță va fi ștearsă odata deci dacă există mai multe copii de siguranță vor trebui șterse manual.",
"LabelBitrate": "Rată de biți",
"LabelBonus": "Bonus",
"LabelBooks": "Cărți",
"LabelButtonText": "Textul Butonului",
"LabelByAuthor": "de {0}",
"LabelChangePassword": "Schimbare Parolă",
"LabelChannels": "Canale",
"LabelChapterCount": "{0} Capitole",
"LabelChapterTitle": "Titlul Capitolului",
"LabelChapters": "Capitole",
"LabelChaptersFound": "capitole găsite",
"LabelClickForMoreInfo": "Click pentru mai multe informații",
"LabelClickToUseCurrentValue": "Click pentru a folosi valoarea curentă",
"LabelClosePlayer": "Închide playerul",
"LabelCodec": "Codec",
"LabelCollapseSeries": "Restrânge seriile",
"LabelCollapseSubSeries": "Restrânge Sub-Seriile",
"LabelCollection": "Colecție",
"LabelCollections": "Colecții",
"LabelComplete": "Finalizat",
"LabelConfirmPassword": "Confirmare Parolă",
"LabelContinueListening": "Ascultă în continuare",
"LabelContinueReading": "Continuă lectura",
"LabelContinueSeries": "Continuă seria",
"LabelCorsAllowed": "Origini CORS Permise",
"LabelCover": "Copertă",
"LabelCoverImageURL": "URL-ul Imaginii de Copertă",
"LabelCoverProvider": "Furnizor Copertă",
"LabelCreatedAt": "Creat la",
"LabelCronExpression": "Expresie Cron",
"LabelCurrent": "Curent",
"LabelCurrently": "Acum:",
"LabelCustomCronExpression": "Expresie Cron Personalizată:",
"LabelDatetime": "Data și ora",
"LabelDays": "Zile",
"LabelDeleteFromFileSystemCheckbox": "Șterge fișierele din sistem (debifeaza pentru a șterge doar din baza de date)",
"LabelDescription": "Descriere",
"LabelDeselectAll": "Deselectați Tot",
"LabelDetectedPattern": "Tipar Identificat:",
"LabelDevice": "Dispozitiv",
"LabelDeviceInfo": "Informații Dispozitiv",
"LabelDeviceIsAvailableTo": "Dispozitiv accesibil lui...",
"LabelDirectory": "Dosar",
"LabelDiscFromFilename": "Disc din Numele Fișierului",
"LabelDiscFromMetadata": "Disc din Metadate",
"LabelDiscover": "Descoperă",
"LabelDownload": "Descarcă",
"LabelDownloadNEpisodes": "Descarcă {0} episoade",
"LabelDownloadable": "Descărcabil",
"LabelDuration": "Durată",
"LabelDurationComparisonExactMatch": "(potrivire exactă)",
"LabelDurationComparisonLonger": "({0} mai lung)",
"LabelDurationComparisonShorter": "({0} mai scurt)",
"LabelDurationFound": "Durată identificată:",
"LabelEbook": "Carte electronică",
"LabelEbooks": "Cărți electronice",
"LabelEdit": "Editare",
"LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "De la Adresa",
"LabelEmailSettingsRejectUnauthorized": "Respingere certificate neautorizate",
"LabelEmailSettingsRejectUnauthorizedHelp": "Dezactivarea verificării certificatelor SSL vă poate expune conexiunea la riscuri de securitate, cum ar fi atacuri de tip man-in-the-middle. Dezactivați această opțiune dacă înțelegeti implicațiile și aveți încredere în serverul de mail la care vă conectați.",
"LabelEmailSettingsSecure": "Sigur",
"LabelEmailSettingsSecureHelp": "Dacă e adevărat, conexiunea se va realiza prin TLS către server. Dacă e fals, TLS este folosit dacă serverul suporta extensia STARTTLS. În majoritatea cazurilor setati adevărat dacă folosiți portul 465. Pentru portul 587 sau 25 setati fals. (referinta nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Adresă de Test",
"LabelEmbeddedCover": "Încorporează Copertă",
"LabelEnable": "Activează",
"LabelEncodingBackupLocation": "O copie de siguranță a fișierului audio original va fi salvată în:",
"LabelEncodingChaptersNotEmbedded": "Capitolele nu sunt încorporate în cărțile audio cu mai multe track-uri.",
"LabelEncodingClearItemCache": "Asigurați-vă că ștergeți articolele din cache periodic.",
"LabelEncodingFinishedM4B": "Fișierul M4B va fi adaugat în dosarul dvs. de cărți audio când codificarea e terminată:",
"LabelEncodingInfoEmbedded": "Metadatele vor fi încorporate în fișierele audio din interiorul dosarului dvs. cu cărți audio.",
"LabelEncodingStartedNavigation": "Odată pornită sarcina poti naviga din această pagină.",
"LabelEncodingTimeWarning": "Codificarea poate dura până la 30 de minute.",
"LabelEncodingWarningAdvancedSettings": "Avertizare: Nu modificați aceste setări dacă nu sunteți familiar cu opțiunile de codare ffmpeg .",
"LabelEncodingWatcherDisabled": "Dacă ați dezactivat funcția de urmările va trebui sa rescanați acestă carte audio la ulterior.",
"LabelEnd": "Sfârșit",
"LabelEndOfChapter": "Sfârșitul capitolului",
"LabelEpisode": "Episod",
"LabelEpisodeNotLinkedToRssFeed": "Episoade nelegate de un flux RSS",
"LabelEpisodeNumber": "Episodul #{0}",
"LabelEpisodeTitle": "Titlul Episodului",
"LabelEpisodeType": "Tipul Episodului",
"LabelEpisodeUrlFromRssFeed": "URL-ul Episodului din Fluxul RSS",
"LabelEpisodes": "Episoade",
"LabelEpisodic": "Episodic",
"LabelExample": "Exemplu",
"LabelExpandSeries": "Extinde Seriile",
"LabelExpandSubSeries": "Extinde Sub-Seriile",
"LabelExpired": "Expirat",
"LabelExpiresAt": "Expiră La",
"LabelExpiresInSeconds": "Expiră în (secunde)",
"LabelExpiresNever": "Niciodată",
"LabelExplicit": "Explicit",
"LabelFeedURL": "URL flux",
"LabelExplicitChecked": "Explicit (verificat)",
"LabelExplicitUnchecked": "Neexplicit (neverificat)",
"LabelExportOPML": "Exportă OPML",
"LabelFeedURL": "Flux URL",
"LabelFetchingMetadata": "Aducere Metadate",
"LabelFile": "Fișier",
"LabelFileBirthtime": "Data creării fișierului",
"LabelFileBornDate": "Creat {0}",
"LabelFileModified": "Fișier modificat",
"LabelFileModifiedDate": "Modificat {0}",
"LabelFilename": "Nume fișier",
"LabelFilterByUser": "Filtrare după Utilizator",
"LabelFindEpisodes": "Găsire Episoade",
"LabelFinished": "Finalizat",
"LabelFinishedDate": "Finalizat {0}",
"LabelFolder": "Dosar",
"LabelFolders": "Dosare",
"LabelFontBold": "Îngroșat",
"LabelFontBoldness": "Grosimea fontului",
"LabelFontFamily": "Familia Fontului",
"LabelFontItalic": "Cursiv",
"LabelFontScale": "Mărimea fontului",
"LabelFontStrikethrough": "Tăiat cu o linie",
"LabelFormat": "Format",
"LabelFull": "Întreg",
"LabelGenre": "Gen",
"LabelGenres": "Genuri",
"LabelHardDeleteFile": "Ștergere definitivă a fișierului",
"LabelHasEbook": "Are carte electronică",
"LabelHasSupplementaryEbook": "Are carte electronică suplimentară",
"LabelHideSubtitles": "Ascunde Subtitrări",
"LabelHighestPriority": "Prioritatea cea mai ridicată",
"LabelHost": "Gazdă",
"LabelHour": "Ora",
"LabelHours": "Ore",
"LabelIcon": "Pictogramă",
"LabelImageURLFromTheWeb": "URL-ul imaginii de pe web",
"LabelInProgress": "În desfășurare",
"LabelIncludeInTracklist": "Include în Lista de Melodii",
"LabelIncomplete": "Incomplet",
"LabelInterval": "Interval",
"LabelIntervalCustomDailyWeekly": "Personalizat zilnic/saptămânal",
"LabelIntervalEvery12Hours": "La fiecare 12 ore",
"LabelIntervalEvery15Minutes": "La fiecare 15 minute",
"LabelIntervalEvery2Hours": "La fiecare 2 ore",
"LabelIntervalEvery30Minutes": "La fiecare 30 minute",
"LabelIntervalEvery6Hours": "La fiecare 6 ore",
"LabelIntervalEveryDay": "În fiecare zi",
"LabelIntervalEveryHour": "În fiecare oră",
"LabelIntervalEveryMinute": "La fiecare minut",
"LabelInvert": "Inversează",
"LabelItem": "Articol",
"LabelJumpBackwardAmount": "Sari înapoi cu",
"LabelJumpForwardAmount": "Sari înainte cu",
"LabelLanguage": "Limbă",
"LabelLanguageDefaultServer": "Limba Prestabilită a Serverului",
"LabelLanguages": "Limbi",
"LabelLastBookAdded": "Ultima Carte Adăugată",
"LabelLastBookUpdated": "Ultima Carte Actualizată",
"LabelLastProgressDate": "Ultimul progres: {0}",
"LabelLastSeen": "Ultima dată văzut",
"LabelLastTime": "Ultima dată",
"LabelLastUpdate": "Ultima actualizare",
"LabelLayout": "Aspect",
"LabelLayoutSinglePage": "Pagină unică",
"LabelLayoutSplitPage": "Pagină împărțită",
"LabelLess": "Mai puțin",
"LabelLibrariesAccessibleToUser": "Biblioteci Accesibile Utilizatorului",
"LabelLibrary": "Bibliotecă",
"LabelLibraryFilterSublistEmpty": "Numărul {0}",
"LabelLibraryItem": "Articol din Bibliotecă",
"LabelLibraryName": "Numele Bibliotecii",
"LabelLibrarySortByProgress": "Progres: Ultima Actualizare",
"LabelLibrarySortByProgressFinished": "Progres: Finalizat",
"LabelLibrarySortByProgressStarted": "Progres: Început",
"LabelLimit": "Limită",
"LabelLineSpacing": "Spațiere între rânduri",
"LabelListenAgain": "Ascultă din nou",
"LabelLogLevelDebug": "Depanare",
"LabelLogLevelInfo": "Informații",
"LabelLogLevelWarn": "Avertizare",
"LabelLookForNewEpisodesAfterDate": "Caută episoade noi după această dată",
"LabelLowestPriority": "Cea Mai Scăzută Prioritate",
"LabelMatchConfidence": "Încredere",
"LabelMatchExistingUsersBy": "Potrivire utilizatori existenți prin",
"LabelMatchExistingUsersByDescription": "Folosit pentru a conecta utilizatorii existenți. Odata conectați, utilizatorii vor fi potriviți după un ID unic trimis de furnizorul SSO",
"LabelMaxEpisodesToDownload": "Numarul maxim # de episoade de descărcat. Folosiți 0 pentru nelimitat.",
"LabelMaxEpisodesToDownloadPerCheck": "Numărul maxim # de episoade de descărcat per verificare",
"LabelMaxEpisodesToKeep": "Numarul maxim # de episoade păstrate",
"LabelMaxEpisodesToKeepHelp": "Valorea 0 nu stabilește o limită maximă. După ce un episod nou a fost descărcat automat, cel mai vechi episod va fi șters dacă aveți mai mult de X episoade. Se va șterge câte un episod vechi pentru fiecare episod nou descărcat.",
"LabelMediaPlayer": "Player Media",
"LabelMediaType": "Tip media",
"LabelMetaTag": "Etichetă Meta",
"LabelMetaTags": "Etichete Meta",
"LabelMetadataOrderOfPrecedenceDescription": "Sursele de metadate cu prioritate mai mare o să suprascrie sursele de metadate cu prioritate mai mică",
"LabelMetadataProvider": "Furnizor Metadate",
"LabelMinute": "Minut",
"LabelMinutes": "Minute",
"LabelMissing": "Lipsă",
"LabelMissingEbook": "Nu are carte electronică",
"LabelMissingSupplementaryEbook": "Nu are carte electronică adițională",
"LabelMobileRedirectURIs": "URL-uri de redirecționare Mobile Permise",
"LabelMobileRedirectURIsDescription": "Aceasta este o listă cu URI-uri valide pentru redirectionare a aplicațiilor mobile. URI-ul predefinit este <code>audiobookshelf://oauth</code>,, care poate fi sters sau suplimentat cu URI-uri adiționale pentru integrarea cu alte aplicații. Folosirea unui asterisc (<code>*</code>) ca singur element permite orice URI.",
"LabelMore": "Mai multe",
"LabelMoreInfo": "Mai multe informații",
"LabelName": "Nume",
"LabelNarrator": "Narator",
"LabelNarrators": "Naratori",
"LabelNew": "Nou",
"LabelNewPassword": "Parolă Nouă",
"LabelNewestAuthors": "Autori noi",
"LabelNewestEpisodes": "Episoade noi",
"LabelNextBackupDate": "Următoarea dată a copiilor de siguranță",
"LabelNextChapters": "Următoarele capitole vor fi:",
"LabelNextScheduledRun": "Urmatoarea rulare programată",
"LabelNoApiKeys": "Nu exista chei API",
"LabelNoCustomMetadataProviders": "Nu există furnizori de metadate personalizați",
"LabelNoEpisodesSelected": "Nici un episod selectat",
"LabelNotFinished": "Nefinalizat",
"LabelNotStarted": "Neînceput",
"LabelNotes": "Note",
"LabelNotificationAppriseURL": "URL-ul(urile) Apprise",
"LabelNotificationAvailableVariables": "Variabile disponibile",
"LabelNotificationBodyTemplate": "Corpul Șablonului",
"LabelNotificationEvent": "Eveniment de notificare",
"LabelNotificationTitleTemplate": "Titlul Șablonului",
"LabelNotificationsMaxFailedAttempts": "Număr de încercări eșuate maxim",
"LabelNotificationsMaxFailedAttemptsHelp": "Notificările sunt dezactivate dacă nu reușesc să fie trimise de acest număr de ori",
"LabelNotificationsMaxQueueSize": "Dimensiunea maximă a cozii pentru evenimentele de notificare",
"LabelNotificationsMaxQueueSizeHelp": "Evenimentele sunt limitate la 1 per secunda. Evenimentele vor fi ignorate dacă coada este plină. Previne spamarea cu notificări.",
"LabelNumberOfBooks": "Numărul de Cărți",
"LabelNumberOfChapters": "Număr de capitole:",
"LabelNumberOfEpisodes": "# de Episoade",
"LabelOpenIDAdvancedPermsClaimDescription": "Numele revendicării OpenID care conține permisiuni avansate pentru acțiunile utilizatorului în interiorul aplicației care vor fi aplicate rolurilor non-administrator (<b>dacă e configurat</b>). Dacă revendicarea nu e prezentă în răspunsul primit, accesul către ABS e refuzat. Dacă o singură opțiune lipsește, va fi tratată ca <code>falsă</code>. Asigurați-vă că revendicările furnizorului de securitate corespund structurii așteptate:",
"LabelOpenIDClaims": "Lăsați urmatoarele opțiuni goale pentru a dezactiva atribuirea avansată de grupuri și permisiuni, asignând grupul \"Utilizatori\" automat.",
"LabelOpenRSSFeed": "Flux Open RSS",
"LabelOverwrite": "Suprascrie",
"LabelPaginationPageXOfY": "Pagina {0} din {1}",
"LabelPassword": "Parolă",
"LabelPath": "Cale",
"LabelPermanent": "Permanent",
"LabelPermissionsAccessAllLibraries": "Poate accesa toate bibliotecile",
"LabelPermissionsAccessAllTags": "Poate accesa toate etichetele",
"LabelPermissionsAccessExplicitContent": "Poate Accesa Conținut Explicit",
"LabelPermissionsCreateEreader": "Poate Crea Cititoare Electronice",
"LabelPermissionsDelete": "Poate Șterge",
"LabelPermissionsDownload": "Poate Descărca",
"LabelPermissionsUpdate": "Poate Actualiza",
"LabelPermissionsUpload": "Poate Încărca",
"LabelPersonalYearReview": "Recapitularea Anului tău ({0})",
"LabelPhotoPathURL": "Calea/URL-ul Fotografiei",
"LabelPlayMethod": "Metoda de Redare",
"LabelPlaybackRateIncrementDecrement": "Incrementare/Decrementare a Ratei de Redare cu",
"LabelPlayerChapterNumberMarker": "{0} din {1}",
"LabelPlaylists": "Liste de redare",
"LabelPodcast": "Podcast",
"LabelPodcastSearchRegion": "Regiunea căutării podcastului",
"LabelPodcastType": "Tipul Podcastului",
"LabelPodcasts": "Podcasturi",
"LabelPort": "Portul",
"LabelPrefixesToIgnore": "Prefix de ignorat (fără a ține cont de majuscule)",
"LabelPreventIndexing": "Împiedică indexarea fluxului în directoarele iTunes și Google Podcasts",
"LabelPrimaryEbook": "eCarte Principală",
"LabelProgress": "Progres",
"LabelProvider": "Furnizor",
"LabelProviderAuthorizationValue": "Valoarea Antetului de Autorizare",
"LabelPubDate": "Data publicării",
"LabelPublishYear": "Anul publicării",
"LabelPublishedDate": "Publicat la {0}",
"LabelPublishedDecade": "Deceniul Publicării",
"LabelPublishedDecades": "Deceniile Publicării",
"LabelPublisher": "Editor",
"LabelPublishers": "Editori",
"LabelRSSFeedCustomOwnerEmail": "Email personalizat al proprietarului",
"LabelRSSFeedCustomOwnerName": "Nume personalizat al proprietarului",
"LabelRSSFeedOpen": "Flux RSS deschis",
"LabelRSSFeedPreventIndexing": "Previne indexarea",
"LabelRSSFeedSlug": "Identificator flux RSS",
"LabelRSSFeedURL": "URL-ul Fluxului RSS",
"LabelRandomly": "Aleatoriu",
"LabelReAddSeriesToContinueListening": "Readăugare serie la \"Continuă să asculți\"",
"LabelRead": "Citește",
"LabelReadAgain": "Citește din nou",
"LabelReadEbookWithoutProgress": "Citire eCarte fără a memora progresul",
"LabelRecentSeries": "Serii recente",
"LabelRecentlyAdded": "Adăugate recent",
"LabelRecommended": "Recomandat",
"LabelRedo": "Refă",
"LabelRegion": "Regiune",
"LabelReleaseDate": "Data Lansării",
"LabelRemoveAllMetadataAbs": "Ștergerea tuturor fișierelor metadata.abs",
"LabelRemoveAllMetadataJson": "Ștergerea tuturor fișierelor metadata.json",
"LabelRemoveAudibleBranding": "Ștergerea Audible intro și outro din capitole",
"LabelRemoveCover": "Șterge coperta",
"LabelRemoveMetadataFile": "Șterge fisierele metadate din dosarele bibliotecii",
"LabelRemoveMetadataFileHelp": "Șterge toate fișierele metadata.json și metadata.abs din {0} dosare.",
"LabelRowsPerPage": "Rânduri pe pagină",
"LabelSearchTerm": "Termen de căutat",
"LabelSearchTitle": "Titlu de căutat",
"LabelSearchTitleOrASIN": "Titlu de căutat sau ASN",
"LabelSeason": "Sezon",
"LabelSeasonNumber": "Sezonul #{0}",
"LabelSelectAll": "Selectează tot",
"LabelSelectAllEpisodes": "Selectează toate episoadele",
"LabelSelectEpisodesShowing": "Selectează {0} episoade dintre cele afișate",
"LabelSelectUser": "Selectare utilizator",
"LabelSelectUsers": "Selectare utilizatori",
"LabelSendEbookToDevice": "Trimite eCarte către...",
"LabelSequence": "Secvență",
"LabelSerial": "Serie",
"LabelSeries": "Serii",
"LabelSeriesName": "Numele Seriilor",
"LabelSeriesProgress": "Progresul Seriilor",
"LabelServerLogLevel": "Nivelul de Jurnal al Serverului",
"LabelServerYearReview": "Anul Serverului în Retrospectivă ({0})",
"LabelSetEbookAsPrimary": "Setează ca principală",
"LabelSetEbookAsSupplementary": "Setează ca suplimentară",
"LabelSettingsAllowIframe": "Permite încorporarea intr-un iframe",
"LabelSettingsAudiobooksOnly": "Doar cărți audio",
"LabelSettingsAudiobooksOnlyHelp": "Activarea acestei set[ri va ignora fișierele eBook daca acestea nu se află într-un dosar al unei cărți audio, caz în care vor fi setate ca eBook suplimentar",
"LabelSettingsBookshelfViewHelp": "Design scheumorf cu rafturi de lemn",
"LabelSettingsChromecastSupport": "Suport Chromecast",
"LabelSettingsDateFormat": "Formatul Datei",
"LabelSettingsEnableWatcher": "Urmărește în mod automat bibliotecile pentru schimbări",
"LabelSettingsEnableWatcherForLibrary": "Urmărește în mod automat biblioteca pentru schimbări",
"LabelShowAll": "Afișează tot",
"LabelSize": "Dimensiune",
"LabelSleepTimer": "Timer de somn",
+4 -4
View File
@@ -275,7 +275,7 @@
"LabelBonus": "Бонус",
"LabelBooks": "Книги",
"LabelButtonText": "Текст кнопки",
"LabelByAuthor": "{0}",
"LabelByAuthor": "от {0}",
"LabelChangePassword": "Изменить пароль",
"LabelChannels": "Ленты",
"LabelChapterCount": "{0} Главы",
@@ -392,7 +392,7 @@
"LabelGenre": "Жанр",
"LabelGenres": "Жанры",
"LabelHardDeleteFile": "Жесткое удаление файла",
"LabelHasEbook": "Есть e-книга",
"LabelHasEbook": "Есть электронная книга",
"LabelHasSupplementaryEbook": "Есть дополнительная e-книга",
"LabelHideSubtitles": "Скрыть серии",
"LabelHighestPriority": "Наивысший приоритет",
@@ -437,8 +437,8 @@
"LabelLibraryItem": "Элемент библиотеки",
"LabelLibraryName": "Имя библиотеки",
"LabelLibrarySortByProgress": "Прогресс: Последнее обновление",
"LabelLibrarySortByProgressFinished": "Прогресс: Завершено",
"LabelLibrarySortByProgressStarted": "Прогресс: Начато",
"LabelLibrarySortByProgressFinished": "Прогресс: Закончена",
"LabelLibrarySortByProgressStarted": "Прогресс: Начата",
"LabelLimit": "Лимит",
"LabelLineSpacing": "Межстрочный интервал",
"LabelListenAgain": "Послушать снова",
+22 -20
View File
@@ -23,7 +23,7 @@
"ButtonClearFilter": "Zrušiť filter",
"ButtonClose": "Uzavrieť",
"ButtonCloseFeed": "Zatvoriť zdroj",
"ButtonCloseSession": "Ukončiť otvorené pripojenie",
"ButtonCloseSession": "Ukončiť aktívne relácie",
"ButtonCollections": "Kolekcie",
"ButtonConfigureScanner": "Nastaviť skener",
"ButtonCreate": "Vytvoriť",
@@ -150,12 +150,12 @@
"HeaderIgnoredFiles": "Ignorované súbory",
"HeaderItemFiles": "Položka Súbory",
"HeaderItemMetadataUtils": "Položka Nástroje metadát",
"HeaderLastListeningSession": "Posledné pripojenie",
"HeaderLastListeningSession": "Posledná relácia",
"HeaderLatestEpisodes": "Posledné epizódy",
"HeaderLibraries": "Knižnice",
"HeaderLibraryFiles": "Súbory knižnice",
"HeaderLibraryStats": "Štatistiky knižnice",
"HeaderListeningSessions": "Pripojenia",
"HeaderListeningSessions": "Relácie",
"HeaderListeningStats": "Štatistiky počúvania",
"HeaderLogin": "Prihlásenie",
"HeaderLogs": "Záznamy udalostí",
@@ -172,7 +172,7 @@
"HeaderNotificationUpdate": "Aktualizovať notifikáciu",
"HeaderNotifications": "Notifikácie",
"HeaderOpenIDConnectAuthentication": "Overenie pripojenia OpenID",
"HeaderOpenListeningSessions": "Aktívne pripojenia",
"HeaderOpenListeningSessions": "Aktívne relácie",
"HeaderOpenRSSFeed": "Otvoriť RSS zdroj",
"HeaderOtherFiles": "Ostatné súbory",
"HeaderPasswordAuthentication": "Overenie heslom",
@@ -189,7 +189,7 @@
"HeaderRSSFeeds": "RSS zdroje",
"HeaderRemoveEpisode": "Odstrániť epizódu",
"HeaderRemoveEpisodes": "Odstrániť {0} epizód",
"HeaderSavedMediaProgress": "Priebeh uložených médií",
"HeaderSavedMediaProgress": "Stav uložených médií",
"HeaderSchedule": "Plán",
"HeaderScheduleEpisodeDownloads": "Naplánovať automatické sťahovanie epizód",
"HeaderScheduleLibraryScans": "Naplánovanovať automatické skenovanie knižnice",
@@ -222,7 +222,7 @@
"LabelAbridged": "Skrátená verzia",
"LabelAbridgedChecked": "Skrátená verzia (zaškrtnuté)",
"LabelAbridgedUnchecked": "Neskrátená verzia (nezaškrtnuté)",
"LabelAccessibleBy": "Prístupné pre",
"LabelAccessibleBy": "Dostupné pre",
"LabelAccountType": "Typ účtu",
"LabelAccountTypeAdmin": "Administrátor",
"LabelAccountTypeGuest": "Hosť",
@@ -230,7 +230,7 @@
"LabelActivities": "Aktivity",
"LabelActivity": "Aktivita",
"LabelAddToCollection": "Pridať do zbierky",
"LabelAddToCollectionBatch": "Pridať {0} kníh do kolekcie",
"LabelAddToCollectionBatch": "Pridať {0} kníh do zbierky",
"LabelAddToPlaylist": "Pridať do playlistu",
"LabelAddToPlaylistBatch": "Pridať {0} položie do playlistu",
"LabelAddedAt": "Pridané",
@@ -288,8 +288,8 @@
"LabelCodec": "Kodek",
"LabelCollapseSeries": "Zbaliť série",
"LabelCollapseSubSeries": "Zbaliť podsérie",
"LabelCollection": "Kolekcia",
"LabelCollections": "Kolekcie",
"LabelCollection": "Zbierka",
"LabelCollections": "Zbierky",
"LabelComplete": "Hotovo",
"LabelConfirmPassword": "Potvrdiť heslo",
"LabelContinueListening": "Pokračovať v počúvaní",
@@ -383,7 +383,7 @@
"LabelFolders": "Priečinky",
"LabelFontBold": "Tučné",
"LabelFontBoldness": "Hrúbka písma",
"LabelFontFamily": "Rodina písiem",
"LabelFontFamily": "písmo",
"LabelFontItalic": "Kurzíva",
"LabelFontScale": "Veľkosť písma",
"LabelFontStrikethrough": "Preškrtnuté",
@@ -436,9 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Žiadne {0}",
"LabelLibraryItem": "Položka knižnice",
"LabelLibraryName": "Názov knižnice",
"LabelLibrarySortByProgress": "Pokrok: Aktualizované",
"LabelLibrarySortByProgressFinished": "Pokrok: Dokončené",
"LabelLibrarySortByProgressStarted": "Pokrok: Začiatok",
"LabelLibrarySortByProgress": "Stav: Naposledy aktualizované",
"LabelLibrarySortByProgressFinished": "Stav: Dokončené",
"LabelLibrarySortByProgressStarted": "Stav: Začal",
"LabelLimit": "Limit",
"LabelLineSpacing": "Riadkovanie",
"LabelListenAgain": "Počúvať znova",
@@ -528,7 +528,7 @@
"LabelPrefixesToIgnore": "Ignorované predpony (bez ohľadu na veľkosť písmen)",
"LabelPreventIndexing": "Zabráni indexácii vašich zdrojov službami iTunes a Google podcast directories",
"LabelPrimaryEbook": "Primárny e-book",
"LabelProgress": "Stav",
"LabelProgress": "Aktuálny stav",
"LabelProvider": "Poskytovateľ",
"LabelProviderAuthorizationValue": "Obsah hlavičky autorizácie",
"LabelPubDate": "Dátum publikovania",
@@ -548,7 +548,7 @@
"LabelReAddSeriesToContinueListening": "Znova pridať série do pokračujúceho počúvania",
"LabelRead": "Načítať",
"LabelReadAgain": "Čítať znova",
"LabelReadEbookWithoutProgress": "Čítať e-knihu bez sledovania pokroku",
"LabelReadEbookWithoutProgress": "Čítať e-knihu bez zmeny stavu",
"LabelRecentSeries": "Posledné série",
"LabelRecentlyAdded": "Posledné pridané",
"LabelRecommended": "Odporúčané",
@@ -577,7 +577,7 @@
"LabelSerial": "Na pokračovanie",
"LabelSeries": "Série",
"LabelSeriesName": "Názov série",
"LabelSeriesProgress": "Pokrok série",
"LabelSeriesProgress": "Aktuálny stav série",
"LabelServerLogLevel": "Úroveň logovania servera",
"LabelServerYearReview": "Rok servera v prehľade ({0})",
"LabelSetEbookAsPrimary": "Nastaviť ako primárny",
@@ -588,8 +588,8 @@
"LabelSettingsBookshelfViewHelp": "Skeuomorfný dizajn s drevenými poličkami",
"LabelSettingsChromecastSupport": "Podpora chromecastu",
"LabelSettingsDateFormat": "Formát dátumu",
"LabelSettingsEnableWatcher": "Automatické skenovanie knižníc pre zmeny",
"LabelSettingsEnableWatcherForLibrary": "Automaticky skenovať knižnicu pre zmeny",
"LabelSettingsEnableWatcher": "Automatické sledovanie zmien v knižniciach",
"LabelSettingsEnableWatcherForLibrary": "Automatické sledovanie zmien v knižnici",
"LabelSettingsEnableWatcherHelp": "Povoliť automatické pridávanie/aktualizácie položiek pri zmene súborov. *Vyžaduje reštart servera",
"LabelSettingsEpubsAllowScriptedContent": "Povoliť v e-knihách skriptovaný obsah",
"LabelSettingsEpubsAllowScriptedContentHelp": "Povoliť e-knihám spúšťanie skriptov. Odporúča sa túto voľbu nepovolovať, pokiaľ plne nedôverujete zdrojom súborov e-kníh.",
@@ -888,7 +888,7 @@
"MessageResetChaptersConfirm": "Ste si istý, že chcete resetnúť kapitoly a zahodiť zmeny, ktoré ste vykonali?",
"MessageRestoreBackupConfirm": "Ste si istí, že chcete obnoviť zálohu vytvorenú",
"MessageRestoreBackupWarning": "Obnovenie zálohy spôsobí kompletný prepis databázy umiestnenej v /config a obrázkov prebalov a autorov v /metadata/items a /metadata/authors.<br /><br />Zálohy nemenia žiadne súbory v priečinkoch vašej knižnice. Ak ste povolili v nastaveniach servera ukladanie obrázkov prebalov a metadát v priečinkoch knižnice, tieto nie sú zálohované a teda ani prepisované.<br /><br />Všetky klienti používajúci váš server budú automaticky obnovené.",
"MessageScheduleLibraryScanNote": "Pre väčšinu používateľov sa odporúča ponechať túto funkciu vypnutú a povoliť nastavenia funkcie sledovania obsahu priečinku. Funkcia sledovania priečinku bude automaticky detekovať zmeny v priečinkoch knižnice. Táto funkcia však nefunguje pre všetky súborové systémy (ako napr. NFS), v tom prípade využite funkciu plánovaného skenovania knižnice.",
"MessageScheduleLibraryScanNote": "Pre väčšinu používateľov sa odporúča nechať túto funkciu vypnutú a ponechať zapnuté nastavenie „Automatické sledovanie zmien v knižnici“ táto funkcia automaticky zistí zmeny vo vašich priečinkoch knižnice. Túto funkciu zapnite, ak „Automatické sledovanie zmien v knižnici“ nefunguje vo vašom súborovom systéme (napr. NFS).",
"MessageScheduleRunEveryWeekdayAtTime": "Spustiť každú {0} o {1}",
"MessageSearchResultsFor": "Výsledky vyhľadávania pre",
"MessageSelected": "{0} vybrané",
@@ -1015,7 +1015,7 @@
"ToastCachePurgeFailed": "Vyčistenie vyrovnávacej pamäte zlyhalo",
"ToastCachePurgeSuccess": "Vyrovnávacia pamäť vyčistená",
"ToastChapterLocked": "Kapitola je zamknutá.",
"ToastChapterStartTimeAdjusted": "Čas začiatku kapitoly upravený o {0} sek.",
"ToastChapterStartTimeAdjusted": "Čas začiatku kapitoly upravený o {0} sekúnd",
"ToastChaptersAllLocked": "Všetky kapitoly sú zamknuté. Odomknite niektoré kapitoly, aby ste posunuli ich časy.",
"ToastChaptersHaveErrors": "Kapitoly obsahujú chyby",
"ToastChaptersInvalidShiftAmountLast": "Neplatná hodnota veľkosti posunutia. Začiatok poslednej kapitoly by ležal za koncom audioknihy.",
@@ -1026,6 +1026,8 @@
"ToastCollectionItemsAddFailed": "Pridanie položky/-iek do zbierky zlyhalo",
"ToastCollectionRemoveSuccess": "Zbierka odstránená",
"ToastCollectionUpdateSuccess": "Zbierka aktualizovaná",
"ToastConnectionNotAvailable": "Pripojenie je nedostupné. Skúste to neskôr",
"ToastCoverSearchFailed": "Vyhľadanie obalu sa nepodarilo",
"ToastCoverUpdateFailed": "Aktualizácia prebalu zlyhala",
"ToastDateTimeInvalidOrIncomplete": "Dátum a čas sú neplatné alebo neúplné",
"ToastDeleteFileFailed": "Odstránenie súboru zlyhalo",
+5 -5
View File
@@ -104,7 +104,7 @@
"ButtonStartM4BEncode": "Zaženi M4B prekodiranje",
"ButtonStartMetadataEmbed": "Začni vdelavo metapodatkov",
"ButtonStats": "Statistika",
"ButtonSubmit": "Potrdi",
"ButtonSubmit": "Pošlji",
"ButtonTest": "Test",
"ButtonUnlinkOpenId": "Prekini povezavo OpenID",
"ButtonUpload": "Naloži",
@@ -383,7 +383,7 @@
"LabelFolders": "Mape",
"LabelFontBold": "Krepko",
"LabelFontBoldness": "Krepkost pisave",
"LabelFontFamily": "Družina pisave",
"LabelFontFamily": "Družina pisav",
"LabelFontItalic": "Ležeče",
"LabelFontScale": "Merilo pisave",
"LabelFontStrikethrough": "Prečrtano",
@@ -436,9 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Ne {0}",
"LabelLibraryItem": "Element knjižnice",
"LabelLibraryName": "Ime knjižnice",
"LabelLibrarySortByProgress": "Napredek: Nazadnje posodobljen",
"LabelLibrarySortByProgressFinished": "Napredej: Končano",
"LabelLibrarySortByProgressStarted": "Napredek: Začeto",
"LabelLibrarySortByProgress": "Napredek: Zadnja posodobitev",
"LabelLibrarySortByProgressFinished": "Napredek: Končano",
"LabelLibrarySortByProgressStarted": "Napredek: Začelo se je",
"LabelLimit": "Omejitev",
"LabelLineSpacing": "Vrstični razmak",
"LabelListenAgain": "Poslušaj znova",
+14 -14
View File
@@ -1,6 +1,6 @@
{
"ButtonAdd": "Lägg till",
"ButtonAddApiKey": "Addera API-nyckel",
"ButtonAddApiKey": "Lägg till API-nyckel",
"ButtonAddChapters": "Lägg till kapitel",
"ButtonAddDevice": "Lägg till enhet",
"ButtonAddLibrary": "Lägg till bibliotek",
@@ -48,7 +48,7 @@
"ButtonLogout": "Logga ut",
"ButtonLookup": "Sök",
"ButtonManageTracks": "Hantera spår",
"ButtonMapChapterTitles": "Karta kapitelrubriker",
"ButtonMapChapterTitles": "Mappa kapitelrubriker",
"ButtonMatchAllAuthors": "Matcha alla författare",
"ButtonMatchBooks": "Matcha böcker",
"ButtonNevermind": "Glöm det",
@@ -104,7 +104,7 @@
"ButtonStartM4BEncode": "Starta M4B-omkodning",
"ButtonStartMetadataEmbed": "Infoga metadata",
"ButtonStats": "Statistik",
"ButtonSubmit": "Spara",
"ButtonSubmit": "Skicka",
"ButtonTest": "Testa",
"ButtonUnlinkOpenId": "Koppla ifrån OpenID",
"ButtonUpload": "Ladda upp",
@@ -123,7 +123,7 @@
"HeaderAdvanced": "Avancerad",
"HeaderApiKeys": "API-nyckel",
"HeaderAppriseNotificationSettings": "Inställningar av meddelanden med Apprise",
"HeaderAudioTracks": "Ljudfiler",
"HeaderAudioTracks": "Ljudspår",
"HeaderAudiobookTools": "Hantering av ljudboksfiler",
"HeaderAuthentication": "Autentisering",
"HeaderBackups": "Säkerhetskopior",
@@ -202,7 +202,7 @@
"HeaderSettingsScanner": "Skanner",
"HeaderSettingsSecurity": "Säkerhet",
"HeaderSettingsWebClient": "Webklient",
"HeaderSleepTimer": "Timer för att sova",
"HeaderSleepTimer": "Insomningstimer",
"HeaderStatsLargestItems": "Största objekten",
"HeaderStatsLongestItems": "Längsta objekten (timmar)",
"HeaderStatsMinutesListeningChart": "Minuters lyssning (senaste 7 dagarna)",
@@ -231,10 +231,10 @@
"LabelActivity": "Aktivitet",
"LabelAddToCollection": "Lägg till i en samling",
"LabelAddToCollectionBatch": "Lägg till {0} böcker i samlingen",
"LabelAddToPlaylist": "Lägg till i en spellista",
"LabelAddToPlaylist": "Lägg till i spellista",
"LabelAddToPlaylistBatch": "Lägg till {0} objekt i Spellistan",
"LabelAddedAt": "Datum adderad",
"LabelAddedDate": "Adderad {0}",
"LabelAddedDate": "Tillagd {0}",
"LabelAdminUsersOnly": "Endast administratörer",
"LabelAll": "Alla",
"LabelAllEpisodesDownloaded": "Alla avsnitt är nedladdade",
@@ -363,14 +363,14 @@
"LabelExpiresAt": "Gäller till och med",
"LabelExpiresInSeconds": "Upphör om (sekunder)",
"LabelExpiresNever": "Aldrig",
"LabelExplicit": "Bestämd",
"LabelExplicit": "Vuxeninnehåll",
"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",
"LabelFile": "Fil",
"LabelFileBirthtime": "Tidpunkt, adderad",
"LabelFileBirthtime": "Tidpunkt, tillagd",
"LabelFileBornDate": "Skapad {0}",
"LabelFileModified": "Tidpunkt, ändrad",
"LabelFileModifiedDate": "Ändrad {0}",
@@ -436,9 +436,9 @@
"LabelLibraryFilterSublistEmpty": "Ingen {0}",
"LabelLibraryItem": "Objekt",
"LabelLibraryName": "Biblioteksnamn",
"LabelLibrarySortByProgress": "Framsteg: senast uppdaterat",
"LabelLibrarySortByProgressFinished": "Framsteg: avslutad",
"LabelLibrarySortByProgressStarted": "Framsteg: påbörjad",
"LabelLibrarySortByProgress": "Status: Senast uppdaterad",
"LabelLibrarySortByProgressFinished": "Status: Avslutad",
"LabelLibrarySortByProgressStarted": "Status: Startad",
"LabelLimit": "Begränsning",
"LabelLineSpacing": "Radavstånd",
"LabelListenAgain": "Lyssna igen",
@@ -629,7 +629,7 @@
"LabelShowSeconds": "Visa i sekunder",
"LabelShowSubtitles": "Visa underrubriker",
"LabelSize": "Storlek",
"LabelSleepTimer": "Sovtimer",
"LabelSleepTimer": "Insomningstimer",
"LabelSlug": "Kortnamn",
"LabelSortAscending": "Stigande",
"LabelSortDescending": "Fallande",
@@ -821,7 +821,7 @@
"MessageImportantNotice": "Viktig meddelande!",
"MessageInsertChapterBelow": "Infoga kapitel nedanför",
"MessageInvalidAsin": "Felaktig ASIN-kod",
"MessageItemsSelected": "{0} objekt markerade",
"MessageItemsSelected": "{0} objekt valda",
"MessageItemsUpdated": "{0} objekt uppdaterade",
"MessageJoinUsOn": "Anslut dig till oss på",
"MessageLoading": "Laddar...",
+5 -5
View File
@@ -275,7 +275,7 @@
"LabelBonus": "Bonus",
"LabelBooks": "Kitaplar",
"LabelButtonText": "Buton Metni",
"LabelByAuthor": "Yazar: {0}",
"LabelByAuthor": "{0} tarafından",
"LabelChangePassword": "Şifreyi Değiştir",
"LabelChannels": "Kanallar",
"LabelChapterCount": "{0} Bölüm",
@@ -383,7 +383,7 @@
"LabelFolders": "Klasörler",
"LabelFontBold": "Kalın",
"LabelFontBoldness": "Yazı Tipi Kalınlığı",
"LabelFontFamily": "Yazı Tipi Ailesi",
"LabelFontFamily": "Yazı tipi ailesi",
"LabelFontItalic": "İtalik",
"LabelFontScale": "Yazı Tipi Ölçeği",
"LabelFontStrikethrough": "Üstü Çizili",
@@ -588,8 +588,8 @@
"LabelSettingsBookshelfViewHelp": "Ahşap raflı skeuomorfik tasarım",
"LabelSettingsChromecastSupport": "Chromecast desteği",
"LabelSettingsDateFormat": "Tarih Formatı",
"LabelSettingsEnableWatcher": "Değişiklikler için kütüphaneleri otomatik olarak tara",
"LabelSettingsEnableWatcherForLibrary": "Değişiklikler için kütüphaneyi otomatik olarak tara",
"LabelSettingsEnableWatcher": "Kütüphanelerdeki değişiklikleri otomatik olarak izle",
"LabelSettingsEnableWatcherForLibrary": "Kütüphanedeki değişiklikleri otomatik olarak izle",
"LabelSettingsEnableWatcherHelp": "Dosya değişiklikleri algılandığında öğelerin otomatik olarak eklenmesini/güncellenmesini sağlar. *Sunucunun yeniden başlatılmasını gerektirir",
"LabelSettingsEpubsAllowScriptedContent": "Epub'larda betiklenmiş içeriğe izin ver",
"LabelSettingsEpubsAllowScriptedContentHelp": "Epub dosyalarının betik çalıştırmasına izin verin. Epub dosyalarının kaynağına güvenmiyorsanız bu ayarı devre dışı bırakmanız önerilir.",
@@ -888,7 +888,7 @@
"MessageResetChaptersConfirm": "Bölümleri sıfırlamak ve yaptığınız değişiklikleri geri almak istediğinizden emin misiniz?",
"MessageRestoreBackupConfirm": "Şu tarihte oluşturulan yedeği geri yüklemek istediğinizden emin misiniz",
"MessageRestoreBackupWarning": "Bir yedeği geri yüklemek, /config konumundaki tüm veritabanının ve /metadata/items & /metadata/authors içindeki kapak resimlerinin üzerine yazacaktır.<br /><br />Yedekler, kütüphane klasörlerinizdeki hiçbir dosyayı değiştirmez. Sunucu ayarlarını kütüphane klasörlerinizde kapak resmi ve üst veri saklamak için etkinleştirdiyseniz, bunlar yedeklenmez veya üzerine yazılmaz.<br /><br />Sunucunuzu kullanan tüm istemciler otomatik olarak yenilenecektir.",
"MessageScheduleLibraryScanNote": "Çoğu kullanıcı için, bu özelliği devre dışı bırakıp klasör izleyici ayarını etkin tutmaları önerilir. Klasör izleyici, kütüphane klasörlerinizdeki değişiklikleri otomatik olarak algılayacaktır. Klasör izleyici her dosya sistemi için (NFS gibi) çalışmaz, bu nedenle bunun yerine zamanlanmış kütüphane taramaları kullanılabilir.",
"MessageScheduleLibraryScanNote": "Çoğu kullanıcı için bu ayarı pasif bırakması ve \"Kütüphanedeki değişiklikleri otomatik olarak izle\" seçeneğini aktif etmesi önerilir. O seçenek kütüphane dizinlerindeki herhangi bir değişikliği otomatik olarak tespit edecektir. Eğer dosya sisteminiz \"Kütüphanedeki değişiklikleri otomatik olarak izle\" yöntemini desteklemiyorsa (örn; NFS dosya sistemi) bu özelliği aktif edebilirsiniz.",
"MessageScheduleRunEveryWeekdayAtTime": "Her {0} günü saat {1}'de çalıştır",
"MessageSearchResultsFor": "Arama sonuçları",
"MessageSelected": "{0} seçildi",
+4 -4
View File
@@ -275,7 +275,7 @@
"LabelBonus": "额外",
"LabelBooks": "图书",
"LabelButtonText": "按钮文本",
"LabelByAuthor": " {0}",
"LabelByAuthor": "作者: {0}",
"LabelChangePassword": "修改密码",
"LabelChannels": "声道",
"LabelChapterCount": "{0} 章节",
@@ -436,9 +436,9 @@
"LabelLibraryFilterSublistEmpty": "没有 {0}",
"LabelLibraryItem": "媒体库项目",
"LabelLibraryName": "媒体库名称",
"LabelLibrarySortByProgress": "收听进度: 上次收听时间",
"LabelLibrarySortByProgressFinished": "收听进度: 已完成",
"LabelLibrarySortByProgressStarted": "收听进度: 已开始",
"LabelLibrarySortByProgress": "进度: 上次更新",
"LabelLibrarySortByProgressFinished": "进度: 已完成",
"LabelLibrarySortByProgressStarted": "进度: 已开始",
"LabelLimit": "限制",
"LabelLineSpacing": "行间距",
"LabelListenAgain": "再次收听",
+1 -1
View File
@@ -127,7 +127,7 @@ components:
duration:
type: integer
format: int64
description: Duration in seconds
description: Duration in minutes
SeriesMetadata:
type: object
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "audiobookshelf",
"version": "2.31.0",
"version": "2.35.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf",
"version": "2.31.0",
"version": "2.35.1",
"license": "GPL-3.0",
"dependencies": {
"axios": "^0.27.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "audiobookshelf",
"version": "2.31.0",
"version": "2.35.1",
"buildNumber": 1,
"description": "Self-hosted audiobook and podcast server",
"main": "index.js",
+22 -2
View File
@@ -3,6 +3,7 @@ const Logger = require('./Logger')
const Database = require('./Database')
const TokenManager = require('./auth/TokenManager')
const CoverSearchManager = require('./managers/CoverSearchManager')
const { LogLevel } = require('./utils/constants')
/**
* @typedef SocketClient
@@ -85,6 +86,14 @@ class SocketAuthority {
}
}
requireAdminSocket(socket, eventName) {
const client = this.clients[socket.id]
if (client?.user?.isAdminOrUp) return true
Logger.warn(`[SocketAuthority] Unauthorized ${eventName} socket event from socket ${socket.id}`)
return false
}
/**
* Emits event with library item to all clients that can access the library item
* Note: Emits toOldJSONExpanded()
@@ -179,14 +188,25 @@ class SocketAuthority {
socket.on('auth', (token) => this.authenticateSocket(socket, token))
// Scanning
socket.on('cancel_scan', (libraryId) => this.cancelScan(libraryId))
socket.on('cancel_scan', (libraryId) => {
if (!this.requireAdminSocket(socket, 'cancel_scan')) return
this.cancelScan(libraryId)
})
// Cover search streaming
socket.on('search_covers', (payload) => this.handleCoverSearch(socket, payload))
socket.on('cancel_cover_search', (requestId) => this.handleCancelCoverSearch(socket, requestId))
// Logs
socket.on('set_log_listener', (level) => Logger.addSocketListener(socket, level))
socket.on('set_log_listener', (level) => {
if (!this.requireAdminSocket(socket, 'set_log_listener')) return
if (!Number.isInteger(level) || !Object.values(LogLevel).includes(level)) {
Logger.warn(`[SocketAuthority] Invalid set_log_listener level from socket ${socket.id}`)
return
}
Logger.addSocketListener(socket, level)
})
socket.on('remove_log_listener', () => Logger.removeSocketListener(socket.id))
// Sent automatically from socket.io clients
+98 -21
View File
@@ -1,4 +1,5 @@
const { Op } = require('sequelize')
const uuid = require('uuid')
const Database = require('../Database')
const Logger = require('../Logger')
@@ -115,6 +116,7 @@ class TokenManager {
const payload = {
userId: user.id,
username: user.username,
jti: uuid.v4(),
type: 'access'
}
const options = {
@@ -138,6 +140,7 @@ class TokenManager {
const payload = {
userId: user.id,
username: user.username,
jti: uuid.v4(),
type: 'refresh'
}
const options = {
@@ -183,20 +186,56 @@ class TokenManager {
* @param {import('../models/User')} user
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {boolean} gracePeriod - whether to use the grace period
* @returns {Promise<{ accessToken:string, refreshToken:string }>}
*/
async rotateTokensForSession(session, user, req, res) {
// Generate new tokens
async rotateTokensForSession(session, user, req, res, gracePeriod = true) {
const previousRefreshToken = session.refreshToken
const newAccessToken = this.generateTempAccessToken(user)
const newRefreshToken = this.generateRefreshToken(user)
// Calculate new expiration time
let newRefreshToken = this.generateRefreshToken(user)
const newExpiresAt = new Date(Date.now() + this.RefreshTokenExpiry * 1000)
// Update the session with the new refresh token and expiration
session.refreshToken = newRefreshToken
session.expiresAt = newExpiresAt
await session.save()
let lastRefreshToken = null
let lastRefreshTokenExpiresAt = null
if (gracePeriod) {
// Set grace period of old refresh token in case of race condition in token rotation.
// This grace period may need to be longer if fetching the user data takes longer due to large progress objects
lastRefreshToken = previousRefreshToken
lastRefreshTokenExpiresAt = new Date(Date.now() + 60 * 1000) // 1 minute grace period
}
// Only update if this session row still has the refresh token we read
const [numUpdated] = await Database.sessionModel.update(
{
refreshToken: newRefreshToken,
expiresAt: newExpiresAt,
lastRefreshToken,
lastRefreshTokenExpiresAt
},
{
where: {
id: session.id,
refreshToken: previousRefreshToken
}
}
)
if (numUpdated === 0) {
Logger.debug(`[TokenManager] Race condition in rotateTokensForSession for user ${user.id}, getting new token`)
const updatedSession = await Database.sessionModel.findOne({ where: { id: session.id } })
newRefreshToken = updatedSession.refreshToken
session.refreshToken = updatedSession.refreshToken
session.expiresAt = updatedSession.expiresAt
session.lastRefreshToken = updatedSession.lastRefreshToken
session.lastRefreshTokenExpiresAt = updatedSession.lastRefreshTokenExpiresAt
} else {
session.refreshToken = newRefreshToken
session.expiresAt = newExpiresAt
session.lastRefreshToken = lastRefreshToken
session.lastRefreshTokenExpiresAt = lastRefreshTokenExpiresAt
}
// Set new refresh token cookie
this.setRefreshTokenCookie(req, res, newRefreshToken)
@@ -234,6 +273,13 @@ class TokenManager {
}
const user = await Database.userModel.getUserById(apiKey.userId)
if (!user?.isActive) {
// deny login
done(null, null)
return
}
done(null, user)
} else {
// JWT based authentication
@@ -287,23 +333,40 @@ class TokenManager {
}
}
const session = await Database.sessionModel.findOne({
where: { refreshToken: refreshToken }
let session = await Database.sessionModel.findOne({
where: {
[Op.or]: [{ refreshToken: refreshToken }, { lastRefreshToken: refreshToken }]
}
})
if (!session) {
Logger.error(`[TokenManager] Failed to refresh token. Session not found for refresh token: ${refreshToken}`)
Logger.error(`[TokenManager] Failed to refresh token. Session not found`)
return {
error: 'Invalid refresh token'
}
}
// Check if session is expired in database
if (session.expiresAt < new Date()) {
Logger.info(`[TokenManager] Session expired in database, cleaning up`)
await session.destroy()
return {
error: 'Refresh token expired'
let isGracePeriod = false
if (session.refreshToken !== refreshToken) {
// Token matched lastRefreshToken
if (session.lastRefreshTokenExpiresAt && session.lastRefreshTokenExpiresAt > new Date()) {
isGracePeriod = true
Logger.debug(`[TokenManager] Grace period hit for user ${session.userId}`)
} else {
Logger.debug(`[TokenManager] Grace period expired for user ${session.userId}`)
return {
error: 'Invalid refresh token'
}
}
} else {
// Token matched current refreshToken
// Check if session is expired in database
if (session.expiresAt < new Date()) {
Logger.info(`[TokenManager] Session expired in database, cleaning up`)
await session.destroy()
return {
error: 'Refresh token expired'
}
}
}
@@ -315,6 +378,20 @@ class TokenManager {
}
}
if (isGracePeriod) {
// Return the already rotated refresh token store in the database,
// and generate a new access token without changing the refresh token
// again
const accessToken = this.generateTempAccessToken(user)
this.setRefreshTokenCookie(req, res, session.refreshToken)
return {
accessToken,
refreshToken: session.refreshToken,
user
}
}
const newTokens = await this.rotateTokensForSession(session, user, req, res)
return {
accessToken: newTokens.accessToken,
@@ -368,7 +445,7 @@ class TokenManager {
// So rotate token for current session
const currentSession = await Database.sessionModel.findOne({ where: { refreshToken: currentRefreshToken } })
if (currentSession) {
const newTokens = await this.rotateTokensForSession(currentSession, user, req, res)
const newTokens = await this.rotateTokensForSession(currentSession, user, req, res, false)
// Invalidate all sessions for the user except the current one
await Database.sessionModel.destroy({
@@ -382,7 +459,7 @@ class TokenManager {
return newTokens.accessToken
} else {
Logger.error(`[TokenManager] No session found to rotate tokens for refresh token ${currentRefreshToken}`)
Logger.error(`[TokenManager] No session found to rotate tokens`)
}
}
@@ -406,7 +483,7 @@ class TokenManager {
try {
const numDeleted = await Database.sessionModel.destroy({ where: { refreshToken: refreshToken } })
Logger.info(`[TokenManager] Refresh token ${refreshToken} invalidated, ${numDeleted} sessions deleted`)
Logger.info(`[TokenManager] Refresh token invalidated, ${numDeleted} sessions deleted`)
return true
} catch (error) {
Logger.error(`[TokenManager] Error invalidating refresh token: ${error.message}`)
+7 -6
View File
@@ -10,7 +10,7 @@ const CacheManager = require('../managers/CacheManager')
const CoverManager = require('../managers/CoverManager')
const AuthorFinder = require('../finders/AuthorFinder')
const { reqSupportsWebp, isValidASIN } = require('../utils/index')
const { reqSupportsWebp, isValidASIN, clampPositiveInt } = require('../utils/index')
const naturalSort = createNewSortInstance({
comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare
@@ -113,7 +113,7 @@ class AuthorController {
payload.lastFirst = Database.authorModel.getLastFirst(payload.name)
}
// Check if author name matches another author and merge the authors
// Check if author name matches another author in the same library and merge the authors
let existingAuthor = null
if (authorNameUpdate) {
existingAuthor = await Database.authorModel.findOne({
@@ -121,7 +121,8 @@ class AuthorController {
id: {
[sequelize.Op.not]: req.author.id
},
name: payload.name
name: payload.name,
libraryId: req.author.libraryId
}
})
}
@@ -148,7 +149,7 @@ class AuthorController {
})
if (libraryItems.length) {
await Database.bookAuthorModel.removeByIds(req.author.id) // Remove all old BookAuthor
await Database.bookAuthorModel.bulkCreate(bookAuthorsToCreate) // Create all new BookAuthor
await Database.bookAuthorModel.bulkCreate(bookAuthorsToCreate, { ignoreDuplicates: true }) // Create all new unique BookAuthor
for (const libraryItem of libraryItems) {
await libraryItem.saveMetadataFile()
}
@@ -411,8 +412,8 @@ class AuthorController {
const options = {
format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'),
height: height ? parseInt(height) : null,
width: width ? parseInt(width) : null
height: clampPositiveInt(height ? parseInt(height) : null, 4096),
width: clampPositiveInt(width ? parseInt(width) : null, 4096)
}
return CacheManager.handleAuthorCache(res, authorId, options)
}
+21 -6
View File
@@ -3,6 +3,7 @@ const Sequelize = require('sequelize')
const Logger = require('../Logger')
const SocketAuthority = require('../SocketAuthority')
const Database = require('../Database')
const htmlSanitizer = require('../utils/htmlSanitizer')
const RssFeedManager = require('../managers/RssFeedManager')
@@ -31,13 +32,19 @@ class CollectionController {
async create(req, res) {
const reqBody = req.body || {}
const nameCleaned = htmlSanitizer.stripAllTags(reqBody.name)
// Validation
if (!reqBody.name || !reqBody.libraryId) {
if (!nameCleaned || !reqBody.libraryId) {
return res.status(400).send('Invalid collection data')
}
if (reqBody.description && typeof reqBody.description !== 'string') {
return res.status(400).send('Invalid collection description')
}
if (!req.user.checkCanAccessLibrary(reqBody.libraryId)) {
Logger.warn(`[CollectionController] User "${req.user.username}" attempted to create collection in inaccessible library ${reqBody.libraryId}`)
return res.sendStatus(403)
}
const libraryItemIds = (reqBody.books || []).filter((b) => !!b && typeof b == 'string')
if (!libraryItemIds.length) {
return res.status(400).send('Invalid collection data. No books')
@@ -65,7 +72,7 @@ class CollectionController {
newCollection = await Database.collectionModel.create(
{
libraryId: reqBody.libraryId,
name: reqBody.name,
name: nameCleaned,
description: reqBody.description || null
},
{ transaction }
@@ -106,8 +113,9 @@ class CollectionController {
*/
async findAll(req, res) {
const collectionsExpanded = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user)
const accessibleCollections = collectionsExpanded.filter((c) => req.user.checkCanAccessLibrary(c.libraryId))
res.json({
collections: collectionsExpanded
collections: accessibleCollections
})
}
@@ -145,9 +153,12 @@ class CollectionController {
collectionUpdatePayload.description = req.body.description
wasUpdated = true
}
if (req.body.name !== undefined && req.body.name !== req.collection.name) {
collectionUpdatePayload.name = req.body.name
wasUpdated = true
if (req.body.name !== undefined && typeof req.body.name === 'string') {
const nameCleaned = htmlSanitizer.stripAllTags(req.body.name)
if (nameCleaned !== req.collection.name) {
collectionUpdatePayload.name = nameCleaned
wasUpdated = true
}
}
if (wasUpdated) {
@@ -425,6 +436,10 @@ class CollectionController {
if (!collection) {
return res.status(404).send('Collection not found')
}
if (!req.user.checkCanAccessLibrary(collection.libraryId)) {
Logger.warn(`[CollectionController] User "${req.user.username}" attempted to access collection ${collection.id} in inaccessible library ${collection.libraryId}`)
return res.status(404).send('Collection not found')
}
req.collection = collection
}
+1 -1
View File
@@ -117,7 +117,7 @@ class FileSystemController {
filepath = fileUtils.filePathToPOSIX(filepath)
// Ensure filepath is inside library folder (prevents directory traversal)
if (!filepath.startsWith(libraryFolder.path)) {
if (!fileUtils.isSameOrSubPath(libraryFolder.path, filepath)) {
Logger.error(`[FileSystemController] Filepath is not inside library folder: ${filepath}`)
return res.sendStatus(400)
}
+9 -4
View File
@@ -462,7 +462,7 @@ class LibraryController {
}
}
Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" from folder "${folder.path}"`)
await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds)
await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds, req.library.id)
}
if (authorIds.length) {
@@ -563,7 +563,7 @@ class LibraryController {
mediaItemIds.push(libraryItem.mediaId)
}
Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" from library "${req.library.name}"`)
await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds)
await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds, req.library.id)
}
// Set PlaybackSessions libraryId to null
@@ -714,7 +714,7 @@ class LibraryController {
}
}
Logger.info(`[LibraryController] Removing library item "${libraryItem.id}" with issue`)
await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds)
await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds, req.library.id)
}
if (authorIds.length) {
@@ -1435,10 +1435,15 @@ class LibraryController {
const libraryItems = await Database.libraryItemModel.findAll({
attributes: ['id', 'libraryId', 'path', 'isFile'],
where: {
id: itemIds
id: itemIds,
libraryId: req.library.id
}
})
if (libraryItems.length < itemIds.length) {
Logger.warn(`[LibraryController] User "${req.user.username}" requested ${itemIds.length} items but only ${libraryItems.length} are in library "${req.library.id}"`)
}
Logger.info(`[LibraryController] User "${req.user.username}" requested download for items "${itemIds}"`)
const filename = `LibraryItems-${Date.now()}.zip`
+55 -5
View File
@@ -1,13 +1,14 @@
const { Request, Response, NextFunction } = require('express')
const Path = require('path')
const fs = require('../libs/fsExtra')
const cron = require('../libs/nodeCron')
const uaParserJs = require('../libs/uaParser')
const Logger = require('../Logger')
const SocketAuthority = require('../SocketAuthority')
const Database = require('../Database')
const zipHelpers = require('../utils/zipHelpers')
const { reqSupportsWebp } = require('../utils/index')
const { reqSupportsWebp, clampPositiveInt } = require('../utils/index')
const { ScanResult, AudioMimeType } = require('../utils/constants')
const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils')
const LibraryItemScanner = require('../scanner/LibraryItemScanner')
@@ -36,6 +37,24 @@ const ShareManager = require('../managers/ShareManager')
* @typedef {RequestWithUser & RequestEntityObject & RequestLibraryFileObject} LibraryItemControllerRequestWithFile
*/
/**
* Enforce per-item access for batch item routes
*
* @param {RequestWithUser} req
* @param {Response} res
* @param {import('../models/LibraryItem')[]} libraryItems
* @returns {boolean} true if the user may access every item; false if 403 was sent
*/
function ensureUserCanAccessLibraryItemsForBatch(req, res, libraryItems) {
for (const libraryItem of libraryItems) {
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
res.sendStatus(403)
return false
}
}
return true
}
class LibraryItemController {
constructor() {}
@@ -111,7 +130,7 @@ class LibraryItemController {
}
}
await this.handleDeleteLibraryItem(req.libraryItem.id, mediaItemIds)
await this.handleDeleteLibraryItem(req.libraryItem.id, mediaItemIds, req.libraryItem.libraryId)
if (hardDelete) {
Logger.info(`[LibraryItemController] Deleting library item from file system at "${libraryItemPath}"`)
await fs.remove(libraryItemPath).catch((error) => {
@@ -202,6 +221,11 @@ class LibraryItemController {
} else if (mediaPayload.autoDownloadSchedule !== undefined && req.libraryItem.media.autoDownloadSchedule !== mediaPayload.autoDownloadSchedule) {
isPodcastAutoDownloadUpdated = true
}
if (mediaPayload.autoDownloadSchedule && !cron.validate(mediaPayload.autoDownloadSchedule)) {
Logger.error(`[LibraryItemController] Invalid auto download schedule cron expression "${mediaPayload.autoDownloadSchedule}" for library item "${req.libraryItem.media.title}"`)
return res.status(400).send('Invalid auto download schedule cron expression')
}
}
let hasUpdates = (await req.libraryItem.media.updateFromRequest(mediaPayload)) || mediaPayload.url
@@ -398,8 +422,8 @@ class LibraryItemController {
const options = {
format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'),
height: height ? parseInt(height) : null,
width: width ? parseInt(width) : null
height: clampPositiveInt(height ? parseInt(height) : null, 4096),
width: clampPositiveInt(width ? parseInt(width) : null, 4096)
}
return CacheManager.handleCoverCache(res, libraryItemId, options)
}
@@ -547,7 +571,13 @@ class LibraryItemController {
return res.sendStatus(404)
}
// Ensure user has permission to delete these library items
if (!ensureUserCanAccessLibraryItemsForBatch(req, res, itemsToDelete)) {
return
}
const libraryId = itemsToDelete[0].libraryId
for (const libraryItem of itemsToDelete) {
const libraryItemPath = libraryItem.path
Logger.info(`[LibraryItemController] (${hardDelete ? 'Hard' : 'Soft'}) deleting Library Item "${libraryItem.media.title}" with id "${libraryItem.id}"`)
@@ -565,7 +595,7 @@ class LibraryItemController {
authorIds.push(...libraryItem.media.authors.map((au) => au.id))
}
}
await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds)
await this.handleDeleteLibraryItem(libraryItem.id, mediaItemIds, libraryItem.libraryId)
if (hardDelete) {
Logger.info(`[LibraryItemController] Deleting library item from file system at "${libraryItemPath}"`)
await fs.remove(libraryItemPath).catch((error) => {
@@ -581,6 +611,7 @@ class LibraryItemController {
}
await Database.resetLibraryIssuesFilterData(libraryId)
res.sendStatus(200)
}
@@ -593,6 +624,11 @@ class LibraryItemController {
* @param {Response} res
*/
async batchUpdate(req, res) {
if (!req.user.canUpdate) {
Logger.warn(`[LibraryItemController] User "${req.user.username}" attempted to batch update without permission`)
return res.sendStatus(403)
}
const updatePayloads = req.body
if (!Array.isArray(updatePayloads) || !updatePayloads.length) {
Logger.error(`[LibraryItemController] Batch update failed. Invalid payload`)
@@ -615,6 +651,11 @@ class LibraryItemController {
return res.sendStatus(404)
}
// Ensure user has permission to update these library items
if (!ensureUserCanAccessLibraryItemsForBatch(req, res, libraryItems)) {
return
}
let itemsUpdated = 0
const seriesIdsRemoved = []
@@ -624,6 +665,11 @@ class LibraryItemController {
const mediaPayload = updatePayload.mediaPayload
const libraryItem = libraryItems.find((li) => li.id === updatePayload.id)
if (libraryItem.isPodcast && mediaPayload.autoDownloadSchedule && !cron.validate(mediaPayload.autoDownloadSchedule)) {
Logger.warn(`[LibraryItemController] Invalid auto download schedule cron expression "${mediaPayload.autoDownloadSchedule}" for library item "${libraryItem.media.title}" - skipping update`)
continue
}
let hasUpdates = await libraryItem.media.updateFromRequest(mediaPayload)
if (libraryItem.isBook && Array.isArray(mediaPayload.metadata?.series)) {
@@ -695,6 +741,10 @@ class LibraryItemController {
const libraryItems = await Database.libraryItemModel.findAllExpandedWhere({
id: libraryItemIds
})
// Ensure user has permission to access these library items
if (!ensureUserCanAccessLibraryItemsForBatch(req, res, libraryItems)) {
return
}
res.json({
libraryItems: libraryItems.map((li) => li.toOldJSONExpanded())
})
+44 -4
View File
@@ -63,7 +63,7 @@ class MeController {
* @param {Response} res
*/
async getItemListeningSessions(req, res) {
const libraryItem = await Database.libraryItemModel.findByPk(req.params.libraryItemId)
const libraryItem = await Database.libraryItemModel.getExpandedById(req.params.libraryItemId)
const episode = await Database.podcastEpisodeModel.findByPk(req.params.episodeId)
if (!libraryItem || (libraryItem.isPodcast && !episode)) {
@@ -71,6 +71,12 @@ class MeController {
return res.sendStatus(404)
}
// Check if user has access to this library item
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
Logger.error(`[MeController] User "${req.user.username}" attempted to access listening sessions for library item "${req.params.libraryItemId}" without access`)
return res.sendStatus(403)
}
const mediaItemId = episode?.id || libraryItem.mediaId
let listeningSessions = await this.getUserItemListeningSessionsHelper(req.user.id, mediaItemId)
@@ -125,6 +131,13 @@ class MeController {
* @param {Response} res
*/
async removeMediaProgress(req, res) {
// Verify the media progress belongs to the current user
const mediaProgress = req.user.mediaProgresses.find((mp) => mp.id === req.params.id)
if (!mediaProgress) {
Logger.error(`[MeController] Media progress not found or does not belong to user "${req.user.username}"`)
return res.sendStatus(404)
}
await Database.mediaProgressModel.removeById(req.params.id)
req.user.mediaProgresses = req.user.mediaProgresses.filter((mp) => mp.id !== req.params.id)
@@ -192,7 +205,16 @@ class MeController {
* @param {Response} res
*/
async createBookmark(req, res) {
if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404)
const libraryItem = await Database.libraryItemModel.getExpandedById(req.params.id)
if (!libraryItem) {
return res.sendStatus(404)
}
// Check if user has access to this library item
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
Logger.error(`[MeController] User "${req.user.username}" attempted to create bookmark for library item "${req.params.id}" without access`)
return res.sendStatus(403)
}
const { time, title } = req.body
if (isNullOrNaN(time)) {
@@ -216,7 +238,16 @@ class MeController {
* @param {Response} res
*/
async updateBookmark(req, res) {
if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404)
const libraryItem = await Database.libraryItemModel.getExpandedById(req.params.id)
if (!libraryItem) {
return res.sendStatus(404)
}
// Check if user has access to this library item
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
Logger.error(`[MeController] User "${req.user.username}" attempted to update bookmark for library item "${req.params.id}" without access`)
return res.sendStatus(403)
}
const { time, title } = req.body
if (isNullOrNaN(time)) {
@@ -245,7 +276,16 @@ class MeController {
* @param {Response} res
*/
async removeBookmark(req, res) {
if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404)
const libraryItem = await Database.libraryItemModel.getExpandedById(req.params.id)
if (!libraryItem) {
return res.sendStatus(404)
}
// Check if user has access to this library item
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
Logger.error(`[MeController] User "${req.user.username}" attempted to remove bookmark for library item "${req.params.id}" without access`)
return res.sendStatus(403)
}
const time = Number(req.params.time)
if (isNaN(time)) {
+5 -7
View File
@@ -8,7 +8,7 @@ const Database = require('../Database')
const Watcher = require('../Watcher')
const libraryItemFilters = require('../utils/queries/libraryItemFilters')
const patternValidation = require('../libs/nodeCron/pattern-validation')
const cron = require('../libs/nodeCron')
const { isObject, getTitleIgnorePrefix } = require('../utils/index')
const { sanitizeFilename } = require('../utils/fileUtils')
@@ -605,13 +605,11 @@ class MiscController {
return res.sendStatus(400)
}
try {
patternValidation(expression)
res.sendStatus(200)
} catch (error) {
Logger.warn(`[MiscController] Invalid cron expression ${expression}`, error.message)
res.status(400).send(error.message)
if (!cron.validate(expression)) {
Logger.warn(`[MiscController] Invalid cron expression ${expression}`)
return res.status(400).send('Invalid cron expression')
}
res.sendStatus(200)
}
/**
+23 -4
View File
@@ -2,6 +2,7 @@ const { Request, Response, NextFunction } = require('express')
const Logger = require('../Logger')
const SocketAuthority = require('../SocketAuthority')
const Database = require('../Database')
const htmlSanitizer = require('../utils/htmlSanitizer')
/**
* @typedef RequestUserObject
@@ -29,12 +30,17 @@ class PlaylistController {
const reqBody = req.body || {}
// Validation
if (!reqBody.name || !reqBody.libraryId) {
const nameCleaned = htmlSanitizer.stripAllTags(reqBody.name)
if (!nameCleaned || !reqBody.libraryId) {
return res.status(400).send('Invalid playlist data')
}
if (reqBody.description && typeof reqBody.description !== 'string') {
return res.status(400).send('Invalid playlist description')
}
if (!req.user.checkCanAccessLibrary(reqBody.libraryId)) {
Logger.warn(`[PlaylistController] User "${req.user.username}" attempted to create playlist in inaccessible library ${reqBody.libraryId}`)
return res.sendStatus(403)
}
const items = reqBody.items || []
const isPodcast = items.some((i) => i.episodeId)
const libraryItemIds = new Set()
@@ -84,7 +90,7 @@ class PlaylistController {
{
libraryId: reqBody.libraryId,
userId: req.user.id,
name: reqBody.name,
name: nameCleaned,
description: reqBody.description || null
},
{ transaction }
@@ -131,8 +137,9 @@ class PlaylistController {
*/
async findAllForUser(req, res) {
const playlistsForUser = await Database.playlistModel.getOldPlaylistsForUserAndLibrary(req.user.id)
const accessiblePlaylists = playlistsForUser.filter((p) => req.user.checkCanAccessLibrary(p.libraryId))
res.json({
playlists: playlistsForUser
playlists: accessiblePlaylists
})
}
@@ -174,7 +181,11 @@ class PlaylistController {
}
const playlistUpdatePayload = {}
if (reqBody.name) playlistUpdatePayload.name = reqBody.name
const nameCleaned = htmlSanitizer.stripAllTags(reqBody.name)
if (nameCleaned) {
playlistUpdatePayload.name = nameCleaned
}
if (reqBody.description) playlistUpdatePayload.description = reqBody.description
// Update name and description
@@ -502,6 +513,10 @@ class PlaylistController {
if (!collection) {
return res.status(404).send('Collection not found')
}
if (!req.user.checkCanAccessLibrary(collection.libraryId)) {
Logger.warn(`[PlaylistController] User "${req.user.username}" attempted to create playlist from collection ${collection.id} in inaccessible library ${collection.libraryId}`)
return res.status(404).send('Collection not found')
}
// Expand collection to get library items
const collectionExpanded = await collection.getOldJsonExpanded(req.user)
if (!collectionExpanded) {
@@ -567,6 +582,10 @@ class PlaylistController {
Logger.warn(`[PlaylistController] Playlist ${req.params.id} requested by user ${req.user.id} that is not the owner`)
return res.sendStatus(403)
}
if (!req.user.checkCanAccessLibrary(playlist.libraryId)) {
Logger.warn(`[PlaylistController] User "${req.user.username}" attempted to access playlist ${playlist.id} in inaccessible library ${playlist.libraryId}`)
return res.status(404).send('Playlist not found')
}
req.playlist = playlist
}
+24 -2
View File
@@ -5,9 +5,10 @@ const SocketAuthority = require('../SocketAuthority')
const Database = require('../Database')
const fs = require('../libs/fsExtra')
const cron = require('../libs/nodeCron')
const { getPodcastFeed, findMatchingEpisodes } = require('../utils/podcastUtils')
const { getFileTimestampsWithIno, filePathToPOSIX } = require('../utils/fileUtils')
const { getFileTimestampsWithIno, filePathToPOSIX, isSameOrSubPath } = require('../utils/fileUtils')
const { validateUrl } = require('../utils/index')
const htmlSanitizer = require('../utils/htmlSanitizer')
@@ -46,6 +47,11 @@ class PodcastController {
return res.status(400).send('Invalid request body. "media" and "media.metadata" are required')
}
if (payload.media.autoDownloadSchedule && !cron.validate(payload.media.autoDownloadSchedule)) {
Logger.error(`[PodcastController] Invalid auto download schedule cron expression "${payload.media.autoDownloadSchedule}"`)
return res.status(400).send('Invalid auto download schedule cron expression')
}
const library = await Database.libraryModel.findByIdWithFolders(payload.libraryId)
if (!library) {
Logger.error(`[PodcastController] Create: Library not found "${payload.libraryId}"`)
@@ -58,8 +64,18 @@ class PodcastController {
return res.status(404).send('Folder not found')
}
if (typeof payload.path !== 'string' || !payload.path.trim()) {
return res.status(400).send('Invalid request body. "path" must be a non-empty string')
}
const libraryFolderPath = filePathToPOSIX(folder.path)
const podcastPath = filePathToPOSIX(payload.path)
if (!isSameOrSubPath(libraryFolderPath, podcastPath)) {
Logger.error(`[PodcastController] Create: Podcast path is outside library folder "${libraryFolderPath}": "${podcastPath}"`)
return res.status(400).send('Podcast path must be inside the selected library folder')
}
// Check if a library item with this podcast folder exists already
const existingLibraryItem =
(await Database.libraryItemModel.count({
@@ -83,7 +99,7 @@ class PodcastController {
const libraryItemFolderStats = await getFileTimestampsWithIno(podcastPath)
let relPath = payload.path.replace(folder.fullPath, '')
let relPath = podcastPath.replace(libraryFolderPath, '')
if (relPath.startsWith('/')) relPath = relPath.slice(1)
let newLibraryItem = null
@@ -412,6 +428,12 @@ class PodcastController {
Logger.debug(`[PodcastController] Sanitized description from "${req.body[key]}" to "${sanitizedDescription}"`)
req.body[key] = sanitizedDescription
}
} else if (key === 'subtitle' && req.body[key]) {
const sanitizedSubtitle = htmlSanitizer.sanitize(req.body[key])
if (sanitizedSubtitle !== req.body[key]) {
Logger.debug(`[PodcastController] Sanitized subtitle from "${req.body[key]}" to "${sanitizedSubtitle}"`)
req.body[key] = sanitizedSubtitle
}
}
updatePayload[key] = req.body[key]
+4
View File
@@ -53,6 +53,10 @@ class ShareController {
if (playbackSession) {
if (mediaItemShare.id === playbackSession.mediaItemShareId) {
Logger.debug(`[ShareController] Found share playback session ${req.cookies.share_session_id}`)
// If ?t was provided, override the cached currentTime
if (startTime > 0 && startTime < playbackSession.duration) {
playbackSession.currentTime = startTime
}
mediaItemShare.playbackSession = playbackSession.toJSONForClient()
return res.json(mediaItemShare)
} else {
+4 -4
View File
@@ -227,7 +227,7 @@ class BookFinder {
title = this.#removeAuthorFromTitle(title)
const titleTransformers = [
[/([,:;_]| by ).*/g, ''], // Remove subtitle
[/(: |[,;_]| by ).*/g, ''], // Remove subtitle
[/(^| )\d+k(bps)?( |$)/, ' '], // Remove bitrate
[/ (2nd|3rd|\d+th)\s+ed(\.|ition)?/g, ''], // Remove edition
[/(^| |\.)(m4b|m4a|mp3)( |$)/g, ''], // Remove file-type
@@ -646,11 +646,11 @@ class BookFinder {
module.exports = new BookFinder()
function hasSubtitle(title) {
return title.includes(':') || title.includes(' - ')
return title.includes(': ') || title.includes(' - ')
}
function stripSubtitle(title) {
if (title.includes(':')) {
return title.split(':')[0].trim()
if (title.includes(': ')) {
return title.split(': ')[0].trim()
} else if (title.includes(' - ')) {
return title.split(' - ')[0].trim()
}
+43 -1
View File
@@ -5,6 +5,9 @@ const Database = require('../Database')
class ApiCacheManager {
defaultCacheOptions = { max: 1000, maxSize: 10 * 1000 * 1000, sizeCalculation: (item) => item.body.length + JSON.stringify(item.headers).length }
defaultTtlOptions = { ttl: 30 * 60 * 1000 }
highChurnModels = new Set(['session', 'mediaProgress', 'playbackSession', 'device'])
modelsInvalidatingPersonalized = new Set(['mediaProgress'])
modelsInvalidatingMe = new Set(['session', 'mediaProgress', 'playbackSession', 'device'])
constructor(cache = new LRUCache(this.defaultCacheOptions), ttlOptions = this.defaultTtlOptions) {
this.cache = cache
@@ -16,8 +19,47 @@ class ApiCacheManager {
hooks.forEach((hook) => database.sequelize.addHook(hook, (model) => this.clear(model, hook)))
}
getModelName(model) {
if (typeof model?.name === 'string') return model.name
if (typeof model?.model?.name === 'string') return model.model.name
if (typeof model?.constructor?.name === 'string' && model.constructor.name !== 'Object') return model.constructor.name
return 'unknown'
}
clearByUrlPattern(urlPattern) {
let removed = 0
for (const key of this.cache.keys()) {
try {
const parsed = JSON.parse(key)
if (typeof parsed?.url === 'string' && urlPattern.test(parsed.url)) {
if (this.cache.delete(key)) removed++
}
} catch {
if (this.cache.delete(key)) removed++
}
}
return removed
}
clearUserProgressSlices(modelName, hook) {
let removedPersonalized = 0
let removedRecentEpisodes = 0
if (this.modelsInvalidatingPersonalized.has(modelName)) {
removedPersonalized = this.clearByUrlPattern(/^\/libraries\/[^/]+\/personalized/)
removedRecentEpisodes = this.clearByUrlPattern(/^\/libraries\/[^/]+\/recent-episodes/)
}
const removedMe = this.modelsInvalidatingMe.has(modelName) ? this.clearByUrlPattern(/^\/me(\/|\?|$)/) : 0
Logger.debug(`[ApiCacheManager] ${modelName}.${hook}: cleared user-progress cache slices (personalized=${removedPersonalized}, recentEpisodes=${removedRecentEpisodes}, me=${removedMe})`)
}
clear(model, hook) {
Logger.debug(`[ApiCacheManager] ${model.constructor.name}.${hook}: Clearing cache`)
const modelName = this.getModelName(model)
if (this.highChurnModels.has(modelName)) {
this.clearUserProgressSlices(modelName, hook)
return
}
Logger.debug(`[ApiCacheManager] ${modelName}.${hook}: Clearing cache`)
this.cache.clear()
}
+38 -1
View File
@@ -126,13 +126,31 @@ class BackupManager {
} catch (error) {
// Not a valid zip file
Logger.error('[BackupManager] Failed to read backup file - backup might not be a valid .zip file', tempPath, error)
await zip.close().catch(() => {})
await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err))
return res.status(400).send('Failed to read backup file - backup might not be a valid .zip file')
}
if (!Object.keys(entries).includes('absdatabase.sqlite')) {
if (!entries['absdatabase.sqlite']) {
Logger.error(`[BackupManager] Invalid backup with no absdatabase.sqlite file - might be a backup created on an old Audiobookshelf server.`)
await zip.close().catch(() => {})
await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err))
return res.status(500).send('Invalid backup with no absdatabase.sqlite file - might be a backup created on an old Audiobookshelf server.')
}
const detailsEntry = entries['details']
if (!detailsEntry) {
Logger.error('[BackupManager] Invalid backup - missing details entry')
await zip.close().catch(() => {})
await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err))
return res.status(400).send('Invalid backup file - missing details entry')
}
if (detailsEntry.size > 1024 * 1024) {
Logger.error(`[BackupManager] Backup details entry too large: ${detailsEntry.size} bytes`)
await zip.close().catch(() => {})
await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err))
return res.status(400).send('Invalid backup file - details entry too large')
}
const data = await zip.entryData('details')
const details = data.toString('utf8').split('\n')
@@ -140,9 +158,13 @@ class BackupManager {
if (!backup.serverVersion) {
Logger.error(`[BackupManager] Invalid backup with no server version - might be a backup created before version 2.0.0`)
await zip.close().catch(() => {})
await fs.remove(tempPath).catch((err) => Logger.error(`[BackupManager] Failed to remove rejected backup file "${tempPath}"`, err))
return res.status(500).send('Invalid backup. Might be a backup created before version 2.0.0.')
}
await zip.close().catch(() => {})
backup.fileSize = await getFileSize(backup.fullPath)
const existingBackupIndex = this.backups.findIndex((b) => b.id === backup.id)
@@ -257,9 +279,24 @@ class BackupManager {
let data = null
try {
zip = new StreamZip.async({ file: fullFilePath })
const entries = await zip.entries()
const detailsEntry = entries['details']
if (!detailsEntry) {
Logger.error(`[BackupManager] Backup "${fullFilePath}" missing details entry - skipping`)
await zip.close().catch(() => {})
continue
}
if (detailsEntry.size > 1024 * 1024) {
Logger.error(`[BackupManager] Backup "${fullFilePath}" details entry too large (${detailsEntry.size} bytes) - skipping`)
await zip.close().catch(() => {})
continue
}
data = await zip.entryData('details')
} catch (error) {
Logger.error(`[BackupManager] Failed to unzip backup "${fullFilePath}"`, error)
if (zip) await zip.close().catch(() => {})
continue
}
+6 -1
View File
@@ -153,6 +153,11 @@ class CronManager {
startPodcastCron(expression, libraryItemIds) {
try {
if (!cron.validate(expression)) {
Logger.error(`[CronManager] Invalid auto download schedule cron expression "${expression}" - not starting podcast episode check cron`)
return
}
Logger.debug(`[CronManager] Scheduling podcast episode check cron "${expression}" for ${libraryItemIds.length} item(s)`)
const task = cron.schedule(expression, () => {
if (this.podcastCronExpressionsExecuting.includes(expression)) {
@@ -167,7 +172,7 @@ class CronManager {
task
})
} catch (error) {
Logger.error(`[PodcastManager] Failed to schedule podcast cron ${this.serverSettings.podcastEpisodeSchedule}`, error)
Logger.error(`[PodcastManager] Failed to schedule podcast cron ${expression}`, error)
}
}
+6
View File
@@ -2,6 +2,7 @@ const { Request, Response } = require('express')
const Path = require('path')
const Logger = require('../Logger')
const { getAudioMimeTypeFromExtname } = require('../utils/fileUtils')
const SocketAuthority = require('../SocketAuthority')
const Database = require('../Database')
@@ -216,6 +217,11 @@ class RssFeedManager {
res.sendStatus(404)
return
}
// Express does not set the correct mimetype for m4b files so use our defined mimetypes if available
const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(episodePath))
if (audioMimeType) {
res.setHeader('Content-Type', audioMimeType)
}
res.sendFile(episodePath)
}
@@ -0,0 +1,74 @@
/**
* @typedef MigrationContext
* @property {import('sequelize').QueryInterface} queryInterface
* @property {import('../Logger')} logger
*
* @typedef MigrationOptions
* @property {MigrationContext} context
*/
const migrationVersion = '2.33.0'
const migrationName = `${migrationVersion}-add-discover-query-indexes`
const loggerPrefix = `[${migrationVersion} migration]`
const indexes = [
{
table: 'mediaProgresses',
name: 'media_progresses_user_item_finished_time',
fields: ['userId', 'mediaItemId', 'isFinished', 'currentTime']
},
{
table: 'bookSeries',
name: 'book_series_series_book',
fields: ['seriesId', 'bookId']
}
]
async function up({ context: { queryInterface, logger } }) {
logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`)
for (const index of indexes) {
await addIndexIfMissing(queryInterface, logger, index)
}
logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`)
}
async function down({ context: { queryInterface, logger } }) {
logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`)
for (const index of indexes) {
await removeIndexIfExists(queryInterface, logger, index)
}
logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`)
}
async function addIndexIfMissing(queryInterface, logger, index) {
const existing = await queryInterface.showIndex(index.table)
if (existing.some((i) => i.name === index.name)) {
logger.info(`${loggerPrefix} index ${index.name} already exists on ${index.table}`)
return
}
logger.info(`${loggerPrefix} adding index ${index.name} on ${index.table}(${index.fields.join(', ')})`)
await queryInterface.addIndex(index.table, {
name: index.name,
fields: index.fields
})
logger.info(`${loggerPrefix} added index ${index.name}`)
}
async function removeIndexIfExists(queryInterface, logger, index) {
const existing = await queryInterface.showIndex(index.table)
if (!existing.some((i) => i.name === index.name)) {
logger.info(`${loggerPrefix} index ${index.name} does not exist on ${index.table}`)
return
}
logger.info(`${loggerPrefix} removing index ${index.name}`)
await queryInterface.removeIndex(index.table, index.name)
logger.info(`${loggerPrefix} removed index ${index.name}`)
}
module.exports = { up, down }
@@ -0,0 +1,84 @@
/**
* @typedef MigrationContext
* @property {import('sequelize').QueryInterface} queryInterface - a Sequelize QueryInterface object.
* @property {import('../Logger')} logger - a Logger object.
*
* @typedef MigrationOptions
* @property {MigrationContext} context - an object containing the migration context.
*/
const migrationVersion = '2.35.0'
const migrationName = `${migrationVersion}-add-last-refresh-token`
const loggerPrefix = `[${migrationVersion} migration]`
/**
* This migration script adds lastRefreshToken and lastRefreshTokenExpiresAt columns to the sessions table.
*
* @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 } }) {
logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`)
if (await queryInterface.tableExists('sessions')) {
const tableDescription = await queryInterface.describeTable('sessions')
if (!tableDescription.lastRefreshToken) {
logger.info(`${loggerPrefix} Adding lastRefreshToken column to sessions table`)
await queryInterface.addColumn('sessions', 'lastRefreshToken', {
type: queryInterface.sequelize.Sequelize.DataTypes.STRING,
allowNull: true
})
} else {
logger.info(`${loggerPrefix} lastRefreshToken column already exists in sessions table`)
}
if (!tableDescription.lastRefreshTokenExpiresAt) {
logger.info(`${loggerPrefix} Adding lastRefreshTokenExpiresAt column to sessions table`)
await queryInterface.addColumn('sessions', 'lastRefreshTokenExpiresAt', {
type: queryInterface.sequelize.Sequelize.DataTypes.DATE,
allowNull: true
})
} else {
logger.info(`${loggerPrefix} lastRefreshTokenExpiresAt column already exists in sessions table`)
}
} else {
logger.info(`${loggerPrefix} sessions table does not exist`)
}
logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`)
}
/**
* This migration script removes the lastRefreshToken and lastRefreshTokenExpiresAt columns from the sessions table.
*
* @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 } }) {
logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`)
if (await queryInterface.tableExists('sessions')) {
const tableDescription = await queryInterface.describeTable('sessions')
if (tableDescription.lastRefreshToken) {
logger.info(`${loggerPrefix} Removing lastRefreshToken column from sessions table`)
await queryInterface.removeColumn('sessions', 'lastRefreshToken')
} else {
logger.info(`${loggerPrefix} lastRefreshToken column does not exist in sessions table`)
}
if (tableDescription.lastRefreshTokenExpiresAt) {
logger.info(`${loggerPrefix} Removing lastRefreshTokenExpiresAt column from sessions table`)
await queryInterface.removeColumn('sessions', 'lastRefreshTokenExpiresAt')
} else {
logger.info(`${loggerPrefix} lastRefreshTokenExpiresAt column does not exist in sessions table`)
}
} else {
logger.info(`${loggerPrefix} sessions table does not exist`)
}
logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`)
}
module.exports = { up, down }
+4 -3
View File
@@ -111,16 +111,17 @@ class Author extends Model {
*
* @param {string} name
* @param {string} libraryId
* @returns {Promise<Author>}
* @returns {Promise<{ author: Author, created: boolean }>}
*/
static async findOrCreateByNameAndLibrary(name, libraryId) {
const author = await this.getByNameAndLibrary(name, libraryId)
if (author) return author
return this.create({
if (author) return { author, created: false }
const newAuthor = await this.create({
name,
lastFirst: this.getLastFirst(name),
libraryId
})
return { author: newAuthor, created: true }
}
/**
+12 -1
View File
@@ -4,6 +4,7 @@ const { getTitlePrefixAtEnd, getTitleIgnorePrefix } = require('../utils')
const parseNameString = require('../utils/parsers/parseNameString')
const htmlSanitizer = require('../utils/htmlSanitizer')
const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilters')
const SocketAuthority = require('../SocketAuthority')
/**
* @typedef EBookFileObject
@@ -470,13 +471,23 @@ class Book extends Model {
for (const author of authorsRemoved) {
await bookAuthorModel.removeByIds(author.id, this.id)
const numBooks = await bookAuthorModel.getCountForAuthor(author.id)
if (numBooks > 0) {
SocketAuthority.emitter('author_updated', author.toOldJSONExpanded(numBooks))
}
Logger.debug(`[Book] "${this.title}" Removed author "${author.name}"`)
this.authors = this.authors.filter((au) => au.id !== author.id)
}
const authorsAdded = []
for (const authorName of newAuthorNames) {
const author = await authorModel.findOrCreateByNameAndLibrary(authorName, libraryId)
const { author, created } = await authorModel.findOrCreateByNameAndLibrary(authorName, libraryId)
await bookAuthorModel.create({ bookId: this.id, authorId: author.id })
if (created) {
SocketAuthority.emitter('author_added', author.toOldJSON())
} else {
const numBooks = await bookAuthorModel.getCountForAuthor(author.id)
SocketAuthority.emitter('author_updated', author.toOldJSONExpanded(numBooks))
}
Logger.debug(`[Book] "${this.title}" Added author "${author.name}"`)
this.authors.push(author)
authorsAdded.push(author)
+4
View File
@@ -48,6 +48,10 @@ class BookSeries extends Model {
{
name: 'bookSeries_seriesId',
fields: ['seriesId']
},
{
name: 'book_series_series_book',
fields: ['seriesId', 'bookId']
}
]
}
+130 -72
View File
@@ -340,6 +340,15 @@ class LibraryItem extends Model {
const shelves = []
const timed = async (loader) => {
const start = Date.now()
const payload = await loader()
return {
payload,
elapsedSeconds: ((Date.now() - start) / 1000).toFixed(2)
}
}
// "Continue Listening" shelf
const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, user, include, limit, false)
if (itemsInProgressPayload.items.length) {
@@ -371,11 +380,18 @@ class LibraryItem extends Model {
}
Logger.debug(`Loaded ${itemsInProgressPayload.items.length} of ${itemsInProgressPayload.count} items for "Continue Listening/Reading" in ${((Date.now() - fullStart) / 1000).toFixed(2)}s`)
let start = Date.now()
if (library.isBook) {
start = Date.now()
const [continueSeriesResult, mostRecentResult, seriesMostRecentResult, discoverResult, mediaFinishedResult, newestAuthorsResult] = await Promise.all([
timed(() => libraryFilters.getLibraryItemsContinueSeries(library, user, include, limit)),
timed(() => libraryFilters.getLibraryItemsMostRecentlyAdded(library, user, include, limit)),
timed(() => libraryFilters.getSeriesMostRecentlyAdded(library, user, include, 5)),
timed(() => libraryFilters.getLibraryItemsToDiscover(library, user, include, limit)),
timed(() => libraryFilters.getMediaFinished(library, user, include, limit)),
timed(() => libraryFilters.getNewestAuthors(library, user, limit))
])
const continueSeriesPayload = continueSeriesResult.payload
// "Continue Series" shelf
const continueSeriesPayload = await libraryFilters.getLibraryItemsContinueSeries(library, user, include, limit)
if (continueSeriesPayload.libraryItems.length) {
shelves.push({
id: 'continue-series',
@@ -386,42 +402,24 @@ class LibraryItem extends Model {
total: continueSeriesPayload.count
})
}
Logger.debug(`Loaded ${continueSeriesPayload.libraryItems.length} of ${continueSeriesPayload.count} items for "Continue Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
} else if (library.isPodcast) {
// "Newest Episodes" shelf
const newestEpisodesPayload = await libraryFilters.getNewestPodcastEpisodes(library, user, limit)
if (newestEpisodesPayload.libraryItems.length) {
Logger.debug(`Loaded ${continueSeriesPayload.libraryItems.length} of ${continueSeriesPayload.count} items for "Continue Series" in ${continueSeriesResult.elapsedSeconds}s`)
const mostRecentPayload = mostRecentResult.payload
// "Recently Added" shelf
if (mostRecentPayload.libraryItems.length) {
shelves.push({
id: 'newest-episodes',
label: 'Newest Episodes',
labelStringKey: 'LabelNewestEpisodes',
type: 'episode',
entities: newestEpisodesPayload.libraryItems,
total: newestEpisodesPayload.count
id: 'recently-added',
label: 'Recently Added',
labelStringKey: 'LabelRecentlyAdded',
type: library.mediaType,
entities: mostRecentPayload.libraryItems,
total: mostRecentPayload.count
})
}
Logger.debug(`Loaded ${newestEpisodesPayload.libraryItems.length} of ${newestEpisodesPayload.count} episodes for "Newest Episodes" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
}
Logger.debug(`Loaded ${mostRecentPayload.libraryItems.length} of ${mostRecentPayload.count} items for "Recently Added" in ${mostRecentResult.elapsedSeconds}s`)
start = Date.now()
// "Recently Added" shelf
const mostRecentPayload = await libraryFilters.getLibraryItemsMostRecentlyAdded(library, user, include, limit)
if (mostRecentPayload.libraryItems.length) {
shelves.push({
id: 'recently-added',
label: 'Recently Added',
labelStringKey: 'LabelRecentlyAdded',
type: library.mediaType,
entities: mostRecentPayload.libraryItems,
total: mostRecentPayload.count
})
}
Logger.debug(`Loaded ${mostRecentPayload.libraryItems.length} of ${mostRecentPayload.count} items for "Recently Added" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
if (library.isBook) {
start = Date.now()
const seriesMostRecentPayload = seriesMostRecentResult.payload
// "Recent Series" shelf
const seriesMostRecentPayload = await libraryFilters.getSeriesMostRecentlyAdded(library, user, include, 5)
if (seriesMostRecentPayload.series.length) {
shelves.push({
id: 'recent-series',
@@ -432,11 +430,10 @@ class LibraryItem extends Model {
total: seriesMostRecentPayload.count
})
}
Logger.debug(`Loaded ${seriesMostRecentPayload.series.length} of ${seriesMostRecentPayload.count} series for "Recent Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
Logger.debug(`Loaded ${seriesMostRecentPayload.series.length} of ${seriesMostRecentPayload.count} series for "Recent Series" in ${seriesMostRecentResult.elapsedSeconds}s`)
start = Date.now()
const discoverLibraryItemsPayload = discoverResult.payload
// "Discover" shelf
const discoverLibraryItemsPayload = await libraryFilters.getLibraryItemsToDiscover(library, user, include, limit)
if (discoverLibraryItemsPayload.libraryItems.length) {
shelves.push({
id: 'discover',
@@ -447,45 +444,41 @@ class LibraryItem extends Model {
total: discoverLibraryItemsPayload.count
})
}
Logger.debug(`Loaded ${discoverLibraryItemsPayload.libraryItems.length} of ${discoverLibraryItemsPayload.count} items for "Discover" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
}
Logger.debug(`Loaded ${discoverLibraryItemsPayload.libraryItems.length} of ${discoverLibraryItemsPayload.count} items for "Discover" in ${discoverResult.elapsedSeconds}s`)
start = Date.now()
// "Listen Again" shelf
const mediaFinishedPayload = await libraryFilters.getMediaFinished(library, user, include, limit)
if (mediaFinishedPayload.items.length) {
const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.ebookFormat && !li.media.numTracks)
const audioItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.numTracks || li.mediaType === 'podcast')
const mediaFinishedPayload = mediaFinishedResult.payload
// "Listen Again" shelf
if (mediaFinishedPayload.items.length) {
const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.ebookFormat && !li.media.numTracks)
const audioItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.numTracks || li.mediaType === 'podcast')
if (audioItemsInProgress.length) {
shelves.push({
id: 'listen-again',
label: 'Listen Again',
labelStringKey: 'LabelListenAgain',
type: library.isPodcast ? 'episode' : 'book',
entities: audioItemsInProgress,
total: mediaFinishedPayload.count
})
if (audioItemsInProgress.length) {
shelves.push({
id: 'listen-again',
label: 'Listen Again',
labelStringKey: 'LabelListenAgain',
type: library.isPodcast ? 'episode' : 'book',
entities: audioItemsInProgress,
total: mediaFinishedPayload.count
})
}
if (ebookOnlyItemsInProgress.length) {
// "Read Again" shelf
shelves.push({
id: 'read-again',
label: 'Read Again',
labelStringKey: 'LabelReadAgain',
type: 'book',
entities: ebookOnlyItemsInProgress,
total: mediaFinishedPayload.count
})
}
}
Logger.debug(`Loaded ${mediaFinishedPayload.items.length} of ${mediaFinishedPayload.count} items for "Listen/Read Again" in ${mediaFinishedResult.elapsedSeconds}s`)
// "Read Again" shelf
if (ebookOnlyItemsInProgress.length) {
shelves.push({
id: 'read-again',
label: 'Read Again',
labelStringKey: 'LabelReadAgain',
type: 'book',
entities: ebookOnlyItemsInProgress,
total: mediaFinishedPayload.count
})
}
}
Logger.debug(`Loaded ${mediaFinishedPayload.items.length} of ${mediaFinishedPayload.count} items for "Listen/Read Again" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
if (library.isBook) {
start = Date.now()
const newestAuthorsPayload = newestAuthorsResult.payload
// "Newest Authors" shelf
const newestAuthorsPayload = await libraryFilters.getNewestAuthors(library, user, limit)
if (newestAuthorsPayload.authors.length) {
shelves.push({
id: 'newest-authors',
@@ -496,7 +489,72 @@ class LibraryItem extends Model {
total: newestAuthorsPayload.count
})
}
Logger.debug(`Loaded ${newestAuthorsPayload.authors.length} of ${newestAuthorsPayload.count} authors for "Newest Authors" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
Logger.debug(`Loaded ${newestAuthorsPayload.authors.length} of ${newestAuthorsPayload.count} authors for "Newest Authors" in ${newestAuthorsResult.elapsedSeconds}s`)
} else if (library.isPodcast) {
const [newestEpisodesResult, mostRecentResult, mediaFinishedResult] = await Promise.all([
timed(() => libraryFilters.getNewestPodcastEpisodes(library, user, limit)),
timed(() => libraryFilters.getLibraryItemsMostRecentlyAdded(library, user, include, limit)),
timed(() => libraryFilters.getMediaFinished(library, user, include, limit))
])
const newestEpisodesPayload = newestEpisodesResult.payload
// "Newest Episodes" shelf
if (newestEpisodesPayload.libraryItems.length) {
shelves.push({
id: 'newest-episodes',
label: 'Newest Episodes',
labelStringKey: 'LabelNewestEpisodes',
type: 'episode',
entities: newestEpisodesPayload.libraryItems,
total: newestEpisodesPayload.count
})
}
Logger.debug(`Loaded ${newestEpisodesPayload.libraryItems.length} of ${newestEpisodesPayload.count} episodes for "Newest Episodes" in ${newestEpisodesResult.elapsedSeconds}s`)
const mostRecentPayload = mostRecentResult.payload
// "Recently Added" shelf
if (mostRecentPayload.libraryItems.length) {
shelves.push({
id: 'recently-added',
label: 'Recently Added',
labelStringKey: 'LabelRecentlyAdded',
type: library.mediaType,
entities: mostRecentPayload.libraryItems,
total: mostRecentPayload.count
})
}
Logger.debug(`Loaded ${mostRecentPayload.libraryItems.length} of ${mostRecentPayload.count} items for "Recently Added" in ${mostRecentResult.elapsedSeconds}s`)
const mediaFinishedPayload = mediaFinishedResult.payload
// "Listen Again" shelf
if (mediaFinishedPayload.items.length) {
const ebookOnlyItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.ebookFormat && !li.media.numTracks)
const audioItemsInProgress = mediaFinishedPayload.items.filter((li) => li.media.numTracks || li.mediaType === 'podcast')
if (audioItemsInProgress.length) {
shelves.push({
id: 'listen-again',
label: 'Listen Again',
labelStringKey: 'LabelListenAgain',
type: 'episode',
entities: audioItemsInProgress,
total: mediaFinishedPayload.count
})
}
if (ebookOnlyItemsInProgress.length) {
// "Read Again" shelf
shelves.push({
id: 'read-again',
label: 'Read Again',
labelStringKey: 'LabelReadAgain',
type: 'book',
entities: ebookOnlyItemsInProgress,
total: mediaFinishedPayload.count
})
}
}
Logger.debug(`Loaded ${mediaFinishedPayload.items.length} of ${mediaFinishedPayload.count} items for "Listen/Read Again" in ${mediaFinishedResult.elapsedSeconds}s`)
}
Logger.debug(`Loaded ${shelves.length} personalized shelves in ${((Date.now() - fullStart) / 1000).toFixed(2)}s`)
+4
View File
@@ -80,6 +80,10 @@ class MediaProgress extends Model {
indexes: [
{
fields: ['updatedAt']
},
{
name: 'media_progresses_user_item_finished_time',
fields: ['userId', 'mediaItemId', 'isFinished', 'currentTime']
}
]
}
+6 -1
View File
@@ -78,6 +78,7 @@ class Podcast extends Model {
*/
static async createFromRequest(payload, transaction) {
const title = typeof payload.metadata.title === 'string' ? payload.metadata.title : null
// cron expression validated in controller
const autoDownloadSchedule = typeof payload.autoDownloadSchedule === 'string' ? payload.autoDownloadSchedule : null
const genres = Array.isArray(payload.metadata.genres) && payload.metadata.genres.every((g) => typeof g === 'string' && g.length) ? payload.metadata.genres : []
const tags = Array.isArray(payload.tags) && payload.tags.every((t) => typeof t === 'string' && t.length) ? payload.tags : []
@@ -89,6 +90,9 @@ class Podcast extends Model {
}
})
const rawDescription = typeof payload.metadata.description === 'string' ? payload.metadata.description : null
const description = rawDescription ? htmlSanitizer.sanitize(rawDescription) : null
return this.create(
{
title,
@@ -97,7 +101,7 @@ class Podcast extends Model {
releaseDate: typeof payload.metadata.releaseDate === 'string' ? payload.metadata.releaseDate : null,
feedURL: typeof payload.metadata.feedUrl === 'string' ? payload.metadata.feedUrl : null,
imageURL: typeof payload.metadata.imageUrl === 'string' ? payload.metadata.imageUrl : null,
description: typeof payload.metadata.description === 'string' ? payload.metadata.description : null,
description,
itunesPageURL: typeof payload.metadata.itunesPageUrl === 'string' ? payload.metadata.itunesPageUrl : null,
itunesId: typeof payload.metadata.itunesId === 'string' ? payload.metadata.itunesId : null,
itunesArtistId: typeof payload.metadata.itunesArtistId === 'string' ? payload.metadata.itunesArtistId : null,
@@ -270,6 +274,7 @@ class Podcast extends Model {
hasUpdates = true
}
if (typeof payload.autoDownloadSchedule === 'string' && payload.autoDownloadSchedule !== this.autoDownloadSchedule) {
// cron expression validated in controller
this.autoDownloadSchedule = payload.autoDownloadSchedule
hasUpdates = true
}
+12
View File
@@ -18,6 +18,10 @@ class Session extends Model {
this.userId
/** @type {Date} */
this.expiresAt
/** @type {string} */
this.lastRefreshToken
/** @type {Date} */
this.lastRefreshTokenExpiresAt
// Expanded properties
@@ -66,6 +70,14 @@ class Session extends Model {
expiresAt: {
type: DataTypes.DATE,
allowNull: false
},
lastRefreshToken: {
type: DataTypes.STRING,
allowNull: true
},
lastRefreshTokenExpiresAt: {
type: DataTypes.DATE,
allowNull: true
}
},
{
+9 -10
View File
@@ -352,11 +352,7 @@ class User extends Model {
if (cachedUser) return cachedUser
const user = await this.findOne({
where: {
username: {
[sequelize.Op.like]: username
}
},
where: sequelize.where(sequelize.fn('lower', sequelize.col('username')), username.toLowerCase()),
include: this.sequelize.models.mediaProgress
})
@@ -377,11 +373,7 @@ class User extends Model {
if (cachedUser) return cachedUser
const user = await this.findOne({
where: {
email: {
[sequelize.Op.like]: email
}
},
where: sequelize.where(sequelize.fn('lower', sequelize.col('email')), email.toLowerCase()),
include: this.sequelize.models.mediaProgress
})
@@ -782,7 +774,14 @@ class User extends Model {
error: 'Library item not found',
statusCode: 404
}
} else if (libraryItem.mediaType !== 'book') {
Logger.error(`[User] createUpdateMediaProgress: library item ${progressPayload.libraryItemId} is not a book`)
return {
error: 'Library item is not a book',
statusCode: 400
}
}
mediaItemId = libraryItem.media.id
mediaProgress = libraryItem.media.mediaProgresses?.[0]
}
+20 -21
View File
@@ -1,6 +1,10 @@
const uuidv4 = require("uuid").v4
const uuidv4 = require('uuid').v4
const { stripAllTags } = require('../utils/htmlSanitizer')
class DeviceInfo {
/** @type {string[]} Fields to sanitize when loading from stored data */
static stringFields = ['deviceId', 'clientVersion', 'manufacturer', 'model', 'sdkVersion', 'clientName', 'deviceName']
constructor(deviceInfo = null) {
this.id = null
this.userId = null
@@ -31,7 +35,7 @@ class DeviceInfo {
construct(deviceInfo) {
for (const key in deviceInfo) {
if (deviceInfo[key] !== undefined && this[key] !== undefined) {
this[key] = deviceInfo[key]
this[key] = DeviceInfo.stringFields.includes(key) ? stripAllTags(deviceInfo[key]) : deviceInfo[key]
}
}
}
@@ -63,7 +67,8 @@ class DeviceInfo {
}
get deviceDescription() {
if (this.model) { // Set from mobile apps
if (this.model) {
// Set from mobile apps
if (this.sdkVersion) return `${this.model} SDK ${this.sdkVersion} / v${this.clientVersion}`
return `${this.model} / v${this.clientVersion}`
}
@@ -72,18 +77,7 @@ class DeviceInfo {
// When client doesn't send a device id
getTempDeviceId() {
const keys = [
this.userId,
this.browserName,
this.browserVersion,
this.osName,
this.osVersion,
this.clientVersion,
this.manufacturer,
this.model,
this.sdkVersion,
this.ipAddress
].map(k => k || '')
const keys = [this.userId, this.browserName, this.browserVersion, this.osName, this.osVersion, this.clientVersion, this.manufacturer, this.model, this.sdkVersion, this.ipAddress].map((k) => k || '')
return 'temp-' + Buffer.from(keys.join('-'), 'utf-8').toString('base64')
}
@@ -99,12 +93,17 @@ class DeviceInfo {
this.osVersion = ua?.os.version || null
this.deviceType = ua?.device.type || null
this.clientVersion = clientDeviceInfo?.clientVersion || serverVersion
this.manufacturer = clientDeviceInfo?.manufacturer || null
this.model = clientDeviceInfo?.model || null
this.sdkVersion = clientDeviceInfo?.sdkVersion || null
this.clientVersion = stripAllTags(clientDeviceInfo?.clientVersion) || serverVersion
this.manufacturer = stripAllTags(clientDeviceInfo?.manufacturer) || null
this.model = stripAllTags(clientDeviceInfo?.model) || null
this.clientName = clientDeviceInfo?.clientName || null
if (typeof clientDeviceInfo?.sdkVersion === 'number') {
this.sdkVersion = clientDeviceInfo.sdkVersion.toString()
} else {
this.sdkVersion = stripAllTags(clientDeviceInfo?.sdkVersion) || null
}
this.clientName = stripAllTags(clientDeviceInfo?.clientName) || null
if (this.sdkVersion) {
if (!this.clientName) this.clientName = 'Abs Android'
this.deviceName = `${this.manufacturer || 'Unknown'} ${this.model || ''}`
@@ -149,4 +148,4 @@ class DeviceInfo {
return hasUpdates
}
}
module.exports = DeviceInfo
module.exports = DeviceInfo
+2 -1
View File
@@ -110,7 +110,8 @@ class PlaybackSession {
startedAt: this.startedAt,
updatedAt: this.updatedAt,
audioTracks: this.audioTracks.map((at) => at.toJSON?.() || { ...at }),
libraryItem: libraryItem?.toOldJSONExpanded() || null
libraryItem: libraryItem?.toOldJSONExpanded() || null,
coverAspectRatio: this.coverAspectRatio !== null ? this.coverAspectRatio : undefined // Used for share sessions
}
}
+1 -1
View File
@@ -73,7 +73,7 @@ class Stream extends EventEmitter {
return [AudioMimeType.FLAC, AudioMimeType.OPUS, AudioMimeType.WMA, AudioMimeType.AIFF, AudioMimeType.WEBM, AudioMimeType.WEBMA, AudioMimeType.AWB, AudioMimeType.CAF]
}
get codecsToForceAAC() {
return ['alac', 'ac3', 'eac3']
return ['alac', 'ac3', 'eac3', 'opus']
}
get userToken() {
return this.user.token
+6 -2
View File
@@ -3,6 +3,7 @@ const packageJson = require('../../../package.json')
const { BookshelfView } = require('../../utils/constants')
const Logger = require('../../Logger')
const User = require('../../models/User')
const { sanitize } = require('../../utils/htmlSanitizer')
class ServerSettings {
constructor(settings) {
@@ -126,7 +127,7 @@ class ServerSettings {
this.version = settings.version || null
this.buildNumber = settings.buildNumber || 0 // Added v2.4.5
this.authLoginCustomMessage = settings.authLoginCustomMessage || null // Added v2.8.0
this.authLoginCustomMessage = sanitize(settings.authLoginCustomMessage) || null // Added v2.8.0
this.authActiveAuthMethods = settings.authActiveAuthMethods || ['local']
this.authOpenIDIssuerURL = settings.authOpenIDIssuerURL || null
@@ -309,7 +310,7 @@ class ServerSettings {
get authFormData() {
const clientFormData = {
authLoginCustomMessage: this.authLoginCustomMessage
authLoginCustomMessage: sanitize(this.authLoginCustomMessage)
}
if (this.authActiveAuthMethods.includes('openid')) {
clientFormData.authOpenIDButtonText = this.authOpenIDButtonText
@@ -327,6 +328,9 @@ class ServerSettings {
update(payload) {
let hasUpdates = false
for (const key in payload) {
if (key === 'authLoginCustomMessage') {
payload[key] = sanitize(payload[key])
}
if (key === 'sortingPrefixes') {
// Sorting prefixes are updated with the /api/sorting-prefixes endpoint
continue
+9 -4
View File
@@ -57,8 +57,13 @@ class Audible {
})
}
const genresFiltered = genres ? genres.filter((g) => g.type == 'genre').map((g) => g.name) : []
const tagsFiltered = genres ? genres.filter((g) => g.type == 'tag').map((g) => g.name) : []
let genresCleaned = []
let tagsCleaned = []
if (genres && Array.isArray(genres)) {
genresCleaned = [...new Set(genres.filter((g) => g.type == 'genre').map((g) => g.name))]
tagsCleaned = [...new Set(genres.filter((g) => g.type == 'tag').map((g) => g.name))]
}
return {
title,
@@ -71,8 +76,8 @@ class Audible {
cover: image,
asin,
isbn,
genres: genresFiltered.length ? genresFiltered : null,
tags: tagsFiltered.length ? tagsFiltered.join(', ') : null,
genres: genresCleaned.length ? genresCleaned : null,
tags: tagsCleaned.length ? tagsCleaned : null,
series: series.length ? series : null,
language: language ? language.charAt(0).toUpperCase() + language.slice(1) : null,
duration: runtimeLengthMin && !isNaN(runtimeLengthMin) ? Number(runtimeLengthMin) : 0,
+23 -2
View File
@@ -89,6 +89,27 @@ class CustomProviderAdapter {
})
.filter((s) => s !== undefined)
}
/**
* Validates and dedupes tags/genres array
* Can be comma separated string or array of strings
* @param {string|string[]} tagsGenres
* @returns {string[]}
*/
const validateTagsGenresArray = (tagsGenres) => {
if (!tagsGenres || (typeof tagsGenres !== 'string' && !Array.isArray(tagsGenres))) return undefined
// If string, split by comma and trim each item
if (typeof tagsGenres === 'string') tagsGenres = tagsGenres.split(',')
// If array, ensure all items are strings
else if (!tagsGenres.every((t) => typeof t === 'string')) return undefined
// Trim and filter out empty strings
tagsGenres = tagsGenres.map((t) => t.trim()).filter(Boolean)
if (!tagsGenres.length) return undefined
// Dedup
return [...new Set(tagsGenres)]
}
// re-map keys to throw out
return matches.map((match) => {
@@ -105,8 +126,8 @@ class CustomProviderAdapter {
cover: toStringOrUndefined(cover),
isbn: toStringOrUndefined(isbn),
asin: toStringOrUndefined(asin),
genres: Array.isArray(genres) && genres.every((g) => typeof g === 'string') ? genres : undefined,
tags: toStringOrUndefined(tags),
genres: validateTagsGenresArray(genres),
tags: validateTagsGenresArray(tags),
series: validateSeriesArray(series),
language: toStringOrUndefined(language),
duration: !isNaN(duration) && duration !== null ? Number(duration) : undefined
+4 -2
View File
@@ -363,8 +363,9 @@ class ApiRouter {
* Remove library item and associated entities
* @param {string} libraryItemId
* @param {string[]} mediaItemIds array of bookId or podcastEpisodeId
* @param {string} libraryId
*/
async handleDeleteLibraryItem(libraryItemId, mediaItemIds) {
async handleDeleteLibraryItem(libraryItemId, mediaItemIds, libraryId) {
const numProgressRemoved = await Database.mediaProgressModel.destroy({
where: {
mediaItemId: mediaItemIds
@@ -395,7 +396,8 @@ class ApiRouter {
await Database.libraryItemModel.removeById(libraryItemId)
SocketAuthority.emitter('item_removed', {
id: libraryItemId
id: libraryItemId,
libraryId
})
}
+15 -14
View File
@@ -5,15 +5,15 @@ const { LogLevel } = require('../utils/constants')
const abmetadataGenerator = require('../utils/generators/abmetadataGenerator')
class AbsMetadataFileScanner {
constructor() { }
constructor() {}
/**
* Check for metadata.json file and set book metadata
*
* @param {import('./LibraryScan')} libraryScan
* @param {import('./LibraryItemScanData')} libraryItemData
* @param {Object} bookMetadata
* @param {string} [existingLibraryItemId]
*
* @param {import('./LibraryScan')} libraryScan
* @param {import('./LibraryItemScanData')} libraryItemData
* @param {Object} bookMetadata
* @param {string} [existingLibraryItemId]
*/
async scanBookMetadataFile(libraryScan, libraryItemData, bookMetadata, existingLibraryItemId = null) {
const metadataLibraryFile = libraryItemData.metadataJsonLibraryFile
@@ -32,7 +32,8 @@ class AbsMetadataFileScanner {
if (metadataText) {
libraryScan.addLog(LogLevel.INFO, `Found metadata file "${metadataFilePath}"`)
const abMetadata = abmetadataGenerator.parseJson(metadataText) || {}
const abMetadata = abmetadataGenerator.parseJson(metadataText, 'book') || {}
for (const key in abMetadata) {
// TODO: When to override with null or empty arrays?
if (abMetadata[key] === undefined || abMetadata[key] === null) continue
@@ -48,11 +49,11 @@ class AbsMetadataFileScanner {
/**
* Check for metadata.json file and set podcast metadata
*
* @param {import('./LibraryScan')} libraryScan
* @param {import('./LibraryItemScanData')} libraryItemData
* @param {Object} podcastMetadata
* @param {string} [existingLibraryItemId]
*
* @param {import('./LibraryScan')} libraryScan
* @param {import('./LibraryItemScanData')} libraryItemData
* @param {Object} podcastMetadata
* @param {string} [existingLibraryItemId]
*/
async scanPodcastMetadataFile(libraryScan, libraryItemData, podcastMetadata, existingLibraryItemId = null) {
const metadataLibraryFile = libraryItemData.metadataJsonLibraryFile
@@ -71,7 +72,7 @@ class AbsMetadataFileScanner {
if (metadataText) {
libraryScan.addLog(LogLevel.INFO, `Found metadata file "${metadataFilePath}"`)
const abMetadata = abmetadataGenerator.parseJson(metadataText) || {}
const abMetadata = abmetadataGenerator.parseJson(metadataText, 'podcast') || {}
for (const key in abMetadata) {
if (abMetadata[key] === undefined || abMetadata[key] === null) continue
if (key === 'tags' && !abMetadata.tags?.length) continue
@@ -81,4 +82,4 @@ class AbsMetadataFileScanner {
}
}
}
module.exports = new AbsMetadataFileScanner()
module.exports = new AbsMetadataFileScanner()
+8
View File
@@ -7,6 +7,7 @@ const parseNameString = require('../utils/parsers/parseNameString')
const parseEbookMetadata = require('../utils/parsers/parseEbookMetadata')
const globals = require('../utils/globals')
const { readTextFile, filePathToPOSIX, getFileTimestampsWithIno } = require('../utils/fileUtils')
const htmlSanitizer = require('../utils/htmlSanitizer')
const AudioFileScanner = require('./AudioFileScanner')
const Database = require('../Database')
@@ -688,6 +689,10 @@ class BookScanner {
bookMetadata.titleIgnorePrefix = getTitleIgnorePrefix(bookMetadata.title)
if (typeof bookMetadata.description === 'string' && bookMetadata.description) {
bookMetadata.description = htmlSanitizer.sanitize(bookMetadata.description)
}
return bookMetadata
}
@@ -820,6 +825,9 @@ class BookScanner {
const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
/**
* Keys must match abmetadataGenerator.js
*/
const jsonObject = {
tags: libraryItem.media.tags || [],
chapters: libraryItem.media.chapters?.map((c) => ({ ...c })) || [],
+8
View File
@@ -11,6 +11,7 @@ const LibraryFile = require('../objects/files/LibraryFile')
const fsExtra = require('../libs/fsExtra')
const PodcastEpisode = require('../models/PodcastEpisode')
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
const htmlSanitizer = require('../utils/htmlSanitizer')
/**
* Metadata for podcasts pulled from files
@@ -398,6 +399,10 @@ class PodcastScanner {
podcastMetadata.titleIgnorePrefix = getTitleIgnorePrefix(podcastMetadata.title)
if (typeof podcastMetadata.description === 'string' && podcastMetadata.description) {
podcastMetadata.description = htmlSanitizer.sanitize(podcastMetadata.description)
}
return podcastMetadata
}
@@ -420,6 +425,9 @@ class PodcastScanner {
const metadataFilePath = Path.join(metadataPath, `metadata.${global.ServerSettings.metadataFileFormat}`)
/**
* Keys must match abmetadataGenerator.js
*/
const jsonObject = {
tags: libraryItem.media.tags || [],
title: libraryItem.media.title,

Some files were not shown because too many files have changed in this diff Show More