Compare commits

..

527 Commits

Author SHA1 Message Date
advplyr
c7c21cc137 Version bump v2.29.0 2025-08-25 17:10:29 -05:00
advplyr
7e4c7a7e3b Merge pull request #4618 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-08-25 17:50:57 -04:00
FiendFEARing
40babc9650 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1161 of 1161 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-08-25 21:50:34 +00:00
Максим Горпиніч
7a94f014ea Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1161 of 1161 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-25 21:50:33 +00:00
Jan-Eric Myhrgren
32adb1bafd Translated using Weblate (Swedish)
Currently translated at 97.5% (1132 of 1161 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-25 21:50:32 +00:00
Yurt Page
f9a6239049 Translated using Weblate (Russian)
Currently translated at 100.0% (1161 of 1161 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-08-25 21:50:32 +00:00
Mathias Franco
8dee1ec942 Translated using Weblate (Dutch)
Currently translated at 100.0% (1161 of 1161 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-25 21:50:31 +00:00
laxandrea
58e43cc6a7 Translated using Weblate (Italian)
Currently translated at 99.6% (1157 of 1161 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-08-25 21:50:30 +00:00
Jan-Eric Myhrgren
b8999fbc37 Translated using Weblate (Swedish)
Currently translated at 97.3% (1126 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-25 21:50:29 +00:00
advplyr
0dda4b6b27 Added translation using Weblate (Basque) 2025-08-25 21:50:29 +00:00
Kabika82
817f2f6915 Translated using Weblate (Hungarian)
Currently translated at 100.0% (1157 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-08-25 21:50:28 +00:00
idojius86
77fc6bba1a Translated using Weblate (Spanish)
Currently translated at 97.1% (1124 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-08-25 21:50:28 +00:00
Jan-Eric Myhrgren
c66d652a53 Translated using Weblate (Swedish)
Currently translated at 97.3% (1126 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-25 21:50:27 +00:00
thehijacker
86bddba5c3 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1157 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-08-25 21:50:26 +00:00
Jan-Eric Myhrgren
7779fd2972 Translated using Weblate (Swedish)
Currently translated at 96.8% (1121 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-25 21:50:26 +00:00
Yurt Page
05a4577792 Translated using Weblate (Russian)
Currently translated at 100.0% (1157 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-08-25 21:50:25 +00:00
laxandrea
56dc042282 Translated using Weblate (Italian)
Currently translated at 99.3% (1149 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-08-25 21:50:24 +00:00
advplyr
95973243a6 Update book library progress sort labels as per discord vote 2025-08-25 16:50:10 -05:00
John
18ad23d016 Issue 4540 New SortBy Options: Started Date & Finished Date (#4575)
---------

Co-authored-by: advplyr <advplyr@protonmail.com>
2025-08-24 16:54:38 -05:00
advplyr
e258f122f1 Update weblate readme widget to use vertical bar chart 2025-08-24 08:41:31 -05:00
advplyr
18200a8f01 Merge pull request #4533 from sir-wilhelm/wmic_replacement
Use PowerShell to get windows drive paths.
2025-08-23 17:55:50 -04:00
advplyr
9c47f404c9 Fix current author not showing in podcast match #4617 2025-08-23 16:55:32 -05:00
advplyr
2f6de71a3a Fix match tab local storage book provider being updated by podcast matching #4615 2025-08-22 08:35:11 -05:00
advplyr
deb121c523 Fix podcast itunesId not set on create or update from match #4614 2025-08-22 08:20:49 -05:00
advplyr
320e4dfb47 Merge pull request #4586 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-08-20 18:47:09 -04:00
FiendFEARing
6194c48549 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1157 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-08-20 22:45:34 +00:00
Максим Горпиніч
6aa9ecaaba Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1157 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-20 22:45:33 +00:00
Jan-Eric Myhrgren
b3d020b89f Translated using Weblate (Swedish)
Currently translated at 96.8% (1121 of 1157 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-20 22:45:33 +00:00
owlcollector
e196a6e5ca Translated using Weblate (Japanese)
Currently translated at 9.7% (113 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/
2025-08-20 22:45:32 +00:00
Jan-Eric Myhrgren
73cf22b499 Translated using Weblate (Swedish)
Currently translated at 96.7% (1118 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-20 22:45:32 +00:00
ugyes
ac7464ce7e Translated using Weblate (Hungarian)
Currently translated at 99.8% (1154 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-08-20 22:45:31 +00:00
B0rax
84e742f2a5 Translated using Weblate (German)
Currently translated at 100.0% (1156 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-20 22:45:30 +00:00
Daniel Schosser
a1e882cbf1 Translated using Weblate (German)
Currently translated at 100.0% (1156 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-20 22:45:29 +00:00
Zhelyan Radoev
09121acbd5 Translated using Weblate (Bulgarian)
Currently translated at 87.3% (1010 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bg/
2025-08-20 22:45:28 +00:00
owlcollector
5b9df84ba3 Translated using Weblate (Japanese)
Currently translated at 8.5% (99 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/
2025-08-20 22:45:28 +00:00
Zhelyan Radoev
266db491aa Translated using Weblate (Bulgarian)
Currently translated at 83.9% (971 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bg/
2025-08-20 22:45:27 +00:00
FiendFEARing
c7a317a87b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1156 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-08-20 22:45:26 +00:00
Максим Горпиніч
b027f3bda1 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1156 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-20 22:45:26 +00:00
vanapro1
cea991b82f Translated using Weblate (Russian)
Currently translated at 100.0% (1156 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-08-20 22:45:25 +00:00
biuklija
7e2b51e6d2 Translated using Weblate (Croatian)
Currently translated at 100.0% (1156 of 1156 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-08-20 22:45:24 +00:00
Hang Pham
8f310b6bf0 Translated using Weblate (Vietnamese)
Currently translated at 62.2% (708 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/vi/
2025-08-20 22:45:24 +00:00
Sneaky
b2a5fb46f1 Translated using Weblate (Swedish)
Currently translated at 96.6% (1100 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-20 22:45:23 +00:00
Jan-Eric Myhrgren
6d7639853b Translated using Weblate (Swedish)
Currently translated at 96.6% (1100 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-20 22:45:22 +00:00
ugyes
3a16acbba4 Translated using Weblate (Hungarian)
Currently translated at 99.8% (1136 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-08-20 22:45:22 +00:00
Troj@
027e1efaca Translated using Weblate (Belarusian)
Currently translated at 66.5% (757 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2025-08-20 22:45:21 +00:00
Ivan Smoliakov
d1fabba86b Translated using Weblate (Russian)
Currently translated at 100.0% (1138 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-08-20 22:45:20 +00:00
Mathias Franco
b290a4ada3 Translated using Weblate (Dutch)
Currently translated at 100.0% (1138 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-20 22:45:19 +00:00
Paolo Ricci
bb477c617e Translated using Weblate (Italian)
Currently translated at 99.7% (1135 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-08-20 22:45:19 +00:00
Michael Förster
9238c38842 Translated using Weblate (German)
Currently translated at 100.0% (1138 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-20 22:45:18 +00:00
Daniel Schosser
d268516fcb Translated using Weblate (German)
Currently translated at 100.0% (1138 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-20 22:45:17 +00:00
Vito0912
d353cff1ae Translated using Weblate (German)
Currently translated at 100.0% (1138 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-20 22:45:17 +00:00
kuci-JK
604f17f60b Translated using Weblate (Czech)
Currently translated at 98.6% (1123 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2025-08-20 22:45:16 +00:00
advplyr
3911a7273b Merge pull request #4595 from renesat/fix/listening-ipv6-url
Fix listening url log with ipv6 host
2025-08-20 18:45:04 -04:00
advplyr
138bb563b8 Update ipv6 server listening log 2025-08-20 17:42:22 -05:00
advplyr
3801ef062a Merge pull request #4596 from renesat/fix/redirect-url
Fix freeze on some audio sources
2025-08-19 18:11:13 -04:00
advplyr
e4b9ac5446 Add episode updated translation for podcast match #4606 2025-08-19 16:52:50 -05:00
advplyr
9987d219f8 Remove success toast on podcast episodes removed #4606 2025-08-19 16:50:43 -05:00
advplyr
dc7045c562 Merge pull request #4608 from laxandrea/remove-token-from-hls-url
Remove token from hls url
2025-08-19 17:29:10 -04:00
laxandrea
2cc6e56bd1 remove token from hls url
- following PR #4263
2025-08-19 15:29:49 +02:00
advplyr
a89a24e48e Merge pull request #4598 from advplyr/episode_meta_tagging
Update podcast episode downloads to always attempt embedding meta tags
2025-08-17 10:20:54 -04:00
advplyr
a968aca304 Update podcast episode downloads to always attempt embedding meta tags regardless of format 2025-08-17 09:05:29 -05:00
renesat
8d1f460640 Fix freeze on some audio sources 2025-08-16 18:56:23 +02:00
renesat
553ffd1934 Fix listening url log with ipv6 host 2025-08-16 18:24:31 +02:00
advplyr
fd4932cdbb Add additional debug logs for OIDC login 2025-08-15 17:23:20 -05:00
advplyr
dcaca43817 Merge pull request #4384 from josh-vin/feat/ChaptersEnhancments
Enhancement: Improves chapter editing and adds bulk import
2025-08-14 17:38:56 -04:00
advplyr
0eed4e82f9 Fix bulk add chapter icon button tooltip 2025-08-14 16:35:28 -05:00
advplyr
2ed2328401 Remove negative chapter end check & tooltip 2025-08-14 16:18:33 -05:00
advplyr
8b260c8bc6 Update bulk chapter modal styles, decreased text and button sizes 2025-08-14 16:16:34 -05:00
advplyr
7dcb9b98a0 Chapter lookup modal add back button to clear lookup results 2025-08-14 16:03:32 -05:00
advplyr
311ac7104e Merge pull request #4590 from advplyr/fix_authorize_race_condition
Fix authorize race condition by not updating the user on token refresh
2025-08-13 09:36:17 -04:00
advplyr
2c45b28d48 Fix authorize race condition by not updating the user on token refresh #4567 2025-08-13 08:31:01 -05:00
advplyr
b53613f82c Merge pull request #4552 from Toby222/master
Replace some SVG icons with material-symbols
2025-08-12 18:55:50 -04:00
advplyr
751371abb8 Update ReadIcon svg with material-symbols 2025-08-12 17:46:01 -05:00
advplyr
6365c02875 Update explicit material symbols icon to fill 2025-08-12 17:40:48 -05:00
advplyr
fb3834156b Version bump v2.28.0 2025-08-10 17:42:32 -05:00
advplyr
c03f3f722d Merge pull request #4559 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-08-10 18:31:40 -04:00
FiendFEARing
a06f48ca29 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1138 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-08-10 22:26:37 +00:00
NickSkier
9d79552dda Translated using Weblate (Russian)
Currently translated at 100.0% (1138 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-08-10 22:26:36 +00:00
Laurin Sorgend
ed98614b6f Translated using Weblate (German)
Currently translated at 99.9% (1137 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-10 22:26:36 +00:00
owlcollector
09dd2cc79c Translated using Weblate (Japanese)
Currently translated at 6.0% (69 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ja/
2025-08-10 22:26:35 +00:00
weblate.user.1274
e87237048a Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.1% (1049 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2025-08-10 22:26:35 +00:00
Kent Henriksen
d71968fd80 Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.1% (1049 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2025-08-10 22:26:34 +00:00
Thomas
f83c605ae1 Translated using Weblate (French)
Currently translated at 99.1% (1128 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2025-08-10 22:26:34 +00:00
J. Lavoie
4325f470dd Translated using Weblate (German)
Currently translated at 99.8% (1136 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-10 22:26:33 +00:00
numerfolt
800ecf8e82 Translated using Weblate (German)
Currently translated at 99.8% (1136 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-10 22:26:32 +00:00
Vito0912
5cb143d50b Translated using Weblate (German)
Currently translated at 99.8% (1136 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-10 22:26:32 +00:00
Troj@
798c73c66c Translated using Weblate (Belarusian)
Currently translated at 64.6% (736 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2025-08-10 22:26:31 +00:00
Максим Горпиніч
0fa7c46274 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1138 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-10 22:26:31 +00:00
Kent Henriksen
c2d420ec70 Translated using Weblate (Norwegian Bokmål)
Currently translated at 91.0% (1036 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2025-08-10 22:26:30 +00:00
biuklija
152daf7bf3 Translated using Weblate (Croatian)
Currently translated at 100.0% (1138 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-08-10 22:26:29 +00:00
Ashish Wadekar
8d99249e50 Translated using Weblate (Hindi)
Currently translated at 8.7% (100 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hi/
2025-08-10 22:26:29 +00:00
Camille de Lune
c6724ba353 Translated using Weblate (French)
Currently translated at 99.1% (1128 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2025-08-10 22:26:28 +00:00
Aleksandr Zakirov
a519d44666 Translated using Weblate (Estonian)
Currently translated at 65.4% (745 of 1138 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/et/
2025-08-10 22:26:27 +00:00
Grzegorz Orlowski
7e8bf977cc Translated using Weblate (Polish)
Currently translated at 82.9% (942 of 1135 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-08-10 22:26:27 +00:00
advplyr
4018be6330 Fix oidc auto-register not cleaning up new user on errors #4563 2025-08-10 17:26:15 -05:00
advplyr
99a3867ce9 Update callback url check
Co-authored-by: Denis Arnst <git@sapd.eu>
2025-08-10 17:08:25 -05:00
advplyr
2116f60133 Merge pull request #4565 from advplyr/redirect_transcode_requests
Fix server crash when transcode requests are made to the direct play endpoint
2025-08-07 18:31:45 -04:00
advplyr
794f0ef42a Fix server crash when transcode requests are made to the direct play endpoint #4555 2025-08-07 17:21:05 -05:00
Josh Vincent
3e423839a1 Fixes UI for Bulk Chapter adder, and changes logic around locking 2025-08-04 18:33:06 -06:00
Josh Vincent
2773c8c4a9 Merge remote-tracking branch 'josh-vin/master' into feat/ChaptersEnhancments 2025-08-04 18:32:28 -06:00
advplyr
e510174f12 Merge pull request #4557 from Vito0912/cors
Allow a whitelist of CORS origins
2025-08-04 19:02:30 -04:00
advplyr
08c9e8d47d Fix i18n string order 2025-08-04 17:56:56 -05:00
advplyr
1908ec3df5 Remove commented out experimental features setting 2025-08-04 17:54:59 -05:00
advplyr
df3878d4ca Add Security section to settings with allowed cors origin setting, increase width of setting inputs 2025-08-04 17:54:29 -05:00
Vito0912
1097de6f1f now updates the input field 2025-08-04 19:17:46 +02:00
Vito0912
e408070b19 better heading 2025-08-03 14:02:33 +02:00
Vito0912
af67c2e86f locale 2025-08-03 13:57:44 +02:00
Vito0912
6a52d2a968 CORS 2025-08-03 13:52:58 +02:00
advplyr
3337b3af18 Version bump v2.27.0 2025-08-02 17:53:27 -05:00
advplyr
835d2c7f36 Merge pull request #4535 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-08-02 18:52:04 -04:00
FiendFEARing
03f91099e0 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1135 of 1135 strings)

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

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-02 15:01:59 +02:00
Grzegorz Orlowski
b03bd79f5d Translated using Weblate (Polish)
Currently translated at 77.6% (881 of 1135 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-08-02 15:01:57 +02:00
Tobias Berger
5ef632a7eb Replace some SVG icons with material-symbols 2025-08-01 09:20:34 +02:00
Troj@
79b4042e8e Translated using Weblate (Belarusian)
Currently translated at 63.6% (721 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2025-08-01 02:51:15 +02:00
FiendFEARing
8f718ef91c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-08-01 02:51:15 +02:00
Pepijn
4053b20623 Translated using Weblate (Dutch)
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-01 02:51:15 +02:00
Remco Schrijver
c4d654635f Translated using Weblate (Dutch)
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-01 02:51:15 +02:00
enosh
ef5d0ffa48 Translated using Weblate (Hebrew)
Currently translated at 74.7% (847 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/he/
2025-08-01 02:51:15 +02:00
Troj@
6a826cdb36 Translated using Weblate (Belarusian)
Currently translated at 58.1% (659 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/be/
2025-08-01 02:51:15 +02:00
thehijacker
1d837f5f21 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1133 of 1133 strings)

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

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-01 02:51:15 +02:00
Remco Schrijver
82a8f8f126 Translated using Weblate (Dutch)
Currently translated at 98.6% (1118 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-01 02:51:15 +02:00
Kabika82
4725a466da Translated using Weblate (Hungarian)
Currently translated at 100.0% (1133 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-08-01 02:51:15 +02:00
Jannik
031edc870c Translated using Weblate (German)
Currently translated at 99.8% (1131 of 1133 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-01 02:51:15 +02:00
Jan-Eric Myhrgren
625e2445b5 Translated using Weblate (Swedish)
Currently translated at 96.2% (1089 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-08-01 02:51:15 +02:00
max grakov
1640af2f1c Translated using Weblate (Russian)
Currently translated at 100.0% (1131 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-08-01 02:51:15 +02:00
Remco Schrijver
c76f76cc27 Translated using Weblate (Dutch)
Currently translated at 98.4% (1113 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-08-01 02:51:15 +02:00
ugyes
74af212293 Translated using Weblate (Hungarian)
Currently translated at 100.0% (1131 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-08-01 02:51:15 +02:00
Vito0912
e04efb9c6a Translated using Weblate (German)
Currently translated at 99.9% (1130 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-01 02:51:14 +02:00
B0rax
ee17e7a555 Translated using Weblate (German)
Currently translated at 99.9% (1130 of 1131 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-08-01 02:51:14 +02:00
FiendFEARing
694a852c07 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1131 of 1131 strings)

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

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-08-01 02:51:14 +02:00
Mikkel Dupont Olesen
71257f6c6c Translated using Weblate (Danish)
Currently translated at 99.4% (1124 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-08-01 02:51:14 +02:00
advplyr
4d70929d2e Add locale strings for user stats heatmap #4550 2025-07-31 19:51:05 -05:00
advplyr
578e9559e4 Merge pull request #4551 from chriscam85/master
Including total durations into the de-branding from #4226 as warning message is always present currently
2025-07-31 20:23:47 -04:00
advplyr
894ea0b80a Update chapter data log 2025-07-31 19:19:11 -05:00
Chris Campanile
e54571f011 Including total durations into the de-branding from #4226 as warning message is always present currently 2025-07-31 16:48:05 -07:00
Josh Vincent
77d7a50b99 Merge remote-tracking branch 'josh-vin/master' into feat/ChaptersEnhancments 2025-07-30 16:51:12 -06:00
advplyr
32da0f1224 Merge pull request #4542 from advplyr/progress_updated_sort
Add book library sort by progress updated #1215
2025-07-28 15:13:40 -04:00
advplyr
2054accdc9 Update library sort dropdown to use max height available 2025-07-28 15:07:57 -04:00
advplyr
7d8b857c77 Add book library sort by progress updated #1215 2025-07-28 14:58:28 -04:00
advplyr
0107cb4782 UI/UX fix x overflow for sessions tables on mobile 2025-07-26 09:45:44 -05:00
advplyr
f273eee807 Merge pull request #4534 from michaeldvinci/sepia-theme
Add 'sepia' theme to EpubReader
2025-07-25 17:34:34 -05:00
advplyr
4af21b079a Fix epub toc search input colors for themes 2025-07-25 17:31:59 -05:00
Michael Vinci
c9eaf2db2d Add 'sepia' theme to EpubReader 2025-07-25 17:01:16 -05:00
sir-wilhelm
cae1560344 Use PowerShell to get windows drive paths.
wmic has been deprecated on newer versions of Windows 11 and is not installed.

resolves #4531
2025-07-25 12:34:29 -05:00
advplyr
a5fb0d9cdb Merge pull request #4530 from advplyr/fix_ereader_socket_event
Fix ereader update socket event sending all devices #4529
2025-07-24 17:33:42 -05:00
advplyr
53c80d9798 Merge pull request #4528 from FelixSche/master
Update SideRail.vue
2025-07-24 17:32:44 -05:00
advplyr
832165716b Fix ereader update socket event sending all devices #4529 2025-07-24 17:29:08 -05:00
Felix
d9f2d8bf1d Update SideRail.vue
Changed cursor at version to pointer
2025-07-24 13:57:26 +02:00
advplyr
a7a3a56509 Version bump v2.26.3 2025-07-23 17:18:51 -05:00
advplyr
4082fadf90 Merge pull request #4525 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-07-23 17:17:51 -05:00
FiendFEARing
93160b83bf Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1130 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-23 13:06:32 +02:00
Максим Горпиніч
472240f994 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1130 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-07-23 13:06:31 +02:00
Dmitry
c3f0fb8e5e Translated using Weblate (Russian)
Currently translated at 100.0% (1130 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-07-23 13:06:30 +02:00
Daniel Schosser
b156ebeb9f Translated using Weblate (German)
Currently translated at 99.9% (1129 of 1130 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-23 13:06:29 +02:00
advplyr
e4c775c847 Merge pull request #4523 from advplyr/fix_change_empty_root_password
Update change password to support null or empty string passwords #4522
2025-07-22 17:01:07 -05:00
advplyr
45e8e72759 Update change password to support null or empty string passwords #4522 2025-07-22 15:17:00 -05:00
advplyr
0ae7340889 Merge pull request #4520 from advplyr/fix_podcast_session_track_index
Fix podcast episode track index null in playback session
2025-07-22 15:04:22 -05:00
advplyr
8c38987d92 Fix podcast episode track index null in playback session 2025-07-22 14:44:36 -05:00
advplyr
878f0787ba Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2025-07-21 17:07:12 -05:00
advplyr
880d85eaef Version bump v2.26.2 2025-07-21 17:07:06 -05:00
advplyr
f7aaebc1de Merge pull request #4508 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-07-21 17:01:15 -05:00
Charlie
d96ebbe82d Translated using Weblate (French)
Currently translated at 100.0% (1129 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2025-07-22 00:00:51 +02:00
kuci-JK
70d67156f0 Translated using Weblate (Czech)
Currently translated at 99.2% (1121 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2025-07-22 00:00:51 +02:00
Serhat Gülaştı
f293b317be Translated using Weblate (Turkish)
Currently translated at 28.3% (320 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/tr/
2025-07-22 00:00:51 +02:00
Simple16
1f23794f88 Translated using Weblate (Russian)
Currently translated at 99.9% (1128 of 1129 strings)

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

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-22 00:00:51 +02:00
SunSpring
1166400ab1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1129 of 1129 strings)

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

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-07-22 00:00:51 +02:00
Angelo Prandelli
3584f6e24f Translated using Weblate (Italian)
Currently translated at 98.2% (1109 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-07-22 00:00:51 +02:00
biuklija
23bf2594c8 Translated using Weblate (Croatian)
Currently translated at 100.0% (1129 of 1129 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-07-22 00:00:51 +02:00
advplyr
8fb460ce05 Merge pull request #4319 from mikiher/audible-confidence-score
Audible confidence score
2025-07-21 17:00:44 -05:00
advplyr
8c4bbfd6a2 Add match confidence as a badge on match book card 2025-07-21 16:52:21 -05:00
advplyr
742961e0b8 Merge pull request #4510 from advplyr/fix_set_token
Fix set token on page load #4509
2025-07-18 17:11:09 -05:00
advplyr
5b6807892f Fix set token on page load #4509 2025-07-18 16:59:27 -05:00
advplyr
b911a25c57 Version bump v2.26.1 2025-07-16 17:16:43 -05:00
advplyr
53110674e4 Merge pull request #4492 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-07-16 17:15:48 -05:00
Grzegorz Orlowski
f963cd4753 Translated using Weblate (Polish)
Currently translated at 76.1% (859 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-07-16 22:11:56 +00:00
Grzegorz Orlowski
0dccc3bcae Translated using Weblate (Polish)
Currently translated at 76.0% (858 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-07-16 22:11:55 +00:00
FiendFEARing
5b4fd5b254 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1128 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-16 22:11:55 +00:00
Fredrik Lindqvist
bdb9d3caeb Translated using Weblate (Swedish)
Currently translated at 95.2% (1074 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-07-16 22:11:54 +00:00
Jannik
9aca824b59 Translated using Weblate (German)
Currently translated at 99.9% (1127 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-16 22:11:53 +00:00
Jannik
8e891805eb Translated using Weblate (German)
Currently translated at 99.9% (1127 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-16 22:11:53 +00:00
Jannik
2760517445 Translated using Weblate (German)
Currently translated at 99.9% (1127 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-16 22:11:52 +00:00
Jannik
889ee33320 Translated using Weblate (German)
Currently translated at 99.9% (1127 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-16 22:11:51 +00:00
Jannik
4f65801713 Translated using Weblate (German)
Currently translated at 99.9% (1127 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-16 22:11:50 +00:00
Jannik
3e75acd4ef Translated using Weblate (German)
Currently translated at 99.9% (1127 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-16 22:11:50 +00:00
Jannik
3e8fe2ef60 Translated using Weblate (German)
Currently translated at 99.9% (1127 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-16 22:11:49 +00:00
Jannik
0bc441de20 Translated using Weblate (German)
Currently translated at 99.9% (1127 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-16 22:11:48 +00:00
Jannik
a8c2f0d4c8 Translated using Weblate (German)
Currently translated at 99.9% (1127 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-16 22:11:48 +00:00
FiendFEARing
b59da8bd0c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1128 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-16 22:11:47 +00:00
FiendFEARing
77cb4f75c6 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1128 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-16 22:11:46 +00:00
Максим Горпиніч
9cf1711fae Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1128 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-07-16 22:11:45 +00:00
Jan-Eric Myhrgren
f472116dc3 Translated using Weblate (Swedish)
Currently translated at 94.5% (1066 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-07-16 22:11:45 +00:00
Jan-Eric Myhrgren
c7eb9d7799 Translated using Weblate (Swedish)
Currently translated at 94.5% (1066 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-07-16 22:11:44 +00:00
Jan-Eric Myhrgren
c66380eaeb Translated using Weblate (Swedish)
Currently translated at 94.5% (1066 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2025-07-16 22:11:43 +00:00
Simple16
1bebb22705 Translated using Weblate (Russian)
Currently translated at 100.0% (1128 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-07-16 22:11:43 +00:00
Simple16
4e96649fe3 Translated using Weblate (Russian)
Currently translated at 100.0% (1128 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-07-16 22:11:42 +00:00
Simple16
a21cec806e Translated using Weblate (Russian)
Currently translated at 100.0% (1128 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-07-16 22:11:41 +00:00
biuklija
8a3b8d2249 Translated using Weblate (Croatian)
Currently translated at 100.0% (1128 of 1128 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-07-16 22:11:40 +00:00
advplyr
581277914c Merge pull request #4503 from advplyr/session_modal_user
Update sessions modal to show username & update legacy token input with warning
2025-07-16 17:11:28 -05:00
advplyr
e678fe6e2f Update sessions modal to show username & update sessions endpoints to always return username 2025-07-16 16:56:07 -05:00
advplyr
3845940245 Add warning under legacy token input on users page to use api keys instead 2025-07-16 16:43:53 -05:00
advplyr
6c63e2131c Update AllowCors to apply to every request #4497 2025-07-15 16:28:41 -05:00
advplyr
e25e2b238f Merge pull request #4493 from advplyr/localize_durations
Localize elapsed duration on sessions tables
2025-07-14 17:28:58 -05:00
advplyr
99110f587a Localize elapsed duration on sessions tables 2025-07-14 17:17:39 -05:00
advplyr
b553e959e2 Merge pull request #4486 from advplyr/fix_oidc_create_user
Fix OIDC auto register user #4485
2025-07-13 17:09:40 -05:00
advplyr
f7b94a4b6d Fix OIDC auto register user #4485 2025-07-13 17:04:02 -05:00
mikiher
e9a705587a Merge branch 'advplyr:master' into audible-confidence-score 2025-07-13 10:13:00 +03:00
advplyr
264ae928a9 Version bump v2.26.0 2025-07-12 11:43:14 -05:00
advplyr
f5248a9f00 Merge pull request #4476 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-07-12 11:41:54 -05:00
FiendFEARing
3473ff594a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-12 18:32:35 +02:00
FiendFEARing
20bb6e13b5 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-12 18:32:35 +02:00
FiendFEARing
a05d32b1d7 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-12 18:32:34 +02:00
Kabika82
c6b3521cb6 Translated using Weblate (Hungarian)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-07-12 18:32:34 +02:00
Kabika82
2444504c6a Translated using Weblate (Hungarian)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-07-12 18:32:33 +02:00
advplyr
d38532c07a Merge pull request #4444 from advplyr/jwt_auth_refactor
Implement new JWT auth
2025-07-12 11:32:22 -05:00
advplyr
4f7831611f Update auth re-login i18n string 2025-07-12 11:23:08 -05:00
advplyr
d09db19cd5 Update re-login message to show for users without github discussion link, add message to i18n strings 2025-07-12 11:21:52 -05:00
advplyr
030e43f382 Support disabled rate limiter by setting max to 0, add logs when rate limit is changed from default 2025-07-12 10:51:07 -05:00
advplyr
f081a7fdc1 Update rate limiter to use requestIp as key, pass in configurable error message 2025-07-12 10:32:35 -05:00
advplyr
f0d5f46199 Merge branch 'master' into jwt_auth_refactor 2025-07-11 16:59:19 -05:00
advplyr
0b8f6db45e Merge pull request #4445 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-07-11 16:58:05 -05:00
advplyr
806c0a2991 Remove return_tokens query param for login 2025-07-11 16:01:45 -05:00
advplyr
7d6d3e6687 Move invalidate refresh token to TokenManager 2025-07-11 14:43:07 -05:00
FiendFEARing
ad07ed7e25 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-11 03:04:29 +02:00
advplyr
d3402e30c2 Update ereaders to handle refreshing, epubjs to use custom request method, separate accessToken in store 2025-07-10 16:54:28 -05:00
advplyr
25fe4dee3a Update epub reader to use axios for handling refresh tokens 2025-07-09 17:03:10 -05:00
advplyr
3c21c82ce1 Merge branch 'master' into jwt_auth_refactor 2025-07-09 14:55:05 -05:00
thehijacker
3c8876a37d Translated using Weblate (Slovenian)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-07-09 19:54:31 +00:00
thehijacker
fba70c9831 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-07-09 19:54:30 +00:00
SunSpring
27e40d16fd Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-09 19:54:30 +00:00
FiendFEARing
448cbf8530 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-09 19:54:29 +00:00
SunSpring
f1153f9da5 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-09 19:54:28 +00:00
Raj
d09a21d922 Translated using Weblate (Gujarati)
Currently translated at 16.6% (184 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/gu/
2025-07-09 19:54:28 +00:00
Richard Požgay
62afa3c3ee Translated using Weblate (Czech)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2025-07-09 19:54:27 +00:00
Richard Požgay
85446be0e5 Translated using Weblate (Czech)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2025-07-09 19:54:27 +00:00
Michal
018ca8e7ee Translated using Weblate (Slovak)
Currently translated at 99.9% (1107 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-07-09 19:54:26 +00:00
Максим Горпиніч
f02453ac92 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-07-09 19:54:25 +00:00
DavevanIersel
84b77f4c7f Translated using Weblate (Dutch)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-07-09 19:54:25 +00:00
FiendFEARing
d41276ba8c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.9% (1107 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-09 19:54:24 +00:00
FiendFEARing
576d7dc024 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.9% (1107 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-07-09 19:54:24 +00:00
Максим Горпиніч
6d2b1df560 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-07-09 19:54:23 +00:00
DavevanIersel
8255e4308c Translated using Weblate (Dutch)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-07-09 19:54:22 +00:00
DavevanIersel
794adf0292 Translated using Weblate (Dutch)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-07-09 19:54:22 +00:00
Daniel Schosser
f2e0b9762c Translated using Weblate (German)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-09 19:54:21 +00:00
Daniel Schosser
7d0def0edb Translated using Weblate (German)
Currently translated at 100.0% (1108 of 1108 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-09 19:54:21 +00:00
Vito0912
0653572396 Translated using Weblate (German)
Currently translated at 99.9% (1106 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-09 19:54:20 +00:00
Vito0912
d9a3750667 Translated using Weblate (German)
Currently translated at 99.9% (1106 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-07-09 19:54:19 +00:00
advplyr
9c0c7b6b08 Merge pull request #4469 from advplyr/fix_scanner_deleting_single_file_books
Fix scanner after deleting single file books #4459
2025-07-09 14:54:05 -05:00
advplyr
df1391d93f Fix scanner after deleting single file books #4459 2025-07-09 13:42:53 -05:00
mikiher
bf6d81b333 Merge branch 'advplyr:master' into audible-confidence-score 2025-07-09 09:04:52 +03:00
advplyr
8775e55762 Update jwt secret handling 2025-07-08 16:39:50 -05:00
advplyr
d0d152c20d Seperate setUserToken from setUser in store 2025-07-08 09:45:24 -05:00
advplyr
4ff7355262 Fix hashPassword 2025-07-08 09:14:07 -05:00
advplyr
6cc7a44a22 Update oidc redirect to pass both new and old token in url 2025-07-07 17:21:25 -05:00
advplyr
ad092ef8f8 Merge branch 'master' into jwt_auth_refactor 2025-07-07 16:50:58 -05:00
advplyr
4102ed8be4 Fix LazySeriesCard component test 2025-07-07 16:49:20 -05:00
advplyr
691f291843 Update LibraryItemController unit test 2025-07-07 16:26:17 -05:00
advplyr
ac381854e5 Add rate limiter for auth endpoints 2025-07-07 16:23:15 -05:00
advplyr
9c8900560c Seperate out auth strategies, update change password to return error status codes 2025-07-07 15:04:40 -05:00
advplyr
d9cfcc86e7 Update oidc to return refresh token in response body for mobile 2025-07-07 09:16:07 -05:00
advplyr
ce803dd6de Use getServerSetting to ensure serverSettings is set before accessing 2025-07-06 17:39:03 -05:00
advplyr
97afd22f81 Refactor Auth to breakout functions in TokenManager, handle token generation for OIDC 2025-07-06 16:43:03 -05:00
advplyr
e24eaab3f1 Log when token expiry is set via env var, api-keys create/update returns with user association 2025-07-06 13:10:14 -05:00
advplyr
e201247d69 Handle socket re-authentication, fix socket toast to be re-usable, socket cleanup 2025-07-06 11:07:01 -05:00
advplyr
a24dae5262 Merge branch 'master' into jwt_auth_refactor 2025-07-06 09:06:39 -05:00
advplyr
e59babdf24 Force re-login if using old token, show alert if admin user, add isOldToken flag to user 2025-07-05 17:46:18 -05:00
advplyr
8dbe1e4e5d Fix express.json position 2025-07-04 16:49:45 -05:00
advplyr
cdc37ddb0f Use x-refresh-token for alt method of passing refresh token, check x-refresh-token for logout 2025-07-04 13:54:37 -05:00
advplyr
f127a7beb5 Update router for internal-api routes 2025-07-03 17:31:38 -05:00
advplyr
df60aeb456 Update narrator name to be clickable to filter by narrator 2025-07-02 17:30:00 -05:00
advplyr
30c327d92a Merge pull request #4454 from advplyr/fix_mediaprogress_updatedat_2
Fix manually setting updatedAt of mediaProgresses using progress sync lastUpdate timestamp
2025-07-01 17:08:50 -05:00
advplyr
596bddf791 Fix manually setting updatedAt of mediaProgresses using progress sync lastUpdate timestamp #4366 2025-07-01 16:48:07 -05:00
advplyr
44ff90a6f2 Update refresh endpoint to support override cookie token 2025-07-01 16:31:26 -05:00
advplyr
293851d931 Fix missing translation in remove podcast episode modal #4434 2025-06-30 17:49:05 -05:00
advplyr
8b995a179d Add support for returning refresh token for mobile clients 2025-06-30 17:31:31 -05:00
advplyr
4d32a22de9 Update API Keys to be tied to a user, add apikey lru-cache, handle deactivating expired keys 2025-06-30 14:53:11 -05:00
advplyr
af1ff12dbb Add get all, update and delete endpoints. Add api keys config page 2025-06-30 11:32:02 -05:00
advplyr
d96ed01ce4 Set up ApiKey model and create Api Key endpoint 2025-06-30 10:12:39 -05:00
advplyr
7610e97f0f Merge pull request #4416 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-06-29 17:32:52 -05:00
advplyr
4f5123e842 Implement new JWT auth 2025-06-29 17:22:58 -05:00
Eigen_art
d102065d02 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-06-27 00:22:11 +02:00
Dan Johansen
34315d4c10 Translated using Weblate (Danish)
Currently translated at 99.7% (1104 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-06-27 00:22:10 +02:00
Michael Förster
276a179446 Translated using Weblate (German)
Currently translated at 99.9% (1106 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-06-27 00:22:10 +02:00
burghy86
4462d32e98 Translated using Weblate (Italian)
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-06-27 00:22:09 +02:00
SunSpring
9722674072 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-06-27 00:22:09 +02:00
Mathias Franco
35bb77c9c2 Translated using Weblate (Dutch)
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-06-27 00:22:08 +02:00
biuklija
cf6f49ce75 Translated using Weblate (Croatian)
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-06-27 00:22:07 +02:00
Daniel Schosser
d614373c64 Translated using Weblate (German)
Currently translated at 99.9% (1106 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-06-27 00:22:07 +02:00
Stefan Ha
b9969c78a6 Translated using Weblate (German)
Currently translated at 99.9% (1106 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-06-27 00:22:06 +02:00
B0rax
fbf482d6b6 Translated using Weblate (German)
Currently translated at 99.9% (1106 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-06-27 00:22:06 +02:00
David Havndrup Munch
dd74d0a726 Translated using Weblate (Danish)
Currently translated at 98.9% (1095 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-06-27 00:22:05 +02:00
petr-prikryl
b13b80e011 Translated using Weblate (Czech)
Currently translated at 99.9% (1106 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2025-06-27 00:22:04 +02:00
advplyr
e384863148 Add support for running in production with dev.js config, node index --prod-with-dev-env 2025-06-26 17:21:58 -05:00
mikiher
9c44fc0d01 Merge branch 'advplyr:master' into audible-confidence-score 2025-06-26 18:09:13 +03:00
advplyr
d21fe49ce2 Merge pull request #4430 from advplyr/experimental_next_client
Add ENV REACT_CLIENT_PATH to target a Nextjs frontend instead of Nuxt
2025-06-23 17:23:15 -05:00
advplyr
a992400d6a Add ENV REACT_CLIENT_PATH to target a Nextjs frontend instead of Nuxt 2025-06-23 16:56:08 -05:00
advplyr
108b2a60f5 Merge pull request #4425 from Vito0912/feat/addExplicit
Add explicit filter
2025-06-21 17:03:25 -05:00
advplyr
af684e6a69 Explicit library filter not shown for users without permission 2025-06-21 17:01:13 -05:00
Vito0912
5336d0525e add explicit to podcasts 2025-06-21 12:29:54 +02:00
Vito0912
bb4eec9355 add explicit 2025-06-21 12:02:44 +02:00
advplyr
28404f37b8 Merge pull request #4422 from advplyr/podcast_episode_duration
Show duration in episode view modal & episode feed modal
2025-06-19 17:35:36 -05:00
advplyr
7b92c15a46 Include durationSeconds on RSS podcast episode parsed from duration 2025-06-19 17:28:21 -05:00
advplyr
c150ed4e98 Update view episode modal to include duration & episode feed modal to include duration & size 2025-06-19 17:14:56 -05:00
advplyr
cb7632b216 Merge pull request #4419 from advplyr/episode-timestamps-clickable
Episode view modal makes timestamps in description clickable
2025-06-18 17:28:55 -05:00
advplyr
b8849677de Episode view modal makes timestamps in description clickable 2025-06-18 17:20:36 -05:00
advplyr
9bf8d7de11 Fix server crash when FantLab provider request times out #4410 2025-06-17 17:21:21 -05:00
advplyr
6634ce8fd4 Merge pull request #4417 from advplyr/book_author_secondary_sort_title
Update book library secondary title sort to use title ignore prefixes
2025-06-17 16:40:59 -05:00
advplyr
9d4303ef7b Update book library secondary title sort to use title ignore prefixes #4414 2025-06-17 16:25:30 -05:00
advplyr
1f7be58124 Fix database cleanup query pulling duplicate mediaProgresses 2025-06-16 17:50:53 -05:00
advplyr
6b8b27b04f Merge pull request #4413 from HadrienPatte/nusqlite3-path
Make `NUSQLITE3_PATH` build arg configurable
2025-06-16 17:22:21 -05:00
Hadrien Patte
ba4061e5a4 Make NUSQLITE3_PATH build arg configurable 2025-06-16 23:03:02 +02:00
mikiher
5017e7ce9e Merge branch 'advplyr:master' into audible-confidence-score 2025-06-16 10:26:58 +03:00
advplyr
693dc00fa3 Update local session sync logs to help debug sync errors 2025-06-15 17:21:47 -05:00
advplyr
f3f5f3b9bd Version bump v2.25.1 2025-06-14 17:57:19 -05:00
advplyr
b515c6c746 Remove mediaProgresses duplicate check 2025-06-14 17:56:35 -05:00
advplyr
35e196238a Version bump v2.25.0 2025-06-14 17:18:53 -05:00
advplyr
2dc93258f1 Merge pull request #4364 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-06-13 17:32:53 -05:00
thehijacker
5123f7d240 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-06-14 00:29:31 +02:00
Usama Khalil
06d3bd76a8 Translated using Weblate (Arabic)
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-06-14 00:29:31 +02:00
Ivan Smoliakov
52196afd99 Translated using Weblate (Russian)
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-06-14 00:29:30 +02:00
ugyes
3e44ee6f50 Translated using Weblate (Hungarian)
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hu/
2025-06-14 00:29:29 +02:00
Максим Горпиніч
9841826e10 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1107 of 1107 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-06-14 00:29:29 +02:00
Dawid Kuźnicki
def93d18ec Translated using Weblate (Polish)
Currently translated at 76.9% (850 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-06-14 00:29:28 +02:00
Rekentek
387a3d05b4 Translated using Weblate (Dutch)
Currently translated at 98.5% (1089 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nl/
2025-06-14 00:29:28 +02:00
Daniel Schosser
398d04fc08 Translated using Weblate (German)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-06-14 00:29:27 +02:00
David Havndrup Munch
c5e5e516af Translated using Weblate (Danish)
Currently translated at 98.9% (1093 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2025-06-14 00:29:27 +02:00
Plazec
1c6f99b876 Translated using Weblate (Czech)
Currently translated at 99.7% (1102 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2025-06-14 00:29:26 +02:00
Grzegorz Orlowski
d0af82e71a Translated using Weblate (Polish)
Currently translated at 76.9% (850 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-06-14 00:29:25 +02:00
Usama Khalil
76e7616439 Translated using Weblate (Arabic)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-06-14 00:29:25 +02:00
max grakov
fe99a269bc Translated using Weblate (Russian)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-06-14 00:29:24 +02:00
thehijacker
5315f65023 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1105 of 1105 strings)

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

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-06-14 00:29:23 +02:00
Anders Norman
204ac4f204 Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.6% (1024 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/nb_NO/
2025-06-14 00:29:22 +02:00
Arieh Kellermann
accd5d1096 Translated using Weblate (German)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-06-14 00:29:22 +02:00
advplyr
5025c6a3ea Merge pull request #4383 from JKubovy/improve-podcast-episode-search
Use fuse.js for podcast episode search
2025-06-13 17:29:13 -05:00
advplyr
6d0d1415e4 Add fuse.basic.min.js in libs instead of full npm package, use lower threshold for quick matching 2025-06-13 17:23:24 -05:00
advplyr
514f5c2409 Merge pull request #4394 from Vito0912/feat/addISBNAudible
Added the ISBN for Audible providers (returned data)
2025-06-13 16:21:32 -05:00
advplyr
2cc58b2c8a Merge pull request #4404 from advplyr/podcast_useragents
Update podcast episode downloads to have a fallback user agent string
2025-06-12 17:40:42 -05:00
advplyr
777a055fcd Update podcast episode downloads to have a fallback user agent string 2025-06-12 17:31:12 -05:00
advplyr
b45085d2d6 Update podcast episode download user agent to fix #4401 2025-06-12 17:19:24 -05:00
advplyr
22f6e86a12 Fix pathexists filepath back to posix 2025-06-11 16:37:07 -05:00
advplyr
dc6783ea76 Merge pull request #4398 from advplyr/pathexists_user_access
Update pathexists endpoint to check user has access to library
2025-06-11 16:31:14 -05:00
advplyr
a6f10ca48e Update upload endpoint to check user has access to library 2025-06-11 16:14:51 -05:00
advplyr
aac01d6d9a Update pathexists endpoint to check user has access to library 2025-06-11 16:04:18 -05:00
Vito0912
a617994207 added isbn 2025-06-11 08:12:23 +02:00
advplyr
7a33a412fc Merge pull request #4393 from advplyr/fix_pathexists_join
Fix filesystem pathexists path join
2025-06-10 17:20:23 -05:00
advplyr
0135b3560c Fix filesystem pathexists path join 2025-06-10 17:02:42 -05:00
advplyr
6968a5c02a Merge pull request #4378 from Vito0912/feat/PodcastNots
Notifications for failed rss feeds and disabled rss feeds
2025-06-09 16:25:19 -05:00
advplyr
5e2bb0b12c Fix notification js docs and update description/defaults 2025-06-09 16:21:05 -05:00
advplyr
7122756e58 Update notification description grammar 2025-06-09 15:51:14 -05:00
advplyr
8ecc912c2d Merge pull request #4388 from advplyr/book_author_secondary_sort
Update book library sort by author to use title as secondary sort #4380
2025-06-08 17:38:45 -05:00
advplyr
c8cea4e6af Update book library sort by author to use title as secondary sort #4380 2025-06-08 17:28:19 -05:00
Josh Vincent
9da0be6d36 Allow clicking on elapsedTime to adjust chapter start 2025-06-08 13:18:41 -06:00
advplyr
0c5d05d319 Fix chapter table on audiobook tools page uneven column widths 2025-06-07 17:10:23 -05:00
Josh Vincent
c41bdb951c Moves the lock button and fixes padding on bulk add feature.
Moves the lock button the right of the Title text box.

Enhances the bulk chapter add feature by preserving zero-padding in chapter titles and prevents editing of locked chapters. Also allows Enter to be pressed in the Add Multiple Chapters modal.

Adds a warning toast when attempting to modify locked chapters.

Fixes sizing of boxes on smaller windows
2025-06-07 14:48:05 -06:00
advplyr
4a3eb7727b Merge pull request #4385 from advplyr/clean_duplicate_mediaprogress
Update cleanDatabase to remove duplicate mediaProgresses
2025-06-06 17:17:43 -05:00
advplyr
81640464ba Update cleanDatabase to remove duplicate mediaProgresses 2025-06-06 17:05:07 -05:00
Josh Vincent
54815ea9c7 Add a second to bulk chapters so its valid
This will enable users to go in and fix the chapter timing later but still save easily with the bulk import.
2025-06-06 13:25:20 -06:00
Josh Vincent
679ffed0ea Alphabetizes strings 2025-06-06 11:50:44 -06:00
Josh Vincent
09397cf3de Improves chapter editing and adds bulk import
Adds chapter locking functionality, allowing users to lock individual chapters or all chapters at once to prevent accidental edits.

Implements time increment buttons to precisely adjust chapter start times.

Introduces bulk chapter import functionality, allowing users to quickly add multiple chapters using a detected numbering pattern.

Adds elapsed time display during chapter playback for better user feedback.

Updates UI tooltips and icons for improved clarity and user experience.
2025-06-06 11:22:38 -06:00
Jan Kubovy
eda7036f70 Use fuse.js for podcast episode search
Replace levenshtein distance with fuse.js fuzzy searching library. Search in episode's title and subtitle
2025-06-06 10:43:52 +00:00
advplyr
e669a8d378 Merge pull request #4370 from Vito0912/feat/MaxFailedEpisodeChecks-
Adds ENV for MaxFailedEpisodeChecks
2025-06-05 15:06:27 -05:00
advplyr
8e01859075 Cast PODCAST_DOWNLOAD_TIMEOUT and MAX_FAILED_EPISODE_CHECKS env vars to numbers 2025-06-05 14:31:12 -05:00
Vito0912
f0525d4f0d abc is hard 2025-06-05 14:09:35 +02:00
Vito0912
84c9c6cb50 move to global 2025-06-05 14:07:35 +02:00
Vito0912
346df3680c local strings 2025-06-05 14:02:29 +02:00
Vito0912
6aa7c8a3d8 added notification 2025-06-05 13:34:18 +02:00
advplyr
704c6f7bde Merge pull request #4374 from Vito0912/feat/allowBase64Images
Corrects removing of attachments for Trix
2025-06-04 16:36:46 -05:00
advplyr
f01055f6e6 Merge pull request #4373 from Vito0912/feat/maybeFixPodcast
Potential fix/new knowledge for hangig podcasts
2025-06-04 16:33:40 -05:00
Vito0912
759c58d3f7 remove any attachment 2025-06-04 16:38:01 +02:00
Vito0912
357176b301 catch timeout 2025-06-04 16:15:18 +02:00
Vito0912
9bb4dc3ab0 potential fix 2025-06-04 10:58:44 +02:00
Vito0912
709c33f27a ensure proper type 2025-06-04 10:05:16 +02:00
Vito0912
4d846e225a Adds ENV for MaxFailedEpisodeChecks 2025-06-04 10:02:17 +02:00
advplyr
5dc6d613bd Merge pull request #4361 from Vito0912/feat/encoderSettings
Fix: Audiobook m4b advanced encoder ignore
2025-06-02 16:53:28 -05:00
advplyr
63ccdb68f0 Fix m4b encoder backup file overwriting the encoded file when they have the same filename 2025-06-02 16:50:03 -05:00
Vito0912
424ef1aec3 prettier 2 2025-06-02 19:34:25 +02:00
Vito0912
b6995ba5d1 prettier 2025-06-02 19:33:50 +02:00
Vito0912
9968743a93 fix wrong display and ignored values 2025-06-02 19:32:52 +02:00
advplyr
c377b57601 Version bump v2.24.0 2025-06-01 16:00:16 -05:00
advplyr
262d0b46e3 Merge pull request #4350 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-06-01 15:40:16 -05:00
Charlie
32fc4f6555 Translated using Weblate (French)
Currently translated at 99.9% (1104 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2025-06-01 15:57:47 +02:00
DR
81572adab6 Translated using Weblate (Hebrew)
Currently translated at 76.4% (845 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/he/
2025-06-01 00:37:34 +02:00
kuci-JK
1ad2e71fd5 Translated using Weblate (Czech)
Currently translated at 98.9% (1093 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/cs/
2025-06-01 00:37:33 +02:00
FiendFEARing
db66b9eaeb Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-06-01 00:37:32 +02:00
Simple16
28c2e62e61 Translated using Weblate (Russian)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-06-01 00:37:32 +02:00
Tommaso Bellandi
96401c377c Translated using Weblate (Italian)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-06-01 00:37:31 +02:00
advplyr
9d45880b37 Merge pull request #4355 from advplyr/sanitize_html_description
Sanitize media item & episode description on update
2025-05-31 17:37:18 -05:00
advplyr
9052ceedd3 Sanitize media item & episode description on update 2025-05-31 17:01:58 -05:00
advplyr
4968864498 Fix safari specific issue with line clamp on description #4348 2025-05-30 17:33:15 -05:00
advplyr
f44c2d9e11 Merge pull request #4349 from advplyr/trix_prevent_attachments
Update rich text editor to prevent pasting in images from the browser
2025-05-29 17:37:31 -05:00
advplyr
0c8e334b1a Update rich text editor to prevent pasting in images from the browser 2025-05-29 17:27:29 -05:00
advplyr
abaa7b5ad0 Add arabic language option 2025-05-28 17:09:39 -05:00
advplyr
df01e493ec Merge pull request #4303 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-05-28 17:05:27 -05:00
Adolfo Jayme Barrientos
949c8ce230 Translated using Weblate (Catalan)
Currently translated at 96.2% (1064 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-05-27 22:57:04 +00:00
Grzegorz Orlowski
9eaa0c26cd Translated using Weblate (Polish)
Currently translated at 73.3% (810 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pl/
2025-05-27 22:57:03 +00:00
Adolfo Jayme Barrientos
d71f091e3e Translated using Weblate (Spanish)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-05-27 22:57:02 +00:00
Biepa
2589121908 Translated using Weblate (German)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-05-27 22:57:02 +00:00
ABS translator
ff425212e7 Translated using Weblate (Arabic)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-05-27 22:57:01 +00:00
thehijacker
243baaf775 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-05-27 22:57:00 +00:00
Jan Schoenfeld
7275b1063b Translated using Weblate (German)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-05-27 22:57:00 +00:00
peter cerny
4fd97510b8 Translated using Weblate (Slovak)
Currently translated at 99.9% (1104 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-27 22:56:59 +00:00
Adolfo Jayme Barrientos
6e67b1d9dd Translated using Weblate (Catalan)
Currently translated at 96.0% (1061 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-05-27 22:56:59 +00:00
SunSpring
0fc6afec26 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-05-27 22:56:58 +00:00
Adolfo Jayme Barrientos
c950ac7d69 Translated using Weblate (Spanish)
Currently translated at 99.9% (1104 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-05-27 22:56:57 +00:00
Usama Khalil
8979e19e92 Translated using Weblate (Arabic)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-05-27 22:56:57 +00:00
Максим Горпиніч
6a51cb07e8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-05-27 22:56:56 +00:00
biuklija
846a8c3881 Translated using Weblate (Croatian)
Currently translated at 100.0% (1105 of 1105 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-05-27 22:56:55 +00:00
peter cerny
0cd698cc8d Translated using Weblate (Slovak)
Currently translated at 99.9% (1103 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-27 22:56:55 +00:00
Antoniy Chonkov
13d9462868 Translated using Weblate (Bulgarian)
Currently translated at 81.7% (903 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bg/
2025-05-27 22:56:54 +00:00
peter cerny
d8e2ff8b0e Translated using Weblate (Slovak)
Currently translated at 99.5% (1099 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-27 22:56:53 +00:00
Usama Khalil
35c2a5c1a3 Translated using Weblate (Arabic)
Currently translated at 100.0% (1104 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-05-27 22:56:53 +00:00
Antoniy Chonkov
19dc096d22 Translated using Weblate (Bulgarian)
Currently translated at 75.5% (834 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/bg/
2025-05-27 22:56:52 +00:00
Usama Khalil
535ebc10f0 Translated using Weblate (Arabic)
Currently translated at 98.5% (1088 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-05-27 22:56:51 +00:00
SunSpring
7486a0659b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1104 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2025-05-27 22:56:51 +00:00
Usama Khalil
273866fe92 Translated using Weblate (Arabic)
Currently translated at 36.5% (404 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-05-27 22:56:50 +00:00
Usama Khalil
6425d95deb Translated using Weblate (Arabic)
Currently translated at 27.4% (303 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-05-27 22:56:50 +00:00
Vito0912
68a39449a2 Translated using Weblate (German)
Currently translated at 99.6% (1100 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-05-27 22:56:49 +00:00
advplyr
8e08458ea2 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2025-05-27 17:56:32 -05:00
advplyr
1119ddef8a Add RSS Feed Open filter for podcast libraries to match book libraries #4335 2025-05-27 17:56:27 -05:00
advplyr
3d0219a866 Merge pull request #4342 from advplyr/check_path_api_fix
Update pathexists file system API endpoint
2025-05-26 17:12:13 -05:00
advplyr
6ce1806359 Update pathexists file system API endpoint 2025-05-26 16:56:50 -05:00
advplyr
f05a513767 Fix m4b encoder bitrate preset selection #4337 2025-05-25 16:12:35 -05:00
advplyr
d03c338b48 Fix log for podcast rss feed with no guid #4325 2025-05-24 17:09:58 -05:00
advplyr
5e5a988f7a Merge pull request #4326 from advplyr/fix_mediaprogress_updatedat
Fix MediaProgress not using the lastUpdate time sent for local progress syncs
2025-05-22 17:43:31 -05:00
advplyr
6d1f0b27df Fix MediaProgress not using the lastUpdate time sent for local progress syncs 2025-05-22 17:30:38 -05:00
mikiher
de25763a74 Add match confidence display to BookMatchCard 2025-05-21 11:16:46 +03:00
mikiher
a894ceb9cf Match confidence calculation for audible results 2025-05-21 10:25:42 +03:00
mikiher
387e58a714 Add levenshteinSimilarity function to utils 2025-05-21 09:57:44 +03:00
advplyr
d01a7cb756 Merge pull request #4318 from advplyr/increase_express_json_limit
Update max allowed json request size #4250
2025-05-20 18:04:02 -05:00
advplyr
cae874ef05 Update max allowed json request size #4250 2025-05-20 17:44:13 -05:00
advplyr
733afc3e29 Update edit series sequence to show error when sequence has spaces #4314 2025-05-19 17:37:11 -05:00
advplyr
0772730336 Merge branch 'master' of https://github.com/advplyr/audiobookshelf 2025-05-16 16:42:05 -05:00
advplyr
8b02fe07c8 Version bump v2.23.0 2025-05-16 16:41:59 -05:00
advplyr
98f93a665c Merge pull request #4288 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-05-16 16:40:31 -05:00
Adolfo Jayme Barrientos
754566b221 Translated using Weblate (Catalan)
Currently translated at 95.5% (1055 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-05-16 23:20:02 +02:00
Adolfo Jayme Barrientos
f4f9adad35 Translated using Weblate (Spanish)
Currently translated at 99.8% (1102 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-05-16 23:20:02 +02:00
Adolfo Jayme Barrientos
16f7f1166e Translated using Weblate (Catalan)
Currently translated at 95.0% (1049 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-05-16 23:20:01 +02:00
Usama Khalil
f527b0f4d5 Translated using Weblate (Arabic)
Currently translated at 24.2% (268 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-05-16 23:20:00 +02:00
thehijacker
4f41df53c9 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1104 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-05-16 23:20:00 +02:00
cebo29
8a15f775a2 Translated using Weblate (German)
Currently translated at 99.4% (1098 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-05-16 23:19:59 +02:00
peter cerny
5e83bcd283 Translated using Weblate (Slovak)
Currently translated at 99.6% (1100 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-16 23:19:59 +02:00
Максим Горпиніч
2fd5dfcb66 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1104 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-05-16 23:19:58 +02:00
Ivan Smoliakov
872ce4fa38 Translated using Weblate (Russian)
Currently translated at 100.0% (1104 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-05-16 23:19:57 +02:00
J. Lavoie
ba792d91e5 Translated using Weblate (Italian)
Currently translated at 99.9% (1103 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-05-16 23:19:57 +02:00
biuklija
4997c716db Translated using Weblate (Croatian)
Currently translated at 100.0% (1104 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2025-05-16 23:19:56 +02:00
J. Lavoie
fd72d05280 Translated using Weblate (French)
Currently translated at 99.2% (1096 of 1104 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fr/
2025-05-16 23:19:56 +02:00
advplyr
241b56ad45 Merge pull request #4166 from balki/patch-1
Support listening on unix socket
2025-05-16 16:19:47 -05:00
advplyr
635c384952 Handle undefined Host and make chmod async 2025-05-16 16:14:13 -05:00
advplyr
ef930fd1b4 Merge pull request #4299 from advplyr/fix_dockerfile_nunicode
Fix Dockerfile to include nunicode in the final stage
2025-05-16 16:08:13 -05:00
advplyr
49997a1336 Fix Dockerfile to include nunicode in the final stage 2025-05-16 15:23:19 -05:00
advplyr
8d0434143c Merge pull request #4293 from advplyr/search_episodes
Add support for searching podcast episode titles #3301
2025-05-15 17:51:51 -05:00
advplyr
8e0319994e Update total results in global search component 2025-05-15 17:22:20 -05:00
advplyr
0ed6045d1e Add support for searching podcast episode titles #3301 2025-05-15 17:16:15 -05:00
advplyr
25c7e95a64 Version bump v2.22.0 2025-05-14 17:04:01 -05:00
advplyr
1781c4bbcb Merge pull request #4285 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-05-14 17:00:23 -05:00
Michal
c4ce72d44e Translated using Weblate (Slovak)
Currently translated at 99.7% (1100 of 1103 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-13 22:01:14 +00:00
Jaakko Rantamäki
78813c4b28 Translated using Weblate (Finnish)
Currently translated at 99.6% (1099 of 1103 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/fi/
2025-05-13 22:01:13 +00:00
peter cerny
990baa2dc6 Translated using Weblate (Slovak)
Currently translated at 99.7% (1100 of 1103 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-13 22:01:12 +00:00
Максим Горпиніч
c85f4467d2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1103 of 1103 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-05-13 22:01:12 +00:00
burghy86
59f7609054 Translated using Weblate (Italian)
Currently translated at 100.0% (1103 of 1103 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-05-13 22:01:11 +00:00
advplyr
2ef827e3fa Add restart server message on authentication page when oidc is enabled #4064 2025-05-13 17:01:00 -05:00
advplyr
5cadc8d90f Merge pull request #4150 from pinjeff/docker-build
Reduce final docker image size
2025-05-12 15:20:51 -05:00
advplyr
40e7e36ef6 Dockerfile unnecessary stage 1 apks 2025-05-12 15:15:18 -05:00
advplyr
d60ad96f8a Update search to exclude returning series with no books #3736 2025-05-11 16:59:11 -05:00
advplyr
46ba342d49 Merge pull request #4237 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2025-05-10 16:56:33 -05:00
peter cerny
ace6b2b81f Translated using Weblate (Slovak)
Currently translated at 100.0% (1100 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-10 23:54:41 +02:00
Adolfo Jayme Barrientos
fa7e2dfafe Translated using Weblate (Catalan)
Currently translated at 95.3% (1049 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-05-10 23:54:41 +02:00
Oleg Ivasenko
015310c15d Translated using Weblate (Russian)
Currently translated at 99.9% (1099 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ru/
2025-05-10 23:54:41 +02:00
Adolfo Jayme Barrientos
f624f04dec Translated using Weblate (Spanish)
Currently translated at 100.0% (1100 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-05-10 23:54:41 +02:00
Michal
7c13cfcda2 Translated using Weblate (Slovak)
Currently translated at 100.0% (1100 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-10 23:54:41 +02:00
Azorimor
fc265dadae Translated using Weblate (German)
Currently translated at 99.6% (1096 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/de/
2025-05-10 23:54:41 +02:00
peter cerny
f9905f887e Translated using Weblate (Slovak)
Currently translated at 96.1% (1058 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-10 23:54:41 +02:00
Michal
eb72bfbbc0 Translated using Weblate (Slovak)
Currently translated at 96.1% (1058 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-10 23:54:41 +02:00
Adolfo Jayme Barrientos
c268cace09 Translated using Weblate (Catalan)
Currently translated at 95.2% (1048 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-05-10 23:54:41 +02:00
Juraj Borza
9666caf7a3 Translated using Weblate (Slovak)
Currently translated at 80.5% (886 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-10 23:54:41 +02:00
Adolfo Jayme Barrientos
9e01e5c24e Translated using Weblate (Catalan)
Currently translated at 94.3% (1038 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-05-10 23:54:41 +02:00
Adolfo Jayme Barrientos
25e613a867 Translated using Weblate (Spanish)
Currently translated at 100.0% (1100 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-05-10 23:54:41 +02:00
Juraj Borza
fe23a86eaa Translated using Weblate (Slovak)
Currently translated at 79.0% (870 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-10 23:54:41 +02:00
peter cerny
cb5a7d6aef Translated using Weblate (Slovak)
Currently translated at 79.0% (870 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-10 23:54:41 +02:00
peter cerny
7deb89ce7a Translated using Weblate (Slovak)
Currently translated at 76.5% (842 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-10 23:54:41 +02:00
Adolfo Jayme Barrientos
1e300c77c9 Translated using Weblate (Catalan)
Currently translated at 94.3% (1038 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-05-10 23:54:41 +02:00
peter cerny
ed7cc42959 Translated using Weblate (Slovak)
Currently translated at 70.3% (774 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sk/
2025-05-10 23:54:41 +02:00
Adolfo Jayme Barrientos
f681ff68a1 Translated using Weblate (Catalan)
Currently translated at 94.3% (1038 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ca/
2025-05-10 23:54:41 +02:00
thehijacker
ba112bf9c2 Translated using Weblate (Slovenian)
Currently translated at 100.0% (1100 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sl/
2025-05-10 23:54:41 +02:00
Adolfo Jayme Barrientos
718434545a Translated using Weblate (Spanish)
Currently translated at 100.0% (1100 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/es/
2025-05-10 23:54:41 +02:00
Максим Горпиніч
0e9a4c95a9 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1100 of 1100 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/uk/
2025-05-10 23:54:41 +02:00
ABS translator
3c997c8468 Translated using Weblate (Arabic)
Currently translated at 23.0% (253 of 1099 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-05-10 23:54:41 +02:00
burghy86
eb49646256 Translated using Weblate (Italian)
Currently translated at 100.0% (1099 of 1099 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/it/
2025-05-10 23:54:41 +02:00
ABS translator
c54b5eadfd Translated using Weblate (Arabic)
Currently translated at 20.7% (228 of 1099 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ar/
2025-05-10 23:54:40 +02:00
advplyr
659c671c25 Merge pull request #4226 from Vito0912/feat/chapterLookUp
Changes to Chapter-Lookup
2025-05-10 16:54:33 -05:00
advplyr
0df5a7816d Update chapter toast strings, update spacing, autoformat 2025-05-10 16:42:34 -05:00
advplyr
26c976b6b9 Merge branch 'master' into feat/chapterLookUp 2025-05-10 16:22:30 -05:00
advplyr
bdeb22615e Merge pull request #4261 from nschum/fix-ignore-parent
Fix .ignore file causing ignores outside the directory
2025-05-09 17:42:26 -05:00
advplyr
257bf2ebe0 Update edit chapters page breakpoint & cleanup padding 2025-05-09 17:24:02 -05:00
advplyr
fc33da447a Remove unused album component 2025-05-09 17:13:17 -05:00
advplyr
df45347690 Merge pull request #4274 from advplyr/audiobook_tools_update
Audiobook tools update
2025-05-08 17:31:46 -05:00
advplyr
b876256736 Audiobook tools page include edit item button and item title linked to item page 2025-05-08 17:17:44 -05:00
advplyr
3ce6e45761 Support m4b encoder tool for single m4b audiobooks 2025-05-08 17:08:11 -05:00
advplyr
5ac6b85da1 Merge pull request #4270 from advplyr/episode_secondary_sorts
Update episode secondary sort to pubDate and episode #4262
2025-05-07 17:45:53 -05:00
advplyr
69e0a0732a Update episode secondary sort to pubDate and episode #4262 2025-05-07 17:30:07 -05:00
advplyr
087835a9f3 Merge pull request #4266 from advplyr/hls_stream_url_update
Update HLS stream endpoints to not include user token
2025-05-06 17:39:16 -05:00
advplyr
1f7b181b7b Update HLS stream endpoints to not include user token 2025-05-06 17:28:19 -05:00
advplyr
1afb8840db Merge pull request #4263 from advplyr/new_session_track_endpoint
Add new api endpoint for direct playing audio files using session id
2025-05-05 17:25:40 -05:00
advplyr
d9531166b6 Fix for HLS transcode urls 2025-05-05 17:07:51 -05:00
advplyr
336de49d8d Add new api endpoint for direct playing audio files using session id #4259 2025-05-05 17:00:43 -05:00
Nikolaj Schumacher
3cc527484d Fix .ignore file causing ignores outside the directory
The file "a/.ignore" should only cause the directory "a" to be ignored.
However, it also ignores all files starting with "a".
After this fix, it will only ignore paths starting with "a/".
2025-05-04 22:43:44 +02:00
advplyr
45987ffd63 Fix library stats returning null instead of 0 #4251 2025-05-03 17:25:01 -05:00
advplyr
1a1ef9c378 Merge pull request #4253 from advplyr/audiobook_tools_enhancements
Audiobook tools enhancements
2025-05-02 17:43:39 -05:00
advplyr
342d100f3e Replace advanced options with presets/advanced card 2025-05-02 17:24:46 -05:00
advplyr
e0b90c6813 Add channels, codec and bitrate to tracks table & breakpoint updates 2025-05-02 15:06:31 -05:00
advplyr
2706a9c4aa Merge pull request #4249 from advplyr/watcher_rescans_update
Update watcher to re-scan library items for non-media file only updates
2025-05-01 17:29:25 -05:00
advplyr
2cc9d1b7f8 Update watcher to re-scan library items when non-media files are added/updated #4245 2025-05-01 17:17:40 -05:00
advplyr
2b7268c952 Merge pull request #4240 from josh-vin/feat/defaultYearInReview
Improves Year in Review display logic
2025-04-30 17:26:00 -05:00
advplyr
e097fe1e88 Merge pull request #4241 from advplyr/player_track_tooltip
Fix player track tooltip overflowing on share player
2025-04-29 18:01:25 -05:00
Josh Vincent
58cd751b43 Improves Year in Review display logic 2025-04-28 21:00:22 -06:00
Vito0912
a1074e69ac Fixed crash 2025-04-27 19:51:56 +02:00
Vito0912
65aec6a099 Adds locale 2025-04-27 19:44:28 +02:00
Vito0912
38957d4f32 fix shift times not works when editing 2025-04-27 19:34:12 +02:00
Vito0912
a2dc76e190 remove brading 2025-04-27 19:21:37 +02:00
Balki
dc3c978f8d Merge branch 'advplyr:master' into patch-1 2025-03-28 04:55:20 +00:00
Balki
13fac2d5bc Support http server listening on unix socket 2025-03-25 19:36:19 -04:00
Jaffar Ashoor
fd0af6b2dd Reduce final docker image size
this adds a third stage to the build, copying the required files only
from the previos stages, this reduces the final image size from 600MB+
down to ~320MB
2025-03-23 02:59:40 +03:00
181 changed files with 9436 additions and 2533 deletions

1
.gitignore vendored
View File

@@ -23,3 +23,4 @@ sw.*
.DS_STORE
.idea/*
tailwind.compiled.css
tailwind.config.js

View File

@@ -1,34 +1,32 @@
ARG NUSQLITE3_DIR="/usr/local/lib/nusqlite3"
ARG NUSQLITE3_PATH="${NUSQLITE3_DIR}/libnusqlite3.so"
### STAGE 0: Build client ###
FROM node:20-alpine AS build
FROM node:20-alpine AS build-client
WORKDIR /client
COPY /client /client
RUN npm ci && npm cache clean --force
RUN npm run generate
### STAGE 1: Build server ###
FROM node:20-alpine
FROM node:20-alpine AS build-server
ARG NUSQLITE3_DIR
ARG TARGETPLATFORM
ENV NODE_ENV=production
RUN apk update && \
apk add --no-cache --update \
RUN apk add --no-cache --update \
curl \
tzdata \
ffmpeg \
make \
python3 \
g++ \
tini \
unzip
COPY --from=build /client/dist /client/dist
COPY index.js package* /
COPY server server
ARG TARGETPLATFORM
ENV NUSQLITE3_DIR="/usr/local/lib/nusqlite3"
ENV NUSQLITE3_PATH="${NUSQLITE3_DIR}/libnusqlite3.so"
WORKDIR /server
COPY index.js package* /server
COPY /server /server/server
RUN case "$TARGETPLATFORM" in \
"linux/amd64") \
@@ -42,14 +40,34 @@ RUN case "$TARGETPLATFORM" in \
RUN npm ci --only=production
RUN apk del make python3 g++
### STAGE 2: Create minimal runtime image ###
FROM node:20-alpine
ARG NUSQLITE3_DIR
ARG NUSQLITE3_PATH
# Install only runtime dependencies
RUN apk add --no-cache --update \
tzdata \
ffmpeg \
tini
WORKDIR /app
# Copy compiled frontend and server from build stages
COPY --from=build-client /client/dist /app/client/dist
COPY --from=build-server /server /app
COPY --from=build-server ${NUSQLITE3_PATH} ${NUSQLITE3_PATH}
EXPOSE 80
ENV PORT=80
ENV NODE_ENV=production
ENV CONFIG_PATH="/config"
ENV METADATA_PATH="/metadata"
ENV SOURCE="docker"
ENV NUSQLITE3_DIR=${NUSQLITE3_DIR}
ENV NUSQLITE3_PATH=${NUSQLITE3_PATH}
ENTRYPOINT ["tini", "--"]
CMD ["node", "index.js"]

View File

@@ -217,6 +217,16 @@ export default {
})
}
if (this.results.episodes?.length) {
shelves.push({
id: 'episodes',
label: 'Episodes',
labelStringKey: 'LabelEpisodes',
type: 'episode',
entities: this.results.episodes.map((res) => res.libraryItem)
})
}
if (this.results.series?.length) {
shelves.push({
id: 'series',

View File

@@ -3,24 +3,18 @@
<div class="flex md:hidden h-10 items-center">
<nuxt-link :to="`/library/${currentLibraryId}`" class="grow h-full flex justify-center items-center" :class="isHomePage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isHomePage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonHome }}</p>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
<span v-else class="material-symbols text-lg">home</span>
</nuxt-link>
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf`" class="grow h-full flex justify-center items-center" :class="isLibraryPage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isLibraryPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonLibrary }}</p>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
<span v-else class="material-symbols text-lg">import_contacts</span>
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary" :to="`/library/${currentLibraryId}/podcast/latest`" class="grow h-full flex justify-center items-center" :class="isPodcastLatestPage ? 'bg-primary/80' : 'bg-primary/40'">
<p class="text-sm">{{ $strings.ButtonLatest }}</p>
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/series`" class="grow h-full flex justify-center items-center" :class="isSeriesPage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isSeriesPage" class="text-sm">{{ $strings.ButtonSeries }}</p>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
</svg>
<span v-else class="material-symbols text-lg">view_column</span>
</nuxt-link>
<nuxt-link v-if="showPlaylists" :to="`/library/${currentLibraryId}/bookshelf/playlists`" class="grow h-full flex justify-center items-center" :class="isPlaylistsPage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isPlaylistsPage || isPodcastLibrary" class="text-sm">{{ $strings.ButtonPlaylists }}</p>
@@ -32,12 +26,7 @@
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/authors`" class="grow h-full flex justify-center items-center" :class="isAuthorsPage ? 'bg-primary/80' : 'bg-primary/40'">
<p v-if="isAuthorsPage" class="text-sm">{{ $strings.ButtonAuthors }}</p>
<svg v-else class="w-5 h-5" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z"
/>
</svg>
<span v-else class="material-symbols text-lg">groups</span>
</nuxt-link>
<nuxt-link v-if="isPodcastLibrary && userIsAdminOrUp" :to="`/library/${currentLibraryId}/podcast/search`" class="grow h-full flex justify-center items-center" :class="isPodcastSearchPage ? 'bg-primary/80' : 'bg-primary/40'">
<p class="text-sm">{{ $strings.ButtonAdd }}</p>
@@ -274,15 +263,10 @@ export default {
isAuthorsPage() {
return this.page === 'authors'
},
isAlbumsPage() {
return this.page === 'albums'
},
numShowing() {
return this.totalEntities
},
entityName() {
if (this.isAlbumsPage) return 'Albums'
if (this.isPodcastLibrary) return this.$strings.LabelPodcasts
if (!this.page) return this.$strings.LabelBooks
if (this.isSeriesPage) return this.$strings.LabelSeries

View File

@@ -70,6 +70,11 @@ export default {
title: this.$strings.HeaderUsers,
path: '/config/users'
},
{
id: 'config-api-keys',
title: this.$strings.HeaderApiKeys,
path: '/config/api-keys'
},
{
id: 'config-sessions',
title: this.$strings.HeaderListeningSessions,

View File

@@ -778,10 +778,6 @@ export default {
windowResize() {
this.executeRebuild()
},
socketInit() {
// Server settings are set on socket init
this.executeRebuild()
},
initListeners() {
window.addEventListener('resize', this.windowResize)
@@ -794,7 +790,6 @@ export default {
})
this.$eventBus.$on('bookshelf_clear_selection', this.clearSelectedEntities)
this.$eventBus.$on('socket_init', this.socketInit)
this.$eventBus.$on('user-settings', this.settingsUpdated)
if (this.$root.socket) {
@@ -826,7 +821,6 @@ export default {
}
this.$eventBus.$off('bookshelf_clear_selection', this.clearSelectedEntities)
this.$eventBus.$off('socket_init', this.socketInit)
this.$eventBus.$off('user-settings', this.settingsUpdated)
if (this.$root.socket) {

View File

@@ -5,9 +5,7 @@
<div id="siderail-buttons-container" role="navigation" aria-label="Library Navigation" :class="{ 'player-open': streamLibraryItem }" class="w-full overflow-y-auto overflow-x-hidden">
<nuxt-link :to="`/library/${currentLibraryId}`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="homePage ? 'bg-primary/80' : 'bg-bg/60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
<span class="material-symbols text-2xl">home</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonHome }}</p>
@@ -23,9 +21,7 @@
</nuxt-link>
<nuxt-link :to="`/library/${currentLibraryId}/bookshelf`" class="w-full h-20 flex flex-col items-center justify-center text-white border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="showLibrary ? 'bg-primary/80' : 'bg-bg/60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
<span class="material-symbols text-2xl">import_contacts</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonLibrary }}</p>
@@ -33,9 +29,7 @@
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/series`" class="w-full h-20 flex flex-col items-center justify-center text-white/80 border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="isSeriesPage ? 'bg-primary/80' : 'bg-bg/60'">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2h-2a2 2 0 00-2 2" />
</svg>
<span class="material-symbols text-2xl">view_column</span>
<p class="pt-1.5 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonSeries }}</p>
@@ -59,12 +53,7 @@
</nuxt-link>
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/bookshelf/authors`" class="w-full h-20 flex flex-col items-center justify-center text-white/80 border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="isAuthorsPage ? 'bg-primary/80' : 'bg-bg/60'">
<svg class="w-6 h-6" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z"
/>
</svg>
<span class="material-symbols text-2xl">groups</span>
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonAuthors }}</p>
@@ -116,7 +105,7 @@
</div>
<div class="w-full h-12 px-1 py-2 border-t border-black/20 bg-bg absolute left-0" :style="{ bottom: streamLibraryItem ? '224px' : '65px' }">
<p class="underline font-mono text-xs text-center text-gray-300 leading-3 mb-1" @click="clickChangelog">v{{ $config.version }}</p>
<p class="underline font-mono text-xs text-center text-gray-300 leading-3 mb-1 cursor-pointer" @click="clickChangelog">v{{ $config.version }}</p>
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-xxs text-center block leading-3">Update</a>
<p v-else class="text-xxs text-gray-400 leading-3 text-center italic">{{ Source }}</p>
</div>

View File

@@ -71,9 +71,6 @@ export default {
coverHeight() {
return this.cardHeight
},
userToken() {
return this.store.getters['user/getToken']
},
_author() {
return this.author || {}
},

View File

@@ -13,9 +13,17 @@
<div class="grow" />
<p class="text-sm md:text-base">{{ book.publishedYear }}</p>
</div>
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">{{ $getString('LabelByAuthor', [book.author]) }}</p>
<p v-if="book.narrator" class="text-gray-400 text-xs">{{ $strings.LabelNarrators }}: {{ book.narrator }}</p>
<p v-if="book.duration" class="text-gray-400 text-xs">{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
<div class="flex items-center">
<div>
<p v-if="book.author" class="text-gray-300 text-xs md:text-sm">{{ $getString('LabelByAuthor', [book.author]) }}</p>
<p v-if="book.narrator" class="text-gray-400 text-xs">{{ $strings.LabelNarrators }}: {{ book.narrator }}</p>
<p v-if="book.duration" class="text-gray-400 text-xs">{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}</p>
</div>
<div class="grow" />
<div v-if="book.matchConfidence" class="rounded-full px-2 py-1 text-xs whitespace-nowrap text-white" :class="book.matchConfidence > 0.95 ? 'bg-success/80' : 'bg-info/80'">{{ $strings.LabelMatchConfidence }}: {{ (book.matchConfidence * 100).toFixed(0) }}%</div>
</div>
<div v-if="book.series?.length" class="flex py-1 -mx-1">
<div v-for="(series, index) in book.series" :key="index" class="bg-white/10 rounded-full px-1 py-0.5 mx-1">
<p class="leading-3 text-xs text-gray-400">

View File

@@ -0,0 +1,60 @@
<template>
<div class="flex items-center h-full px-1 overflow-hidden">
<covers-book-cover :library-item="libraryItem" :width="coverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div class="grow px-2 episodeSearchCardContent">
<p class="truncate text-sm">{{ episodeTitle }}</p>
<p class="text-xs text-gray-200 truncate">{{ podcastTitle }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
libraryItem: {
type: Object,
default: () => {}
},
episode: {
type: Object,
default: () => {}
}
},
data() {
return {}
},
computed: {
bookCoverAspectRatio() {
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
coverWidth() {
if (this.bookCoverAspectRatio === 1) return 50 * 1.2
return 50
},
media() {
return this.libraryItem?.media || {}
},
mediaMetadata() {
return this.media.metadata || {}
},
episodeTitle() {
return this.episode.title || 'No Title'
},
podcastTitle() {
return this.mediaMetadata.title || 'No Title'
}
},
methods: {},
mounted() {}
}
</script>
<style>
.episodeSearchCardContent {
width: calc(100% - 80px);
height: 75px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

View File

@@ -1,142 +0,0 @@
<template>
<div ref="card" :id="`album-card-${index}`" :style="{ width: cardWidth + 'px' }" class="absolute top-0 left-0 rounded-xs z-30 cursor-pointer" @mousedown.prevent @mouseup.prevent @mousemove.prevent @mouseover="mouseover" @mouseleave="mouseleave" @click="clickCard">
<div class="relative" :style="{ height: coverHeight + 'px' }">
<div class="absolute top-0 left-0 w-full box-shadow-book shadow-height" />
<div class="w-full h-full bg-primary relative rounded-sm overflow-hidden">
<covers-preview-cover ref="cover" :src="coverSrc" :width="cardWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
</div>
</div>
<div class="relative w-full">
<div v-if="!isAlternativeBookshelfView" class="categoryPlacard absolute z-30 left-0 right-0 mx-auto -bottom-6e h-6e rounded-md text-center" :style="{ width: Math.min(200, cardWidth) + 'px' }">
<div class="w-full h-full shinyBlack flex items-center justify-center rounded-xs border" :style="{ padding: `0em ${0.5}em` }">
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
</div>
</div>
<div v-else class="absolute z-30 left-0 right-0 mx-auto -bottom-8e h-8e py-1e rounded-md text-center">
<p class="truncate" :style="{ fontSize: labelFontSize + 'em' }">{{ title }}</p>
<p class="truncate text-gray-400" :style="{ fontSize: 0.8 + 'em' }">{{ artist || '&nbsp;' }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
index: Number,
width: Number,
height: {
type: Number,
default: 192
},
bookshelfView: {
type: Number,
default: 0
},
albumMount: {
type: Object,
default: () => null
}
},
data() {
return {
album: null,
isSelectionMode: false,
selected: false,
isHovering: false
}
},
computed: {
bookCoverAspectRatio() {
return this.store.getters['libraries/getBookCoverAspectRatio']
},
cardWidth() {
return this.width || this.coverHeight
},
coverHeight() {
return this.height * this.sizeMultiplier
},
/*
cardHeight() {
return this.coverHeight + this.bottomTextHeight
},
bottomTextHeight() {
if (!this.isAlternativeBookshelfView) return 0
const lineHeight = 1.5
const remSize = 16
const baseHeight = this.sizeMultiplier * lineHeight * remSize
const titleHeight = this.labelFontSize * baseHeight
const paddingHeight = 4 * 2 * this.sizeMultiplier // py-1
return titleHeight + paddingHeight
},
*/
coverSrc() {
const config = this.$config || this.$nuxt.$config
if (!this.album || !this.album.libraryItemId) return `${config.routerBasePath}/book_placeholder.jpg`
return this.store.getters['globals/getLibraryItemCoverSrcById'](this.album.libraryItemId)
},
labelFontSize() {
if (this.width < 160) return 0.75
return 0.9
},
sizeMultiplier() {
return this.store.getters['user/getSizeMultiplier']
},
title() {
return this.album ? this.album.title : ''
},
artist() {
return this.album ? this.album.artist : ''
},
store() {
return this.$store || this.$nuxt.$store
},
currentLibraryId() {
return this.store.state.libraries.currentLibraryId
},
isAlternativeBookshelfView() {
const constants = this.$constants || this.$nuxt.$constants
return this.bookshelfView == constants.BookshelfView.DETAIL
}
},
methods: {
setEntity(album) {
this.album = album
},
setSelectionMode(val) {
this.isSelectionMode = val
},
mouseover() {
this.isHovering = true
},
mouseleave() {
this.isHovering = false
},
clickCard() {
if (!this.album) return
// const router = this.$router || this.$nuxt.$router
// router.push(`/album/${this.$encode(this.title)}`)
},
clickEdit() {
this.$emit('edit', this.album)
},
destroy() {
// destroy the vue listeners, etc
this.$destroy()
// remove the element from the DOM
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el)
} else if (this.$el && this.$el.remove) {
this.$el.remove()
}
}
},
mounted() {
if (this.albumMount) {
this.setEntity(this.albumMount)
}
}
}
</script>

View File

@@ -101,7 +101,8 @@
<!-- Podcast Episode # -->
<div cy-id="podcastEpisodeNumber" v-if="recentEpisodeNumber !== null && !isHovering && !isSelectionMode && !processing" class="absolute rounded-lg bg-black/90 box-shadow-md z-10" :style="{ top: 0.375 + 'em', right: 0.375 + 'em', padding: `${0.1}em ${0.25}em` }">
<p :style="{ fontSize: 0.8 + 'em' }">
Episode<span v-if="recentEpisodeNumber"> #{{ recentEpisodeNumber }}</span>
Episode
<span v-if="recentEpisodeNumber">#{{ recentEpisodeNumber }}</span>
</p>
</div>
@@ -198,7 +199,10 @@ export default {
return this.store.getters['user/getSizeMultiplier']
},
dateFormat() {
return this.store.state.serverSettings.dateFormat
return this.store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.store.getters['getServerSetting']('timeFormat')
},
_libraryItem() {
return this.libraryItem || {}
@@ -345,6 +349,18 @@ export default {
if (this.mediaMetadata.publishedYear) return this.$getString('LabelPublishedDate', [this.mediaMetadata.publishedYear])
return '\u00A0'
}
if (this.orderBy === 'progress') {
if (!this.userProgressLastUpdated) return '\u00A0'
return this.$getString('LabelLastProgressDate', [this.$formatDatetime(this.userProgressLastUpdated, this.dateFormat, this.timeFormat)])
}
if (this.orderBy === 'progress.createdAt') {
if (!this.userProgressStartedDate) return '\u00A0'
return this.$getString('LabelStartedDate', [this.$formatDatetime(this.userProgressStartedDate, this.dateFormat, this.timeFormat)])
}
if (this.orderBy === 'progress.finishedAt') {
if (!this.userProgressFinishedDate) return '\u00A0'
return this.$getString('LabelFinishedDate', [this.$formatDatetime(this.userProgressFinishedDate, this.dateFormat, this.timeFormat)])
}
return null
},
episodeProgress() {
@@ -377,6 +393,18 @@ export default {
let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0
return Math.max(Math.min(1, progressPercent), 0)
},
userProgressLastUpdated() {
if (!this.userProgress) return null
return this.userProgress.lastUpdate
},
userProgressStartedDate() {
if (!this.userProgress) return null
return this.userProgress.startedAt
},
userProgressFinishedDate() {
if (!this.userProgress) return null
return this.userProgress.finishedAt
},
itemIsFinished() {
if (this.booksInSeries) return this.seriesIsFinished
return this.userProgress ? !!this.userProgress.isFinished : false

View File

@@ -71,7 +71,7 @@ export default {
return this.height * this.sizeMultiplier
},
dateFormat() {
return this.store.state.serverSettings.dateFormat
return this.store.getters['getServerSetting']('dateFormat')
},
labelFontSize() {
if (this.width < 160) return 0.75

View File

@@ -39,6 +39,15 @@
</li>
</template>
<p v-if="episodeResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">{{ $strings.LabelEpisodes }}</p>
<template v-for="item in episodeResults">
<li :key="item.libraryItem.recentEpisode.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
<nuxt-link :to="`/item/${item.libraryItem.id}`">
<cards-episode-search-card :episode="item.libraryItem.recentEpisode" :library-item="item.libraryItem" />
</nuxt-link>
</li>
</template>
<p v-if="authorResults.length" class="uppercase text-xs text-gray-400 mb-1 mt-3 px-1 font-semibold">{{ $strings.LabelAuthors }}</p>
<template v-for="item in authorResults">
<li :key="item.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1" role="option" @click="clickOption">
@@ -100,6 +109,7 @@ export default {
isFetching: false,
search: null,
podcastResults: [],
episodeResults: [],
bookResults: [],
authorResults: [],
seriesResults: [],
@@ -115,7 +125,7 @@ export default {
return this.$store.state.libraries.currentLibraryId
},
totalResults() {
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.genreResults.length + this.podcastResults.length + this.narratorResults.length
return this.bookResults.length + this.seriesResults.length + this.authorResults.length + this.tagResults.length + this.genreResults.length + this.podcastResults.length + this.narratorResults.length + this.episodeResults.length
}
},
methods: {
@@ -132,6 +142,7 @@ export default {
this.search = null
this.lastSearch = null
this.podcastResults = []
this.episodeResults = []
this.bookResults = []
this.authorResults = []
this.seriesResults = []
@@ -175,6 +186,7 @@ export default {
if (!this.isFetching) return
this.podcastResults = searchResults.podcast || []
this.episodeResults = searchResults.episodes || []
this.bookResults = searchResults.book || []
this.authorResults = searchResults.authors || []
this.seriesResults = searchResults.series || []

View File

@@ -94,6 +94,9 @@ export default {
userIsAdminOrUp() {
return this.$store.getters['user/getIsAdminOrUp']
},
userCanAccessExplicitContent() {
return this.$store.getters['user/getUserCanAccessExplicitContent']
},
libraryMediaType() {
return this.$store.getters['libraries/getCurrentLibraryMediaType']
},
@@ -239,6 +242,15 @@ export default {
sublist: false
}
]
if (this.userCanAccessExplicitContent) {
items.push({
text: this.$strings.LabelExplicit,
value: 'explicit',
sublist: false
})
}
if (this.userIsAdminOrUp) {
items.push({
text: this.$strings.LabelShareOpen,
@@ -249,7 +261,7 @@ export default {
return items
},
podcastItems() {
return [
const items = [
{
text: this.$strings.LabelAll,
value: 'all'
@@ -276,8 +288,23 @@ export default {
text: this.$strings.ButtonIssues,
value: 'issues',
sublist: false
},
{
text: this.$strings.LabelRSSFeedOpen,
value: 'feed-open',
sublist: false
}
]
if (this.userCanAccessExplicitContent) {
items.push({
text: this.$strings.LabelExplicit,
value: 'explicit',
sublist: false
})
}
return items
},
selectItems() {
if (this.isSeries) return this.seriesItems

View File

@@ -7,7 +7,7 @@
</span>
</button>
<ul v-show="showMenu" class="absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 ring-1 ring-black/5 overflow-auto focus:outline-hidden text-sm" role="menu">
<ul v-show="showMenu" class="librarySortMenu absolute z-10 mt-1 w-full bg-bg border border-black-200 shadow-lg max-h-96 rounded-md py-1 ring-1 ring-black/5 overflow-auto focus:outline-hidden text-sm" role="menu">
<template v-for="item in selectItems">
<li :key="item.value" class="select-none relative py-2 pr-9 cursor-pointer hover:bg-white/5" :class="item.value === selected ? 'bg-white/5 text-yellow-400' : 'text-gray-200 hover:text-white'" role="menuitem" @click="clickedOption(item.value)">
<div class="flex items-center">
@@ -130,6 +130,18 @@ export default {
text: this.$strings.LabelFileModified,
value: 'mtimeMs'
},
{
text: this.$strings.LabelLibrarySortByProgress,
value: 'progress'
},
{
text: this.$strings.LabelLibrarySortByProgressStarted,
value: 'progress.createdAt'
},
{
text: this.$strings.LabelLibrarySortByProgressFinished,
value: 'progress.finishedAt'
},
{
text: this.$strings.LabelRandomly,
value: 'random'
@@ -191,3 +203,9 @@ export default {
}
}
</script>
<style scoped>
.librarySortMenu {
max-height: calc(100vh - 125px);
}
</style>

View File

@@ -39,9 +39,6 @@ export default {
}
},
computed: {
userToken() {
return this.$store.getters['user/getToken']
},
_author() {
return this.author || {}
},

View File

@@ -309,9 +309,9 @@ export default {
} else {
console.log('Account updated', data.user)
if (data.user.id === this.user.id && data.user.token !== this.user.token) {
console.log('Current user token was updated')
this.$store.commit('user/setUserToken', data.user.token)
if (data.user.id === this.user.id && data.user.accessToken !== this.user.accessToken) {
console.log('Current user access token was updated')
this.$store.commit('user/setAccessToken', data.user.accessToken)
}
this.$toast.success(this.$strings.ToastAccountUpdateSuccess)
@@ -351,9 +351,6 @@ export default {
this.$toast.error(errMsg || 'Failed to create account')
})
},
toggleActive() {
this.newUser.isActive = !this.newUser.isActive
},
userTypeUpdated(type) {
this.newUser.permissions = {
download: type !== 'guest',

View File

@@ -0,0 +1,60 @@
<template>
<modals-modal ref="modal" v-model="show" name="api-key-created" :width="800" :height="'unset'" persistent>
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
<p class="text-3xl text-white truncate">{{ title }}</p>
</div>
</template>
<form @submit.prevent="submitForm">
<div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="min-height: 200px; max-height: 80vh">
<div class="w-full p-8">
<p class="text-lg text-white mb-4">{{ $getString('LabelApiKeyCreated', [apiKeyName]) }}</p>
<p class="text-lg text-white mb-4">{{ $strings.LabelApiKeyCreatedDescription }}</p>
<ui-text-input label="API Key" :value="apiKeyKey" readonly show-copy />
<div class="flex justify-end mt-4">
<ui-btn color="bg-primary" @click="show = false">{{ $strings.ButtonClose }}</ui-btn>
</div>
</div>
</div>
</form>
</modals-modal>
</template>
<script>
export default {
props: {
value: Boolean,
apiKey: {
type: Object,
default: () => null
}
},
data() {
return {}
},
computed: {
show: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
},
title() {
return this.$strings.HeaderNewApiKey
},
apiKeyName() {
return this.apiKey?.name || ''
},
apiKeyKey() {
return this.apiKey?.apiKey || ''
}
},
methods: {},
mounted() {}
}
</script>

View File

@@ -0,0 +1,198 @@
<template>
<modals-modal ref="modal" v-model="show" name="api-key" :width="800" :height="'unset'" :processing="processing">
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
<p class="text-3xl text-white truncate">{{ title }}</p>
</div>
</template>
<form @submit.prevent="submitForm">
<div class="px-4 w-full text-sm py-6 rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="min-height: 400px; max-height: 80vh">
<div class="w-full p-8">
<div class="flex py-2">
<div class="w-1/2 px-2">
<ui-text-input-with-label v-model.trim="newApiKey.name" :readonly="!isNew" :label="$strings.LabelName" />
</div>
<div v-if="isNew" class="w-1/2 px-2">
<ui-text-input-with-label v-model.trim="newApiKey.expiresIn" :label="$strings.LabelExpiresInSeconds" type="number" :min="0" />
</div>
</div>
<div class="flex items-center pt-4 pb-2 gap-2">
<div class="flex items-center px-2">
<p class="px-3 font-semibold" id="user-enabled-toggle">{{ $strings.LabelEnable }}</p>
<ui-toggle-switch :disabled="isExpired && !apiKey.isActive" labeledBy="user-enabled-toggle" v-model="newApiKey.isActive" />
</div>
<div v-if="isExpired" class="px-2">
<p class="text-sm text-error">{{ $strings.LabelExpired }}</p>
</div>
</div>
<div class="w-full border-t border-b border-black-200 py-4 px-3 mt-4">
<p class="text-lg mb-2 font-semibold">{{ $strings.LabelApiKeyUser }}</p>
<p class="text-sm mb-2 text-gray-400">{{ $strings.LabelApiKeyUserDescription }}</p>
<ui-select-input v-model="newApiKey.userId" :disabled="isExpired && !apiKey.isActive" :items="userItems" :placeholder="$strings.LabelSelectUser" :label="$strings.LabelApiKeyUser" label-hidden />
</div>
<div class="flex pt-4 px-2">
<div class="grow" />
<ui-btn color="bg-success" type="submit">{{ $strings.ButtonSubmit }}</ui-btn>
</div>
</div>
</div>
</form>
</modals-modal>
</template>
<script>
export default {
props: {
value: Boolean,
apiKey: {
type: Object,
default: () => null
},
users: {
type: Array,
default: () => []
}
},
data() {
return {
processing: false,
newApiKey: {},
isNew: true
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.init()
}
}
}
},
computed: {
show: {
get() {
return this.value
},
set(val) {
this.$emit('input', val)
}
},
title() {
return this.isNew ? this.$strings.HeaderNewApiKey : this.$strings.HeaderUpdateApiKey
},
userItems() {
return this.users
.filter((u) => {
// Only show root user if the current user is root
return u.type !== 'root' || this.$store.getters['user/getIsRoot']
})
.map((u) => ({ text: u.username, value: u.id, subtext: u.type }))
},
isExpired() {
if (!this.apiKey || !this.apiKey.expiresAt) return false
return new Date(this.apiKey.expiresAt).getTime() < Date.now()
}
},
methods: {
submitForm() {
if (!this.newApiKey.name) {
this.$toast.error(this.$strings.ToastNameRequired)
return
}
if (!this.newApiKey.userId) {
this.$toast.error(this.$strings.ToastNewApiKeyUserError)
return
}
if (this.isNew) {
this.submitCreateApiKey()
} else {
this.submitUpdateApiKey()
}
},
submitUpdateApiKey() {
if (this.newApiKey.isActive === this.apiKey.isActive && this.newApiKey.userId === this.apiKey.userId) {
this.$toast.info(this.$strings.ToastNoUpdatesNecessary)
this.show = false
return
}
const apiKey = {
isActive: this.newApiKey.isActive,
userId: this.newApiKey.userId
}
this.processing = true
this.$axios
.$patch(`/api/api-keys/${this.apiKey.id}`, apiKey)
.then((data) => {
this.processing = false
if (data.error) {
this.$toast.error(`${this.$strings.ToastFailedToUpdate}: ${data.error}`)
} else {
this.show = false
this.$emit('updated', data.apiKey)
}
})
.catch((error) => {
this.processing = false
console.error('Failed to update apiKey', error)
var errMsg = error.response ? error.response.data || '' : ''
this.$toast.error(errMsg || this.$strings.ToastFailedToUpdate)
})
},
submitCreateApiKey() {
const apiKey = { ...this.newApiKey }
if (this.newApiKey.expiresIn) {
apiKey.expiresIn = parseInt(this.newApiKey.expiresIn)
} else {
delete apiKey.expiresIn
}
this.processing = true
this.$axios
.$post('/api/api-keys', apiKey)
.then((data) => {
this.processing = false
if (data.error) {
this.$toast.error(this.$strings.ToastFailedToCreate + ': ' + data.error)
} else {
this.show = false
this.$emit('created', data.apiKey)
}
})
.catch((error) => {
this.processing = false
console.error('Failed to create apiKey', error)
var errMsg = error.response ? error.response.data || '' : ''
this.$toast.error(errMsg || this.$strings.ToastFailedToCreate)
})
},
init() {
this.isNew = !this.apiKey
if (this.apiKey) {
this.newApiKey = {
name: this.apiKey.name,
isActive: this.apiKey.isActive,
userId: this.apiKey.userId
}
} else {
this.newApiKey = {
name: null,
expiresIn: null,
isActive: true,
userId: null
}
}
}
},
mounted() {}
}
</script>

View File

@@ -79,10 +79,10 @@ export default {
return !this.bookmarks.find((bm) => Math.abs(this.currentTime - bm.time) < 1)
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
return this.$store.getters['getServerSetting']('timeFormat')
}
},
methods: {

View File

@@ -14,6 +14,7 @@
<ui-text-input-with-label ref="sequenceInput" v-model="selectedSeries.sequence" :label="$strings.LabelSequence" />
</div>
</div>
<div v-if="error" class="text-error text-sm mt-2 p-1">{{ error }}</div>
<div class="flex justify-end mt-2 p-1">
<ui-btn type="submit">{{ $strings.ButtonSubmit }}</ui-btn>
</div>
@@ -34,12 +35,17 @@ export default {
existingSeriesNames: {
type: Array,
default: () => []
},
originalSeriesSequence: {
type: String,
default: null
}
},
data() {
return {
el: null,
content: null
content: null,
error: null
}
},
watch: {
@@ -85,10 +91,17 @@ export default {
}
},
submitSeriesForm() {
this.error = null
if (this.$refs.newSeriesSelect) {
this.$refs.newSeriesSelect.blur()
}
if (this.selectedSeries.sequence !== this.originalSeriesSequence && this.selectedSeries.sequence.includes(' ')) {
this.error = this.$strings.MessageSeriesSequenceCannotContainSpaces
return
}
this.$emit('submit')
},
clickClose() {
@@ -100,6 +113,7 @@ export default {
}
},
setShow() {
this.error = null
if (!this.el || !this.content) {
this.init()
}

View File

@@ -81,7 +81,7 @@
</div>
<div class="w-full md:w-1/3">
<p v-if="!isMediaItemShareSession" class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
<p v-if="!isMediaItemShareSession" class="mb-1 text-xs">{{ _session.userId }}</p>
<p v-if="!isMediaItemShareSession" class="mb-1 text-xs">{{ username }}</p>
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelMediaPlayer }}</p>
<p class="mb-1">{{ playMethodName }}</p>
@@ -132,6 +132,9 @@ export default {
_session() {
return this.session || {}
},
username() {
return this._session.user?.username || this._session.userId || ''
},
deviceInfo() {
return this._session.deviceInfo || {}
},
@@ -159,10 +162,10 @@ export default {
return 'Unknown'
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
return this.$store.getters['getServerSetting']('timeFormat')
},
isOpenSession() {
return !!this._session.open

View File

@@ -23,7 +23,7 @@ export default {
processing: Boolean,
persistent: {
type: Boolean,
default: true
default: false
},
width: {
type: [String, Number],
@@ -99,7 +99,7 @@ export default {
this.preventClickoutside = false
return
}
if (this.processing && this.persistent) return
if (this.processing || this.persistent) return
if (ev.srcElement && ev.srcElement.classList.contains('modal-bg')) {
this.show = false
}

View File

@@ -144,7 +144,7 @@ export default {
expirationDateString() {
if (!this.expireDurationSeconds) return this.$strings.LabelPermanent
const dateMs = Date.now() + this.expireDurationSeconds * 1000
return this.$formatDatetime(dateMs, this.$store.state.serverSettings.dateFormat, this.$store.state.serverSettings.timeFormat)
return this.$formatDatetime(dateMs, this.$store.getters['getServerSetting']('dateFormat'), this.$store.getters['getServerSetting']('timeFormat'))
}
},
methods: {

View File

@@ -40,7 +40,7 @@ export default {
}
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
releasesToShow() {
return this.versionData?.releasesToShow || []

View File

@@ -29,9 +29,6 @@ export default {
media() {
return this.libraryItem.media || {}
},
userToken() {
return this.$store.getters['user/getToken']
},
userCanUpdate() {
return this.$store.getters['user/getUserCanUpdate']
},

View File

@@ -77,8 +77,8 @@
<ui-checkbox v-model="selectedMatchUsage.author" checkbox-bg="bg" @input="checkboxToggled" />
<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" class="text-xs ml-1 text-white/60">
{{ $strings.LabelCurrently }} <a title="$strings.LabelClickToUseCurrentValue" class="cursor-pointer hover:underline" @click.stop="setMatchFieldValue('author', mediaMetadata.authorName)">{{ mediaMetadata.authorName }}</a>
<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>
</p>
</div>
</div>
@@ -400,7 +400,9 @@ export default {
this.$toast.warning(this.$strings.ToastTitleRequired)
return
}
this.persistProvider()
if (!this.isPodcast) {
this.persistProvider()
}
this.runSearch()
},
async runSearch() {

View File

@@ -74,19 +74,12 @@ export default {
mediaTracks() {
return this.media.tracks || []
},
isSingleM4b() {
return this.mediaTracks.length === 1 && this.mediaTracks[0].metadata.ext.toLowerCase() === '.m4b'
},
chapters() {
return this.media.chapters || []
},
showM4bDownload() {
if (!this.mediaTracks.length) return false
return !this.isSingleM4b
},
showMp3Split() {
if (!this.mediaTracks.length) return false
return this.isSingleM4b && this.chapters.length
return true
},
queuedEmbedLIds() {
return this.$store.state.tasks.queuedEmbedLIds || []

View File

@@ -35,7 +35,14 @@
<widgets-podcast-type-indicator :type="episode.episodeType" />
</div>
<p v-if="episode.subtitle" class="mb-1 text-sm text-gray-300 line-clamp-2">{{ episode.subtitle }}</p>
<p class="text-xs text-gray-300">Published {{ episode.publishedAt ? $dateDistanceFromNow(episode.publishedAt) : 'Unknown' }}</p>
<div class="flex items-center space-x-2">
<!-- published -->
<p class="text-xs text-gray-300 w-40">Published {{ episode.publishedAt ? $dateDistanceFromNow(episode.publishedAt) : 'Unknown' }}</p>
<!-- duration -->
<p v-if="episode.durationSeconds && !isNaN(episode.durationSeconds)" class="text-xs text-gray-300 min-w-28">{{ $strings.LabelDuration }}: {{ $elapsedPretty(episode.durationSeconds) }}</p>
<!-- size -->
<p v-if="episode.enclosure?.length && !isNaN(episode.enclosure.length) && Number(episode.enclosure.length) > 0" class="text-xs text-gray-300">{{ $strings.LabelSize }}: {{ $bytesPretty(Number(episode.enclosure.length)) }}</p>
</div>
</div>
</div>
</div>
@@ -244,8 +251,8 @@ export default {
const sizeInMb = payloadSize / 1024 / 1024
const sizeInMbPretty = sizeInMb.toFixed(2) + 'MB'
console.log('Request size', sizeInMb)
if (sizeInMb > 4.99) {
return this.$toast.error(`Request is too large (${sizeInMbPretty}) should be < 5Mb`)
if (sizeInMb > 9.99) {
return this.$toast.error(`Request is too large (${sizeInMbPretty}) should be < 10Mb`)
}
this.processing = true

View File

@@ -11,7 +11,7 @@
{{ $getString('MessageConfirmRemoveEpisode', [episodeTitle]) }}
</p>
<p v-else class="text-lg text-gray-200 mb-4">{{ $getString('MessageConfirmRemoveEpisodes', [episodes.length]) }}</p>
<p class="text-xs font-semibold text-warning/90">Note: This does not delete the audio file unless toggling "Hard delete file"</p>
<p class="text-xs font-semibold text-warning/90">{{ $strings.MessageConfirmRemoveEpisodeNote }}</p>
</div>
<div class="flex justify-between items-center pt-4">
<ui-checkbox v-model="hardDeleteFile" :label="$strings.LabelHardDeleteFile" check-color="error" checkbox-bg="bg" small label-class="text-base text-gray-200 pl-3" />
@@ -94,7 +94,6 @@ export default {
}
this.processing = false
this.$toast.success(`${this.episodes.length} episode${this.episodes.length > 1 ? 's' : ''} removed`)
this.show = false
this.$emit('clearSelected')
}

View File

@@ -16,7 +16,7 @@
</div>
</div>
<p dir="auto" class="text-lg font-semibold mb-6">{{ title }}</p>
<div v-if="description" dir="auto" class="default-style less-spacing" v-html="description" />
<div v-if="description" dir="auto" class="default-style less-spacing" @click="handleDescriptionClick" v-html="description" />
<p v-else class="mb-2">{{ $strings.MessageNoDescription }}</p>
<div class="w-full h-px bg-white/5 my-4" />
@@ -34,6 +34,12 @@
{{ audioFileSize }}
</p>
</div>
<div class="grow">
<p class="font-semibold text-xs mb-1">{{ $strings.LabelDuration }}</p>
<p class="mb-2 text-xs">
{{ audioFileDuration }}
</p>
</div>
</div>
</div>
</modals-modal>
@@ -68,7 +74,7 @@ export default {
return this.episode.title || 'No Episode Title'
},
description() {
return this.episode.description || ''
return this.parseDescription(this.episode.description || '')
},
media() {
return this.libraryItem?.media || {}
@@ -90,11 +96,49 @@ export default {
return this.$bytesPretty(size)
},
audioFileDuration() {
const duration = this.episode.duration || 0
return this.$elapsedPretty(duration)
},
bookCoverAspectRatio() {
return this.$store.getters['libraries/getBookCoverAspectRatio']
}
},
methods: {},
methods: {
handleDescriptionClick(e) {
if (e.target.matches('span.time-marker')) {
const time = parseInt(e.target.dataset.time)
if (!isNaN(time)) {
this.$eventBus.$emit('play-item', {
episodeId: this.episodeId,
libraryItemId: this.libraryItem.id,
startTime: time
})
}
e.preventDefault()
}
},
parseDescription(description) {
const timeMarkerLinkRegex = /<a href="#([^"]*?\b\d{1,2}:\d{1,2}(?::\d{1,2})?)">(.*?)<\/a>/g
const timeMarkerRegex = /\b\d{1,2}:\d{1,2}(?::\d{1,2})?\b/g
function convertToSeconds(time) {
const timeParts = time.split(':').map(Number)
return timeParts.reduce((acc, part, index) => acc * 60 + part, 0)
}
return description
.replace(timeMarkerLinkRegex, (match, href, displayTime) => {
const time = displayTime.match(timeMarkerRegex)[0]
const seekTimeInSeconds = convertToSeconds(time)
return `<span class="time-marker cursor-pointer text-blue-400 hover:text-blue-300" data-time="${seekTimeInSeconds}">${displayTime}</span>`
})
.replace(timeMarkerRegex, (match) => {
const seekTimeInSeconds = convertToSeconds(match)
return `<span class="time-marker cursor-pointer text-blue-400 hover:text-blue-300" data-time="${seekTimeInSeconds}">${match}</span>`
})
}
},
mounted() {}
}
</script>

View File

@@ -114,7 +114,7 @@ export default {
.$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, updatePayload)
.then(() => {
this.isProcessing = false
this.$toast.success('Podcast episode updated')
this.$toast.success(this.$strings.ToastPodcastEpisodeUpdated)
this.$emit('selectTab', 'details')
})
.catch((error) => {

View File

@@ -129,9 +129,6 @@ export default {
return `${hoursRounded}h`
}
},
token() {
return this.$store.getters['user/getToken']
},
timeRemaining() {
if (this.useChapterTrack && this.currentChapter) {
var currChapTime = this.currentTime - this.currentChapter.start

View File

@@ -104,9 +104,6 @@ export default {
}
},
computed: {
userToken() {
return this.$store.getters['user/getToken']
},
libraryItemId() {
return this.libraryItem?.id
},
@@ -234,10 +231,7 @@ export default {
async extract() {
this.loading = true
var buff = await this.$axios.$get(this.ebookUrl, {
responseType: 'blob',
headers: {
Authorization: `Bearer ${this.userToken}`
}
responseType: 'blob'
})
const archive = await Archive.open(buff)
const originalFilesObject = await archive.getFilesObject()

View File

@@ -57,9 +57,6 @@ export default {
}
},
computed: {
userToken() {
return this.$store.getters['user/getToken']
},
/** @returns {string} */
libraryItemId() {
return this.libraryItem?.id
@@ -97,27 +94,37 @@ export default {
},
ebookUrl() {
if (this.fileId) {
return `${this.$config.routerBasePath}/api/items/${this.libraryItemId}/ebook/${this.fileId}`
return `/api/items/${this.libraryItemId}/ebook/${this.fileId}`
}
return `${this.$config.routerBasePath}/api/items/${this.libraryItemId}/ebook`
return `/api/items/${this.libraryItemId}/ebook`
},
themeRules() {
const isDark = this.ereaderSettings.theme === 'dark'
const fontColor = isDark ? '#fff' : '#000'
const backgroundColor = isDark ? 'rgb(35 35 35)' : 'rgb(255, 255, 255)'
const theme = this.ereaderSettings.theme
const isDark = theme === 'dark'
const isSepia = theme === 'sepia'
const fontColor = isDark
? '#fff'
: isSepia
? '#5b4636'
: '#000'
const backgroundColor = isDark
? 'rgb(35 35 35)'
: isSepia
? 'rgb(244, 236, 216)'
: 'rgb(255, 255, 255)'
const lineSpacing = this.ereaderSettings.lineSpacing / 100
const fontScale = this.ereaderSettings.fontScale / 100
const textStroke = this.ereaderSettings.textStroke / 100
const fontScale = this.ereaderSettings.fontScale / 100
const textStroke = this.ereaderSettings.textStroke / 100
return {
'*': {
color: `${fontColor}!important`,
'background-color': `${backgroundColor}!important`,
'line-height': lineSpacing * fontScale + 'rem!important',
'-webkit-text-stroke': textStroke + 'px ' + fontColor + '!important'
'line-height': `${lineSpacing * fontScale}rem!important`,
'-webkit-text-stroke': `${textStroke}px ${fontColor}!important`
},
a: {
color: `${fontColor}!important`
@@ -309,14 +316,24 @@ export default {
/** @type {EpubReader} */
const reader = this
// Use axios to make request because we have token refresh logic in interceptor
const customRequest = async (url) => {
try {
return this.$axios.$get(url, {
responseType: 'arraybuffer'
})
} catch (error) {
console.error('EpubReader.initEpub customRequest failed:', error)
throw error
}
}
/** @type {ePub.Book} */
reader.book = new ePub(reader.ebookUrl, {
width: this.readerWidth,
height: this.readerHeight - 50,
openAs: 'epub',
requestHeaders: {
Authorization: `Bearer ${this.userToken}`
}
requestMethod: customRequest
})
/** @type {ePub.Rendition} */
@@ -337,29 +354,33 @@ export default {
this.applyTheme()
})
reader.book.ready.then(() => {
// set up event listeners
reader.rendition.on('relocated', reader.relocated)
reader.rendition.on('keydown', reader.keyUp)
reader.book.ready
.then(() => {
// set up event listeners
reader.rendition.on('relocated', reader.relocated)
reader.rendition.on('keydown', reader.keyUp)
reader.rendition.on('touchstart', (event) => {
this.$emit('touchstart', event)
})
reader.rendition.on('touchend', (event) => {
this.$emit('touchend', event)
})
// load ebook cfi locations
const savedLocations = this.loadLocations()
if (savedLocations) {
reader.book.locations.load(savedLocations)
} else {
reader.book.locations.generate().then(() => {
this.checkSaveLocations(reader.book.locations.save())
reader.rendition.on('touchstart', (event) => {
this.$emit('touchstart', event)
})
}
this.getChapters()
})
reader.rendition.on('touchend', (event) => {
this.$emit('touchend', event)
})
// load ebook cfi locations
const savedLocations = this.loadLocations()
if (savedLocations) {
reader.book.locations.load(savedLocations)
} else {
reader.book.locations.generate().then(() => {
this.checkSaveLocations(reader.book.locations.save())
})
}
this.getChapters()
})
.catch((error) => {
console.error('EpubReader.initEpub failed:', error)
})
},
getChapters() {
// Load the list of chapters in the book. See https://github.com/futurepress/epub.js/issues/759

View File

@@ -26,9 +26,6 @@ export default {
return {}
},
computed: {
userToken() {
return this.$store.getters['user/getToken']
},
libraryItemId() {
return this.libraryItem?.id
},
@@ -96,11 +93,8 @@ export default {
},
async initMobi() {
// Fetch mobi file as blob
var buff = await this.$axios.$get(this.ebookUrl, {
responseType: 'blob',
headers: {
Authorization: `Bearer ${this.userToken}`
}
const buff = await this.$axios.$get(this.ebookUrl, {
responseType: 'blob'
})
var reader = new FileReader()
reader.onload = async (event) => {

View File

@@ -55,7 +55,8 @@ export default {
loadedRatio: 0,
page: 1,
numPages: 0,
pdfDocInitParams: null
pdfDocInitParams: null,
isRefreshing: false
}
},
computed: {
@@ -152,7 +153,34 @@ export default {
this.page++
this.updateProgress()
},
error(err) {
async refreshToken() {
if (this.isRefreshing) return
this.isRefreshing = true
const newAccessToken = await this.$store.dispatch('user/refreshToken').catch((error) => {
console.error('Failed to refresh token', error)
return null
})
if (!newAccessToken) {
// Redirect to login on failed refresh
this.$router.push('/login')
return
}
// Force Vue to re-render the PDF component by creating a new object
this.pdfDocInitParams = {
url: this.ebookUrl,
httpHeaders: {
Authorization: `Bearer ${newAccessToken}`
}
}
this.isRefreshing = false
},
async error(err) {
if (err && err.status === 401) {
console.log('Received 401 error, refreshing token')
await this.refreshToken()
return
}
console.error(err)
},
resize() {

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="show" id="reader" :data-theme="ereaderTheme" class="group absolute top-0 left-0 w-full z-60 data-[theme=dark]:bg-primary data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black" :class="{ 'reader-player-open': !!streamLibraryItem }">
<div v-if="show" id="reader" :data-theme="ereaderTheme" class="group absolute top-0 left-0 w-full z-60 data-[theme=dark]:bg-primary data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black data-[theme=sepia]:bg-[rgb(244,236,216)] data-[theme=sepia]:text-[#5b4636]" :class="{ 'reader-player-open': !!streamLibraryItem }">
<div class="absolute top-4 left-4 z-20 flex items-center">
<button v-if="isEpub" @click="toggleToC" type="button" aria-label="Table of contents menu" class="inline-flex opacity-80 hover:opacity-100">
<span class="material-symbols text-2xl">menu</span>
@@ -27,7 +27,12 @@
<!-- TOC side nav -->
<div v-if="tocOpen" class="w-full h-full overflow-y-scroll absolute inset-0 bg-black/20 z-20" @click.stop.prevent="toggleToC"></div>
<div v-if="isEpub" class="w-96 h-full max-h-full absolute top-0 left-0 shadow-xl transition-transform z-30 group-data-[theme=dark]:bg-primary group-data-[theme=dark]:text-white group-data-[theme=light]:bg-white group-data-[theme=light]:text-black" :class="tocOpen ? 'translate-x-0' : '-translate-x-96'" @click.stop.prevent>
<div
v-if="isEpub"
class="w-96 h-full max-h-full absolute top-0 left-0 shadow-xl transition-transform z-30 group-data-[theme=dark]:bg-primary group-data-[theme=dark]:text-white group-data-[theme=light]:bg-white group-data-[theme=light]:text-black group-data-[theme=sepia]:bg-[rgb(244,236,216)] group-data-[theme=sepia]:text-[#5b4636]"
:class="tocOpen ? 'translate-x-0' : '-translate-x-96'"
@click.stop.prevent
>
<div class="flex flex-col p-4 h-full">
<div class="flex items-center mb-2">
<button @click.stop.prevent="toggleToC" type="button" aria-label="Close table of contents" class="inline-flex opacity-80 hover:opacity-100">
@@ -37,7 +42,7 @@
<p class="text-lg font-semibold ml-2">{{ $strings.HeaderTableOfContents }}</p>
</div>
<form @submit.prevent="searchBook" @click.stop.prevent>
<ui-text-input clearable ref="input" @clear="searchBook" v-model="searchQuery" :placeholder="$strings.PlaceholderSearch" class="h-8 w-full text-sm flex mb-2" />
<ui-text-input clearable ref="input" @clear="searchBook" v-model="searchQuery" :placeholder="$strings.PlaceholderSearch" custom-input-class="text-inherit !bg-inherit" class="h-8 w-full text-sm flex mb-2" />
</form>
<div class="overflow-y-auto">
@@ -181,6 +186,10 @@ export default {
text: this.$strings.LabelThemeDark,
value: 'dark'
},
{
text: this.$strings.LabelThemeSepia,
value: 'sepia'
},
{
text: this.$strings.LabelThemeLight,
value: 'light'
@@ -266,9 +275,6 @@ export default {
isComic() {
return this.ebookFormat == 'cbz' || this.ebookFormat == 'cbr'
},
userToken() {
return this.$store.getters['user/getToken']
},
keepProgress() {
return this.$store.state.ereaderKeepProgress
},

View File

@@ -152,7 +152,7 @@ export default {
this.showingTooltipIndex = index
this.tooltipEl.style.display = 'block'
this.tooltipTextEl.innerHTML = block.value ? `<strong>${this.$elapsedPretty(block.value, true)} listening</strong> on ${block.datePretty}` : `No listening sessions on ${block.datePretty}`
this.tooltipTextEl.innerHTML = block.value ? this.$getString('MessageHeatmapListeningTimeTooltip', [this.$elapsedPrettyLocalized(block.value, true), block.datePretty]) : this.$getString('MessageHeatmapNoListeningSessions', [block.datePretty])
const calculateRect = this.tooltipEl.getBoundingClientRect()

View File

@@ -1,9 +1,7 @@
<template>
<div class="flex flex-wrap justify-center mt-6">
<div class="flex p-2">
<svg class="h-14 w-14" viewBox="0 0 24 24">
<path fill="currentColor" d="M9 3V18H12V3H9M12 5L16 18L19 17L15 4L12 5M5 5V18H8V5H5M3 19V21H21V19H3Z" />
</svg>
<span class="material-symbols text-5xl py-1">newsstand</span>
<div class="px-1">
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalItems) }}</p>
<p class="text-xs md:text-sm text-white/80">{{ $strings.LabelStatsItemsInLibrary }}</p>
@@ -19,9 +17,7 @@
</div>
<div v-if="isBookLibrary" class="flex p-2">
<svg class="h-14 w-14" viewBox="0 0 24 24">
<path fill="currentColor" d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9.03,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 14.97,14.9 12,14.9Z" />
</svg>
<span class="material-symbols text-5xl py-1">person</span>
<div class="px-1">
<p class="text-4.5xl leading-none font-bold">{{ $formatNumber(totalAuthors) }}</p>
<p class="text-xs md:text-sm text-white/80">{{ $strings.LabelStatsAuthors }}</p>

View File

@@ -164,14 +164,15 @@ export default {
beforeMount() {
this.yearInReviewYear = new Date().getFullYear()
// When not December show previous year
if (new Date().getMonth() < 11) {
this.availableYears = this.getAvailableYears()
const availableYearValues = this.availableYears.map((y) => y.value)
// When not December show previous year if data is available
if (new Date().getMonth() < 11 && availableYearValues.includes(this.yearInReviewYear - 1)) {
this.yearInReviewYear--
}
},
mounted() {
this.availableYears = this.getAvailableYears()
if (typeof navigator.share !== 'undefined' && navigator.share) {
this.showShareButton = true
} else {

View File

@@ -0,0 +1,177 @@
<template>
<div>
<div class="text-center">
<table v-if="apiKeys.length > 0" id="api-keys">
<tr>
<th>{{ $strings.LabelName }}</th>
<th class="w-44">{{ $strings.LabelApiKeyUser }}</th>
<th class="w-32">{{ $strings.LabelExpiresAt }}</th>
<th class="w-32">{{ $strings.LabelCreatedAt }}</th>
<th class="w-32"></th>
</tr>
<tr v-for="apiKey in apiKeys" :key="apiKey.id" :class="apiKey.isActive ? '' : 'bg-error/10!'">
<td>
<div class="flex items-center">
<p class="pl-2 truncate">{{ apiKey.name }}</p>
</div>
</td>
<td class="text-xs">
<nuxt-link v-if="apiKey.user" :to="`/config/users/${apiKey.user.id}`" class="text-xs hover:underline">
{{ apiKey.user.username }}
</nuxt-link>
<p v-else class="text-xs">Error</p>
</td>
<td class="text-xs">
<p v-if="apiKey.expiresAt" class="text-xs" :title="apiKey.expiresAt">{{ getExpiresAtText(apiKey) }}</p>
<p v-else class="text-xs">{{ $strings.LabelExpiresNever }}</p>
</td>
<td class="text-xs font-mono">
<ui-tooltip direction="top" :text="$formatJsDatetime(new Date(apiKey.createdAt), dateFormat, timeFormat)">
{{ $formatJsDate(new Date(apiKey.createdAt), dateFormat) }}
</ui-tooltip>
</td>
<td class="py-0">
<div class="w-full flex justify-left">
<div class="h-8 w-8 flex items-center justify-center text-white/50 hover:text-white/100 cursor-pointer" @click.stop="editApiKey(apiKey)">
<button type="button" :aria-label="$strings.ButtonEdit" class="material-symbols text-base">edit</button>
</div>
<div class="h-8 w-8 flex items-center justify-center text-white/50 hover:text-error cursor-pointer" @click.stop="deleteApiKeyClick(apiKey)">
<button type="button" :aria-label="$strings.ButtonDelete" class="material-symbols text-base">delete</button>
</div>
</div>
</td>
</tr>
</table>
<p v-else class="text-base text-gray-300 py-4">{{ $strings.LabelNoApiKeys }}</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
apiKeys: [],
isDeletingApiKey: false
}
},
computed: {
dateFormat() {
return this.$store.state.serverSettings.dateFormat
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
}
},
methods: {
getExpiresAtText(apiKey) {
if (new Date(apiKey.expiresAt).getTime() < Date.now()) {
return this.$strings.LabelExpired
}
return this.$formatJsDatetime(new Date(apiKey.expiresAt), this.dateFormat, this.timeFormat)
},
deleteApiKeyClick(apiKey) {
if (this.isDeletingApiKey) return
const payload = {
message: this.$getString('MessageConfirmDeleteApiKey', [apiKey.name]),
callback: (confirmed) => {
if (confirmed) {
this.deleteApiKey(apiKey)
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
deleteApiKey(apiKey) {
this.isDeletingApiKey = true
this.$axios
.$delete(`/api/api-keys/${apiKey.id}`)
.then((data) => {
if (data.error) {
this.$toast.error(data.error)
} else {
this.removeApiKey(apiKey.id)
this.$emit('numApiKeys', this.apiKeys.length)
}
})
.catch((error) => {
console.error('Failed to delete apiKey', error)
this.$toast.error(this.$strings.ToastFailedToDelete)
})
.finally(() => {
this.isDeletingApiKey = false
})
},
editApiKey(apiKey) {
this.$emit('edit', apiKey)
},
addApiKey(apiKey) {
this.apiKeys.push(apiKey)
},
removeApiKey(apiKeyId) {
this.apiKeys = this.apiKeys.filter((a) => a.id !== apiKeyId)
},
updateApiKey(apiKey) {
this.apiKeys = this.apiKeys.map((a) => (a.id === apiKey.id ? apiKey : a))
},
loadApiKeys() {
this.$axios
.$get('/api/api-keys')
.then((res) => {
this.apiKeys = res.apiKeys.sort((a, b) => {
return a.createdAt - b.createdAt
})
this.$emit('numApiKeys', this.apiKeys.length)
})
.catch((error) => {
console.error('Failed to load apiKeys', error)
})
}
},
mounted() {
this.loadApiKeys()
}
}
</script>
<style>
#api-keys {
table-layout: fixed;
border-collapse: collapse;
border: 1px solid #474747;
width: 100%;
}
#api-keys td,
#api-keys th {
/* border: 1px solid #2e2e2e; */
padding: 8px 8px;
text-align: left;
}
#api-keys td.py-0 {
padding: 0px 8px;
}
#api-keys tr:nth-child(even) {
background-color: #373838;
}
#api-keys tr:nth-child(odd) {
background-color: #2f2f2f;
}
#api-keys tr:hover {
background-color: #444;
}
#api-keys th {
font-size: 0.8rem;
font-weight: 600;
padding-top: 5px;
padding-bottom: 5px;
background-color: #272727;
}
</style>

View File

@@ -78,10 +78,10 @@ export default {
return this.$store.getters['user/getToken']
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
return this.$store.getters['getServerSetting']('timeFormat')
}
},
methods: {

View File

@@ -49,9 +49,6 @@ export default {
libraryItemId() {
return this.libraryItem.id
},
userToken() {
return this.$store.getters['user/getToken']
},
userCanDownload() {
return this.$store.getters['user/getUserCanDownload']
},

View File

@@ -53,9 +53,6 @@ export default {
libraryItemId() {
return this.libraryItem.id
},
userToken() {
return this.$store.getters['user/getToken']
},
userCanDownload() {
return this.$store.getters['user/getUserCanDownload']
},

View File

@@ -76,10 +76,10 @@ export default {
return usermap
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
return this.$store.getters['getServerSetting']('timeFormat')
}
},
methods: {

View File

@@ -112,7 +112,7 @@ export default {
return this.episode?.publishedAt
},
dateFormat() {
return this.store.state.serverSettings.dateFormat
return this.store.getters['getServerSetting']('dateFormat')
},
itemProgress() {
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId, this.episodeId)

View File

@@ -1,4 +1,3 @@
<template>
<div id="lazy-episodes-table" class="w-full py-6">
<div class="flex flex-wrap flex-col md:flex-row md:items-center mb-4">
@@ -176,6 +175,13 @@ export default {
return episodeProgress && !episodeProgress.isFinished
})
.sort((a, b) => {
// Swap values if sort descending
if (this.sortDesc) {
const temp = a
a = b
b = temp
}
let aValue
let bValue
@@ -194,10 +200,23 @@ export default {
if (!bValue) bValue = Number.MAX_VALUE
}
if (this.sortDesc) {
return String(bValue).localeCompare(String(aValue), undefined, { numeric: true, sensitivity: 'base' })
const primaryCompare = String(aValue).localeCompare(String(bValue), undefined, { numeric: true, sensitivity: 'base' })
if (primaryCompare !== 0 || this.sortKey === 'publishedAt') return primaryCompare
// When sorting by season, secondary sort is by episode number
if (this.sortKey === 'season') {
const aEpisode = a.episode || ''
const bEpisode = b.episode || ''
const secondaryCompare = String(aEpisode).localeCompare(String(bEpisode), undefined, { numeric: true, sensitivity: 'base' })
if (secondaryCompare !== 0) return secondaryCompare
}
return String(aValue).localeCompare(String(bValue), undefined, { numeric: true, sensitivity: 'base' })
// Final sort by publishedAt
let aPubDate = a.publishedAt || Number.MAX_VALUE
let bPubDate = b.publishedAt || Number.MAX_VALUE
return String(aPubDate).localeCompare(String(bPubDate), undefined, { numeric: true, sensitivity: 'base' })
})
},
episodesList() {
@@ -220,10 +239,10 @@ export default {
})
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
return this.$store.getters['getServerSetting']('timeFormat')
}
},
methods: {

View File

@@ -85,9 +85,6 @@ export default {
this.$emit('input', val)
}
},
userToken() {
return this.$store.getters['user/getToken']
},
wrapperClass() {
var classes = []
if (this.disabled) classes.push('bg-black-300')

View File

@@ -1,12 +1,8 @@
<template>
<button :aria-label="isRead ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" class="icon-btn rounded-md flex items-center justify-center h-9 w-9 relative" :class="borderless ? '' : 'bg-primary border border-gray-600'" @click="clickBtn">
<div class="w-5 h-5 text-white relative">
<svg v-if="isRead" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(63, 181, 68)">
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-7 19.6l-7-4.66V3h14v12.93l-7 4.67zm-2.01-7.42l-2.58-2.59L6 12l4 4 8-8-1.42-1.42z" />
</svg>
<div class="w-5 h-5 relative">
<span v-if="isRead" class="material-symbols fill text-xl text-success">beenhere</span>
<span v-else class="material-symbols text-xl text-white">beenhere</span>
</div>
</button>
</template>

View File

@@ -1,9 +1,9 @@
<template>
<div class="relative w-full">
<p v-if="label" class="text-sm font-semibold px-1" :class="disabled ? 'text-gray-300' : ''">{{ label }}</p>
<p v-if="label && !labelHidden" class="text-sm font-semibold px-1" :class="disabled ? 'text-gray-300' : ''">{{ label }}</p>
<button ref="buttonWrapper" type="button" :aria-label="longLabel" :disabled="disabled" class="relative w-full border rounded-sm shadow-xs pl-3 pr-8 py-2 text-left sm:text-sm" :class="buttonClass" aria-haspopup="listbox" aria-expanded="true" @click.stop.prevent="clickShowMenu">
<span class="flex items-center">
<span class="block truncate font-sans" :class="{ 'font-semibold': selectedSubtext, 'text-sm': small }">{{ selectedText }}</span>
<span class="block truncate font-sans" :class="{ 'font-semibold': selectedSubtext, 'text-sm': small, 'text-gray-400': !selectedText }">{{ selectedText || placeholder }}</span>
<span v-if="selectedSubtext">:&nbsp;</span>
<span v-if="selectedSubtext" class="font-normal block truncate font-sans text-sm text-gray-400">{{ selectedSubtext }}</span>
</span>
@@ -36,10 +36,15 @@ export default {
type: String,
default: ''
},
labelHidden: Boolean,
items: {
type: Array,
default: () => []
},
placeholder: {
type: String,
default: ''
},
disabled: Boolean,
small: Boolean,
menuMaxHeight: {

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" :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" class="w-full" :class="inputClass" :trim-whitespace="trimWhitespace" @blur="inputBlurred" />
</div>
</template>
@@ -21,6 +21,7 @@ export default {
type: String,
default: 'text'
},
min: [String, Number],
readonly: Boolean,
disabled: Boolean,
inputClass: String,

View File

@@ -1,6 +1,6 @@
<template>
<div class="inline-flex toggle-btn-wrapper shadow-md">
<button v-for="item in items" :key="item.value" type="button" class="toggle-btn outline-hidden relative border border-gray-600 px-4 py-1" :class="{ selected: item.value === value }" @click.stop="clickBtn(item.value)">
<button v-for="item in items" :key="item.value" type="button" :disabled="disabled" class="toggle-btn outline-hidden relative border border-gray-600 px-4 py-1" :class="{ selected: item.value === value }" @click.stop="clickBtn(item.value)">
{{ item.text }}
</button>
</div>
@@ -9,13 +9,17 @@
<script>
export default {
props: {
value: String,
value: [String, Number],
/**
* [{ "text", "", "value": "" }]
*/
items: {
type: Array,
default: Object
},
disabled: {
type: Boolean,
default: false
}
},
data() {
@@ -76,10 +80,19 @@ export default {
.toggle-btn.selected {
color: white;
}
.toggle-btn.selected:disabled {
color: white;
}
.toggle-btn.selected::before {
background-color: rgba(255, 255, 255, 0.1);
}
button.toggle-btn.selected:disabled::before {
background-color: rgba(255, 255, 255, 0.05);
}
button.toggle-btn:disabled::before {
background-color: rgba(0, 0, 0, 0.2);
}
button.toggle-btn:disabled {
cursor: not-allowed;
}
</style>

View File

@@ -31,7 +31,7 @@
</div>
</div>
</trix-toolbar>
<trix-editor :toolbar="toolbarId" :contenteditable="!disabledEditor" :class="['trix-content']" ref="trix" :input="computedId" :placeholder="placeholder" @trix-change="handleContentChange" @trix-initialize="handleInitialize" @trix-focus="processTrixFocus" @trix-blur="processTrixBlur" />
<trix-editor :toolbar="toolbarId" :contenteditable="!disabledEditor" :class="['trix-content']" ref="trix" :input="computedId" :placeholder="placeholder" @trix-change="handleContentChange" @trix-initialize="handleInitialize" @trix-focus="processTrixFocus" @trix-blur="processTrixBlur" @trix-attachment-add="handleAttachmentAdd" />
<input type="hidden" :name="inputName" :id="computedId" :value="editorContent" />
</div>
</template>
@@ -316,6 +316,10 @@ export default {
if (this.$refs.trix && this.$refs.trix.blur) {
this.$refs.trix.blur()
}
},
handleAttachmentAdd(event) {
// Prevent pasting in images/any files from the browser
event.attachment.remove()
}
},
mounted() {

View File

@@ -85,7 +85,7 @@ export default {
nextRun() {
if (!this.cronExpression) return ''
const parsed = this.$getNextScheduledDate(this.cronExpression)
return this.$formatJsDatetime(parsed, this.$store.state.serverSettings.dateFormat, this.$store.state.serverSettings.timeFormat) || ''
return this.$formatJsDatetime(parsed, this.$store.getters['getServerSetting']('dateFormat'), this.$store.getters['getServerSetting']('timeFormat')) || ''
},
description() {
if ((this.selectedInterval !== 'custom' || !this.selectedWeekdays.length) && this.selectedInterval !== 'daily') return ''

View File

@@ -0,0 +1,219 @@
<template>
<div class="w-full py-2">
<div class="flex -mb-px">
<button type="button" :disabled="disabled" class="w-1/2 h-8 rounded-tl-md relative border border-black-200 flex items-center justify-center disabled:cursor-not-allowed" :class="!showAdvancedView ? 'text-white bg-bg hover:bg-bg/60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary/70 hover:bg-primary/60'" @click="showAdvancedView = false">
<p class="text-sm">{{ $strings.HeaderPresets }}</p>
</button>
<button type="button" :disabled="disabled" class="w-1/2 h-8 rounded-tr-md relative border border-black-200 flex items-center justify-center -ml-px disabled:cursor-not-allowed" :class="showAdvancedView ? 'text-white bg-bg hover:bg-bg/60 border-b-bg' : 'text-gray-400 hover:text-gray-300 bg-primary/70 hover:bg-primary/60'" @click="showAdvancedView = true">
<p class="text-sm">{{ $strings.HeaderAdvanced }}</p>
</button>
</div>
<div class="p-4 md:p-8 border border-black-200 rounded-b-md mr-px bg-bg">
<template v-if="!showAdvancedView">
<div class="flex flex-wrap gap-4 sm:gap-8 justify-start sm:justify-center">
<div class="flex flex-col items-start gap-2">
<p class="text-sm w-40">{{ $strings.LabelCodec }}</p>
<ui-toggle-btns v-model="selectedCodec" :items="codecItems" :disabled="disabled" />
<p class="text-xs text-gray-300">
{{ $strings.LabelCurrently }} <span class="text-white">{{ currentCodec }}</span> <span v-if="isCodecsDifferent" class="text-warning">(mixed)</span>
</p>
</div>
<div class="flex flex-col items-start gap-2">
<p class="text-sm w-40">{{ $strings.LabelBitrate }}</p>
<ui-toggle-btns v-model="selectedBitrate" :items="bitrateItems" :disabled="disabled" />
<p class="text-xs text-gray-300">
{{ $strings.LabelCurrently }} <span class="text-white">{{ currentBitrate }} KB/s</span>
</p>
</div>
<div class="flex flex-col items-start gap-2">
<p class="text-sm w-40">{{ $strings.LabelChannels }}</p>
<ui-toggle-btns v-model="selectedChannels" :items="channelsItems" :disabled="disabled" />
<p class="text-xs text-gray-300">
{{ $strings.LabelCurrently }} <span class="text-white">{{ currentChannels }} ({{ currentChanelLayout }})</span>
</p>
</div>
</div>
</template>
<template v-else>
<div>
<div class="flex flex-wrap gap-4 sm:gap-8 justify-start sm:justify-center mb-4">
<div class="w-40">
<ui-text-input-with-label v-model="customCodec" :label="$strings.LabelAudioCodec" :disabled="disabled" @input="customCodecChanged" />
</div>
<div class="w-40">
<ui-text-input-with-label v-model="customBitrate" :label="$strings.LabelAudioBitrate" :disabled="disabled" @input="customBitrateChanged" />
</div>
<div class="w-40">
<ui-text-input-with-label v-model="customChannels" :label="$strings.LabelAudioChannels" type="number" :disabled="disabled" @input="customChannelsChanged" />
</div>
</div>
<p class="text-xs sm:text-sm text-warning sm:text-center">{{ $strings.LabelEncodingWarningAdvancedSettings }}</p>
</div>
</template>
</div>
</div>
</template>
<script>
export default {
props: {
audioTracks: {
type: Array,
default: () => []
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
showAdvancedView: false,
selectedCodec: 'aac',
selectedBitrate: '128k',
selectedChannels: 2,
customCodec: 'aac',
customBitrate: '128k',
customChannels: 2,
currentCodec: '',
currentBitrate: '',
currentChannels: '',
currentChanelLayout: '',
isCodecsDifferent: false
}
},
computed: {
codecItems() {
return [
{
text: 'Copy',
value: 'copy'
},
{
text: 'AAC',
value: 'aac'
},
{
text: 'OPUS',
value: 'opus'
}
]
},
bitrateItems() {
return [
{
text: '32k',
value: '32k'
},
{
text: '64k',
value: '64k'
},
{
text: '128k',
value: '128k'
},
{
text: '192k',
value: '192k'
}
]
},
channelsItems() {
return [
{
text: '1 (mono)',
value: 1
},
{
text: '2 (stereo)',
value: 2
}
]
}
},
methods: {
customBitrateChanged(val) {
localStorage.setItem('embedMetadataBitrate', val)
},
customChannelsChanged(val) {
localStorage.setItem('embedMetadataChannels', val)
},
customCodecChanged(val) {
localStorage.setItem('embedMetadataCodec', val)
},
getEncodingOptions() {
if (this.showAdvancedView) {
return {
codec: this.customCodec || this.selectedCodec || 'aac',
bitrate: this.customBitrate || this.selectedBitrate || '128k',
channels: this.customChannels || this.selectedChannels || 2
}
} else {
return {
codec: this.selectedCodec || 'aac',
bitrate: this.selectedBitrate || '128k',
channels: this.selectedChannels || 2
}
}
},
setPreset() {
// If already AAC and not mixed, set copy
if (this.currentCodec === 'aac' && !this.isCodecsDifferent) {
this.selectedCodec = 'copy'
} else {
this.selectedCodec = 'aac'
}
if (!this.currentBitrate) {
this.selectedBitrate = '128k'
} else {
// Find closest bitrate rounding up
const bitratesToMatch = [32, 64, 128, 192]
const closestBitrate = bitratesToMatch.find((bitrate) => bitrate >= this.currentBitrate) || 192
this.selectedBitrate = closestBitrate + 'k'
}
if (!this.currentChannels || isNaN(this.currentChannels)) {
this.selectedChannels = 2
} else {
// Either 1 or 2
this.selectedChannels = Math.max(Math.min(Number(this.currentChannels), 2), 1)
}
},
setCurrentValues() {
if (this.audioTracks.length === 0) return
this.currentChannels = this.audioTracks[0].channels
this.currentChanelLayout = this.audioTracks[0].channelLayout
this.currentCodec = this.audioTracks[0].codec
let totalBitrate = 0
for (const track of this.audioTracks) {
const trackBitrate = !isNaN(track.bitRate) ? track.bitRate : 0
totalBitrate += trackBitrate
if (track.channels > this.currentChannels) this.currentChannels = track.channels
if (track.codec !== this.currentCodec) {
console.warn('Audio track codec is different from the first track', track.codec)
this.isCodecsDifferent = true
}
}
this.currentBitrate = Math.round(totalBitrate / this.audioTracks.length / 1000)
},
init() {
this.customBitrate = localStorage.getItem('embedMetadataBitrate') || '128k'
this.customChannels = localStorage.getItem('embedMetadataChannels') || 2
this.customCodec = localStorage.getItem('embedMetadataCodec') || 'aac'
this.setCurrentValues()
this.setPreset()
}
},
mounted() {
this.init()
}
}
</script>

View File

@@ -1,40 +1,6 @@
<template>
<ui-tooltip :text="$strings.LabelExplicit" direction="top">
<svg xmlns="http://www.w3.org/2000/svg" width="12px" height="12px" viewBox="0 0 512 512" class="ml-1">
<path
fill="white"
d="M 89.00,40.12
C 89.00,40.12 127.00,40.12 127.00,40.12
127.00,40.12 198.00,40.12 198.00,40.12
198.00,40.12 416.00,40.12 416.00,40.12
446.58,40.05 472.95,66.42 473.00,97.00
473.00,97.00 473.00,303.00 473.00,303.00
473.00,303.00 473.00,418.00 473.00,418.00
472.65,447.55 445.06,472.95 416.00,473.00
416.00,473.00 210.00,473.00 210.00,473.00
210.00,473.00 95.00,473.00 95.00,473.00
65.45,472.65 40.05,445.06 40.00,416.00
40.00,416.00 40.00,136.00 40.00,136.00
40.00,136.00 40.00,109.00 40.00,109.00
40.00,109.00 40.00,96.00 40.00,96.00
40.07,81.58 46.89,67.14 57.01,57.01
61.17,52.86 64.86,50.13 70.00,47.31
77.25,43.33 81.02,42.18 89.00,40.12 Z
M 337.00,121.00
C 337.00,121.00 175.00,121.00 175.00,121.00
175.00,121.00 175.00,392.00 175.00,392.00
175.00,392.00 337.00,392.00 337.00,392.00
337.00,392.00 337.00,349.00 337.00,349.00
337.00,349.00 226.00,349.00 226.00,349.00
226.00,349.00 226.00,274.00 226.00,274.00
226.00,274.00 332.00,274.00 332.00,274.00
332.00,274.00 332.00,232.00 332.00,232.00
332.00,232.00 226.00,232.00 226.00,232.00
226.00,232.00 226.00,164.00 226.00,164.00
226.00,164.00 337.00,164.00 337.00,164.00
337.00,164.00 337.00,121.00 337.00,121.00 Z"
/>
</svg>
<span class="material-symbols fill text-sm ml-1 !block">explicit</span>
</ui-tooltip>
</template>

View File

@@ -248,4 +248,4 @@ export default {
transform: scale(0);
}
}
</style>
</style>

View File

@@ -2,7 +2,7 @@
<div>
<ui-multi-select-query-input v-model="seriesItems" text-key="displayName" :label="$strings.LabelSeries" :disabled="disabled" readonly show-edit @edit="editSeriesItem" @add="addNewSeries" />
<modals-edit-series-input-inner-modal v-model="showSeriesForm" :selected-series="selectedSeries" :existing-series-names="existingSeriesNames" @submit="submitSeriesForm" />
<modals-edit-series-input-inner-modal v-model="showSeriesForm" :selected-series="selectedSeries" :existing-series-names="existingSeriesNames" :original-series-sequence="originalSeriesSequence" @submit="submitSeriesForm" />
</div>
</template>
@@ -18,6 +18,7 @@ export default {
data() {
return {
selectedSeries: null,
originalSeriesSequence: null,
showSeriesForm: false
}
},
@@ -59,6 +60,7 @@ export default {
..._series
}
this.originalSeriesSequence = _series.sequence
this.showSeriesForm = true
},
addNewSeries() {
@@ -68,6 +70,7 @@ export default {
sequence: ''
}
this.originalSeriesSequence = null
this.showSeriesForm = true
},
submitSeriesForm() {
@@ -106,4 +109,4 @@ export default {
}
}
}
</script>
</script>

View File

@@ -40,6 +40,7 @@ describe('LazySeriesCard', () => {
},
$store: {
getters: {
getServerSetting: () => 'MM/dd/yyyy',
'user/getUserCanUpdate': true,
'user/getUserMediaProgress': (id) => null,
'user/getSizeMultiplier': 1,

View File

@@ -33,6 +33,7 @@ export default {
return {
socket: null,
isSocketConnected: false,
isSocketAuthenticated: false,
isFirstSocketConnection: true,
socketConnectionToastId: null,
currentLang: null,
@@ -81,9 +82,28 @@ export default {
document.body.classList.add('app-bar')
}
},
tokenRefreshed(newAccessToken) {
if (this.isSocketConnected && !this.isSocketAuthenticated) {
console.log('[SOCKET] Re-authenticating socket after token refresh')
this.socket.emit('auth', newAccessToken)
}
},
updateSocketConnectionToast(content, type, timeout) {
if (this.socketConnectionToastId !== null && this.socketConnectionToastId !== undefined) {
this.$toast.update(this.socketConnectionToastId, { content: content, options: { timeout: timeout, type: type, closeButton: false, position: 'bottom-center', onClose: () => null, closeOnClick: timeout !== null } }, false)
const toastUpdateOptions = {
content: content,
options: {
timeout: timeout,
type: type,
closeButton: false,
position: 'bottom-center',
onClose: () => {
this.socketConnectionToastId = null
},
closeOnClick: timeout !== null
}
}
this.$toast.update(this.socketConnectionToastId, toastUpdateOptions, false)
} else {
this.socketConnectionToastId = this.$toast[type](content, { position: 'bottom-center', timeout: timeout, closeButton: false, closeOnClick: timeout !== null })
}
@@ -109,7 +129,7 @@ export default {
this.updateSocketConnectionToast(this.$strings.ToastSocketDisconnected, 'error', null)
},
reconnect() {
console.error('[SOCKET] reconnected')
console.log('[SOCKET] reconnected')
},
reconnectAttempt(val) {
console.log(`[SOCKET] reconnect attempt ${val}`)
@@ -120,6 +140,10 @@ export default {
reconnectFailed() {
console.error('[SOCKET] reconnect failed')
},
authFailed(payload) {
console.error('[SOCKET] auth failed', payload.message)
this.isSocketAuthenticated = false
},
init(payload) {
console.log('Init Payload', payload)
@@ -127,7 +151,7 @@ export default {
this.$store.commit('users/setUsersOnline', payload.usersOnline)
}
this.$eventBus.$emit('socket_init')
this.isSocketAuthenticated = true
},
streamOpen(stream) {
if (this.$refs.mediaPlayerContainer) this.$refs.mediaPlayerContainer.streamOpen(stream)
@@ -175,7 +199,7 @@ export default {
}
} else {
console.error('User has no more accessible libraries')
this.$store.commit('libraries/setCurrentLibrary', null)
this.$store.commit('libraries/setCurrentLibrary', { id: null })
}
}
},
@@ -354,6 +378,15 @@ export default {
this.$store.commit('scanners/removeCustomMetadataProvider', provider)
},
initializeSocket() {
if (this.$root.socket) {
// Can happen in dev due to hot reload
console.warn('Socket already initialized')
this.socket = this.$root.socket
this.isSocketConnected = this.$root.socket?.connected
this.isFirstSocketConnection = false
this.socketConnectionToastId = null
return
}
this.socket = this.$nuxtSocket({
name: process.env.NODE_ENV === 'development' ? 'dev' : 'prod',
persist: 'main',
@@ -364,6 +397,7 @@ export default {
path: `${this.$config.routerBasePath}/socket.io`
})
this.$root.socket = this.socket
this.isSocketAuthenticated = false
console.log('Socket initialized')
// Pre-defined socket events
@@ -377,6 +411,7 @@ export default {
// Event received after authorizing socket
this.socket.on('init', this.init)
this.socket.on('auth_failed', this.authFailed)
// Stream Listeners
this.socket.on('stream_open', this.streamOpen)
@@ -571,6 +606,7 @@ export default {
this.updateBodyClass()
this.resize()
this.$eventBus.$on('change-lang', this.changeLanguage)
this.$eventBus.$on('token_refreshed', this.tokenRefreshed)
window.addEventListener('resize', this.resize)
window.addEventListener('keydown', this.keyDown)
@@ -594,6 +630,7 @@ export default {
},
beforeDestroy() {
this.$eventBus.$off('change-lang', this.changeLanguage)
this.$eventBus.$off('token_refreshed', this.tokenRefreshed)
window.removeEventListener('resize', this.resize)
window.removeEventListener('keydown', this.keyDown)
}

View File

@@ -3,7 +3,6 @@ import LazyBookCard from '@/components/cards/LazyBookCard'
import LazySeriesCard from '@/components/cards/LazySeriesCard'
import LazyCollectionCard from '@/components/cards/LazyCollectionCard'
import LazyPlaylistCard from '@/components/cards/LazyPlaylistCard'
import LazyAlbumCard from '@/components/cards/LazyAlbumCard'
import AuthorCard from '@/components/cards/AuthorCard'
export default {
@@ -20,7 +19,6 @@ export default {
if (this.entityName === 'series') return Vue.extend(LazySeriesCard)
if (this.entityName === 'collections') return Vue.extend(LazyCollectionCard)
if (this.entityName === 'playlists') return Vue.extend(LazyPlaylistCard)
if (this.entityName === 'albums') return Vue.extend(LazyAlbumCard)
if (this.entityName === 'authors') return Vue.extend(AuthorCard)
return Vue.extend(LazyBookCard)
},
@@ -28,7 +26,6 @@ export default {
if (this.entityName === 'series') return 'cards-lazy-series-card'
if (this.entityName === 'collections') return 'cards-lazy-collection-card'
if (this.entityName === 'playlists') return 'cards-lazy-playlist-card'
if (this.entityName === 'albums') return 'cards-lazy-album-card'
if (this.entityName === 'authors') return 'cards-author-card'
return 'cards-lazy-book-card'
},

View File

@@ -73,7 +73,8 @@ module.exports = {
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
baseURL: routerBasePath
baseURL: routerBasePath,
progress: false
},
// nuxt/pwa https://pwa.nuxtjs.org

View File

@@ -1,12 +1,12 @@
{
"name": "audiobookshelf-client",
"version": "2.21.0",
"version": "2.29.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "audiobookshelf-client",
"version": "2.21.0",
"version": "2.29.0",
"license": "ISC",
"dependencies": {
"@nuxtjs/axios": "^5.13.6",

View File

@@ -1,6 +1,6 @@
{
"name": "audiobookshelf-client",
"version": "2.21.0",
"version": "2.29.0",
"buildNumber": 1,
"description": "Self-hosted audiobook and podcast client",
"main": "index.js",

View File

@@ -182,18 +182,19 @@ export default {
password: this.password,
newPassword: this.newPassword
})
.then((res) => {
if (res.success) {
this.$toast.success(this.$strings.ToastUserPasswordChangeSuccess)
this.resetForm()
} else {
this.$toast.error(res.error || this.$strings.ToastUnknownError)
}
this.changingPassword = false
.then(() => {
this.$toast.success(this.$strings.ToastUserPasswordChangeSuccess)
this.resetForm()
})
.catch((error) => {
console.error(error)
this.$toast.error(this.$strings.ToastUnknownError)
console.error('Failed to change password', error)
let errorMessage = this.$strings.ToastUnknownError
if (error.response?.data && typeof error.response.data === 'string') {
errorMessage = error.response.data
}
this.$toast.error(errorMessage)
})
.finally(() => {
this.changingPassword = false
})
},

View File

@@ -1,6 +1,6 @@
<template>
<div id="page-wrapper" class="bg-bg page overflow-y-auto relative" :class="streamLibraryItem ? 'streaming' : ''">
<div class="flex items-center py-4 px-2 md:px-0 max-w-7xl mx-auto">
<div class="flex items-center py-4 px-4 max-w-7xl mx-auto">
<nuxt-link :to="`/item/${libraryItem.id}`" class="hover:underline">
<h1 class="text-lg lg:text-xl">{{ title }}</h1>
</nuxt-link>
@@ -12,7 +12,7 @@
<p class="text-base font-mono ml-4 hidden md:block">{{ $secondsToTimestamp(mediaDurationRounded) }}</p>
</div>
<div class="flex flex-wrap-reverse justify-center py-4 px-2">
<div class="flex flex-wrap-reverse lg:flex-nowrap justify-center py-4 px-4">
<div class="w-full max-w-3xl py-4">
<div class="flex items-center">
<div class="w-12 hidden lg:block" />
@@ -23,8 +23,8 @@
</div>
<div class="flex items-center mb-3 py-1 -mx-1">
<div class="w-12 hidden lg:block" />
<ui-btn v-if="chapters.length" color="bg-primary" small class="mx-1" @click.stop="removeAllChaptersClick">{{ $strings.ButtonRemoveAll }}</ui-btn>
<ui-btn v-if="newChapters.length > 1" :color="showShiftTimes ? 'bg' : 'primary'" class="mx-1" small @click="showShiftTimes = !showShiftTimes">{{ $strings.ButtonShiftTimes }}</ui-btn>
<ui-btn v-if="chapters.length" color="bg-primary" small class="mx-1 whitespace-nowrap" @click.stop="removeAllChaptersClick">{{ $strings.ButtonRemoveAll }}</ui-btn>
<ui-btn v-if="newChapters.length > 1" :color="showShiftTimes ? 'bg-bg' : 'bg-primary'" class="mx-1 whitespace-nowrap" small @click="showShiftTimes = !showShiftTimes">{{ $strings.ButtonShiftTimes }}</ui-btn>
<ui-btn color="bg-primary" small :class="{ 'mx-1': newChapters.length > 1 }" @click="showFindChaptersModal = true">{{ $strings.ButtonLookup }}</ui-btn>
<div class="grow" />
<ui-btn v-if="hasChanges" small class="mx-1" @click.stop="resetChapters">{{ $strings.ButtonReset }}</ui-btn>
@@ -53,51 +53,101 @@
<div class="flex text-xs uppercase text-gray-300 font-semibold mb-2">
<div class="w-8 min-w-8 md:w-12 md:min-w-12"></div>
<div class="w-24 min-w-24 md:w-32 md:min-w-32 px-2">{{ $strings.LabelStart }}</div>
<div class="grow px-2">{{ $strings.LabelTitle }}</div>
<div class="w-38 min-w-38 md:w-40 md:min-w-40 px-1 pl-8">{{ $strings.LabelStart }}</div>
<div class="grow px-1 min-w-54">{{ $strings.LabelTitle }}</div>
<div class="w-7 min-w-7 px-1 flex items-center justify-center">
<ui-tooltip :text="allChaptersLocked ? $strings.TooltipUnlockAllChapters : $strings.TooltipLockAllChapters" direction="bottom">
<button class="w-7 h-7 rounded-full flex items-center justify-center cursor-pointer transition-colors duration-150" :class="allChaptersLocked ? 'text-orange-400 hover:text-orange-300' : 'text-gray-300 hover:text-white'" @click="toggleAllChaptersLock">
<span class="material-symbols text-xl">{{ allChaptersLocked ? 'lock' : 'lock_open' }}</span>
</button>
</ui-tooltip>
</div>
<div class="w-32"></div>
</div>
<template v-for="chapter in newChapters">
<div :key="chapter.id" class="flex py-1">
<div class="w-8 min-w-8 md:w-12 md:min-w-12">#{{ chapter.id + 1 }}</div>
<div class="w-24 min-w-24 md:w-32 md:min-w-32 px-1">
<ui-text-input v-if="showSecondInputs" v-model="chapter.start" type="number" class="text-xs" @change="checkChapters" />
<ui-time-picker v-else class="text-xs" v-model="chapter.start" :show-three-digit-hour="mediaDuration >= 360000" @change="checkChapters" />
</div>
<div class="grow px-1">
<ui-text-input v-model="chapter.title" @change="checkChapters" class="text-xs" />
</div>
<div class="w-32 min-w-32 px-2 py-1">
<div class="flex items-center">
<ui-tooltip :text="$strings.MessageRemoveChapter" direction="bottom">
<button v-if="newChapters.length > 1" class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-error transform hover:scale-110 duration-150" @click="removeChapter(chapter)">
<span class="material-symbols text-base">remove</span>
</button>
</ui-tooltip>
<div v-for="chapter in newChapters" :key="chapter.id" class="flex py-1">
<div class="w-8 min-w-8 md:w-12 md:min-w-12">#{{ chapter.id + 1 }}</div>
<div class="w-38 min-w-38 md:w-40 md:min-w-40 px-1">
<div class="flex items-center gap-1">
<ui-tooltip :text="$strings.TooltipSubtractOneSecond" direction="bottom">
<button
class="w-6 h-6 rounded-full flex items-center justify-center text-gray-300 hover:text-white transform hover:scale-110 duration-150 flex-shrink-0"
:class="{ 'opacity-50 cursor-not-allowed': chapter.id === 0 && chapter.start - timeIncrementAmount < 0 }"
@click="incrementChapterTime(chapter, -timeIncrementAmount)"
:disabled="chapter.id === 0 && chapter.start - timeIncrementAmount < 0"
>
<span class="material-symbols text-sm">remove</span>
</button>
</ui-tooltip>
<ui-tooltip :text="$strings.MessageInsertChapterBelow" direction="bottom">
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-success transform hover:scale-110 duration-150" @click="addChapter(chapter)">
<span class="material-symbols text-lg">add</span>
</button>
</ui-tooltip>
<ui-tooltip :text="selectedChapterId === chapter.id && isPlayingChapter ? $strings.MessagePauseChapter : $strings.MessagePlayChapter" direction="bottom">
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-white transform hover:scale-110 duration-150" @click="playChapter(chapter)">
<widgets-loading-spinner v-if="selectedChapterId === chapter.id && isLoadingChapter" />
<span v-else-if="selectedChapterId === chapter.id && isPlayingChapter" class="material-symbols text-base">pause</span>
<span v-else class="material-symbols text-base">play_arrow</span>
</button>
</ui-tooltip>
<ui-tooltip v-if="chapter.error" :text="chapter.error" 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>
</ui-tooltip>
<div class="flex-1 min-w-0">
<ui-text-input v-if="showSecondInputs" v-model="chapter.start" type="number" class="text-xs" @change="checkChapters" />
<ui-time-picker v-else class="text-xs" v-model="chapter.start" :show-three-digit-hour="mediaDuration >= 360000" @change="checkChapters" />
</div>
<ui-tooltip :text="$strings.TooltipAddOneSecond" direction="bottom">
<button class="w-6 h-6 rounded-full flex items-center justify-center text-gray-300 hover:text-white transform hover:scale-110 duration-150 flex-shrink-0" :class="{ 'opacity-50 cursor-not-allowed': chapter.start + timeIncrementAmount >= mediaDuration }" @click="incrementChapterTime(chapter, timeIncrementAmount)" :disabled="chapter.start + timeIncrementAmount >= mediaDuration">
<span class="material-symbols text-sm">add</span>
</button>
</ui-tooltip>
</div>
</div>
</template>
<div class="grow px-1">
<ui-text-input v-model="chapter.title" @change="checkChapters" class="text-xs min-w-52" />
</div>
<div class="w-7 min-w-7 px-1 py-1">
<div class="flex items-center justify-center">
<ui-tooltip :text="lockedChapters.has(chapter.id) ? $strings.TooltipUnlockChapter : $strings.TooltipLockChapter" direction="bottom">
<button class="w-7 h-7 rounded-full flex items-center justify-center transform hover:scale-110 duration-150 flex-shrink-0" :class="lockedChapters.has(chapter.id) ? 'text-orange-400 hover:text-orange-300' : 'text-gray-300 hover:text-white'" @click="toggleChapterLock(chapter, $event)">
<span class="material-symbols text-base">{{ lockedChapters.has(chapter.id) ? 'lock' : 'lock_open' }}</span>
</button>
</ui-tooltip>
</div>
</div>
<div class="w-32 min-w-32 px-2 py-1">
<div class="flex items-center">
<ui-tooltip :text="$strings.MessageRemoveChapter" direction="bottom">
<button v-if="newChapters.length > 1" class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-error transform hover:scale-110 duration-150" @click="removeChapter(chapter)">
<span class="material-symbols text-base">delete</span>
</button>
</ui-tooltip>
<ui-tooltip :text="$strings.MessageInsertChapterBelow" direction="bottom">
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-success transform hover:scale-110 duration-150" @click="addChapter(chapter)">
<span class="material-symbols text-lg">add_row_below</span>
</button>
</ui-tooltip>
<ui-tooltip :text="selectedChapterId === chapter.id && isPlayingChapter ? $strings.MessagePauseChapter : $strings.MessagePlayChapter" direction="bottom">
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-white transform hover:scale-110 duration-150" @click="playChapter(chapter)">
<widgets-loading-spinner v-if="selectedChapterId === chapter.id && isLoadingChapter" />
<span v-else-if="selectedChapterId === chapter.id && isPlayingChapter" class="material-symbols text-base">pause</span>
<span v-else class="material-symbols text-base">play_arrow</span>
</button>
</ui-tooltip>
<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">
<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>
</ui-tooltip>
</div>
</div>
</div>
<div class="flex items-center mt-4 mb-2">
<div class="w-8 min-w-8 md:w-12 md:min-w-12"></div>
<div class="w-38 min-w-38 md:w-40 md:min-w-40 px-1"></div>
<div class="flex items-center gap-2 grow px-1">
<ui-text-input v-model="bulkChapterInput" :placeholder="$strings.PlaceholderBulkChapterInput" class="text-xs grow min-w-52" @keyup.enter="handleBulkChapterAdd" />
</div>
<div class="w-39 min-w-39 px-1 py-1">
<ui-tooltip :text="$strings.TooltipAddChapters" direction="bottom" class="inline-block align-middle">
<button class="w-5 h-5 rounded-full flex items-center justify-center text-gray-300 hover:text-success transform hover:scale-110 duration-150 flex-shrink-0" :aria-label="$strings.TooltipAddChapters" :class="{ 'opacity-50 cursor-not-allowed': !bulkChapterInput.trim() }" :disabled="!bulkChapterInput.trim()" @click="handleBulkChapterAdd">
<span class="material-symbols text-lg">add</span>
</button>
</ui-tooltip>
</div>
</div>
</div>
<div class="w-full max-w-xl py-4 px-2">
@@ -114,19 +164,15 @@
<div class="w-20">{{ $strings.LabelDuration }}</div>
<div class="w-20 hidden md:block text-center">{{ $strings.HeaderChapters }}</div>
</div>
<template v-for="track in audioTracks">
<div :key="track.ino" class="flex items-center py-2" :class="currentTrackIndex === track.index && isPlayingChapter ? 'bg-success/10' : ''">
<div class="grow max-w-[calc(100%-80px)] pr-2">
<p class="text-xs truncate max-w-sm">{{ track.metadata.filename }}</p>
</div>
<div class="w-20" style="min-width: 80px">
<p class="text-xs font-mono text-gray-200">{{ $secondsToTimestamp(Math.round(track.duration), false, true) }}</p>
</div>
<div class="w-20 hidden md:flex justify-center" style="min-width: 80px">
<span v-if="(track.chapters || []).length" class="material-symbols text-success text-sm">check</span>
</div>
<div v-for="track in audioTracks" :key="track.ino" class="flex items-center py-2" :class="currentTrackIndex === track.index && isPlayingChapter ? 'bg-success/10' : ''">
<div class="grow max-w-[calc(100%-80px)] pr-2">
<p class="text-xs truncate max-w-sm">{{ track.metadata.filename }}</p>
</div>
</template>
<div class="w-20" style="min-width: 80px">
<p class="text-xs font-mono text-gray-200">{{ $secondsToTimestamp(Math.round(track.duration), false, true) }}</p>
</div>
<div class="w-20 hidden md:flex justify-center" style="min-width: 80px"><span v-if="(track.chapters || []).length" class="material-symbols text-success text-sm">check</span></div>
</div>
</div>
</div>
@@ -134,6 +180,7 @@
<ui-loading-indicator />
</div>
<!-- audible chapter lookup modal -->
<modals-modal v-model="showFindChaptersModal" name="edit-book" :width="500" :processing="findingChapters">
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
@@ -144,26 +191,31 @@
<div v-if="!chapterData" class="flex flex-col items-center justify-center p-20">
<div class="relative">
<div class="flex items-end space-x-2">
<ui-text-input-with-label v-model.trim="asinInput" label="ASIN" />
<ui-dropdown v-model="regionInput" :label="$strings.LabelRegion" small :items="audibleRegions" class="w-32" />
<ui-btn small color="bg-primary" @click="findChapters">{{ $strings.ButtonSearch }}</ui-btn>
<ui-text-input-with-label v-model.trim="asinInput" label="ASIN" class="flex-grow" />
<ui-dropdown v-model="regionInput" :label="$strings.LabelRegion" small :items="audibleRegions" class="w-20 max-w-20" />
<ui-btn color="bg-primary" @click="findChapters">{{ $strings.ButtonSearch }}</ui-btn>
</div>
<div class="mt-4">
<ui-checkbox v-model="removeBranding" :label="$strings.LabelRemoveAudibleBranding" small checkbox-bg="bg" label-class="pl-2 text-base text-sm" @click="toggleRemoveBranding" />
</div>
<div class="absolute left-0 mt-1.5 text-error text-s h-5">
<p v-if="asinError">{{ asinError }}</p>
<p v-if="asinError">{{ $strings.MessageAsinCheck }}</p>
</div>
<div class="invisible h-5 mt-1 text-xs"></div>
<div class="invisible mt-1 text-xs"></div>
</div>
</div>
<div v-else class="w-full p-4">
<div class="flex justify-between mb-4">
<div class="flex mb-4">
<button class="w-7 h-7 rounded-full flex items-center justify-center text-gray-300 hover:text-white flex-shrink-0" :aria-label="$strings.ButtonBack" @click="resetChapterLookupData">
<span class="material-symbols text-lg">arrow_back</span>
</button>
<p>
{{ $strings.LabelDurationFound }} <span class="font-semibold">{{ $secondsToTimestamp(chapterData.runtimeLengthSec) }}</span
><br />
{{ $strings.LabelDurationFound }} <span class="font-semibold">{{ $secondsToTimestamp(chapterData.runtimeLengthSec) }}</span>
<br />
<span class="font-semibold" :class="{ 'text-warning': chapters.length !== chapterData.chapters.length }">{{ chapterData.chapters.length }}</span> {{ $strings.LabelChaptersFound }}
</p>
<div class="grow" />
<p>
{{ $strings.LabelYourAudiobookDuration }}: <span class="font-semibold">{{ $secondsToTimestamp(mediaDurationRounded) }}</span
><br />
@@ -197,17 +249,49 @@
<p class="pl-2">{{ $strings.MessageChapterStartIsAfter }}</p>
</div>
</div>
<div class="flex items-center pt-2">
<ui-btn small color="bg-primary" class="mr-1" @click="applyChapterNamesOnly">{{ $strings.ButtonMapChapterTitles }}</ui-btn>
<ui-tooltip :text="$strings.MessageMapChapterTitles" direction="top" class="flex items-center">
<span class="material-symbols text-xl text-gray-200">info</span>
</ui-tooltip>
<div class="grow" />
<div class="flex items-center pt-2 justify-between">
<div class="flex items-center gap-2">
<ui-btn small color="bg-primary" @click="applyChapterNamesOnly">{{ $strings.ButtonMapChapterTitles }}</ui-btn>
<ui-tooltip :text="$strings.MessageMapChapterTitles" direction="top" class="flex items-center">
<span class="material-symbols text-xl text-gray-200">info</span>
</ui-tooltip>
</div>
<ui-btn small color="bg-success" @click="applyChapterData">{{ $strings.ButtonApplyChapters }}</ui-btn>
</div>
</div>
</div>
</modals-modal>
<!-- create bulk chapters modal -->
<modals-modal v-model="showBulkChapterModal" name="bulk-chapters" :width="400">
<template #outer>
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden pointer-events-none">
<p class="text-3xl text-white truncate pointer-events-none">{{ $strings.HeaderBulkChapterModal }}</p>
</div>
</template>
<div class="w-full h-full max-h-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 relative p-6">
<div class="flex flex-col space-y-8">
<p class="text-base">{{ $strings.MessageBulkChapterPattern }}</p>
<div v-if="detectedPattern" class="text-sm text-gray-400 bg-gray-800 p-2 rounded">
<strong>{{ $strings.LabelDetectedPattern }}</strong> "{{ detectedPattern.before }}{{ formatNumberWithPadding(detectedPattern.startingNumber, detectedPattern) }}{{ detectedPattern.after }}"
<br />
<strong>{{ $strings.LabelNextChapters }}</strong>
"{{ detectedPattern.before }}{{ formatNumberWithPadding(detectedPattern.startingNumber + 1, detectedPattern) }}{{ detectedPattern.after }}", "{{ detectedPattern.before }}{{ formatNumberWithPadding(detectedPattern.startingNumber + 2, detectedPattern) }}{{ detectedPattern.after }}", etc.
</div>
<div class="flex px-1 items-center">
<label class="text-base font-medium">{{ $strings.LabelNumberOfChapters }}</label>
<div class="grow" />
<ui-text-input v-model="bulkChapterCount" type="number" min="1" max="50" class="w-14" :style="{ height: `2em` }" @keyup.enter="addBulkChapters" />
</div>
<div class="flex px-1 items-center">
<ui-btn small @click="showBulkChapterModal = false">{{ $strings.ButtonCancel }}</ui-btn>
<div class="grow" />
<ui-btn small color="bg-success" @click="addBulkChapters">{{ $strings.ButtonAddChapters }}</ui-btn>
</div>
</div>
</div>
</modals-modal>
</div>
</template>
@@ -261,9 +345,20 @@ export default {
showFindChaptersModal: false,
chapterData: null,
asinError: null,
removeBranding: false,
showSecondInputs: false,
audibleRegions: ['US', 'CA', 'UK', 'AU', 'FR', 'DE', 'JP', 'IT', 'IN', 'ES'],
hasChanges: false
hasChanges: false,
timeIncrementAmount: 1,
elapsedTime: 0,
playStartTime: null,
elapsedTimeInterval: null,
lockedChapters: new Set(),
lastSelectedLockIndex: null,
bulkChapterInput: '',
showBulkChapterModal: false,
bulkChapterCount: 1,
detectedPattern: null
}
},
computed: {
@@ -302,9 +397,18 @@ export default {
},
selectedChapterId() {
return this.selectedChapter ? this.selectedChapter.id : null
},
allChaptersLocked() {
return this.newChapters.length > 0 && this.newChapters.every((chapter) => this.lockedChapters.has(chapter.id))
}
},
methods: {
formatNumberWithPadding(number, pattern) {
if (!pattern || !pattern.hasLeadingZeros || !pattern.originalPadding) {
return number.toString()
}
return number.toString().padStart(pattern.originalPadding, '0')
},
setChaptersFromTracks() {
let currentStartTime = 0
let index = 0
@@ -319,9 +423,12 @@ export default {
currentStartTime += track.duration
}
this.newChapters = chapters
this.lockedChapters = new Set()
this.checkChapters()
},
toggleRemoveBranding() {
this.removeBranding = !this.removeBranding
},
shiftChapterTimes() {
if (!this.shiftAmount || isNaN(this.shiftAmount) || this.newChapters.length <= 1) {
return
@@ -329,19 +436,22 @@ export default {
const amount = Number(this.shiftAmount)
const lastChapter = this.newChapters[this.newChapters.length - 1]
if (lastChapter.start + amount > this.mediaDurationRounded) {
this.$toast.error('Invalid shift amount. Last chapter start time would extend beyond the duration of this audiobook.')
return
}
// Check if any unlocked chapters would be affected negatively
const unlockedChapters = this.newChapters.filter((chap) => !this.lockedChapters.has(chap.id))
if (this.newChapters[0].end + amount <= 0) {
this.$toast.error('Invalid shift amount. First chapter would have zero or negative length.')
if (unlockedChapters.length === 0) {
this.$toast.warning(this.$strings.ToastChaptersAllLocked)
return
}
for (let i = 0; i < this.newChapters.length; i++) {
const chap = this.newChapters[i]
// Skip locked chapters
if (this.lockedChapters.has(chap.id)) {
continue
}
chap.end = Math.min(chap.end + amount, this.mediaDuration)
if (i > 0) {
chap.start = Math.max(0, chap.start + amount)
@@ -349,6 +459,83 @@ export default {
}
this.checkChapters()
},
incrementChapterTime(chapter, amount) {
if (chapter.id === 0 && chapter.start + amount < 0) {
return
}
if (chapter.start + amount >= this.mediaDuration) {
return
}
chapter.start = Math.max(0, chapter.start + amount)
this.checkChapters()
},
adjustChapterStartTime(chapter) {
const newStartTime = chapter.start + this.elapsedTime
chapter.start = newStartTime
this.checkChapters()
this.$toast.success(this.$strings.ToastChapterStartTimeAdjusted.replace('{0}', this.elapsedTime))
this.destroyAudioEl()
},
startElapsedTimeTracking() {
this.elapsedTime = 0
this.playStartTime = Date.now()
this.elapsedTimeInterval = setInterval(() => {
this.elapsedTime = Math.floor((Date.now() - this.playStartTime) / 1000)
}, 100)
},
stopElapsedTimeTracking() {
if (this.elapsedTimeInterval) {
clearInterval(this.elapsedTimeInterval)
this.elapsedTimeInterval = null
}
this.elapsedTime = 0
this.playStartTime = null
},
toggleChapterLock(chapter, event) {
const chapterId = chapter.id
if (event.shiftKey && this.lastSelectedLockIndex !== null) {
const startIndex = Math.min(this.lastSelectedLockIndex, chapterId)
const endIndex = Math.max(this.lastSelectedLockIndex, chapterId)
const shouldLock = !this.lockedChapters.has(chapterId)
for (let i = startIndex; i <= endIndex; i++) {
if (shouldLock) {
this.lockedChapters.add(i)
} else {
this.lockedChapters.delete(i)
}
}
} else {
if (this.lockedChapters.has(chapterId)) {
this.lockedChapters.delete(chapterId)
} else {
this.lockedChapters.add(chapterId)
}
}
this.lastSelectedLockIndex = chapterId
this.lockedChapters = new Set(this.lockedChapters)
},
lockAllChapters() {
this.newChapters.forEach((chapter) => {
this.lockedChapters.add(chapter.id)
})
this.lockedChapters = new Set(this.lockedChapters)
},
unlockAllChapters() {
this.lockedChapters.clear()
this.lockedChapters = new Set(this.lockedChapters)
},
toggleAllChaptersLock() {
if (this.allChaptersLocked) {
this.unlockAllChapters()
} else {
this.lockAllChapters()
}
},
editItem() {
this.$store.commit('showEditModal', this.libraryItem)
},
@@ -363,6 +550,10 @@ export default {
this.checkChapters()
},
removeChapter(chapter) {
if (this.lockedChapters.has(chapter.id)) {
this.$toast.warning(this.$strings.ToastChapterLocked)
return
}
this.newChapters = this.newChapters.filter((ch) => ch.id !== chapter.id)
this.checkChapters()
},
@@ -446,6 +637,7 @@ export default {
console.log('Audio playing')
this.isLoadingChapter = false
this.isPlayingChapter = true
this.startElapsedTimeTracking()
})
audioEl.addEventListener('ended', () => {
console.log('Audio ended')
@@ -468,6 +660,10 @@ export default {
this.selectedChapter = null
this.isPlayingChapter = false
this.isLoadingChapter = false
this.stopElapsedTimeTracking()
},
resetChapterLookupData() {
this.chapterData = null
},
saveChapters() {
this.checkChapters()
@@ -518,7 +714,7 @@ export default {
},
applyChapterNamesOnly() {
this.newChapters.forEach((chapter, index) => {
if (this.chapterData.chapters[index]) {
if (this.chapterData.chapters[index] && !this.lockedChapters.has(chapter.id)) {
chapter.title = this.chapterData.chapters[index].title
}
})
@@ -530,7 +726,7 @@ export default {
},
applyChapterData() {
let index = 0
this.newChapters = this.chapterData.chapters
const audibleChapters = this.chapterData.chapters
.filter((chap) => chap.startOffsetSec < this.mediaDuration)
.map((chap) => {
return {
@@ -540,6 +736,21 @@ export default {
title: chap.title
}
})
const merged = []
let audibleIdx = 0
for (let i = 0; i < Math.max(this.newChapters.length, audibleChapters.length); i++) {
const isLocked = this.lockedChapters.has(i)
if (isLocked && this.newChapters[i]) {
merged.push({ ...this.newChapters[i], id: i })
} else if (audibleChapters[audibleIdx]) {
merged.push({ ...audibleChapters[audibleIdx], id: i })
audibleIdx++
} else if (this.newChapters[i]) {
merged.push({ ...this.newChapters[i], id: i })
}
}
this.newChapters = merged
this.showFindChaptersModal = false
this.chapterData = null
@@ -567,8 +778,8 @@ export default {
if (data.error) {
this.asinError = this.$getString(data.stringKey)
} else {
console.log('Chapter data', data)
this.chapterData = data
console.log('Chapter data', { ...data })
this.chapterData = this.removeBranding ? this.removeBrandingFromData(data) : data
}
})
.catch((error) => {
@@ -578,6 +789,42 @@ export default {
this.showFindChaptersModal = false
})
},
removeBrandingFromData(data) {
if (!data) return data
try {
const introDuration = data.brandIntroDurationMs
const outroDuration = data.brandOutroDurationMs
for (let i = 0; i < data.chapters.length; i++) {
const chapter = data.chapters[i]
if (chapter.startOffsetMs < introDuration) {
// This should never happen, as the intro is not longer than the first chapter
// If this happens set to the next second
// Will be 0 for the first chapter anayways
chapter.startOffsetMs = i * 1000
chapter.startOffsetSec = i
} else {
chapter.startOffsetMs -= introDuration
chapter.startOffsetSec = Math.floor(chapter.startOffsetMs / 1000)
}
}
const lastChapter = data.chapters[data.chapters.length - 1]
// If there is an outro that's in the outro duration, remove it
if (lastChapter && lastChapter.lengthMs <= outroDuration) {
data.chapters.pop()
}
// Remove Branding durations from Runtime totals
data.runtimeLengthMs -= introDuration + outroDuration
data.runtimeLengthSec = Math.floor(data.runtimeLengthMs / 1000)
console.log('Brandless Chapter data', data)
return data
} catch {
return data
}
},
resetChapters() {
const payload = {
message: this.$strings.MessageResetChaptersConfirm,
@@ -602,6 +849,7 @@ export default {
}
]
}
this.lockedChapters = new Set()
this.checkChapters()
},
removeAllChaptersClick() {
@@ -643,6 +891,91 @@ export default {
this.saving = false
})
},
handleBulkChapterAdd() {
const input = this.bulkChapterInput.trim()
if (!input) return
const numberMatch = input.match(/(\d+)/)
if (numberMatch) {
// Extract the base pattern and number, preserving zero-padding
const originalNumberString = numberMatch[1]
const foundNumber = parseInt(originalNumberString)
const numberIndex = numberMatch.index
const beforeNumber = input.substring(0, numberIndex)
const afterNumber = input.substring(numberIndex + originalNumberString.length)
this.detectedPattern = {
before: beforeNumber,
after: afterNumber,
startingNumber: foundNumber,
originalPadding: originalNumberString.length,
hasLeadingZeros: originalNumberString.length > 1 && originalNumberString.startsWith('0')
}
this.bulkChapterCount = 1
this.showBulkChapterModal = true
} else {
this.addSingleChapterFromInput(input)
}
},
addSingleChapterFromInput(title) {
// Find the last chapter to determine where to add the new one
const lastChapter = this.newChapters[this.newChapters.length - 1]
const newStart = lastChapter ? lastChapter.end : 0
const newEnd = Math.min(newStart + 300, this.mediaDuration)
const newChapter = {
id: this.newChapters.length,
start: newStart,
end: newEnd,
title: title
}
this.newChapters.push(newChapter)
this.bulkChapterInput = ''
this.checkChapters()
},
addBulkChapters() {
const count = parseInt(this.bulkChapterCount)
if (!count || count < 1 || count > 150) {
this.$toast.error(this.$strings.ToastBulkChapterInvalidCount)
return
}
const { before, after, startingNumber, originalPadding, hasLeadingZeros } = this.detectedPattern
const lastChapter = this.newChapters[this.newChapters.length - 1]
const baseStart = lastChapter ? lastChapter.start + 1 : 0
// Add multiple chapters with the detected pattern
for (let i = 0; i < count; i++) {
const chapterNumber = startingNumber + i
let formattedNumber = chapterNumber.toString()
// Apply zero-padding if the original had leading zeros
if (hasLeadingZeros && originalPadding > 1) {
formattedNumber = chapterNumber.toString().padStart(originalPadding, '0')
}
const newStart = baseStart + i
const newEnd = Math.min(newStart + i + i, this.mediaDuration)
const newChapter = {
id: this.newChapters.length,
start: newStart,
end: newEnd,
title: `${before}${formattedNumber}${after}`
}
this.newChapters.push(newChapter)
}
this.bulkChapterInput = ''
this.showBulkChapterModal = false
this.detectedPattern = null
this.checkChapters()
},
libraryItemUpdated(libraryItem) {
if (libraryItem.id === this.libraryItem.id) {
if (!!libraryItem.media.metadata.asin && this.mediaMetadata.asin !== libraryItem.media.metadata.asin) {

View File

@@ -2,7 +2,14 @@
<div id="page-wrapper" class="bg-bg page p-8 overflow-auto relative" :class="streamLibraryItem ? 'streaming' : ''">
<div class="flex items-center justify-center mb-6">
<div class="w-full max-w-2xl">
<p class="text-2xl mb-2">{{ $strings.HeaderAudiobookTools }}</p>
<div class="flex items-center mb-4">
<nuxt-link :to="`/item/${libraryItem.id}`" class="hover:underline">
<h1 class="text-lg lg:text-xl">{{ mediaMetadata.title }}</h1>
</nuxt-link>
<button class="w-7 h-7 flex items-center justify-center mx-4 hover:scale-110 duration-100 transform text-gray-200 hover:text-white" @click="editItem">
<span class="material-symbols text-base">edit</span>
</button>
</div>
</div>
<div class="w-full max-w-2xl">
<div class="flex justify-end">
@@ -13,43 +20,43 @@
<div class="flex justify-center mb-2">
<div class="w-full max-w-2xl">
<p class="text-xl">{{ $strings.HeaderMetadataToEmbed }}</p>
<p class="text-lg">{{ $strings.HeaderMetadataToEmbed }}</p>
</div>
<div class="w-full max-w-2xl"></div>
</div>
<div class="flex justify-center flex-wrap">
<div class="w-full max-w-2xl border border-white/10 bg-bg mx-2">
<div class="flex justify-center flex-wrap lg:flex-nowrap gap-4">
<div class="w-full max-w-2xl border border-white/10 bg-bg">
<div class="flex py-2 px-4">
<div class="w-1/3 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelMetaTag }}</div>
<div class="w-2/3 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelValue }}</div>
<div class="w-28 min-w-28 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelMetaTag }}</div>
<div class="grow text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelValue }}</div>
</div>
<div class="w-full max-h-72 overflow-auto">
<template v-for="(value, key, index) in metadataObject">
<div :key="key" class="flex py-1 px-4 text-sm" :class="index % 2 === 0 ? 'bg-primary/25' : ''">
<div class="w-1/3 font-semibold">{{ key }}</div>
<div class="w-2/3">
<div class="w-28 min-w-28 font-semibold">{{ key }}</div>
<div class="grow">
{{ value }}
</div>
</div>
</template>
</div>
</div>
<div class="w-full max-w-2xl border border-white/10 bg-bg mx-2">
<div class="w-full max-w-2xl border border-white/10 bg-bg">
<div class="flex py-2 px-4 bg-primary/25">
<div class="grow text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelChapterTitle }}</div>
<div class="w-24 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelStart }}</div>
<div class="w-24 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelEnd }}</div>
<div class="w-16 min-w-16 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelStart }}</div>
<div class="w-16 min-w-16 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelEnd }}</div>
</div>
<div class="w-full max-h-72 overflow-auto">
<p v-if="!metadataChapters.length" class="py-5 text-center text-gray-200">{{ $strings.MessageNoChapters }}</p>
<template v-for="(chapter, index) in metadataChapters">
<div :key="index" class="flex py-1 px-4 text-sm" :class="index % 2 === 1 ? 'bg-primary/25' : ''">
<div class="grow font-semibold">{{ chapter.title }}</div>
<div class="w-24">
<div class="w-16 min-w-16">
{{ $secondsToTimestamp(chapter.start) }}
</div>
<div class="w-24">
<div class="w-16 min-w-16">
{{ $secondsToTimestamp(chapter.end) }}
</div>
</div>
@@ -77,10 +84,6 @@
</div>
<!-- m4b embed action buttons -->
<div v-else class="w-full flex items-center mb-4">
<button :disabled="processing" class="text-sm uppercase text-gray-200 flex items-center pt-px pl-1 pr-2 hover:bg-white/5 rounded-md" @click="showEncodeOptions = !showEncodeOptions">
<span class="material-symbols text-xl">{{ showEncodeOptions || usingCustomEncodeOptions ? 'check_box' : 'check_box_outline_blank' }}</span> <span class="pl-1">{{ $strings.LabelUseAdvancedOptions }}</span>
</button>
<div class="grow" />
<ui-btn v-if="!isTaskFinished && processing" color="bg-error" :loading="isCancelingEncode" class="mr-2" @click.stop="cancelEncodeClick">{{ $strings.ButtonCancelEncode }}</ui-btn>
@@ -89,18 +92,16 @@
<p v-else class="text-success text-lg font-semibold">{{ $strings.MessageM4BFinished }}</p>
</div>
<!-- advanced encoding options -->
<div v-if="isM4BTool" class="overflow-hidden">
<transition name="slide">
<div v-if="showEncodeOptions || usingCustomEncodeOptions" class="mb-4 pb-4 border-b border-white/10">
<div class="flex flex-wrap -mx-2">
<ui-text-input-with-label ref="bitrateInput" v-model="encodingOptions.bitrate" :disabled="processing || isTaskFinished" :label="$strings.LabelAudioBitrate" class="m-2 max-w-40" @input="bitrateChanged" />
<ui-text-input-with-label ref="channelsInput" v-model="encodingOptions.channels" :disabled="processing || isTaskFinished" :label="$strings.LabelAudioChannels" class="m-2 max-w-40" @input="channelsChanged" />
<ui-text-input-with-label ref="codecInput" v-model="encodingOptions.codec" :disabled="processing || isTaskFinished" :label="$strings.LabelAudioCodec" class="m-2 max-w-40" @input="codecChanged" />
</div>
<p class="text-sm text-warning">{{ $strings.LabelEncodingWarningAdvancedSettings }}</p>
</div>
</transition>
<!-- show encoding options for running task -->
<div v-if="encodeTaskHasEncodingOptions" class="mb-4 pb-4 border-b border-white/10">
<div class="flex flex-wrap -mx-2">
<ui-text-input-with-label ref="bitrateInput" v-model="encodingOptions.bitrate" readonly :label="$strings.LabelAudioBitrate" class="m-2 max-w-40" @input="bitrateChanged" />
<ui-text-input-with-label ref="channelsInput" v-model="encodingOptions.channels" readonly :label="$strings.LabelAudioChannels" class="m-2 max-w-40" @input="channelsChanged" />
<ui-text-input-with-label ref="codecInput" v-model="encodingOptions.codec" readonly :label="$strings.LabelAudioCodec" class="m-2 max-w-40" @input="codecChanged" />
</div>
</div>
<div v-else-if="isM4BTool" class="mb-4">
<widgets-encoder-options-card ref="encoderOptionsCard" :audio-tracks="audioFiles" :disabled="processing || isTaskFinished" />
</div>
<div class="mb-4">
@@ -146,19 +147,29 @@
<div class="flex py-2 px-4 bg-primary/25">
<div class="w-10 text-xs font-semibold text-gray-200">#</div>
<div class="grow text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelFilename }}</div>
<div class="w-20 text-xs font-semibold uppercase text-gray-200 hidden lg:block">{{ $strings.LabelChannels }}</div>
<div class="w-16 text-xs font-semibold uppercase text-gray-200 hidden md:block">{{ $strings.LabelCodec }}</div>
<div class="w-16 text-xs font-semibold uppercase text-gray-200 hidden md:block">{{ $strings.LabelBitrate }}</div>
<div class="w-16 text-xs font-semibold uppercase text-gray-200">{{ $strings.LabelSize }}</div>
<div class="w-24"></div>
</div>
<template v-for="file in audioFiles">
<div :key="file.index" class="flex py-2 px-4 text-sm" :class="file.index % 2 === 0 ? 'bg-primary/25' : ''">
<div class="w-10">{{ file.index }}</div>
<div :key="file.index" class="flex py-2 px-4 text-xs sm:text-sm" :class="file.index % 2 === 0 ? 'bg-primary/25' : ''">
<div class="w-10 min-w-10">{{ file.index }}</div>
<div class="grow">
{{ file.metadata.filename }}
</div>
<div class="w-16 font-mono text-gray-200">
<div class="w-20 min-w-20 text-gray-200 hidden lg:block">{{ file.channels || 'unknown' }} ({{ file.channelLayout || 'unknown' }})</div>
<div class="w-16 min-w-16 text-gray-200 hidden md:block">
{{ file.codec || 'unknown' }}
</div>
<div class="w-16 min-w-16 text-gray-200 hidden md:block">
{{ $bytesPretty(file.bitRate || 0, 0) }}
</div>
<div class="w-16 min-w-16 text-gray-200">
{{ $bytesPretty(file.metadata.size) }}
</div>
<div class="w-24">
<div class="w-24 min-w-24">
<div class="flex justify-center">
<span v-if="audioFilesFinished[file.ino]" class="material-symbols text-xl text-success leading-none">check_circle</span>
<div v-else-if="audioFilesEncoding[file.ino]">
@@ -214,7 +225,6 @@ export default {
metadataObject: null,
selectedTool: 'embed',
isCancelingEncode: false,
showEncodeOptions: false,
shouldBackupAudioFiles: true,
encodingOptions: {
bitrate: '128k',
@@ -263,9 +273,6 @@ export default {
audioFiles() {
return (this.media.audioFiles || []).filter((af) => !af.exclude)
},
isSingleM4b() {
return this.audioFiles.length === 1 && this.audioFiles[0].metadata.ext.toLowerCase() === '.m4b'
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
@@ -273,14 +280,10 @@ export default {
return this.media.chapters || []
},
availableTools() {
if (this.isSingleM4b) {
return [{ value: 'embed', text: this.$strings.LabelToolsEmbedMetadata }]
} else {
return [
{ value: 'embed', text: this.$strings.LabelToolsEmbedMetadata },
{ value: 'm4b', text: this.$strings.LabelToolsM4bEncoder }
]
}
return [
{ value: 'embed', text: this.$strings.LabelToolsEmbedMetadata },
{ value: 'm4b', text: this.$strings.LabelToolsM4bEncoder }
]
},
taskFailed() {
return this.isTaskFinished && this.task.isFailed
@@ -314,8 +317,8 @@ export default {
isMetadataEmbedQueued() {
return this.queuedEmbedLIds.some((lid) => lid === this.libraryItemId)
},
usingCustomEncodeOptions() {
return this.isM4BTool && this.encodeTask && this.encodeTask.data.encodeOptions && Object.keys(this.encodeTask.data.encodeOptions).length > 0
encodeTaskHasEncodingOptions() {
return this.isM4BTool && !!this.encodeTask?.data.encodeOptions && Object.keys(this.encodeTask.data.encodeOptions).length > 0
}
},
methods: {
@@ -351,19 +354,15 @@ export default {
if (this.$refs.channelsInput) this.$refs.channelsInput.blur()
if (this.$refs.codecInput) this.$refs.codecInput.blur()
let queryStr = ''
if (this.showEncodeOptions) {
const options = []
if (this.encodingOptions.bitrate) options.push(`bitrate=${this.encodingOptions.bitrate}`)
if (this.encodingOptions.channels) options.push(`channels=${this.encodingOptions.channels}`)
if (this.encodingOptions.codec) options.push(`codec=${this.encodingOptions.codec}`)
if (options.length) {
queryStr = `?${options.join('&')}`
}
}
const encodeOptions = this.$refs.encoderOptionsCard.getEncodingOptions()
this.encodingOptions = encodeOptions
const queryParams = new URLSearchParams(encodeOptions)
this.processing = true
this.$axios
.$post(`/api/tools/item/${this.libraryItemId}/encode-m4b${queryStr}`)
.$post(`/api/tools/item/${this.libraryItemId}/encode-m4b?${queryParams.toString()}`)
.then(() => {
console.log('Ab m4b merge started')
})
@@ -416,14 +415,10 @@ export default {
const shouldBackupAudioFiles = localStorage.getItem('embedMetadataShouldBackup')
this.shouldBackupAudioFiles = shouldBackupAudioFiles != 0
if (this.usingCustomEncodeOptions) {
if (this.encodeTaskHasEncodingOptions) {
if (this.encodeTask.data.encodeOptions.bitrate) this.encodingOptions.bitrate = this.encodeTask.data.encodeOptions.bitrate
if (this.encodeTask.data.encodeOptions.channels) this.encodingOptions.channels = this.encodeTask.data.encodeOptions.channels
if (this.encodeTask.data.encodeOptions.codec) this.encodingOptions.codec = this.encodeTask.data.encodeOptions.codec
} else {
this.encodingOptions.bitrate = localStorage.getItem('embedMetadataBitrate') || '128k'
this.encodingOptions.channels = localStorage.getItem('embedMetadataChannels') || '2'
this.encodingOptions.codec = localStorage.getItem('embedMetadataCodec') || 'aac'
}
},
fetchMetadataEmbedObject() {
@@ -438,10 +433,24 @@ export default {
},
taskUpdated(task) {
this.processing = !task.isFinished
},
editItem() {
this.$store.commit('showEditModal', this.libraryItem)
},
libraryItemUpdated(libraryItem) {
if (libraryItem.id === this.libraryItem.id) {
this.libraryItem = libraryItem
this.fetchMetadataEmbedObject()
}
}
},
mounted() {
this.init()
this.$eventBus.$on(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
},
beforeDestroy() {
this.$eventBus.$off(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
}
}
</script>

View File

@@ -53,6 +53,7 @@ export default {
else if (pageName === 'sessions') return this.$strings.HeaderListeningSessions
else if (pageName === 'stats') return this.$strings.HeaderYourStats
else if (pageName === 'users') return this.$strings.HeaderUsers
else if (pageName === 'api-keys') return this.$strings.HeaderApiKeys
else if (pageName === 'item-metadata-utils') return this.$strings.HeaderItemMetadataUtils
else if (pageName === 'rss-feeds') return this.$strings.HeaderRSSFeeds
else if (pageName === 'email') return this.$strings.HeaderEmail

View File

@@ -0,0 +1,84 @@
<template>
<div>
<app-settings-content :header-text="$strings.HeaderApiKeys">
<template #header-items>
<div v-if="numApiKeys" class="mx-2 px-1.5 rounded-lg bg-primary/50 text-gray-300/90 text-sm inline-flex items-center justify-center">
<span>{{ numApiKeys }}</span>
</div>
<ui-tooltip :text="$strings.LabelClickForMoreInfo" class="inline-flex ml-2">
<a href="https://www.audiobookshelf.org/guides/api-keys" target="_blank" class="inline-flex">
<span class="material-symbols text-xl w-5 text-gray-200">help_outline</span>
</a>
</ui-tooltip>
<div class="grow" />
<ui-btn color="bg-primary" :disabled="loadingUsers || users.length === 0" small @click="setShowApiKeyModal()">{{ $strings.ButtonAddApiKey }}</ui-btn>
</template>
<tables-api-keys-table ref="apiKeysTable" class="pt-2" @edit="setShowApiKeyModal" @numApiKeys="(count) => (numApiKeys = count)" />
</app-settings-content>
<modals-api-key-modal ref="apiKeyModal" v-model="showApiKeyModal" :api-key="selectedApiKey" :users="users" @created="apiKeyCreated" @updated="apiKeyUpdated" />
<modals-api-key-created-modal ref="apiKeyCreatedModal" v-model="showApiKeyCreatedModal" :api-key="selectedApiKey" />
</div>
</template>
<script>
export default {
asyncData({ store, redirect }) {
if (!store.getters['user/getIsAdminOrUp']) {
redirect('/')
}
},
data() {
return {
loadingUsers: false,
selectedApiKey: null,
showApiKeyModal: false,
showApiKeyCreatedModal: false,
numApiKeys: 0,
users: []
}
},
methods: {
apiKeyCreated(apiKey) {
this.numApiKeys++
this.selectedApiKey = apiKey
this.showApiKeyCreatedModal = true
if (this.$refs.apiKeysTable) {
this.$refs.apiKeysTable.addApiKey(apiKey)
}
},
apiKeyUpdated(apiKey) {
if (this.$refs.apiKeysTable) {
this.$refs.apiKeysTable.updateApiKey(apiKey)
}
},
setShowApiKeyModal(selectedApiKey) {
this.selectedApiKey = selectedApiKey
this.showApiKeyModal = true
},
loadUsers() {
this.loadingUsers = true
this.$axios
.$get('/api/users')
.then((res) => {
this.users = res.users.sort((a, b) => {
return a.createdAt - b.createdAt
})
})
.catch((error) => {
console.error('Failed', error)
})
.finally(() => {
this.loadingUsers = false
})
}
},
mounted() {
this.loadUsers()
},
beforeDestroy() {}
}
</script>

View File

@@ -122,7 +122,8 @@
</div>
</transition>
</div>
<div class="w-full flex items-center justify-end p-4">
<div class="w-full flex items-center justify-between p-4">
<p v-if="enableOpenIDAuth" class="text-sm text-warning">{{ $strings.MessageAuthenticationOIDCChangesRestart }}</p>
<ui-btn color="bg-success" :padding-x="8" small class="text-base" :loading="savingSettings" @click="saveSettings">{{ $strings.ButtonSave }}</ui-btn>
</div>
</app-settings-content>

View File

@@ -131,35 +131,26 @@
</div>
<div class="grow py-2">
<ui-dropdown :label="$strings.LabelSettingsDateFormat" v-model="newServerSettings.dateFormat" :items="dateFormats" small class="max-w-52" @input="(val) => updateSettingsKey('dateFormat', val)" />
<ui-dropdown :label="$strings.LabelSettingsDateFormat" v-model="newServerSettings.dateFormat" :items="dateFormats" small class="max-w-72" @input="(val) => updateSettingsKey('dateFormat', val)" />
<p class="text-xs ml-1 text-white/60">{{ $strings.LabelExample }}: {{ dateExample }}</p>
</div>
<div class="grow py-2">
<ui-dropdown :label="$strings.LabelSettingsTimeFormat" v-model="newServerSettings.timeFormat" :items="timeFormats" small class="max-w-52" @input="(val) => updateSettingsKey('timeFormat', val)" />
<ui-dropdown :label="$strings.LabelSettingsTimeFormat" v-model="newServerSettings.timeFormat" :items="timeFormats" small class="max-w-72" @input="(val) => updateSettingsKey('timeFormat', val)" />
<p class="text-xs ml-1 text-white/60">{{ $strings.LabelExample }}: {{ timeExample }}</p>
</div>
<div class="py-2">
<ui-dropdown :label="$strings.LabelLanguageDefaultServer" ref="langDropdown" v-model="newServerSettings.language" :items="$languageCodeOptions" small class="max-w-52" @input="updateServerLanguage" />
<ui-dropdown :label="$strings.LabelLanguageDefaultServer" ref="langDropdown" v-model="newServerSettings.language" :items="$languageCodeOptions" small class="max-w-72" @input="updateServerLanguage" />
</div>
<!-- old experimental features -->
<!-- <div class="pt-4">
<h2 class="font-semibold">{{ $strings.HeaderSettingsExperimental }}</h2>
<div class="pt-4">
<h2 class="font-semibold">{{ $strings.HeaderSettingsSecurity }}</h2>
</div>
<div class="flex items-center py-2">
<ui-toggle-switch labeledBy="settings-experimental-features" v-model="showExperimentalFeatures" />
<ui-tooltip :text="$strings.LabelSettingsExperimentalFeaturesHelp">
<p class="pl-4">
<span id="settings-experimental-features">{{ $strings.LabelSettingsExperimentalFeatures }}</span>
<a :aria-label="$strings.LabelSettingsExperimentalFeaturesHelp" href="https://github.com/advplyr/audiobookshelf/discussions/75" target="_blank">
<span class="material-symbols icon-text">info</span>
</a>
</p>
</ui-tooltip>
</div> -->
<div class="py-2">
<ui-multi-select v-model="newServerSettings.allowedOrigins" :items="newServerSettings.allowedOrigins" :label="$strings.LabelCorsAllowed" class="max-w-72" @input="updateCorsOrigins" />
</div>
</div>
</div>
</app-settings-content>
@@ -323,6 +314,27 @@ export default {
updateServerLanguage(val) {
this.updateSettingsKey('language', val)
},
updateCorsOrigins(val) {
const validOrigins = []
const invalidOrigins = []
val.forEach((origin) => {
const trimmedOrigin = origin.trim().toLowerCase()
try {
new URL(trimmedOrigin)
validOrigins.push(trimmedOrigin)
} catch {
invalidOrigins.push(trimmedOrigin)
}
})
if (invalidOrigins.length > 0) {
this.$toast.error(this.$strings.ToastInvalidUrls)
}
this.newServerSettings.allowedOrigins = validOrigins
this.updateSettingsKey('allowedOrigins', validOrigins)
},
updateSettingsKey(key, val) {
if (key === 'scannerDisableWatcher') {
this.newServerSettings.scannerDisableWatcher = val
@@ -352,6 +364,7 @@ export default {
initServerSettings() {
this.newServerSettings = this.serverSettings ? { ...this.serverSettings } : {}
this.newServerSettings.sortingPrefixes = [...(this.newServerSettings.sortingPrefixes || [])]
this.newServerSettings.allowedOrigins = [...(this.newServerSettings.allowedOrigins || [])]
this.scannerEnableWatcher = !this.newServerSettings.scannerDisableWatcher
this.homepageUseBookshelfView = this.newServerSettings.homeBookshelfView != this.$constants.BookshelfView.DETAIL

View File

@@ -78,10 +78,10 @@ export default {
},
computed: {
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
return this.$store.getters['getServerSetting']('timeFormat')
}
},
methods: {

View File

@@ -6,80 +6,82 @@
</div>
<div v-if="listeningSessions.length" class="block max-w-full relative">
<table class="userSessionsTable">
<tr class="bg-primary/40">
<th class="w-6 min-w-6 text-left hidden md:table-cell h-11">
<ui-checkbox v-model="isAllSelected" :partial="numSelected > 0 && !isAllSelected" small checkbox-bg="bg" />
</th>
<th v-if="numSelected" class="grow text-left" :colspan="7">
<div class="flex items-center">
<p>{{ $getString('MessageSelected', [numSelected]) }}</p>
<div class="grow" />
<ui-btn small color="bg-error" :loading="deletingSessions" @click.stop="removeSessionsClick">{{ $strings.ButtonRemove }}</ui-btn>
</div>
</th>
<th v-if="!numSelected" class="grow sm:grow-0 sm:w-48 sm:max-w-48 text-left group cursor-pointer" @click.stop="sortColumn('displayTitle')">
<div class="inline-flex items-center">
{{ $strings.LabelItem }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('displayTitle') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
<th v-if="!numSelected" class="w-20 min-w-20 text-left hidden md:table-cell">{{ $strings.LabelUser }}</th>
<th v-if="!numSelected" class="w-26 min-w-26 text-left hidden md:table-cell group cursor-pointer" @click.stop="sortColumn('playMethod')">
<div class="inline-flex items-center">
{{ $strings.LabelPlayMethod }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('playMethod') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
<th v-if="!numSelected" class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
<th v-if="!numSelected" class="w-24 min-w-24 sm:w-32 sm:min-w-32 group cursor-pointer" @click.stop="sortColumn('timeListening')">
<div class="inline-flex items-center">
{{ $strings.LabelTimeListened }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('timeListening') }" class="material-symbols text-base pl-px hidden sm:inline-block">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
<th v-if="!numSelected" class="w-24 min-w-24 group cursor-pointer" @click.stop="sortColumn('currentTime')">
<div class="inline-flex items-center">
{{ $strings.LabelLastTime }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('currentTime') }" class="material-symbols text-base pl-px hidden sm:inline-block">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
<th v-if="!numSelected" class="grow hidden sm:table-cell cursor-pointer group" @click.stop="sortColumn('updatedAt')">
<div class="inline-flex items-center">
{{ $strings.LabelLastUpdate }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('updatedAt') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
</tr>
<div class="overflow-x-auto">
<table class="userSessionsTable">
<tr class="bg-primary/40">
<th class="w-6 min-w-6 text-left hidden md:table-cell h-11">
<ui-checkbox v-model="isAllSelected" :partial="numSelected > 0 && !isAllSelected" small checkbox-bg="bg" />
</th>
<th v-if="numSelected" class="grow text-left" :colspan="7">
<div class="flex items-center">
<p>{{ $getString('MessageSelected', [numSelected]) }}</p>
<div class="grow" />
<ui-btn small color="bg-error" :loading="deletingSessions" @click.stop="removeSessionsClick">{{ $strings.ButtonRemove }}</ui-btn>
</div>
</th>
<th v-if="!numSelected" class="grow sm:grow-0 sm:w-48 sm:max-w-48 text-left group cursor-pointer" @click.stop="sortColumn('displayTitle')">
<div class="inline-flex items-center">
{{ $strings.LabelItem }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('displayTitle') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
<th v-if="!numSelected" class="w-20 min-w-20 text-left hidden md:table-cell">{{ $strings.LabelUser }}</th>
<th v-if="!numSelected" class="w-26 min-w-26 text-left hidden md:table-cell group cursor-pointer" @click.stop="sortColumn('playMethod')">
<div class="inline-flex items-center">
{{ $strings.LabelPlayMethod }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('playMethod') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
<th v-if="!numSelected" class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
<th v-if="!numSelected" class="w-24 min-w-24 sm:w-32 sm:min-w-32 group cursor-pointer" @click.stop="sortColumn('timeListening')">
<div class="inline-flex items-center">
{{ $strings.LabelTimeListened }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('timeListening') }" class="material-symbols text-base pl-px hidden sm:inline-block">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
<th v-if="!numSelected" class="w-24 min-w-24 group cursor-pointer" @click.stop="sortColumn('currentTime')">
<div class="inline-flex items-center">
{{ $strings.LabelLastTime }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('currentTime') }" class="material-symbols text-base pl-px hidden sm:inline-block">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
<th v-if="!numSelected" class="grow hidden sm:table-cell cursor-pointer group" @click.stop="sortColumn('updatedAt')">
<div class="inline-flex items-center">
{{ $strings.LabelLastUpdate }} <span :class="{ 'opacity-0 group-hover:opacity-30': !isSortSelected('updatedAt') }" class="material-symbols text-base pl-px">{{ sortDesc ? 'arrow_drop_down' : 'arrow_drop_up' }}</span>
</div>
</th>
</tr>
<tr v-for="session in listeningSessions" :key="session.id" :class="{ selected: session.selected }" class="cursor-pointer" @click="clickSessionRow(session)">
<td class="hidden md:table-cell py-1 max-w-6 relative">
<ui-checkbox v-model="session.selected" small checkbox-bg="bg" />
<!-- overlay of the checkbox so that the entire box is clickable -->
<div class="absolute inset-0 w-full h-full" @click.stop="session.selected = !session.selected" />
</td>
<td class="py-1 grow sm:grow-0 sm:w-48 sm:max-w-48">
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
</td>
<td class="hidden md:table-cell w-20 min-w-20">
<p v-if="filteredUserUsername" class="text-xs">{{ filteredUserUsername }}</p>
<p v-else class="text-xs">{{ session.user ? session.user.username : 'N/A' }}</p>
</td>
<td class="hidden md:table-cell w-26 min-w-26">
<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)" />
</td>
<td class="text-center w-24 min-w-24 sm:w-32 sm:min-w-32">
<p class="text-xs font-mono">{{ $elapsedPretty(session.timeListening) }}</p>
</td>
<td class="text-center hover:underline w-24 min-w-24" @click.stop="clickCurrentTime(session)">
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
</td>
<td class="text-center hidden sm:table-cell">
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
</ui-tooltip>
</td>
</tr>
</table>
<tr v-for="session in listeningSessions" :key="session.id" :class="{ selected: session.selected }" class="cursor-pointer" @click="clickSessionRow(session)">
<td class="hidden md:table-cell py-1 max-w-6 relative">
<ui-checkbox v-model="session.selected" small checkbox-bg="bg" />
<!-- overlay of the checkbox so that the entire box is clickable -->
<div class="absolute inset-0 w-full h-full" @click.stop="session.selected = !session.selected" />
</td>
<td class="py-1 grow sm:grow-0 sm:w-48 sm:max-w-48">
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
</td>
<td class="hidden md:table-cell w-20 min-w-20">
<p v-if="filteredUserUsername" class="text-xs">{{ filteredUserUsername }}</p>
<p v-else class="text-xs">{{ session.user ? session.user.username : 'N/A' }}</p>
</td>
<td class="hidden md:table-cell w-26 min-w-26">
<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)" />
</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>
</td>
<td class="text-center hover:underline w-24 min-w-24" @click.stop="clickCurrentTime(session)">
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
</td>
<td class="text-center hidden sm:table-cell">
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
</ui-tooltip>
</td>
</tr>
</table>
</div>
<!-- table bottom options -->
<div class="flex items-center my-2">
<div class="grow" />
@@ -250,10 +252,10 @@ export default {
return user?.username || null
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
return this.$store.getters['getServerSetting']('timeFormat')
},
numSelected() {
return this.listeningSessions.filter((s) => s.selected).length

View File

@@ -13,8 +13,10 @@
<widgets-online-indicator :value="!!userOnline" />
<h1 class="text-xl pl-2">{{ username }}</h1>
</div>
<div v-if="userToken" class="flex text-xs mt-4">
<ui-text-input-with-label :label="$strings.LabelApiToken" :value="userToken" readonly show-copy />
<div v-if="legacyToken" class="text-xs space-y-2 mt-4">
<ui-text-input-with-label label="Legacy API Token" :value="legacyToken" readonly show-copy />
<p class="text-warning" v-html="$strings.MessageAuthenticationLegacyTokenWarning" />
</div>
<div class="w-full h-px bg-white/10 my-2" />
<div class="py-2">
@@ -100,9 +102,12 @@ export default {
}
},
computed: {
userToken() {
legacyToken() {
return this.user.token
},
userToken() {
return this.user.accessToken
},
bookCoverAspectRatio() {
return this.$store.getters['libraries/getBookCoverAspectRatio']
},
@@ -129,10 +134,10 @@ export default {
return this.listeningSessions.sessions[0]
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
return this.$store.getters['getServerSetting']('timeFormat')
}
},
methods: {

View File

@@ -19,39 +19,41 @@
<div class="py-2">
<h1 class="text-lg mb-2 text-white/90 px-2 sm:px-0">{{ $strings.HeaderListeningSessions }}</h1>
<div v-if="listeningSessions.length">
<table class="userSessionsTable">
<tr class="bg-primary/40">
<th class="w-48 min-w-48 text-left">{{ $strings.LabelItem }}</th>
<th class="w-32 min-w-32 text-left hidden md:table-cell">{{ $strings.LabelPlayMethod }}</th>
<th class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
<th class="w-32 min-w-32">{{ $strings.LabelTimeListened }}</th>
<th class="w-16 min-w-16">{{ $strings.LabelLastTime }}</th>
<th class="grow hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th>
</tr>
<tr v-for="session in listeningSessions" :key="session.id" class="cursor-pointer" @click="showSession(session)">
<td class="py-1 max-w-48">
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
</td>
<td class="hidden md:table-cell">
<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>
<td class="text-center">
<p class="text-xs font-mono">{{ $elapsedPretty(session.timeListening) }}</p>
</td>
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
</td>
<td class="text-center hidden sm:table-cell">
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
</ui-tooltip>
</td>
</tr>
</table>
<div class="overflow-x-auto">
<table class="userSessionsTable">
<tr class="bg-primary/40">
<th class="w-48 min-w-48 text-left">{{ $strings.LabelItem }}</th>
<th class="w-32 min-w-32 text-left hidden md:table-cell">{{ $strings.LabelPlayMethod }}</th>
<th class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
<th class="w-32 min-w-32">{{ $strings.LabelTimeListened }}</th>
<th class="w-16 min-w-16">{{ $strings.LabelLastTime }}</th>
<th class="grow hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th>
</tr>
<tr v-for="session in listeningSessions" :key="session.id" class="cursor-pointer" @click="showSession(session)">
<td class="py-1 max-w-48">
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
</td>
<td class="hidden md:table-cell">
<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>
<td class="text-center">
<p class="text-xs font-mono">{{ $elapsedPrettyLocalized(session.timeListening) }}</p>
</td>
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
</td>
<td class="text-center hidden sm:table-cell">
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
</ui-tooltip>
</td>
</tr>
</table>
</div>
<div class="flex items-center justify-end py-1">
<ui-icon-btn icon="arrow_back_ios_new" :size="7" icon-font-size="1rem" class="mx-1" :disabled="currentPage === 0" @click="prevPage" />
<p class="text-sm mx-1">{{ $getString('LabelPaginationPageXOfY', [currentPage + 1, numPages]) }}</p>
@@ -98,10 +100,10 @@ export default {
return this.$store.getters['users/getIsUserOnline'](this.user.id)
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.state.serverSettings.timeFormat
return this.$store.getters['getServerSetting']('timeFormat')
}
},
methods: {

View File

@@ -193,7 +193,7 @@ export default {
return `${process.env.serverUrl}/api/items/${this.libraryItemId}/download?token=${this.userToken}`
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
},
userIsAdminOrUp() {
return this.$store.getters['user/getIsAdminOrUp']
@@ -819,6 +819,17 @@ export default {
-webkit-line-clamp: 4;
max-height: calc(6 * 1lh);
}
/* Safari-specific fix for the description clamping */
@supports (-webkit-touch-callout: none) {
#item-description {
position: relative;
display: block;
overflow: hidden;
max-height: calc(6 * 1lh);
}
}
#item-description.show-full {
-webkit-line-clamp: unset;
max-height: 999rem;

View File

@@ -10,7 +10,7 @@
</tr>
<tr v-for="narrator in narrators" :key="narrator.id">
<td>
<p v-if="selectedNarrator?.id !== narrator.id" class="text-sm md:text-base text-gray-100">{{ narrator.name }}</p>
<nuxt-link v-if="selectedNarrator?.id !== narrator.id" :to="`/library/${currentLibraryId}/bookshelf?filter=narrators.${narrator.id}`" class="text-sm md:text-base text-gray-100 hover:underline">{{ narrator.name }}</nuxt-link>
<form v-else @submit.prevent="saveClick">
<ui-text-input v-model="newNarratorName" />
</form>

View File

@@ -141,7 +141,7 @@ export default {
return episodeIds
},
dateFormat() {
return this.$store.state.serverSettings.dateFormat
return this.$store.getters['getServerSetting']('dateFormat')
}
},
methods: {

View File

@@ -22,6 +22,7 @@ export default {
})
results = {
podcasts: results?.podcast || [],
episodes: results?.episodes || [],
books: results?.book || [],
authors: results?.authors || [],
series: results?.series || [],
@@ -61,6 +62,7 @@ export default {
})
this.results = {
podcasts: results?.podcast || [],
episodes: results?.episodes || [],
books: results?.book || [],
authors: results?.authors || [],
series: results?.series || [],

View File

@@ -40,6 +40,15 @@
<p v-if="error" class="text-error text-center py-2">{{ error }}</p>
<div v-if="showNewAuthSystemMessage" class="mb-4">
<widgets-alert type="warning">
<div>
<p>{{ $strings.MessageAuthenticationSecurityMessage }}</p>
<a v-if="showNewAuthSystemAdminMessage" href="https://github.com/advplyr/audiobookshelf/discussions/4460" target="_blank" class="underline">{{ $strings.LabelMoreInfo }}</a>
</div>
</widgets-alert>
</div>
<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" />
@@ -85,7 +94,10 @@ export default {
MetadataPath: '',
login_local: true,
login_openid: false,
authFormData: null
authFormData: null,
// New JWT auth system re-login flags
showNewAuthSystemMessage: false,
showNewAuthSystemAdminMessage: false
}
},
watch: {
@@ -177,13 +189,19 @@ export default {
require('@/plugins/chromecast.js').default(this)
}
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
this.$store.commit('libraries/setCurrentLibrary', { id: userDefaultLibraryId })
this.$store.commit('user/setUser', user)
// Access token only returned from login, not authorize
if (user.accessToken) {
this.$store.commit('user/setAccessToken', user.accessToken)
}
this.$store.dispatch('user/loadUserSettings')
},
async submitForm() {
this.error = null
this.showNewAuthSystemMessage = false
this.showNewAuthSystemAdminMessage = false
this.processing = true
const payload = {
@@ -210,6 +228,8 @@ export default {
this.processing = true
this.$store.commit('user/setAccessToken', token)
return this.$axios
.$post('/api/authorize', null, {
headers: {
@@ -217,15 +237,25 @@ export default {
}
})
.then((res) => {
// Force re-login if user is using an old token with no expiration
if (res.user.isOldToken) {
this.username = res.user.username
this.showNewAuthSystemMessage = true
// Admin user sees link to github discussion
this.showNewAuthSystemAdminMessage = res.user.type === 'admin' || res.user.type === 'root'
return false
}
this.setUser(res)
this.processing = false
return true
})
.catch((error) => {
console.error('Authorize error', error)
this.processing = false
return false
})
.finally(() => {
this.processing = false
})
},
checkStatus() {
this.processing = true
@@ -280,8 +310,9 @@ export default {
}
},
async mounted() {
if (this.$route.query?.setToken) {
localStorage.setItem('token', this.$route.query.setToken)
// Token passed as query parameter after successful oidc login
if (this.$route.query?.accessToken) {
localStorage.setItem('token', this.$route.query.accessToken)
}
if (localStorage.getItem('token')) {
if (await this.checkAuth()) return // if valid user no need to check status

View File

@@ -359,15 +359,14 @@ export default {
// Check if path already exists before starting upload
// uploading fails if path already exists
for (const item of items) {
const filepath = Path.join(this.selectedFolder.fullPath, item.directory)
const exists = await this.$axios
.$post(`/api/filesystem/pathexists`, { filepath, directory: item.directory, folderPath: this.selectedFolder.fullPath })
.$post(`/api/filesystem/pathexists`, { directory: item.directory, folderPath: this.selectedFolder.fullPath })
.then((data) => {
if (data.exists) {
if (data.libraryItemTitle) {
this.$toast.error(this.$getString('ToastUploaderItemExistsInSubdirectoryError', [data.libraryItemTitle]))
} else {
this.$toast.error(this.$getString('ToastUploaderFilepathExistsError', [filepath]))
this.$toast.error(this.$getString('ToastUploaderFilepathExistsError', [Path.join(this.selectedFolder.fullPath, item.directory)]))
}
}
return data.exists

View File

@@ -1,5 +1,5 @@
export default class AudioTrack {
constructor(track, userToken, routerBasePath) {
constructor(track, sessionId, routerBasePath) {
this.index = track.index || 0
this.startOffset = track.startOffset || 0 // Total time of all previous tracks
this.duration = track.duration || 0
@@ -8,28 +8,29 @@ export default class AudioTrack {
this.mimeType = track.mimeType
this.metadata = track.metadata || {}
this.userToken = userToken
this.sessionId = sessionId
this.routerBasePath = routerBasePath || ''
if (this.contentUrl?.startsWith('/hls')) {
this.sessionTrackUrl = this.contentUrl
} else {
this.sessionTrackUrl = `/public/session/${sessionId}/track/${this.index}`
}
}
/**
* Used for CastPlayer
*/
get fullContentUrl() {
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
if (process.env.NODE_ENV === 'development') {
return `${process.env.serverUrl}${this.contentUrl}?token=${this.userToken}`
return `${process.env.serverUrl}${this.sessionTrackUrl}`
}
return `${window.location.origin}${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
return `${window.location.origin}${this.routerBasePath}${this.sessionTrackUrl}`
}
/**
* Used for LocalPlayer
*/
get relativeContentUrl() {
if (!this.contentUrl || this.contentUrl.startsWith('http')) return this.contentUrl
return `${this.routerBasePath}${this.contentUrl}?token=${this.userToken}`
return `${this.routerBasePath}${this.sessionTrackUrl}`
}
}

View File

@@ -37,9 +37,6 @@ export default class PlayerHandler {
get isPlayingLocalItem() {
return this.libraryItem && this.player instanceof LocalAudioPlayer
}
get userToken() {
return this.ctx.$store.getters['user/getToken']
}
get playerPlaying() {
return this.playerState === 'PLAYING'
}
@@ -226,7 +223,7 @@ export default class PlayerHandler {
console.log('[PlayerHandler] Preparing Session', session)
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, this.userToken, this.ctx.$config.routerBasePath))
var audioTracks = session.audioTracks.map((at) => new AudioTrack(at, session.id, this.ctx.$config.routerBasePath))
this.ctx.playerLoading = true
this.isHlsTranscode = true

View File

@@ -1,4 +1,19 @@
export default function ({ $axios, store, $config }) {
export default function ({ $axios, store, $root, app }) {
// Track if we're currently refreshing to prevent multiple refresh attempts
let isRefreshing = false
let failedQueue = []
const processQueue = (error, token = null) => {
failedQueue.forEach(({ resolve, reject }) => {
if (error) {
reject(error)
} else {
resolve(token)
}
})
failedQueue = []
}
$axios.onRequest((config) => {
if (!config.url) {
console.error('Axios request invalid config', config)
@@ -7,7 +22,7 @@ export default function ({ $axios, store, $config }) {
if (config.url.startsWith('http:') || config.url.startsWith('https:')) {
return
}
const bearerToken = store.state.user.user?.token || null
const bearerToken = store.getters['user/getToken']
if (bearerToken) {
config.headers.common['Authorization'] = `Bearer ${bearerToken}`
}
@@ -17,9 +32,79 @@ export default function ({ $axios, store, $config }) {
}
})
$axios.onError((error) => {
$axios.onError(async (error) => {
const originalRequest = error.config
const code = parseInt(error.response && error.response.status)
const message = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
console.error('Axios error', code, message)
// Handle 401 Unauthorized (token expired)
if (code === 401 && !originalRequest._retry) {
// Skip refresh for auth endpoints to prevent infinite loops
if (originalRequest.url === '/auth/refresh' || originalRequest.url === '/login') {
// Refresh failed or login failed, redirect to login
store.commit('user/setUser', null)
store.commit('user/setAccessToken', null)
app.router.push('/login')
return Promise.reject(error)
}
if (isRefreshing) {
// If already refreshing, queue this request
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject })
})
.then((token) => {
if (!originalRequest.headers) {
originalRequest.headers = {}
}
originalRequest.headers['Authorization'] = `Bearer ${token}`
return $axios(originalRequest)
})
.catch((err) => {
return Promise.reject(err)
})
}
originalRequest._retry = true
isRefreshing = true
try {
// Attempt to refresh the token
// Updates store if successful, otherwise clears store and throw error
const newAccessToken = await store.dispatch('user/refreshToken')
if (!newAccessToken) {
console.error('No new access token received')
return Promise.reject(error)
}
// Update the original request with new token
if (!originalRequest.headers) {
originalRequest.headers = {}
}
originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`
// Process any queued requests
processQueue(null, newAccessToken)
// Retry the original request
return $axios(originalRequest)
} catch (refreshError) {
console.error('Token refresh failed:', refreshError)
// Process queued requests with error
processQueue(refreshError, null)
// Redirect to login
app.router.push('/login')
return Promise.reject(refreshError)
} finally {
isRefreshing = false
}
}
return Promise.reject(error)
})
}

View File

@@ -5,6 +5,7 @@ import { supplant } from './utils'
const defaultCode = 'en-us'
const languageCodeMap = {
ar: { label: 'عربي', dateFnsLocale: 'ar' },
bg: { label: 'Български', dateFnsLocale: 'bg' },
bn: { label: 'বাংলা', dateFnsLocale: 'bn' },
ca: { label: 'Català', dateFnsLocale: 'ca' },

View File

@@ -37,6 +37,48 @@ Vue.prototype.$elapsedPretty = (seconds, useFullNames = false, useMilliseconds =
return `${hours} ${useFullNames ? `hour${hours === 1 ? '' : 's'}` : 'hr'} ${minutes} ${useFullNames ? `minute${minutes === 1 ? '' : 's'}` : 'min'}`
}
Vue.prototype.$elapsedPrettyLocalized = (seconds, useFullNames = false, useMilliseconds = false) => {
if (isNaN(seconds) || seconds === null) return ''
try {
const df = new Intl.DurationFormat(Vue.prototype.$languageCodes.current, {
style: useFullNames ? 'long' : 'short'
})
const duration = {}
if (seconds < 60) {
if (useMilliseconds && seconds < 1) {
duration.milliseconds = Math.floor(seconds * 1000)
} else {
duration.seconds = Math.floor(seconds)
}
} else if (seconds < 3600) {
// 1 hour
duration.minutes = Math.floor(seconds / 60)
} else if (seconds < 86400) {
// 1 day
duration.hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
if (minutes > 0) {
duration.minutes = minutes
}
} else {
duration.days = Math.floor(seconds / 86400)
const hours = Math.floor((seconds % 86400) / 3600)
if (hours > 0) {
duration.hours = hours
}
}
return df.format(duration)
} catch (error) {
// Handle not supported
console.warn('Intl.DurationFormat not supported, not localizing duration')
return Vue.prototype.$elapsedPretty(seconds, useFullNames, useMilliseconds)
}
}
Vue.prototype.$secondsToTimestamp = (seconds, includeMs = false, alwaysIncludeHours = false) => {
if (!seconds) {
return alwaysIncludeHours ? '00:00:00' : '0:00'

View File

@@ -133,7 +133,7 @@ export const actions = {
commit('setNumUserPlaylists', numUserPlaylists)
commit('scanners/setCustomMetadataProviders', customMetadataProviders, { root: true })
commit('setCurrentLibrary', libraryId)
commit('setCurrentLibrary', { id: libraryId })
return data
})
.catch((error) => {
@@ -182,8 +182,8 @@ export const mutations = {
setLibraryIssues(state, val) {
state.issues = val
},
setCurrentLibrary(state, val) {
state.currentLibraryId = val
setCurrentLibrary(state, { id }) {
state.currentLibraryId = id
},
set(state, libraries) {
state.libraries = libraries

View File

@@ -1,5 +1,6 @@
export const state = () => ({
user: null,
accessToken: null,
settings: {
orderBy: 'media.metadata.title',
orderDesc: false,
@@ -25,19 +26,19 @@ export const getters = {
getIsRoot: (state) => state.user && state.user.type === 'root',
getIsAdminOrUp: (state) => state.user && (state.user.type === 'admin' || state.user.type === 'root'),
getToken: (state) => {
return state.user?.token || null
return state.accessToken || null
},
getUserMediaProgress:
(state) =>
(libraryItemId, episodeId = null) => {
if (!state.user.mediaProgress) return null
if (!state.user?.mediaProgress) return null
return state.user.mediaProgress.find((li) => {
if (episodeId && li.episodeId !== episodeId) return false
return li.libraryItemId == libraryItemId
})
},
getUserBookmarksForItem: (state) => (libraryItemId) => {
if (!state.user.bookmarks) return []
if (!state.user?.bookmarks) return []
return state.user.bookmarks.filter((bm) => bm.libraryItemId === libraryItemId)
},
getUserSetting: (state) => (key) => {
@@ -58,6 +59,9 @@ export const getters = {
getUserCanAccessAllLibraries: (state) => {
return !!state.user?.permissions?.accessAllLibraries
},
getUserCanAccessExplicitContent: (state) => {
return !!state.user?.permissions?.accessExplicitContent
},
getLibrariesAccessible: (state, getters) => {
if (!state.user) return []
if (getters.getUserCanAccessAllLibraries) return []
@@ -88,7 +92,7 @@ export const actions = {
if (state.settings.orderBy == 'media.duration') {
settingsUpdate.orderBy = 'media.numTracks'
}
if (state.settings.orderBy == 'media.metadata.publishedYear') {
if (state.settings.orderBy == 'media.metadata.publishedYear' || state.settings.orderBy == 'progress') {
settingsUpdate.orderBy = 'media.metadata.title'
}
const invalidFilters = ['series', 'authors', 'narrators', 'publishers', 'publishedDecades', 'languages', 'progress', 'issues', 'ebooks', 'abridged']
@@ -142,21 +146,41 @@ export const actions = {
} catch (error) {
console.error('Failed to load userSettings from local storage', error)
}
},
refreshToken({ state, commit }) {
return this.$axios
.$post('/auth/refresh')
.then(async (response) => {
const newAccessToken = response.user.accessToken
commit('setAccessToken', newAccessToken)
// Emit event used to re-authenticate socket in default.vue since $root is not available here
if (this.$eventBus) {
this.$eventBus.$emit('token_refreshed', newAccessToken)
}
return newAccessToken
})
.catch((error) => {
console.error('Failed to refresh token', error)
commit('setUser', null)
commit('setAccessToken', null)
// Calling function handles redirect to login
throw error
})
}
}
export const mutations = {
setUser(state, user) {
state.user = user
if (user) {
if (user.token) localStorage.setItem('token', user.token)
} else {
localStorage.removeItem('token')
}
},
setUserToken(state, token) {
state.user.token = token
localStorage.setItem('token', token)
setAccessToken(state, token) {
if (!token) {
localStorage.removeItem('token')
state.accessToken = null
} else {
state.accessToken = token
localStorage.setItem('token', token)
}
},
updateMediaProgress(state, { id, data }) {
if (!state.user) return

View File

@@ -10,6 +10,8 @@
"ButtonApplyChapters": "حفظ الفصول",
"ButtonAuthors": "المؤلفون",
"ButtonBack": "الرجوع",
"ButtonBatchEditPopulateFromExisting": "تعبئة من الموجود",
"ButtonBatchEditPopulateMapDetails": "تعبئة تفاصيل الخريطة",
"ButtonBrowseForFolder": "البحث عن المجلد",
"ButtonCancel": "إلغاء",
"ButtonCancelEncode": "إلغاء الترميز",
@@ -30,8 +32,8 @@
"ButtonEditChapters": "تعديل الفصول",
"ButtonEditPodcast": "تعديل البودكاست",
"ButtonEnable": "تفعيل",
"ButtonFireAndFail": "النار والفشل",
"ButtonFireOnTest": "حادثة إطلاق النار",
"ButtonFireAndFail": "محاولة فاشلة",
"ButtonFireOnTest": "تشغيل حدث الاختبار",
"ButtonForceReScan": "فرض إعادة المسح",
"ButtonFullPath": "المسار الكامل",
"ButtonHide": "إخفاء",
@@ -57,7 +59,7 @@
"ButtonPause": "إيقاف مؤقت",
"ButtonPlay": "تشغيل",
"ButtonPlayAll": "تشغيل الكل",
"ButtonPlaying": "مشغل الآن",
"ButtonPlaying": "جاري التشغيل",
"ButtonPlaylists": "قوائم التشغيل",
"ButtonPrevious": "سابِق",
"ButtonPreviousChapter": "الفصل السابق",
@@ -88,13 +90,15 @@
"ButtonSaveTracklist": "حفظ قائمة التشغيل",
"ButtonScan": "تَحَقُق",
"ButtonScanLibrary": "تَحَقُق من المكتبة",
"ButtonScrollLeft": "تمرير لليسار",
"ButtonScrollRight": "تمرير لليمين",
"ButtonSearch": "بحث",
"ButtonSelectFolderPath": "حدد مسار المجلد",
"ButtonSeries": "سلسلة",
"ButtonSetChaptersFromTracks": "تعيين الفصول من الملفات",
"ButtonShare": "نشر",
"ButtonShiftTimes": "أوقات العمل",
"ButtonShow": "عرض",
"ButtonShow": "أعرض",
"ButtonStartM4BEncode": "ابدأ ترميز M4B",
"ButtonStartMetadataEmbed": "ابدأ تضمين البيانات الوصفية",
"ButtonStats": "الإحصائيات",
@@ -153,64 +157,953 @@
"HeaderLogs": "السجلات",
"HeaderManageGenres": "إدارة الانواع",
"HeaderManageTags": "إدارة العلامات",
"HeaderOpenRSSFeed": "فتح تغذية RSS",
"HeaderMapDetails": "تفاصيل الخريطة",
"HeaderMatch": "مطابقة",
"HeaderMetadataOrderOfPrecedence": "ترتيب أولوية البيانات الوصفية",
"HeaderMetadataToEmbed": "البيانات الوصفية المراد تضمينها",
"HeaderNewAccount": "حساب جديد",
"HeaderNewLibrary": "مكتبة جديدة",
"HeaderNotificationCreate": "إنشاء إشعار",
"HeaderNotificationUpdate": "تحديث إشعار",
"HeaderNotifications": "إشعارات",
"HeaderOpenIDConnectAuthentication": "مصادقة OpenID Connect",
"HeaderOpenListeningSessions": "جلسات الاستماع المفتوحة",
"HeaderOpenRSSFeed": "عرض تغذية RSS",
"HeaderOtherFiles": "ملفات أخرى",
"HeaderPasswordAuthentication": "مصادقة كلمة المرور",
"HeaderPermissions": "الصلاحيات",
"HeaderPlayerQueue": "قائمة انتظار المشغل",
"HeaderPlayerSettings": "إعدادات المشغل",
"HeaderPlaylist": "قائمة تشغيل",
"HeaderPlaylistItems": "عناصر قائمة التشغيل",
"HeaderPodcastsToAdd": "بودكاست لإضافتها",
"HeaderPresets": "إعدادات مسبقة",
"HeaderPreviewCover": "معاينة الغلاف",
"HeaderRSSFeedGeneral": "تفاصيل RSS",
"HeaderRSSFeedIsOpen": "مغذي RSS مفتوح",
"HeaderRSSFeeds": "خلاصات RSS",
"HeaderRemoveEpisode": "إزالة حلقة",
"HeaderRemoveEpisodes": "إزالة {0} حلقات",
"HeaderSavedMediaProgress": "تقدم الوسائط المحفوظة",
"HeaderSchedule": "جَدْوَل",
"HeaderScheduleEpisodeDownloads": "جدولة التنزيلات التلقائية للحلقات",
"HeaderScheduleLibraryScans": "جدولة عمليات المسح التلقائي للمكتبة",
"HeaderSession": "الجلسة",
"HeaderSetBackupSchedule": "تعيين جدول النسخ الاحتياطي",
"HeaderSettings": "إعدادات",
"HeaderSettingsDisplay": "عرض",
"HeaderSettingsExperimental": "ميزات تجريبية",
"HeaderSettingsGeneral": "عام",
"HeaderSettingsScanner": "إعدادات المسح",
"HeaderSettingsWebClient": "عميل الويب",
"HeaderSleepTimer": "مؤقت النوم",
"HeaderStatsLargestItems": "أكبر العناصر حجماً",
"HeaderStatsLongestItems": "أطول العناصر (بالساعات)",
"HeaderStatsMinutesListeningChart": "الدقائق المسموعة (آخر 7 أيام)",
"HeaderStatsRecentSessions": "الجلسات الأخيرة",
"HeaderStatsTop10Authors": "أفضل 10 مؤلفين",
"HeaderStatsTop5Genres": "أفضل 5 أنواع",
"HeaderTableOfContents": "جدول المحتويات",
"HeaderTools": "أدوات",
"HeaderUpdateAccount": "تحديث الحساب",
"HeaderUpdateAuthor": "تحديث المؤلف",
"HeaderUpdateDetails": "تحديث التفاصيل",
"HeaderUpdateLibrary": "تحديث المكتبة",
"HeaderUsers": "المستخدمون",
"HeaderYearReview": "ملخص عام {0}",
"HeaderYourStats": "إحصائياتك",
"LabelAbridged": "مختصر",
"LabelAbridgedChecked": "مختصر (محدد)",
"LabelAbridgedUnchecked": "غير مختصر (غير محدد)",
"LabelAccessibleBy": "يمكن الوصول إليه بواسطة",
"LabelAccountType": "نوع الحساب",
"LabelAccountTypeAdmin": "مدير",
"LabelAccountTypeGuest": "ضيف",
"LabelAccountTypeUser": "مستخدم",
"LabelActivities": "النشاطات",
"LabelActivity": "نشاط",
"LabelAddToCollection": "إضافة إلى المجموعة",
"LabelAddToCollectionBatch": "إضافة {0} كتابًا إلى المجموعة",
"LabelAddToPlaylist": "أضف إلى قائمة التشغيل",
"LabelAddToPlaylistBatch": "إضافة {0} عناصر إلى قائمة التشغيل",
"LabelAddedAt": "أضيفت على",
"LabelAddedDate": "تمت الإضافة",
"LabelAdminUsersOnly": "للمستخدمين المديرين فقط",
"LabelAll": "الكل",
"LabelAllEpisodesDownloaded": "تم تنزيل جميع الحلقات",
"LabelAllUsers": "جميع المستخدمين",
"LabelAllUsersExcludingGuests": "جميع المستخدمين باستثناء الضيوف",
"LabelAllUsersIncludingGuests": "جميع المستخدمين بما في ذلك الضيوف",
"LabelAlreadyInYourLibrary": "موجود بالفعل في مكتبتك",
"LabelApiToken": "رمز API",
"LabelAppend": "إلحاق",
"LabelAudioBitrate": "معدل بت الصوت (على سبيل المثال 128 كيلو بايت)",
"LabelAudioChannels": "قنوات الصوت (1 أو 2)",
"LabelAudioCodec": "برنامج ترميز الصوت",
"LabelAuthor": "المؤلف",
"LabelAuthorFirstLast": "المؤلف (الاسم الأول الأخير)",
"LabelAuthorLastFirst": "المؤلف (الاسم الأخير، الأول)",
"LabelAuthors": "المؤلفون",
"LabelAutoDownloadEpisodes": "تنزيل الحلقات تلقائيًا",
"LabelAutoFetchMetadata": "جلب البيانات الوصفية تلقائيًا",
"LabelAutoFetchMetadataHelp": "يجلب البيانات الوصفية للعنوان والمؤلف والسلسلة لتسهيل عملية الرفع. قد يلزم مطابقة بيانات وصفية إضافية بعد الرفع.",
"LabelAutoLaunch": "تشغيل تلقائي",
"LabelAutoLaunchDescription": "إعادة التوجيه إلى مزود المصادقة تلقائيًا عند الانتقال إلى صفحة تسجيل الدخول (مسار التجاوز اليدوي <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "تسجيل تلقائي",
"LabelAutoRegisterDescription": "إنشاء مستخدمين جدد تلقائيًا بعد تسجيل الدخول",
"LabelBackToUser": "العودة إلى المستخدم",
"LabelBackupAudioFiles": "نسخ ملفات الصوت احتياطيًا",
"LabelBackupLocation": "موقع النسخ الاحتياطي",
"LabelBackupsEnableAutomaticBackups": "نسخ احتياطية تلقائية",
"LabelBackupsEnableAutomaticBackupsHelp": "النسخ الاحتياطية المحفوظة في / البيانات الوصفية / النسخ الاحتياطية",
"LabelBackupsMaxBackupSize": "الحد الأقصى لحجم النسخ الاحتياطي (بالجيجابايت) (0 لغير محدود)",
"LabelBackupsMaxBackupSizeHelp": "كإجراء وقائي ضد سوء التكوين، ستفشل عمليات النسخ الاحتياطي إذا تجاوزت الحجم المحدد.",
"LabelBackupsNumberToKeep": "عدد النسخ الاحتياطية التي يجب الاحتفاظ بها",
"LabelBackupsNumberToKeepHelp": "ستتم إزالة نسخة احتياطية واحدة فقط في كل مرة، لذا إذا كان لديك بالفعل عدد نسخ احتياطية أكبر من هذا، فيجب عليك إزالتها يدويًا.",
"LabelBitrate": "معدل البت",
"LabelBonus": "مكافأة",
"LabelBooks": "الكتب",
"LabelButtonText": "نص الزر",
"LabelByAuthor": "بواسطة {0}",
"LabelChangePassword": "تغيير كلمة المرور",
"LabelChannels": "قنوات",
"LabelChapterCount": "{0} فصول",
"LabelChapterTitle": "عنوان الفصل",
"LabelChapters": "الفصول",
"LabelChaptersFound": "تم العثور على فصول",
"LabelClickForMoreInfo": "انقر لمزيد من المعلومات",
"LabelClickToUseCurrentValue": "انقر لاستخدام القيمة الحالية",
"LabelClosePlayer": "إغلاق المشغل",
"LabelCodec": "برنامج الترميز",
"LabelCollapseSeries": "إخفاء المسلسلات",
"LabelCollapseSubSeries": "إخفاء المسلسلات الفرعية",
"LabelCollection": "مجموعة",
"LabelCollections": "مجموعات",
"LabelComplete": "مكتمل",
"LabelConfirmPassword": "تأكيد كلمة المرور",
"LabelContinueListening": "استمرار الاستماع",
"LabelContinueReading": "استمرار القراءة",
"LabelContinueSeries": "استمرار المسلسلات",
"LabelCover": "الغلاف",
"LabelCoverImageURL": "رابط صورة الغلاف",
"LabelCoverProvider": "مزود الغلاف",
"LabelCreatedAt": "تاريخ الإنشاء",
"LabelCronExpression": "تعبير Cron",
"LabelCurrent": "الحالي",
"LabelCurrently": "حاليًا:",
"LabelCustomCronExpression": "تعبير Cron مخصص:",
"LabelDatetime": "التاريخ والوقت",
"LabelDays": "أيام",
"LabelDeleteFromFileSystemCheckbox": "حذف من نظام الملفات (إلغاء التحديد للإزالة من قاعدة البيانات فقط)",
"LabelDescription": "الوصف",
"LabelDeselectAll": "إلغاء تحديد الكل",
"LabelDevice": "الجهاز",
"LabelDeviceInfo": "معلومات الجهاز",
"LabelDeviceIsAvailableTo": "الجهاز متاح لـ...",
"LabelDirectory": "مجلد / دليل",
"LabelDiscFromFilename": "القرص من اسم الملف",
"LabelDiscFromMetadata": "القرص من البيانات الوصفية",
"LabelDiscover": "استكشف",
"LabelDownload": "تنزيل",
"LabelDownloadNEpisodes": "تنزيل {0} حلقات",
"LabelDownloadable": "قابل للتنزيل",
"LabelDuration": "المدة",
"LabelDurationComparisonExactMatch": "(تطابق تام)",
"LabelDurationComparisonLonger": "(أطول بـ {0})",
"LabelDurationComparisonShorter": "({0} أقصر)",
"LabelDurationFound": "المدة الموجودة:",
"LabelEbook": "الكتاب الإلكتروني",
"LabelEbooks": "الكتب الإلكترونية",
"LabelEdit": "تعديل",
"LabelEmail": "البريد الإلكتروني",
"LabelEmailSettingsFromAddress": "عنوان المرسل",
"LabelEmailSettingsRejectUnauthorized": "رفض الشهادات غير المصرح بها",
"LabelEmailSettingsRejectUnauthorizedHelp": "قد يؤدي تعطيل التحقق من شهادة SSL إلى تعريض اتصالك لمخاطر أمنية، مثل هجمات الوسيط. لا تقم بتعطيل هذا الخيار إلا إذا كنت تفهم الآثار المترتبة عليه وتثق في خادم البريد الذي تتصل به.",
"LabelEmailSettingsSecure": "آمن",
"LabelEmailSettingsSecureHelp": "إذا كانت القيمة true، فسيستخدم الاتصال TLS عند الاتصال بالخادم. وإذا كانت false، فسيتم استخدام TLS إذا كان الخادم يدعم امتداد STARTTLS. في معظم الحالات، اضبط هذه القيمة على true إذا كنت تتصل بالمنفذ 465. أما بالنسبة للمنفذ 587 أو 25، فاحتفظ بها على false. (من nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "عنوان الاختبار",
"LabelEmbeddedCover": "غلاف مضمن",
"LabelEnable": "تمكين",
"LabelEncodingBackupLocation": "سيتم تخزين نسخة احتياطية من ملفاتك الصوتية الأصلية في:",
"LabelEncodingChaptersNotEmbedded": "الفصول غير مضمنة في الكتب الصوتية متعددة المسارات.",
"LabelEncodingClearItemCache": "تأكد من مسح ذاكرة التخزين المؤقت للعناصر بشكل دوري.",
"LabelEncodingFinishedM4B": "سيتم وضع ملف M4B النهائي في مجلد الكتب الصوتية الخاص بك في:",
"LabelEncodingInfoEmbedded": "سيتم تضمين البيانات الوصفية في المسارات الصوتية داخل مجلد الكتب الصوتية الخاص بك.",
"LabelEncodingStartedNavigation": "بمجرد بدء المهمة، يمكنك الانتقال من هذه الصفحة.",
"LabelEncodingTimeWarning": "قد تستغرق عملية الترميز ما يصل إلى 30 دقيقة.",
"LabelEncodingWarningAdvancedSettings": "تحذير: لا تقم بتحديث هذه الإعدادات إلا إذا كنت على دراية بخيارات ترميز ffmpeg.",
"LabelEncodingWatcherDisabled": "إذا قمت بتعطيل المراقب، فستحتاج إلى إعادة فحص هذا الكتاب الصوتي بعد ذلك.",
"LabelEnd": "انهاء",
"LabelEndOfChapter": "نهاية الفصل",
"LabelEpisode": "الحلقة",
"LabelEpisodeNotLinkedToRssFeed": "الحلقة غير مرتبطة بخلاصة RSS",
"LabelEpisodeNumber": "الحلقة #{0}",
"LabelEpisodeTitle": "عنوان الحلقة",
"LabelEpisodeType": "نوع الحلقة",
"LabelEpisodeUrlFromRssFeed": "رابط الحلقة من خلاصة RSS",
"LabelEpisodes": "حلقات",
"LabelEpisodic": "عرضي / حلقي",
"LabelExample": "مثال",
"LabelExpandSeries": "توسيع السلاسل",
"LabelExpandSubSeries": "توسيع السلاسل الفرعية",
"LabelExplicit": "صريح",
"LabelExplicitChecked": "صريح (محدد)",
"LabelExplicitUnchecked": "غير صريح (غير محدد)",
"LabelExportOPML": "تصدير OPML",
"LabelFeedURL": "عنوان التغذية",
"LabelFetchingMetadata": "جلب البيانات الوصفية",
"LabelFile": "الملف",
"LabelFileBirthtime": "وقت انشاء الملف",
"LabelFileBornDate": "تاريخ الإنشاء {0}",
"LabelFileModified": "تم تعديل الملف",
"LabelFileModifiedDate": "تم التعديل في {0}",
"LabelFilename": "اسم الملف",
"LabelFilterByUser": "تصفية حسب المستخدم",
"LabelFindEpisodes": "البحث عن حلقات",
"LabelFinished": "المنجزة",
"LabelFolder": "المجلد",
"LabelFolders": "مجلدات",
"LabelFontBold": "عريض",
"LabelFontBoldness": "تعريض الخط",
"LabelFontFamily": "عائلة الخط",
"LabelFontItalic": "مائل",
"LabelFontScale": "نطاق الخط",
"LabelFontStrikethrough": "يتوسطه خط",
"LabelFormat": "تنسيق",
"LabelFull": "كامل",
"LabelGenre": "التصنيف",
"LabelGenres": "التصانيف",
"LabelHardDeleteFile": "حذف الملف نهائيًا",
"LabelHasEbook": "يحتوي كتاب إلكتروني",
"LabelHasSupplementaryEbook": "يحتوي كتاب إلكتروني تكميلي",
"LabelHideSubtitles": "إخفاء الترجمة",
"LabelHighestPriority": "الأولوية القصوى",
"LabelHost": "المضيف",
"LabelHour": "ساعة",
"LabelHours": "ساعات",
"LabelIcon": "أيقونة",
"LabelImageURLFromTheWeb": "رابط الصورة من الويب",
"LabelInProgress": "تحت التنفيذ",
"LabelIncludeInTracklist": "تضمين في قائمة المسارات",
"LabelIncomplete": "غير مكتمل",
"LabelInterval": "فاصل زمني",
"LabelIntervalCustomDailyWeekly": "يومي/أسبوعي مخصص",
"LabelIntervalEvery12Hours": "كل 12 ساعة",
"LabelIntervalEvery15Minutes": "كل 15 دقيقة",
"LabelIntervalEvery2Hours": "كل ساعتين",
"LabelIntervalEvery30Minutes": "كل 30 دقيقة",
"LabelIntervalEvery6Hours": "كل 6 ساعات",
"LabelIntervalEveryDay": "كل يوم",
"LabelIntervalEveryHour": "كل ساعة",
"LabelIntervalEveryMinute": "كل دقيقة",
"LabelInvert": "عكس",
"LabelItem": "عنصر",
"LabelJumpBackwardAmount": "مقدار الرجوع للخلف",
"LabelJumpForwardAmount": "مقدار التقدم للأمام",
"LabelLanguage": "اللغة",
"LabelLanguageDefaultServer": "لغة الخادم الافتراضية",
"LabelLanguages": "اللغات",
"LabelLastBookAdded": "آخر كتاب تمت إضافته",
"LabelLastBookUpdated": "آخر كتاب تم تحديثه",
"LabelLastSeen": "آخر ظهور",
"LabelLastTime": "آخر مرة",
"LabelLastUpdate": "آخر تحديث",
"LabelLayout": "التنسيق",
"LabelLayoutSinglePage": "صفحة واحدة",
"LabelLayoutSplitPage": "صفحتان متقابلتان",
"LabelLess": "أقل",
"LabelLibrariesAccessibleToUser": "المكتبات المتاحة للمستخدم",
"LabelLibrary": "مكتبة",
"LabelLibraryFilterSublistEmpty": "لا يوجد {0}",
"LabelLibraryItem": "عنصر المكتبة",
"LabelLibraryName": "اسم المكتبة",
"LabelLimit": "حد",
"LabelLineSpacing": "تباعد الأسطر",
"LabelListenAgain": "الاستماع مجدداً",
"LabelMediaType": "نوع الوسائط"
"LabelLogLevelDebug": "تصحيح الأخطاء",
"LabelLogLevelInfo": "معلومات",
"LabelLogLevelWarn": "تحذير",
"LabelLookForNewEpisodesAfterDate": "البحث عن حلقات جديدة بعد هذا التاريخ",
"LabelLowestPriority": "الأولوية الأدنى",
"LabelMatchExistingUsersBy": "مطابقة المستخدمين الحاليين بواسطة",
"LabelMatchExistingUsersByDescription": "يستخدم لربط المستخدمين الحاليين. بمجرد الاتصال، سيتم مطابقة المستخدمين بواسطة معرف فريد من مزود SSO الخاص بك",
"LabelMaxEpisodesToDownload": "الحد الأقصى لعدد الحلقات التي سيتم تنزيلها. استخدم 0 لغير محدود.",
"LabelMaxEpisodesToDownloadPerCheck": "الحد الأقصى لعدد الحلقات الجديدة التي سيتم تنزيلها في كل فحص",
"LabelMaxEpisodesToKeep": "الحد الأقصى لعدد الحلقات التي سيتم الاحتفاظ بها",
"LabelMaxEpisodesToKeepHelp": "القيمة 0 لا تضع حدًا أقصى. بعد تنزيل حلقة جديدة تلقائيًا، سيؤدي هذا إلى حذف أقدم حلقة إذا كان لديك أكثر من X حلقة. سيؤدي هذا إلى حذف حلقة واحدة فقط لكل تنزيل جديد.",
"LabelMediaPlayer": "مشغل الوسائط",
"LabelMediaType": "نوع الوسائط",
"LabelMetaTag": "علامة بيانات وصفية",
"LabelMetaTags": "علامات البيانات الوصفية",
"LabelMetadataOrderOfPrecedenceDescription": "ستتجاوز مصادر البيانات الوصفية ذات الأولوية الأعلى مصادر البيانات الوصفية ذات الأولوية الأقل",
"LabelMetadataProvider": "مزود البيانات الوصفية",
"LabelMinute": "دقيقة",
"LabelMinutes": "دقائق",
"LabelMissing": "مفقود",
"LabelMissingEbook": "لا يوجد كتاب إلكتروني",
"LabelMissingSupplementaryEbook": "لا يوجد كتاب إلكتروني تكميلي",
"LabelMobileRedirectURIs": "معرفات URI لإعادة التوجيه المسموح بها لتطبيقات الجوال",
"LabelMobileRedirectURIsDescription": "هذه قائمة بيضاء لمعرفات URI لإعادة التوجيه الصالحة لتطبيقات الجوال. المعرف الافتراضي هو <code>audiobookshelf://oauth</code>، والذي يمكنك إزالته أو استكماله بمعرفات URI إضافية لتكامل تطبيقات الطرف الثالث. استخدام علامة النجمة (<code>*</code>) كإدخال وحيد يسمح بأي معرف URI.",
"LabelMore": "أكثر",
"LabelMoreInfo": "معلومات أكثر",
"LabelName": "الاسم",
"LabelNarrator": "الراوي",
"LabelNarrators": "الرواة",
"LabelNew": "جديد",
"LabelNewPassword": "كلمة سر جديدة",
"LabelNewestAuthors": "أجدد المؤلفين",
"LabelNewestEpisodes": "أجدد الحلقات",
"LabelNextBackupDate": "تاريخ النسخ الاحتياطي التالي",
"LabelNextScheduledRun": "التشغيل المجدول التالي",
"LabelNoCustomMetadataProviders": "لا يوجد مزودو بيانات وصفية مخصصون",
"LabelNoEpisodesSelected": "لم يتم تحديد أي حلقات",
"LabelNotFinished": "لم يتم الانتهاء",
"LabelNotStarted": "لم يتم البدء",
"LabelNotes": "ملاحظات",
"LabelNotificationAppriseURL": "رابط (روابط) Apprise",
"LabelNotificationAvailableVariables": "المتغيرات المتاحة",
"LabelNotificationBodyTemplate": "قالب النص",
"LabelNotificationEvent": "حدث الإشعار",
"LabelNotificationTitleTemplate": "قالب العنوان",
"LabelNotificationsMaxFailedAttempts": "الحد الأقصى لعدد المحاولات الفاشلة",
"LabelNotificationsMaxFailedAttemptsHelp": "يتم تعطيل الإشعارات بمجرد فشل إرسالها لهذا العدد من المرات",
"LabelNotificationsMaxQueueSize": "الحد الأقصى لحجم قائمة انتظار أحداث الإشعارات",
"LabelNotificationsMaxQueueSizeHelp": "تقتصر الأحداث على التشغيل مرة واحدة في الثانية. سيتم تجاهل الأحداث إذا كانت قائمة الانتظار في الحد الأقصى لحجمها. هذا يمنع إرسال الإشعارات بشكل متكرر.",
"LabelNumberOfBooks": "عدد الكتب",
"LabelNumberOfEpisodes": "# من الحلقات",
"LabelOpenIDAdvancedPermsClaimDescription": "اسم مطالبة OpenID التي تحتوي على أذونات متقدمة لإجراءات المستخدم داخل التطبيق والتي ستطبق على الأدوار غير الإدارية (<b>إذا تم تكوينها</b>). إذا كانت المطالبة مفقودة من الاستجابة، فسيتم رفض الوصول إلى ABS. إذا كان هناك خيار واحد مفقودًا، فسيتم التعامل معه على أنه <code>false</code>. تأكد من أن مطالبة موفر الهوية تطابق البنية المتوقعة:",
"LabelOpenIDClaims": "اترك الخيارات التالية فارغة لتعطيل تعيين المجموعة والأذونات المتقدمة، وسيتم تعيين مجموعة \"مستخدم\" تلقائيًا بعد ذلك.",
"LabelOpenIDGroupClaimDescription": "اسم مطالبة OpenID التي تحتوي على قائمة بمجموعات المستخدم. يشار إليها عادةً باسم <code>groups</code>.<b>إذا تم تكوينها</b>، فسيقوم التطبيق تلقائيًا بتعيين الأدوار بناءً على عضويات مجموعة المستخدم، بشرط أن تسمى هذه المجموعات بشكل غير حساس لحالة الأحرف \"admin\" أو \"user\" أو \"guest\" في المطالبة. يجب أن تحتوي المطالبة على قائمة، وإذا كان المستخدم ينتمي إلى مجموعات متعددة، فسيقوم التطبيق بتعيين الدور المقابل لأعلى مستوى من الوصول. إذا لم تتطابق أي مجموعة، فسيتم رفض الوصول.",
"LabelOpenRSSFeed": "تغذية RSS مفتوحة",
"LabelOverwrite": "استبدال",
"LabelPaginationPageXOfY": "صفحة {0} من {1}",
"LabelPassword": "كلمة المرور",
"LabelPath": "مسار",
"LabelPermanent": "دائم",
"LabelPermissionsAccessAllLibraries": "يمكنه الوصول إلى جميع المكتبات",
"LabelPermissionsAccessAllTags": "يمكنه الوصول إلى جميع العلامات",
"LabelPermissionsAccessExplicitContent": "يمكنه الوصول إلى المحتوى الصريح",
"LabelPermissionsCreateEreader": "يمكنه إنشاء قارئ إلكتروني",
"LabelPermissionsDelete": "يمكنه الحذف",
"LabelPermissionsDownload": "يمكنه التنزيل",
"LabelPermissionsUpdate": "يمكنه التحديث",
"LabelPermissionsUpload": "يمكنه الرفع",
"LabelPersonalYearReview": "ملخص عامك ({0})",
"LabelPhotoPathURL": "مسار/رابط الصورة",
"LabelPlayMethod": "طريقة التشغيل",
"LabelPlaybackRateIncrementDecrement": "مقدار زيادة/نقصان سرعة التشغيل",
"LabelPlayerChapterNumberMarker": "{0} من {1}",
"LabelPlaylists": "قوائم التشغيل",
"LabelPodcast": "مدونة صوتية",
"LabelPodcastSearchRegion": "منطقة البحث عن البودكاست",
"LabelPodcastType": "نوع البودكاست",
"LabelPodcasts": "بودكاست",
"LabelPort": "منفذ",
"LabelPrefixesToIgnore": "البادئات التي يجب تجاهلها (غير حساسة لحالة الأحرف)",
"LabelPreventIndexing": "منع فهرسة تغذيتك بواسطة دليل آيتونز وقوقل بودكاست",
"LabelPrimaryEbook": "الكتاب الإلكتروني الأساسي",
"LabelProgress": "تقدم",
"LabelProvider": "مزود",
"LabelProviderAuthorizationValue": "قيمة رأس التفويض",
"LabelPubDate": "تاريخ النشر",
"LabelPublishYear": "سنة النشر",
"LabelPublishedDate": "منشور {0}",
"LabelPublishedDecade": "عقد النشر",
"LabelPublishedDecades": "عقود النشر",
"LabelPublisher": "الناشر",
"LabelPublishers": "الناشرون",
"LabelRSSFeedCustomOwnerEmail": "البريد الالكتروني المخصص للمالك",
"LabelRSSFeedCustomOwnerName": "الاسم المخصص للمالك",
"LabelRSSFeedOpen": "موجز RSS مفتوح",
"LabelRSSFeedPreventIndexing": "منع الفهرسة",
"LabelRSSFeedSlug": "اسم تعريف تغذية RSS",
"LabelRSSFeedURL": "رابط تغذية RSS",
"LabelRandomly": "عشوائياً",
"LabelReAddSeriesToContinueListening": "إعادة إضافة السلسلة إلى \"متابعة الاستماع\"",
"LabelRead": "اقرأ",
"LabelReadAgain": "اقرأ مرة أخرى",
"LabelReadEbookWithoutProgress": "قراءة الكتاب الإلكتروني دون حفظ التقدم",
"LabelRecentSeries": "المسلسلات الحديثة",
"LabelRecentlyAdded": "المضافة حديثاً",
"LabelRecommended": "موصى به",
"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} حلقة معروضة",
"LabelSelectUsers": "تحديد المستخدمين",
"LabelSendEbookToDevice": "إرسال الكتاب الإلكتروني إلى...",
"LabelSequence": "تسلسل",
"LabelSerial": "مسلسل",
"LabelSeries": "المسلسلات",
"LabelSeriesName": "اسم السلسلة",
"LabelSeriesProgress": "تقدم السلسلة",
"LabelServerLogLevel": "مستوى سجل الخادم",
"LabelServerYearReview": "ملخص عام الخادم ({0})",
"LabelSetEbookAsPrimary": "تعيين كرئيسي",
"LabelSetEbookAsSupplementary": "تعيين كتكميلي",
"LabelSettingsAllowIframe": "السماح بالتضمين في إطار iframe",
"LabelSettingsAudiobooksOnly": "كتب صوتية فقط",
"LabelSettingsAudiobooksOnlyHelp": "سيؤدي تمكين هذا الإعداد إلى تجاهل ملفات الكتب الإلكترونية ما لم تكن داخل مجلد كتاب صوتي، وفي هذه الحالة سيتم تعيينها ككتب إلكترونية تكميلية",
"LabelSettingsBookshelfViewHelp": "تصميم يحاكي الواقع مع رفوف خشبية",
"LabelSettingsChromecastSupport": "دعم Chromecast",
"LabelSettingsDateFormat": "تنسيق التاريخ",
"LabelSettingsEnableWatcher": "فحص المكتبات تلقائيًا بحثًا عن تغييرات",
"LabelSettingsEnableWatcherForLibrary": "فحص المكتبة تلقائيًا بحثًا عن تغييرات",
"LabelSettingsEnableWatcherHelp": "يمكّن الإضافة/التحديث التلقائي للعناصر عند اكتشاف تغييرات في الملفات. *يتطلب إعادة تشغيل الخادم",
"LabelSettingsEpubsAllowScriptedContent": "السماح بالمحتوى النصي في ملفات epub",
"LabelSettingsEpubsAllowScriptedContentHelp": "السماح لملفات epub بتنفيذ النصوص البرمجية. يوصى بإبقاء هذا الإعداد معطلاً ما لم تثق في مصدر ملفات epub.",
"LabelSettingsExperimentalFeatures": "ميزات تجريبية",
"LabelSettingsExperimentalFeaturesHelp": "ميزات قيد التطوير يمكنها استخدام ملاحظاتك والمساعدة في اختبارها. انقر لفتح مناقشة على GitHub.",
"LabelSettingsFindCovers": "البحث عن الأغلفة",
"LabelSettingsFindCoversHelp": "إذا لم يكن لدى كتابك الصوتي غلاف مضمن أو صورة غلاف داخل المجلد، فسيحاول الماسح الضوئي العثور على غلاف.&lt;br> ملاحظة: سيؤدي هذا إلى إطالة وقت الفحص",
"LabelSettingsHideSingleBookSeries": "إخفاء السلاسل ذات الكتاب الواحد",
"LabelSettingsHideSingleBookSeriesHelp": "سيتم إخفاء السلاسل التي تحتوي على كتاب واحد من صفحة السلاسل وأرفف الصفحة الرئيسية.",
"LabelSettingsHomePageBookshelfView": "استخدام عرض الرفوف في الصفحة الرئيسية",
"LabelSettingsLibraryBookshelfView": "استخدام عرض الرفوف في المكتبة",
"LabelSettingsLibraryMarkAsFinishedPercentComplete": "النسبة المئوية المكتملة أكبر من",
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "الوقت المتبقي أقل من (ثواني)",
"LabelSettingsLibraryMarkAsFinishedWhen": "تعليم عنصر الوسائط على أنه منتهٍ عند",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "تخطي الكتب السابقة في \"متابعة السلسلة\"",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "يعرض رف \"متابعة السلسلة\" في الصفحة الرئيسية أول كتاب لم يبدأ في السلاسل التي تحتوي على كتاب واحد على الأقل منتهي ولا يوجد كتب قيد التقدم. سيؤدي تمكين هذا الإعداد إلى متابعة السلاسل من أبعد كتاب مكتمل بدلاً من أول كتاب لم يبدأ.",
"LabelSettingsParseSubtitles": "تحليل الترجمة",
"LabelSettingsParseSubtitlesHelp": "استخراج الترجمة من أسماء مجلدات الكتب الصوتية.&lt;br>يجب فصل الترجمة بـ \" - \"&lt;br>مثال: \"عنوان الكتاب - ترجمة هنا\" تحتوي على الترجمة \"ترجمة هنا\"",
"LabelSettingsPreferMatchedMetadata": "تفضيل البيانات الوصفية المطابقة",
"LabelSettingsPreferMatchedMetadataHelp": "ستتجاوز البيانات المطابقة تفاصيل العنصر عند استخدام المطابقة السريعة. بشكل افتراضي، ستملأ المطابقة السريعة التفاصيل المفقودة فقط.",
"LabelSettingsSkipMatchingBooksWithASIN": "تخطي مطابقة الكتب التي لديها ASIN بالفعل",
"LabelSettingsSkipMatchingBooksWithISBN": "تخطي مطابقة الكتب التي لديها ISBN بالفعل",
"LabelSettingsSortingIgnorePrefixes": "تجاهل البادئات عند الفرز",
"LabelSettingsSortingIgnorePrefixesHelp": "مثال: بالنسبة للبادئة \"the\"، سيتم فرز عنوان الكتاب \"The Book Title\" كـ \"Book Title, The\"",
"LabelSettingsSquareBookCovers": "استخدام أغلفة كتب مربعة",
"LabelSettingsSquareBookCoversHelp": "تفضيل استخدام الأغلفة المربعة على أغلفة الكتب القياسية بنسبة 1.6:1",
"LabelSettingsStoreCoversWithItem": "تخزين الأغلفة مع العنصر",
"LabelSettingsStoreCoversWithItemHelp": "بشكل افتراضي، يتم تخزين الأغلفة في /metadata/items، وسيؤدي تمكين هذا الإعداد إلى تخزين الأغلفة في مجلد عنصر المكتبة الخاص بك. سيتم الاحتفاظ بملف واحد فقط باسم \"cover\"",
"LabelSettingsStoreMetadataWithItem": "تخزين البيانات الوصفية مع العنصر",
"LabelSettingsStoreMetadataWithItemHelp": "بشكل افتراضي، يتم تخزين ملفات البيانات الوصفية في /metadata/items، وسيؤدي تمكين هذا الإعداد إلى تخزين ملفات البيانات الوصفية في مجلدات عناصر المكتبة الخاصة بك",
"LabelSettingsTimeFormat": "تنسيق الوقت",
"LabelShare": "مشاركة",
"LabelShareDownloadableHelp": "يسمح للمستخدمين الذين لديهم رابط المشاركة بتنزيل ملف مضغوط لعنصر المكتبة.",
"LabelShareOpen": "فتح المشاركة",
"LabelShareURL": "رابط المشاركة",
"LabelShowAll": "إظهار الكل",
"LabelShowSeconds": "إظهار الثواني",
"LabelShowSubtitles": "إظهار الترجمة",
"LabelSize": "الحجم",
"LabelSleepTimer": "مؤقت النوم",
"LabelSlug": "اسم تعريفي سهل القراءة",
"LabelSortAscending": "تصاعدي",
"LabelSortDescending": "تنازلي",
"LabelSortPubDate": "فرز حسب تاريخ النشر",
"LabelStart": "ابدأ",
"LabelStartTime": "وقت البدء",
"LabelStarted": "بدأ",
"LabelStartedAt": "بدأ في",
"LabelStatsAudioTracks": "مسارات الصوت",
"LabelStatsAuthors": "المؤلفون",
"LabelStatsBestDay": "أفضل يوم",
"LabelStatsDailyAverage": "المتوسط اليومي",
"LabelStatsDays": "أيام",
"LabelStatsDaysListened": "أيام الاستماع",
"LabelStatsHours": "ساعات",
"LabelStatsInARow": "على التوالي",
"LabelStatsItemsFinished": "العناصر المنتهية",
"LabelStatsItemsInLibrary": "العناصر في المكتبة",
"LabelStatsMinutes": "دقائق",
"LabelStatsMinutesListening": "دقائق الاستماع",
"LabelStatsOverallDays": "إجمالي الأيام",
"LabelStatsOverallHours": "إجمالي الساعات",
"LabelStatsWeekListening": "استماع هذا الأسبوع",
"LabelSubtitle": "عنوان فرعي / ترجمة",
"LabelSupportedFileTypes": "أنواع الملفات المدعومة",
"LabelTag": "علامة",
"LabelTags": "علامات",
"LabelTagsAccessibleToUser": "العلامات المتاحة للمستخدم",
"LabelTagsNotAccessibleToUser": "العلامات غير المتاحة للمستخدم",
"LabelTasks": "المهام قيد التشغيل",
"LabelTextEditorBulletedList": "قائمة نقطية",
"LabelTextEditorLink": "رابط",
"LabelTextEditorNumberedList": "قائمة مرقمة",
"LabelTextEditorUnlink": "إزالة الرابط",
"LabelTheme": "النمط",
"LabelThemeDark": "غامق",
"LabelThemeLight": "فاتح",
"LabelTimeBase": "قاعدة الوقت",
"LabelTimeDurationXHours": "{0} ساعات",
"LabelTimeDurationXMinutes": "{0} دقائق",
"LabelTimeDurationXSeconds": "{0} ثواني",
"LabelTimeInMinutes": "الوقت بالدقائق",
"LabelTimeLeft": "باقي {0}",
"LabelTimeListened": "الوقت المستمع إليه",
"LabelTimeListenedToday": "الوقت المستمع إليه اليوم",
"LabelTimeRemaining": "{0} متبقية",
"LabelTimeToShift": "الوقت المراد إزاحته بالثواني",
"LabelTitle": "عنوان",
"LabelToolsEmbedMetadata": "تضمين البيانات الوصفية",
"LabelToolsEmbedMetadataDescription": "تضمين البيانات الوصفية في ملفات الصوت بما في ذلك صورة الغلاف والفصول.",
"LabelToolsM4bEncoder": "ترميز M4B",
"LabelToolsMakeM4b": "إنشاء ملف كتاب صوتي M4B",
"LabelToolsMakeM4bDescription": "إنشاء ملف كتاب صوتي .M4B مع بيانات وصفية مضمنة وصورة غلاف وفصول.",
"LabelToolsSplitM4b": "تقسيم M4B إلى ملفات MP3",
"LabelToolsSplitM4bDescription": "إنشاء ملفات MP3 من ملف M4B مقسم حسب الفصول مع بيانات وصفية مضمنة وصورة غلاف وفصول.",
"LabelTotalDuration": "المدة الكلية",
"LabelTotalTimeListened": "إجمالي وقت الاستماع",
"LabelTrackFromFilename": "المسار من اسم الملف",
"LabelTrackFromMetadata": "المسار من البيانات الوصفية",
"LabelTracks": "المسارات",
"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": "قم بتخويل عناوين URL هذه في موفر OAuth الخاص بك للسماح بإعادة التوجيه إلى تطبيق الويب بعد تسجيل الدخول:",
"LabelWebRedirectURLsSubfolder": "مجلد فرعي لعناوين URL لإعادة التوجيه",
"LabelWeekdaysToRun": "أيام الأسبوع المراد التشغيل فيها",
"LabelXBooks": "{0} كتب",
"LabelXItems": "{0} عناصر",
"LabelYearReviewHide": "إخفاء ملخص العام",
"LabelYearReviewShow": "عرض ملخص العام",
"LabelYourAudiobookDuration": "مدة كتابك الصوتي",
"LabelYourBookmarks": "علاماتك المرجعية",
"LabelYourPlaylists": "قوائم التشغيل الخاصة بك",
"LabelYourProgress": "تقدمك",
"MessageAddToPlayerQueue": "إضافة إلى قائمة انتظار المشغل",
"MessageAppriseDescription": "لاستخدام هذه الميزة، ستحتاج إلى تشغيل مثيل <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> أو واجهة برمجة تطبيقات تتعامل مع نفس الطلبات. <br />يجب أن يكون عنوان URL الخاص بـ Apprise API هو مسار URL الكامل لإرسال الإشعار، على سبيل المثال، إذا كان مثيل واجهة برمجة التطبيقات الخاصة بك يعمل على <code>http://192.168.1.1:8337</code>، فستضع <code>http://192.168.1.1:8337/notify</code>.",
"MessageAsinCheck": "تأكد من أنك تستخدم ASIN من منطقة Audible الصحيحة، وليس Amazon.",
"MessageAuthenticationOIDCChangesRestart": "أعد تشغيل الخادم بعد الحفظ لتطبيق تغييرات OIDC.",
"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": "ليس لديك أي مسلسلات",
"MessageChapterEndIsAfter": "نهاية الفصل بعد نهاية كتابك الصوتي",
"MessageChapterErrorFirstNotZero": "يجب أن يبدأ الفصل الأول عند 0",
"MessageChapterErrorStartGteDuration": "يجب أن يكون وقت البدء غير الصالح أقل من مدة الكتاب الصوتي",
"MessageChapterErrorStartLtPrev": "يجب أن يكون وقت البدء غير الصالح أكبر من أو يساوي وقت بدء الفصل السابق",
"MessageChapterStartIsAfter": "بداية الفصل بعد نهاية كتابك الصوتي",
"MessageChaptersNotFound": "لم يتم العثور على فصول",
"MessageCheckingCron": "جارٍ فحص cron...",
"MessageConfirmCloseFeed": "هل أنت متأكد أنك تريد إغلاق هذه التغذية؟",
"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}\"؟",
"MessageConfirmRemoveCollection": "هل أنت متأكد أنك تريد إزالة المجموعة \"{0}\"؟",
"MessageConfirmRemoveEpisode": "هل أنت متأكد أنك تريد إزالة الحلقة \"{0}\"؟",
"MessageConfirmRemoveEpisodes": "هل أنت متأكد أنك تريد إزالة {0} حلقة؟",
"MessageConfirmRemoveListeningSessions": "هل أنت متأكد أنك تريد إزالة {0} جلسة استماع؟",
"MessageConfirmRemoveMetadataFiles": "هل أنت متأكد أنك تريد إزالة جميع ملفات البيانات الوصفية {0} الموجودة في مجلدات عناصر مكتبتك؟",
"MessageConfirmRemoveNarrator": "هل أنت متأكد أنك تريد إزالة الراوي \"{0}\"؟",
"MessageConfirmRemovePlaylist": "هل أنت متأكد أنك تريد إزالة قائمة التشغيل الخاصة بك \"{0}\"؟",
"MessageConfirmRenameGenre": "هل أنت متأكد أنك تريد إعادة تسمية النوع \"{0}\" إلى \"{1}\" لجميع العناصر؟",
"MessageConfirmRenameGenreMergeNote": "ملاحظة: هذا النوع موجود بالفعل لذا سيتم دمجهما.",
"MessageConfirmRenameGenreWarning": "تحذير! يوجد نوع مشابه بحالة أحرف مختلفة بالفعل \"{0}\".",
"MessageConfirmRenameTag": "هل أنت متأكد أنك تريد إعادة تسمية العلامة \"{0}\" إلى \"{1}\" لجميع العناصر؟",
"MessageConfirmRenameTagMergeNote": "ملاحظة: هذه العلامة موجودة بالفعل لذا سيتم دمجهما.",
"MessageConfirmRenameTagWarning": "تحذير! توجد علامة مشابهة بحالة أحرف مختلفة بالفعل \"{0}\".",
"MessageConfirmResetProgress": "هل أنت متأكد أنك تريد إعادة تعيين تقدمك؟",
"MessageConfirmSendEbookToDevice": "هل أنت متأكد أنك تريد إرسال الكتاب الإلكتروني \"{1}\" ({0}) إلى الجهاز \"{2}\"؟",
"MessageConfirmUnlinkOpenId": "هل أنت متأكد أنك تريد فصل هذا المستخدم عن OpenID؟",
"MessageDaysListenedInTheLastYear": "تم الاستماع لمدة {0} يومًا في العام الماضي",
"MessageDownloadingEpisode": "جاري تنزيل الحلقة",
"MessageDragFilesIntoTrackOrder": "اسحب الملفات إلى ترتيب المسارات الصحيح",
"MessageEmbedFailed": "فشل التضمين!",
"MessageEmbedFinished": "تم الانتهاء من التضمين!",
"MessageEmbedQueue": "تمت إضافته إلى قائمة انتظار تضمين البيانات الوصفية ({0} في قائمة الانتظار)",
"MessageEpisodesQueuedForDownload": "تمت إضافة {0} حلقة (حلقات) إلى قائمة انتظار التنزيل",
"MessageEreaderDevices": "لضمان تسليم الكتب الإلكترونية، قد تحتاج إلى إضافة عنوان البريد الإلكتروني أعلاه كمرسل صالح لكل جهاز مدرج أدناه.",
"MessageFeedURLWillBe": "سيكون رابط التغذية هو {0}",
"MessageFetching": "جاري الجلب...",
"MessageForceReScanDescription": "سيقوم بفحص جميع الملفات مرة أخرى كفحص جديد. سيتم فحص علامات ID3 لملفات الصوت وملفات OPF والملفات النصية كأنها جديدة.",
"MessageImportantNotice": "إشعار هام!",
"MessageInsertChapterBelow": "إدراج فصل أدناه",
"MessageInvalidAsin": "ASIN غير صالح",
"MessageItemsSelected": "تم تحديد {0} عنصرًا",
"MessageItemsUpdated": "تم تحديث {0} عنصرًا",
"MessageJoinUsOn": "انضم إلينا على",
"MessageLoading": "جاري التحميل...",
"MessageLoadingFolders": "جاري تحميل المجلدات...",
"MessageLogsDescription": "يتم تخزين السجلات في <code>/metadata/logs</code> كملفات JSON. يتم تخزين سجلات الأعطال في <code>/metadata/logs/crash_logs.txt</code>.",
"MessageM4BFailed": "فشل M4B!",
"MessageM4BFinished": "تم الانتهاء من M4B!",
"MessageMapChapterTitles": "ربط عناوين الفصول بفصول كتابك الصوتي الحالي دون تعديل الطوابع الزمنية",
"MessageMarkAllEpisodesFinished": "تعليم جميع الحلقات على أنها منتهية",
"MessageMarkAllEpisodesNotFinished": "تعليم جميع الحلقات على أنها غير منتهية",
"MessageMarkAsFinished": "وضع علامة \"تم الإنتهاء\"",
"MessageMarkAsNotFinished": "وضع علامة \"غير منته\"",
"MessageMatchBooksDescription": "سيحاول مطابقة الكتب في المكتبة مع كتاب من مزود البحث المحدد وملء التفاصيل الفارغة وصورة الغلاف. لا يستبدل التفاصيل الموجودة.",
"MessageNoAudioTracks": "لا توجد مسارات صوتية",
"MessageNoAuthors": "لا يوجد مؤلفون",
"MessageNoBackups": "لا توجد نسخ احتياطية",
"MessageNoBookmarks": "لا توجد علامات مرجعية",
"MessageNoChapters": "لا توجد فصول",
"MessageNoCollections": "لا توجد مجموعات",
"MessageNoCoversFound": "لم يتم العثور على أغلفة",
"MessageNoDescription": "لا يوجد وصف",
"MessageNoDevices": "لا توجد أجهزة",
"MessageNoDownloadsInProgress": "لا توجد تنزيلات قيد التقدم حاليًا",
"MessageNoDownloadsQueued": "لا توجد تنزيلات في قائمة الانتظار",
"MessageNoEpisodeMatchesFound": "لم يتم العثور على أي تطابقات للحلقات",
"MessageNoEpisodes": "لا توجد حلقات",
"MessageNoFoldersAvailable": "لا توجد مجلدات متاحة",
"MessageNoGenres": "لا توجد تصانيف",
"MessageNoIssues": "لا توجد مشاكل",
"MessageNoItems": "لا توجد عناصر",
"MessageNoItemsFound": "لم يتم العثور على عناصر",
"MessageNoListeningSessions": "لا توجد جلسات استماع",
"MessageNoLogs": "لا توجد سجلات",
"MessageNoMediaProgress": "لا يوجد تقدم للوسائط",
"MessageNoNotifications": "لا توجد إشعارات",
"MessageNoPodcastFeed": "بودكاست غير صالح: لا يوجد تغذية",
"MessageNoPodcastsFound": "لم يتم العثور على أي بودكاست",
"MessageNoResults": "لا توجد نتائج",
"MessageNoSearchResultsFor": "لا توجد نتائج بحث عن \"{0}\"",
"MessageNoSeries": "لا توجد مسلسلات",
"MessageNoTags": "لا توجد علامات",
"MessageNoTasksRunning": "لا توجد مهام قيد التشغيل",
"MessageNoUpdatesWereNecessary": "لا حاجة لأي تحديثات",
"MessageNoUserPlaylists": "ليس لديك أي قوائم تشغيل",
"MessageNoUserPlaylistsHelp": "قوائم التشغيل خاصة. لا يمكن إلا للمستخدم الذي ينشئها رؤيتها.",
"MessageNotYetImplemented": "لم يتم تنفيذه بعد",
"MessageOpmlPreviewNote": "ملاحظة: هذه معاينة لملف OPML الذي تم تحليله. سيتم أخذ عنوان البودكاست الفعلي من خلاصة RSS.",
"MessageOr": "أو",
"MessagePauseChapter": "إيقاف تشغيل الفصل مؤقتًا",
"MessagePlayChapter": "الاستماع إلى بداية الفصل",
"MessagePlaylistCreateFromCollection": "إنشاء قائمة تشغيل من المجموعة",
"MessagePleaseWait": "الرجاء الانتظار...",
"MessagePodcastHasNoRSSFeedForMatching": "لا يحتوي البودكاست على عنوان URL لخلاصة RSS لاستخدامه في المطابقة",
"MessagePodcastSearchField": "أدخل مصطلح البحث أو عنوان URL الخاص بتغذية RSS",
"MessageQuickEmbedInProgress": "التضمين السريع قيد التقدم",
"MessageQuickEmbedQueue": "تمت إضافته إلى قائمة انتظار التضمين السريع ({0} في قائمة الانتظار)",
"MessageQuickMatchAllEpisodes": "مطابقة سريعة لجميع الحلقات",
"MessageQuickMatchDescription": "املأ تفاصيل العنصر الفارغة والغلاف بأول نتيجة مطابقة من '{0}'. لا يستبدل التفاصيل إلا إذا تم تمكين إعداد الخادم 'تفضيل البيانات الوصفية المطابقة'.",
"MessageRemoveChapter": "إزالة الفصل",
"MessageRemoveEpisodes": "إزالة {0} حلقة (حلقات)",
"MessageRemoveFromPlayerQueue": "إزالة من قائمة انتظار المشغل",
"MessageRemoveUserWarning": "هل أنت متأكد أنك تريد حذف المستخدم \"{0}\" نهائيًا؟",
"MessageReportBugsAndContribute": "أبلغ عن الأخطاء، واطلب الميزات، وساهم في",
"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": "سيكون رابط المشاركة هو <strong>{0}</strong>",
"MessageStartPlaybackAtTime": "هل تريد بدء التشغيل لـ \"{0}\" في الساعة {1}؟",
"MessageTaskAudioFileNotWritable": "الملف الصوتي \"{0}\" غير قابل للكتابة",
"MessageTaskCanceledByUser": "تم إلغاء المهمة بواسطة المستخدم",
"MessageTaskDownloadingEpisodeDescription": "جاري تنزيل الحلقة \"{0}\"",
"MessageTaskEmbeddingMetadata": "جاري تضمين البيانات الوصفية",
"MessageTaskEmbeddingMetadataDescription": "جاري تضمين البيانات الوصفية في الكتاب الصوتي \"{0}\"",
"MessageTaskEncodingM4b": "جاري ترميز M4B",
"MessageTaskEncodingM4bDescription": "جاري ترميز الكتاب الصوتي \"{0}\" في ملف m4b واحد",
"MessageTaskFailed": "فشل",
"MessageTaskFailedToBackupAudioFile": "فشل في نسخ الملف الصوتي \"{0}\" احتياطيًا",
"MessageTaskFailedToCreateCacheDirectory": "فشل في إنشاء دليل ذاكرة التخزين المؤقت",
"MessageTaskFailedToEmbedMetadataInFile": "فشل في تضمين البيانات الوصفية في الملف \"{0}\"",
"MessageTaskFailedToMergeAudioFiles": "فشل في دمج الملفات الصوتية",
"MessageTaskFailedToMoveM4bFile": "فشل في نقل ملف m4b",
"MessageTaskFailedToWriteMetadataFile": "فشل في كتابة ملف البيانات الوصفية",
"MessageTaskMatchingBooksInLibrary": "جارٍ مطابقة الكتب في المكتبة \"{0}\"",
"MessageTaskNoFilesToScan": "لا توجد ملفات للفحص",
"MessageTaskOpmlImport": "استيراد OPML",
"MessageTaskOpmlImportDescription": "جارٍ إنشاء بودكاست من {0} خلاصة RSS",
"MessageTaskOpmlImportFeed": "استيراد تغذية OPML",
"MessageTaskOpmlImportFeedDescription": "جارٍ استيراد خلاصة RSS \"{0}\"",
"MessageTaskOpmlImportFeedFailed": "فشل في الحصول على تغذية البودكاست",
"MessageTaskOpmlImportFeedPodcastDescription": "جارٍ إنشاء بودكاست \"{0}\"",
"MessageTaskOpmlImportFeedPodcastExists": "البودكاست موجود بالفعل في المسار",
"MessageTaskOpmlImportFeedPodcastFailed": "فشل في إنشاء البودكاست",
"MessageTaskOpmlImportFinished": "تمت إضافة {0} بودكاست",
"MessageTaskOpmlParseFailed": "فشل في تحليل ملف OPML",
"MessageTaskOpmlParseFastFail": "ملف OPML غير صالح، لم يتم العثور على علامة <opml> أو لم يتم العثور على علامة <outline>",
"MessageTaskOpmlParseNoneFound": "لم يتم العثور على أي خلاصات في ملف OPML",
"MessageTaskScanItemsAdded": "تمت إضافة {0}",
"MessageTaskScanItemsMissing": "{0} مفقود",
"MessageTaskScanItemsUpdated": "{0} تم تحديثه",
"MessageTaskScanNoChangesNeeded": "لا توجد تغييرات مطلوبة",
"MessageTaskScanningFileChanges": "جاري فحص تغييرات الملفات في \"{0}\"",
"MessageTaskScanningLibrary": "جاري فحص مكتبة \"{0}\"",
"MessageTaskTargetDirectoryNotWritable": "الدليل الهدف غير قابل للكتابة",
"MessageThinking": "جاري التفكير...",
"MessageUploaderItemFailed": "فشل الرفع",
"MessageUploaderItemSuccess": "تم الرفع بنجاح!",
"MessageUploading": "جاري الرفع...",
"MessageValidCronExpression": "تعبير Cron صالح",
"MessageWatcherIsDisabledGlobally": "المراقب معطل عالميًا في إعدادات الخادم",
"MessageXLibraryIsEmpty": "مكتبة {0} فارغة!",
"MessageYourAudiobookDurationIsLonger": "مدة كتابك الصوتي أطول من المدة التي تم العثور عليها",
"MessageYourAudiobookDurationIsShorter": "مدة كتابك الصوتي أقصر من المدة التي تم العثور عليها",
"NoteChangeRootPassword": "مستخدم الجذر هو المستخدم الوحيد الذي يمكن أن يكون لديه كلمة مرور فارغة",
"NoteChapterEditorTimes": "ملاحظة: يجب أن يظل وقت بدء الفصل الأول عند 0:00 ولا يمكن أن يتجاوز وقت بدء الفصل الأخير مدة هذا الكتاب الصوتي.",
"NoteFolderPicker": "ملاحظة: لن يتم عرض المجلدات التي تم تعيينها بالفعل",
"NoteRSSFeedPodcastAppsHttps": "تحذير: تتطلب معظم تطبيقات البث الصوتي أن يكون عنوان URL الخاص بتغذية RSS يستخدم HTTPS",
"NoteRSSFeedPodcastAppsPubDate": "تحذير: حلقة واحدة أو أكثر من حلقاتك ليس لها تاريخ نشر. بعض تطبيقات البودكاست تتطلب هذا.",
"NoteUploaderFoldersWithMediaFiles": "سيتم التعامل مع المجلدات التي تحتوي على ملفات وسائط كعناصر مكتبة منفصلة.",
"NoteUploaderOnlyAudioFiles": "في حالة رفع ملفات صوتية فقط، سيتم التعامل مع كل ملف صوتي ككتاب صوتي منفصل.",
"NoteUploaderUnsupportedFiles": "يتم تجاهل الملفات غير المدعومة. عند اختيار مجلد أو إسقاطه، يتم تجاهل الملفات الأخرى التي ليست في مجلد عنصر.",
"NotificationOnBackupCompletedDescription": "يتم تشغيله عند اكتمال النسخ الاحتياطي",
"NotificationOnBackupFailedDescription": "يتم تشغيله عند فشل النسخ الاحتياطي",
"NotificationOnEpisodeDownloadedDescription": "يتم تشغيله عند تنزيل حلقة بودكاست تلقائيًا",
"NotificationOnRSSFeedDisabledDescription": "يتم تشغيله عندما يتم تعطيل تنزيلات الحلقة التلقائية بسبب الكثير من المحاولات الفاشلة",
"NotificationOnRSSFeedFailedDescription": "يتم تشغيله عند فشل طلب تغذية RSS في تنزيل حلقة تلقائية",
"NotificationOnTestDescription": "حدث لاختبار نظام الإشعارات",
"PlaceholderNewCollection": "اسم المجموعة الجديدة",
"PlaceholderNewFolderPath": "مسار المجلد الجديد",
"PlaceholderNewPlaylist": "اسم قائمة التشغيل الجديدة",
"PlaceholderSearch": "بحث..",
"PlaceholderSearchEpisode": "بحث عن حلقة..",
"StatsAuthorsAdded": "تمت إضافة مؤلفين",
"StatsBooksAdded": "تمت إضافة كتب",
"StatsBooksAdditional": "تتضمن بعض الإضافات…",
"StatsBooksFinished": "كتب تم الانتهاء منها",
"StatsBooksFinishedThisYear": "بعض الكتب التي تم الانتهاء منها هذا العام…",
"StatsBooksListenedTo": "كتب تم الاستماع إليها",
"StatsCollectionGrewTo": "نما مجموعتك من الكتب لتصبح…",
"StatsSessions": "جلسات",
"StatsSpentListening": "تم قضاء وقت في الاستماع",
"StatsTopAuthor": "أفضل مؤلف",
"StatsTopAuthors": "أفضل المؤلفين",
"StatsTopGenre": "أفضل تصنيف",
"StatsTopGenres": "أفضل التصنيفات",
"StatsTopMonth": "أفضل شهر",
"StatsTopNarrator": "أفضل راوي",
"StatsTopNarrators": "أفضل الرواة",
"StatsTotalDuration": "بإجمالي مدة…",
"StatsYearInReview": "ملخص العام",
"ToastAccountUpdateSuccess": "تم تحديث الحساب",
"ToastAppriseUrlRequired": "يجب إدخال عنوان URL لـ Apprise",
"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": "تمت إزالة الإشارة المرجعية",
"ToastCachePurgeFailed": "فشل مسح ذاكرة التخزين المؤقت",
"ToastCachePurgeSuccess": "تم مسح ذاكرة التخزين المؤقت بنجاح",
"ToastChaptersHaveErrors": "الفصول تحتوي على أخطاء",
"ToastChaptersInvalidShiftAmountLast": "مقدار إزاحة غير صالح. سيمتد وقت بدء الفصل الأخير إلى ما بعد مدة هذا الكتاب الصوتي.",
"ToastChaptersInvalidShiftAmountStart": "مقدار إزاحة غير صالح. سيكون للفصل الأول طول صفري أو سالب وسيتم الكتابة فوقه بواسطة الفصل الثاني. قم بزيادة مدة بدء الفصل الثاني.",
"ToastChaptersMustHaveTitles": "يجب أن تحتوي الفصول على عناوين",
"ToastChaptersRemoved": "تمت إزالة الفصول",
"ToastChaptersUpdated": "تم تحديث الفصول",
"ToastCollectionItemsAddFailed": "فشل إضافة عنصر (عناصر) إلى المجموعة",
"ToastCollectionRemoveSuccess": "تمت إزالة المجموعة",
"ToastCollectionUpdateSuccess": "تم تحديث المجموعة",
"ToastCoverUpdateFailed": "فشل تحديث الغلاف",
"ToastDateTimeInvalidOrIncomplete": "التاريخ والوقت غير صالحين أو غير مكتملين",
"ToastDeleteFileFailed": "فشل حذف الملف",
"ToastDeleteFileSuccess": "تم حذف الملف",
"ToastDeviceAddFailed": "فشل إضافة الجهاز",
"ToastDeviceNameAlreadyExists": "جهاز قارئ إلكتروني بهذا الاسم موجود بالفعل",
"ToastDeviceTestEmailFailed": "فشل إرسال البريد الإلكتروني التجريبي",
"ToastDeviceTestEmailSuccess": "تم إرسال البريد الإلكتروني التجريبي",
"ToastEmailSettingsUpdateSuccess": "تم تحديث إعدادات البريد الإلكتروني",
"ToastEncodeCancelFailed": "فشل إلغاء الترميز",
"ToastEncodeCancelSucces": "تم إلغاء الترميز",
"ToastEpisodeDownloadQueueClearFailed": "فشل مسح قائمة انتظار تنزيل الحلقات",
"ToastEpisodeDownloadQueueClearSuccess": "تم مسح قائمة انتظار تنزيل الحلقات",
"ToastEpisodeUpdateSuccess": "تم تحديث {0} حلقة",
"ToastErrorCannotShare": "لا يمكن المشاركة محليًا على هذا الجهاز",
"ToastFailedToLoadData": "فشل تحميل البيانات",
"ToastFailedToMatch": "فشل التطابق",
"ToastFailedToShare": "فشل المشاركة",
"ToastFailedToUpdate": "فشل التحديث",
"ToastInvalidImageUrl": "رابط صورة غير صالح",
"ToastInvalidMaxEpisodesToDownload": "الحد الأقصى غير صالح لعدد الحلقات المراد تنزيلها",
"ToastInvalidUrl": "رابط غير صالح",
"ToastItemCoverUpdateSuccess": "تم تحديث غلاف العنصر",
"ToastItemDeletedFailed": "فشل حذف العنصر",
"ToastItemDeletedSuccess": "تم حذف العنصر",
"ToastItemDetailsUpdateSuccess": "تم تحديث تفاصيل العنصر",
"ToastItemMarkedAsFinishedFailed": "فشل وضع علامة \"مكتمل\"",
"ToastItemMarkedAsFinishedSuccess": "تم وضع علامة \"تم الانتهاء\" على العنصر",
"ToastItemMarkedAsNotFinishedFailed": "فشل وضع علامة \"غير مكتمل\"",
"ToastItemMarkedAsNotFinishedSuccess": "تم وضع علامة \"غير مكتمل\" على العنصر",
"ToastItemUpdateSuccess": "تم تحديث العنصر",
"ToastLibraryCreateFailed": "فشل إنشاء المكتبة",
"ToastLibraryCreateSuccess": "تم إنشاء المكتبة \"{0}\"",
"ToastLibraryDeleteFailed": "فشل حذف المكتبة",
"ToastLibraryDeleteSuccess": "تم حذف المكتبة",
"ToastLibraryScanFailedToStart": "فشل بدء الفحص",
"ToastLibraryScanStarted": "بدأ فحص المكتبة",
"ToastLibraryUpdateSuccess": "تم تحديث المكتبة \"{0}\"",
"ToastMatchAllAuthorsFailed": "فشل مطابقة جميع المؤلفين",
"ToastMetadataFilesRemovedError": "حدث خطأ أثناء إزالة ملفات البيانات الوصفية. {0}",
"ToastMetadataFilesRemovedNoneFound": "لا توجد بيانات وصفية. تم العثور على {0} ملف في المكتبة",
"ToastMetadataFilesRemovedNoneRemoved": "لا توجد بيانات وصفية. تمت إزالة {0} ملفًا",
"ToastMetadataFilesRemovedSuccess": "{0} بيانات وصفية. تم إزالة {1} ملف",
"ToastMustHaveAtLeastOnePath": "يجب أن يكون هناك مسار واحد على الأقل",
"ToastNameEmailRequired": "الاسم والبريد الإلكتروني مطلوبان",
"ToastNameRequired": "الاسم مطلوب",
"ToastNewEpisodesFound": "تم العثور على {0} حلقة جديدة",
"ToastNewUserCreatedFailed": "فشل إنشاء الحساب: \"{0}\"",
"ToastNewUserCreatedSuccess": "تم إنشاء حساب جديد",
"ToastNewUserLibraryError": "يجب تحديد مكتبة واحدة على الأقل",
"ToastNewUserPasswordError": "يجب أن يكون لديك كلمة مرور، يمكن لمستخدم الجذر فقط أن يكون لديه كلمة مرور فارغة",
"ToastNewUserTagError": "يجب تحديد علامة واحدة على الأقل",
"ToastNewUserUsernameError": "أدخل اسم مستخدم",
"ToastNoNewEpisodesFound": "لم يتم العثور على حلقات جديدة",
"ToastNoRSSFeed": "لا يحتوي البودكاست على خلاصة RSS",
"ToastNoUpdatesNecessary": "لا توجد تحديثات ضرورية",
"ToastNotificationCreateFailed": "فشل إنشاء الإشعار",
"ToastNotificationDeleteFailed": "فشل حذف الإشعار",
"ToastNotificationFailedMaximum": "يجب أن يكون الحد الأقصى للمحاولات الفاشلة >= 0",
"ToastNotificationQueueMaximum": "يجب أن يكون الحد الأقصى لقائمة انتظار الإشعارات >= 0",
"ToastNotificationSettingsUpdateSuccess": "تم تحديث إعدادات الإشعارات",
"ToastNotificationTestTriggerFailed": "فشل تشغيل إشعار الاختبار",
"ToastNotificationTestTriggerSuccess": "تم تشغيل إشعار الاختبار",
"ToastNotificationUpdateSuccess": "تم تحديث الإشعار",
"ToastPlaylistCreateFailed": "فشل إنشاء قائمة التشغيل",
"ToastPlaylistCreateSuccess": "تم إنشاء قائمة التشغيل",
"ToastPlaylistRemoveSuccess": "تمت إزالة قائمة التشغيل",
"ToastPlaylistUpdateSuccess": "تم تحديث قائمة التشغيل",
"ToastPodcastCreateFailed": "فشل إنشاء البودكاست",
"ToastPodcastCreateSuccess": "تم إنشاء البودكاست بنجاح",
"ToastPodcastGetFeedFailed": "فشل في الحصول على تغذية البودكاست",
"ToastPodcastNoEpisodesInFeed": "لم يتم العثور على حلقات في خلاصة RSS",
"ToastPodcastNoRssFeed": "لا يحتوي البودكاست على خلاصة RSS",
"ToastProgressIsNotBeingSynced": "لا تتم مزامنة التقدم، أعد تشغيل التشغيل",
"ToastProviderCreatedFailed": "فشل إضافة المزود",
"ToastProviderCreatedSuccess": "تمت إضافة مزود جديد",
"ToastProviderNameAndUrlRequired": "الاسم والرابط مطلوبان",
"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": "انتهى مؤقت النوم... ششششش",
"ToastSlugMustChange": "يحتوي الاسم التعريفي على أحرف غير صالحة",
"ToastSlugRequired": "الاسم التعريفي مطلوب",
"ToastSocketConnected": "تم الاتصال بالمقبس",
"ToastSocketDisconnected": "تم قطع الاتصال بالمقبس",
"ToastSocketFailedToConnect": "فشل الاتصال بالمقبس",
"ToastSortingPrefixesEmptyError": "يجب أن يكون هناك بادئة فرز واحدة على الأقل",
"ToastSortingPrefixesUpdateSuccess": "تم تحديث بادئات الفرز ({0} عنصرًا)",
"ToastTitleRequired": "العنوان مطلوب",
"ToastUnknownError": "خطأ غير معروف",
"ToastUnlinkOpenIdFailed": "فشل فصل المستخدم عن OpenID",
"ToastUnlinkOpenIdSuccess": "تم فصل المستخدم عن OpenID",
"ToastUploaderFilepathExistsError": "مسار الملف \"{0}\" موجود بالفعل على الخادم",
"ToastUploaderItemExistsInSubdirectoryError": "يستخدم العنصر \"{0}\" دليلًا فرعيًا لمسار الرفع.",
"ToastUserDeleteFailed": "فشل حذف المستخدم",
"ToastUserDeleteSuccess": "تم حذف المستخدم",
"ToastUserPasswordChangeSuccess": "تم تغيير كلمة المرور بنجاح",
"ToastUserPasswordMismatch": "كلمات المرور غير متطابقة",
"ToastUserPasswordMustChange": "يجب ألا تطابق كلمة المرور الجديدة كلمة المرور القديمة",
"ToastUserRootRequireName": "يجب إدخال اسم مستخدم الجذر"
}

View File

@@ -1,5 +1,6 @@
{
"ButtonAdd": "Дадаць",
"ButtonAddApiKey": "Дадаць API-ключ",
"ButtonAddChapters": "Дадаць раздзелы",
"ButtonAddDevice": "Дадаць прыладу",
"ButtonAddLibrary": "Дадаць бібліятэку",
@@ -20,6 +21,7 @@
"ButtonChooseAFolder": "Выбраць тэчку",
"ButtonChooseFiles": "Выбраць файлы",
"ButtonClearFilter": "Ачысціць фільтр",
"ButtonClose": "Закрыць",
"ButtonCloseFeed": "Закрыць стужку",
"ButtonCloseSession": "Закрыць адкрыты сеанс",
"ButtonCollections": "Калекцыі",
@@ -69,7 +71,7 @@
"ButtonQueueAddItem": "Дадаць у чаргу",
"ButtonQueueRemoveItem": "Выдаліць з чаргі",
"ButtonQuickEmbed": "Хуткае ўбудаванне",
"ButtonQuickEmbedMetadata": "Хуткае ўбудаванне метаданых",
"ButtonQuickEmbedMetadata": "Хуткае ўбудаванне метададзеных",
"ButtonQuickMatch": "Хуткі пошук",
"ButtonReScan": "Паўторнае сканаванне",
"ButtonRead": "Чытаць",
@@ -98,8 +100,9 @@
"ButtonSetChaptersFromTracks": "Усталяваць раздзелы з трэкаў",
"ButtonShare": "Падзяліцца",
"ButtonShiftTimes": "Карэкцыя часу",
"ButtonShow": "Паказаць",
"ButtonStartM4BEncode": "Пачаць кадзіраванне ў M4B",
"ButtonStartMetadataEmbed": "Пачаць убудаванне метаданых",
"ButtonStartMetadataEmbed": "Пачаць убудаванне метададзеных",
"ButtonStats": "Статыстыка",
"ButtonSubmit": "Адправіць",
"ButtonTest": "Тэст",
@@ -107,7 +110,7 @@
"ButtonUpload": "Загрузіць",
"ButtonUploadBackup": "Загрузіць рэзервовую копію",
"ButtonUploadCover": "Загрузіць вокладку",
"ButtonUploadOPMLFile": "Загрузіць OPML файл",
"ButtonUploadOPMLFile": "Загрузіць файл OPML",
"ButtonUserDelete": "Выдаліць карыстальніка {0}",
"ButtonUserEdit": "Рэдагаваць карыстальніка {0}",
"ButtonViewAll": "Прагледзець усе",
@@ -116,8 +119,9 @@
"ErrorUploadFetchMetadataNoResults": "Не ўдалося атрымаць метададзеныя паспрабуйце абнавіць назву і/або аўтара",
"ErrorUploadLacksTitle": "Павінна быць назва",
"HeaderAccount": "Уліковы запіс",
"HeaderAddCustomMetadataProvider": "Дадаць карыстальніцкага пастаўшчыка метаданных",
"HeaderAddCustomMetadataProvider": "Дадаць карыстальніцкага пастаўшчыка метададзенных",
"HeaderAdvanced": "Дадаткова",
"HeaderApiKeys": "API-ключы",
"HeaderAppriseNotificationSettings": "Налады апавяшчэнняў Apprise",
"HeaderAudioTracks": "Аўдыядарожкі",
"HeaderAudiobookTools": "Сродкі кіравання файламі аўдыякніг",
@@ -157,9 +161,11 @@
"HeaderManageGenres": "Кіраванне жанрамі",
"HeaderManageTags": "Кіраванне тэгамі",
"HeaderMapDetails": "Падрабязнасці адлюстравання",
"HeaderMatch": "Супадзенне",
"HeaderMetadataOrderOfPrecedence": "Парадак прыярытэтнасці метададзеных",
"HeaderMetadataToEmbed": "Метададзеныя для ўбудавання",
"HeaderNewAccount": "Новы ўліковы запіс",
"HeaderNewApiKey": "Новы API-ключ",
"HeaderNewLibrary": "Новая бібліятэка",
"HeaderNotificationCreate": "Стварыць апавяшчэнне",
"HeaderNotificationUpdate": "Абнавіць апавяшчэнне",
@@ -175,9 +181,10 @@
"HeaderPlaylist": "Спіс прайгравання",
"HeaderPlaylistItems": "Элементы спіса прайгравання",
"HeaderPodcastsToAdd": "Падкасты для дадання",
"HeaderPresets": "Прадустаноўкі",
"HeaderPreviewCover": "Прадпрагляд вокладкі",
"HeaderRSSFeedGeneral": "Падрабязнасці RSS",
"HeaderRSSFeedIsOpen": "RSS-стужка адкрыта",
"HeaderRSSFeedIsOpen": "RSS-стужка адкрытая",
"HeaderRSSFeeds": "RSS-стужкі",
"HeaderRemoveEpisode": "Выдаліць эпізод",
"HeaderRemoveEpisodes": "Выдаліць {0} эпізодаў",
@@ -203,6 +210,7 @@
"HeaderTableOfContents": "Змест",
"HeaderTools": "Інструменты",
"HeaderUpdateAccount": "Абнавіць уліковы запіс",
"HeaderUpdateApiKey": "Абнавіць API-ключ",
"HeaderUpdateAuthor": "Абнавіць аўтара",
"HeaderUpdateDetails": "Абнавіць падрабязнасці",
"HeaderUpdateLibrary": "Абнавіць бібліятэку",
@@ -227,10 +235,15 @@
"LabelAddedDate": "Дададзена {0}",
"LabelAdminUsersOnly": "Толькі для адміністратараў",
"LabelAll": "Усе",
"LabelAllEpisodesDownloaded": "Усе эпізоды спампаваныя",
"LabelAllUsers": "Усе карыстальнікі",
"LabelAllUsersExcludingGuests": "Усе карыстальнікі, акрамя гасцей",
"LabelAllUsersIncludingGuests": "Усе карыстальнікі, уключаючы гасцей",
"LabelAlreadyInYourLibrary": "Ужо ў вашай бібліятэцы",
"LabelApiKeyCreated": "API-ключ \"{0}\" паспяхова створаны.",
"LabelApiKeyCreatedDescription": "Пераканайцеся, што вы скапіявалі API-ключ зараз, бо паўторна яго ўбачыць не атрымаецца.",
"LabelApiKeyUser": "Дзейнічаць ад імя карыстальніка",
"LabelApiKeyUserDescription": "Гэты API-ключ будзе мець тыя ж правы, што і карыстальнік, ад імя якога ён дзейнічае. У журналах гэта будзе выглядаць так, быццам запыт робіць сам карыстальнік.",
"LabelApiToken": "Токен API",
"LabelAppend": "Дадаць",
"LabelAudioBitrate": "Бітрэйт аўдыё (напрыклад, 128к)",
@@ -242,39 +255,108 @@
"LabelAuthors": "Аўтары",
"LabelAutoDownloadEpisodes": "Аўтаматычнае спампаванне эпізодаў",
"LabelAutoFetchMetadata": "Аўтаматычнае атрыманне метададзеных",
"LabelAutoFetchMetadataHelp": "Атрыманне звестак пра назву, аўтара і серыю для падыходнага фарматавання перад загрузкай. Далей можа быць неабходна дапоўніць метададзеныя.",
"LabelAutoLaunch": "Аўтазапуск",
"LabelAutoLaunchDescription": "Аўтаматычна перанакіроўваць да пастаўшчыка аўтэнтыфікацыі пры переходзе на старонку ўваходу (ручное пераключэнне праз шлях <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Аўтарэгістрацыя",
"LabelAutoRegisterDescription": "Аўтаматычна ствараць новых карыстальнікаў пасля ўваходу ў сістэму",
"LabelBackToUser": "Вярнуцца да карыстальніка",
"LabelBackupAudioFiles": "Рэзервовае капіраванне аўдыёфайлаў",
"LabelBackupLocation": "Месцазнаходжанне рэзервовых копій",
"LabelBackupsEnableAutomaticBackups": "Аўтаматычнае рэзервовае капіраванне",
"LabelBackupsEnableAutomaticBackupsHelp": "Рэзервовыя копіі захаваныя ў /metadata/backups",
"LabelBackupsMaxBackupSize": "Максімальны памер рэзервовай копіі (у ГБ) (0 — неабмежавана)",
"LabelBackupsMaxBackupSizeHelp": "Для таго, каб пазбегнуць няправільных налад, рэзервовыя копіі не будуць створаны, калі іх памер будзе больш за дапушчальны.",
"LabelBackupsNumberToKeep": "Колькасць захаваных рэзервовых копій",
"LabelBackupsNumberToKeepHelp": "Адначасова будзе выдаляцца толькі 1 рэзервовая копія, таму, калі ў вас іх больш, вам варта выдаліць іх уручную.",
"LabelBitrate": "Бітрэйт",
"LabelBonus": "Бонус",
"LabelBooks": "Кнігі",
"LabelButtonText": "Тэкст кнопкі",
"LabelByAuthor": "ад {0}",
"LabelChangePassword": "Змяніць пароль",
"LabelChannels": "Каналы",
"LabelChapterCount": "{0} раздзелаў",
"LabelChapterTitle": "Назва раздзела",
"LabelChapters": "Раздзелы",
"LabelChaptersFound": "раздзелаў знойдзена",
"LabelClickForMoreInfo": "Націсніце для больш падрабязнай інфармацыі",
"LabelClickToUseCurrentValue": "Націсніце, каб выкарыстоўваць бягучае значэнне",
"LabelClosePlayer": "Зачыніць прайгравальнік",
"LabelCodec": "Кодэк",
"LabelCollapseSeries": "Згарнуць серыі",
"LabelCollapseSubSeries": "Згарнуць падсерыі",
"LabelCollection": "Калекцыя",
"LabelCollections": "Калекцыі",
"LabelComplete": "Завершана",
"LabelConfirmPassword": "Пацвердзіце пароль",
"LabelContinueListening": "Працягваць слухаць",
"LabelContinueReading": "Працягнуць чытанне",
"LabelContinueSeries": "Працягнуць серыі",
"LabelCover": "Вокладка",
"LabelCoverImageURL": "URL выявы вокладкі",
"LabelCoverProvider": "Крыніца вокладак",
"LabelCreatedAt": "Дата стварэння",
"LabelCronExpression": "Запіс Cron",
"LabelCurrent": "Бягучы",
"LabelCurrently": "Бягучы:",
"LabelCustomCronExpression": "Уласны запіс Cron:",
"LabelDatetime": "Дата і час",
"LabelDays": "Дзён",
"LabelDeleteFromFileSystemCheckbox": "Выдаліць з файлавай сістэмы (зніміце галачку, каб выдаліць толькі з базы даных)",
"LabelDescription": "Апісанне",
"LabelDeselectAll": "Скасаваць выбар усяго",
"LabelDevice": "Прылада",
"LabelDeviceInfo": "Інфармацыя пра прыладу",
"LabelDeviceIsAvailableTo": "Прылада даступная для...",
"LabelDirectory": "Каталог",
"LabelDiscFromFilename": "Дыск з імя файла",
"LabelDiscFromMetadata": "Дыск па метададзеных",
"LabelDiscover": "Знайсці",
"LabelDownload": "Спампаваць",
"LabelDownloadNEpisodes": "Спампована {0} эпізодаў",
"LabelDownloadable": "Спампоўваецца",
"LabelDuration": "Працягласць",
"LabelDurationComparisonExactMatch": "(дакладнае супадзенне)",
"LabelDurationComparisonLonger": "(на {0} даўжэй)",
"LabelDurationComparisonShorter": "(на {0} карацей)",
"LabelDurationFound": "Знойдзеная працягласць:",
"LabelEbook": "Электронная кніга",
"LabelEbooks": "Электронныя кнігі",
"LabelEdit": "Рэдагаваць",
"LabelEmail": "Электронная пошта",
"LabelEmailSettingsFromAddress": "Адрас адпраўніка",
"LabelEmailSettingsRejectUnauthorized": "Адхіляць неаўтарызаваныя сертыфікаты",
"LabelEmailSettingsRejectUnauthorizedHelp": "Адключэнне праверкі SSL-сертыфіката можа зрабіць ваша злучэнне ўразлівым перад пагрозамі бяспекі, такімі як атакі \"чалавек пасярэдзіне\". Адключайце гэтую опцыю толькі калі цалкам разумееце наступствы і ўпэўнены ў надзейнасці паштовага сервера.",
"LabelEmailSettingsSecure": "Бяспечныя",
"LabelEmailSettingsSecureHelp": "Калі ўключана, злучэнне будзе выкарыстоўваць TLS пры падключэнні да сервера. Калі выключана, TLS будзе выкарыстоўвацца толькі ў выпадку падтрымкі пашырэння STARTTLS на серверы. У большасці выпадкаў усталюйце значэнне true пры падключэнні да порта 465. Для партоў 587 або 25 не ўключайце яго. (інфармацыя з nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Тэставы адрас",
"LabelEmbeddedCover": "Убудаваная вокладка",
"LabelEnable": "Уключыць",
"LabelEncodingBackupLocation": "Рэзервовая копія вашых арыгінальных аўдыёфайлаў будзе захавана ў:",
"LabelEncodingChaptersNotEmbedded": "Раздзелы не ўбудаваны ў шматдарожкавыя аўдыякнігі.",
"LabelEncodingClearItemCache": "Пераканайцеся, што перыядычна ачышчаеце кэш элементаў.",
"LabelEncodingFinishedM4B": "Гатовы файл M4B будзе змешчаны ў вашу тэчку з аўдыякнігамі па адрасе:",
"LabelEncodingInfoEmbedded": "Метаданыя будуць убудаваны ў аўдыядарожкі ўнутры вашай тэчкі з аўдыякнігамі.",
"LabelEncodingInfoEmbedded": "Метададзеныя будуць убудаваны ў аўдыядарожкі ўнутры вашай тэчкі з аўдыякнігамі.",
"LabelEncodingStartedNavigation": "Пасля запуску задачы вы можаце перайсці на іншую старонку.",
"LabelEncodingTimeWarning": "Кадаванне можа заняць да 30 хвілін.",
"LabelEnd": "Канец",
"LabelEndOfChapter": "Канец раздзела",
"LabelEpisode": "Эпізод",
"LabelEpisodeNotLinkedToRssFeed": "Эпізод не звязаны з RSS-стужкай",
"LabelEpisodeUrlFromRssFeed": "URL эпізоду з RSS-стужкі",
"LabelEpisodic": "Эпізадычны",
"LabelExample": "Прыклад",
"LabelExpandSeries": "Разгарнуць серыю",
"LabelExpandSubSeries": "Разгарнуць падсерыі",
"LabelExpired": "Пратэрмінаваны",
"LabelExpiresAt": "Тэрмін дзеяння заканчваецца ў",
"LabelExpiresInSeconds": "Тэрмін дзеяння заканчваецца праз (секунд)",
"LabelExpiresNever": "Ніколі",
"LabelExplicit": "Відверты",
"LabelExportOPML": "Экспарт OPML",
"LabelFeedURL": "URL стужкі",
"LabelFetchingMetadata": "Атрыманне метададзеных",
"LabelFile": "Файл",
"LabelFileBirthtime": "Час стварэння файла",
"LabelFileModified": "Час змянення файла",
@@ -289,6 +371,7 @@
"LabelHasSupplementaryEbook": "Мае дадатковую электронную кнігу",
"LabelHideSubtitles": "Схаваць падзагалоўкі",
"LabelHost": "Хост",
"LabelImageURLFromTheWeb": "URL выявы з інтэрнэту",
"LabelInProgress": "У працэсе",
"LabelIncomplete": "Незавершана",
"LabelIntervalCustomDailyWeekly": "Карыстальніцкі штодзённы/штотыднёвы",
@@ -319,6 +402,7 @@
"LabelLibraryFilterSublistEmpty": "Не {0}",
"LabelLibraryItem": "Элемент бібліятэкі",
"LabelLibraryName": "Імя бібліятэкі",
"LabelLibrarySortByProgress": "Прагрэс абноўлены",
"LabelLimit": "Абмежаванне",
"LabelLineSpacing": "Міжрадковы інтэрвал",
"LabelListenAgain": "Паслухаць зноў",
@@ -327,6 +411,8 @@
"LabelMaxEpisodesToKeepHelp": "Значэнне 0 не ўстанаўлівае максімальнага абмежавання. Пасля аўтаматычнай спампоўкі новага эпізоду будзе выдалены самы стары эпізод, калі ў вас больш за X эпізодаў. Пры кожнай новай спампоўцы будзе выдаляцца толькі 1 эпізод.",
"LabelMediaPlayer": "Медыяпрайгравальнік",
"LabelMediaType": "Тып медыя",
"LabelMetadataOrderOfPrecedenceDescription": "Крыніцы метададзеных з вышэйшым прыярытэтам будуць замяняць крыніцы з ніжэйшым прыярытэтам",
"LabelMetadataProvider": "Пастаўшчык метададзеных",
"LabelMissing": "Адсутнічае",
"LabelMore": "Больш",
"LabelMoreInfo": "Больш інфармацыі",
@@ -335,6 +421,7 @@
"LabelNarrators": "Чытальнікі",
"LabelNewestAuthors": "Новыя аўтары",
"LabelNewestEpisodes": "Новыя эпізоды",
"LabelNoCustomMetadataProviders": "Няма карыстацкіх пастаўшчыкоў метададзеных",
"LabelNotFinished": "Не скончана",
"LabelNotStarted": "Не пачата",
"LabelNotificationsMaxFailedAttemptsHelp": "Апавяшчэнні адключаюцца пасля таго, як не ўдаецца іх адправіць гэтулькі разоў",
@@ -353,7 +440,7 @@
"LabelPublishedDate": "Апублікавана {0}",
"LabelRSSFeedCustomOwnerEmail": "Карыстальніцкая электронная пошта ўладальніка",
"LabelRSSFeedCustomOwnerName": "Карыстальніцкае імя ўладальніка",
"LabelRSSFeedOpen": "RSS-стужка адкрытая",
"LabelRSSFeedOpen": "RSS-стужка адкрыта",
"LabelRSSFeedPreventIndexing": "Прадухіліць індэксацыю",
"LabelRSSFeedSlug": "Ідэнтыфікатар RSS-стужкі",
"LabelRSSFeedURL": "URL RSS-стужкі",
@@ -392,6 +479,7 @@
"LabelSettingsAudiobooksOnly": "Толькі аўдыякнігі",
"LabelSettingsAudiobooksOnlyHelp": "Уключэнне гэтай налады будзе ігнараваць файлы электронных кніг, калі толькі яны не знаходзяцца ў тэчцы з аўдыякнігамі. У такім выпадку яны будуць пазначаны як дадатковыя электронныя кнігі.",
"LabelSettingsBookshelfViewHelp": "Рэалістычны дызайн з драўлянымі паліцамі",
"LabelSettingsEnableWatcherForLibrary": "Аўтаматычна правяраць бібліятэку на змены",
"LabelSettingsEnableWatcherHelp": "Адключае аўтаматычнае дадаванне/абнаўленне элементаў пры выяўленні змен у файлах. *Патрабуецца перазапуск сервера",
"LabelSettingsEpubsAllowScriptedContent": "Дазволіць скрыптавы кантэнт у EPUB",
"LabelSettingsEpubsAllowScriptedContentHelp": "Дазволіць EPUB-файлам выконваць скрыпты. Рэкамендуецца пакінуць гэтую наладу выключанай, калі вы не давяраеце крыніцы EPUB-файлаў.",
@@ -409,6 +497,11 @@
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Палка \"Працягнуць серыю\" на галоўнай старонцы паказвае першую не пачатую кнігу ў серыях, дзе завершана хаця б адна кніга і няма кніг у працэсе чытання. Уключэнне гэтай налады дазволіць працягваць серыю з самай апошняй завершанай кнігі замест першай не пачатай.",
"LabelSettingsParseSubtitles": "Разабраць падзагалоўкі",
"LabelSettingsParseSubtitlesHelp": "Выдзяляць падзагаловак з назваў тэчак аўдыякніг.<br>Падзагаловак павінен быць аддзелены знакам \" - \".<br>Напрыклад, \"Назва кнігі - Падзагаловак тут\" мае падзагаловак \"Падзагаловак тут\"",
"LabelSettingsPreferMatchedMetadata": "Аддаваць перавагу супадаючым метададзеным",
"LabelSettingsPreferMatchedMetadataHelp": "Супадаючыя дадзеныя будуць замяняць дэталі элемента пры выкарыстанні функцыі Хуткі пошук. Па змаўчанні Хуткі пошук запаўняе толькі адсутныя дэталі.",
"LabelSettingsStoreCoversWithItemHelp": "Па змаўчанні вокладкі захоўваюцца ў /metadata/items, уключэнне гэтай опцыі забяспечыць захоўванне вокладак у тэчцы элемента вашай бібліятэкі. Захоўвацца будзе толькі адзін файл з назвай \"cover\"",
"LabelSettingsStoreMetadataWithItem": "Захоўваць метададзеныя разам з элементам",
"LabelSettingsStoreMetadataWithItemHelp": "Па змаўчанні метададзеныя захоўваюцца ў /metadata/items. Уключэнне гэтай опцыі забяспечыць захоўванне файлаў метададзеных у тэчках элементаў вашай бібліятэкі",
"LabelSettingsTimeFormat": "Фармат часу",
"LabelShareDownloadableHelp": "Дазваляе карыстальнікам, якія маюць спасылку на доступ, спампаваць ZIP-файл элемента бібліятэкі.",
"LabelShowAll": "Паказаць усё",
@@ -438,7 +531,7 @@
"LabelTags": "Меткі",
"LabelTagsAccessibleToUser": "Меткі, даступныя карыстальніку",
"LabelTagsNotAccessibleToUser": "Меткі, недаступныя карыстальніку",
"LabelTasks": "Выконваюцца задачы",
"LabelTasks": "Запушчаныя задачы",
"LabelTextEditorBulletedList": "Маркіраваны спіс",
"LabelTextEditorLink": "Спасылка",
"LabelTextEditorNumberedList": "Нумараваны спіс",
@@ -457,11 +550,14 @@
"LabelTimeRemaining": "Засталося {0}",
"LabelTimeToShift": "Час зрушэння ў секундах",
"LabelTitle": "Назва",
"LabelToolsSplitM4bDescription": "Стварэнне MP3 з M4B, падзеленага па раздзелах, з убудаванымі метаданымі, вокладкай і раздзеламі.",
"LabelToolsEmbedMetadata": "Убудаваць метададзеныя",
"LabelToolsEmbedMetadataDescription": "Убудаваць метададзеныя ў аўдыёфайлы, уключаючы выяву вокладкі і раздзелы.",
"LabelToolsMakeM4bDescription": "Стварыць аўдыёкнігу ў фармаце .M4B з убудаванымі метададзенымі, выявай вокладкі і раздзеламі.",
"LabelToolsSplitM4bDescription": "Стварэнне MP3 з M4B, падзеленага па раздзелах, з убудаванымі метададзенымі, выявай вокладкі і раздзеламі.",
"LabelTotalDuration": "Агульная працягласць",
"LabelTotalTimeListened": "Агульны час праслухоўвання",
"LabelTrackFromFilename": "Дарожка з імя файла",
"LabelTrackFromMetadata": "Дарожка з метаданых",
"LabelTrackFromMetadata": "Дарожка з метададзеных",
"LabelTracks": "Дарожкі",
"LabelTracksMultiTrack": "Шматдарожкавы",
"LabelTracksNone": "Няма дарожак",
@@ -510,19 +606,30 @@
"MessageBackupsLocationPathEmpty": "Шлях да месцазнаходжання рэзервовых копій не можа быць пустым",
"MessageBatchEditPopulateMapDetailsAllHelp": "Запоўніце ўключаныя палі дадзенымі з усіх элементаў. Палі з некалькімі значэннямі будуць аб'яднаны",
"MessageBatchEditPopulateMapDetailsItemHelp": "Запоўніце ўключаныя палі падрабязнасцей карты дадзенымі з гэтага элемента",
"MessageBatchQuickMatchDescription": "Хуткі пошук паспрабуе дадаць адсутныя вокладкі і метададзеныя для выбраных элементаў. Уключыце ніжэй выкладзеныя опцыі, каб дазволіць Хуткаму пошуку замяняць існуючыя вокладкі і/або метададзеныя.",
"MessageBookshelfNoRSSFeeds": "Няма адкрытых RSS-стужак",
"MessageChapterErrorStartGteDuration": "Няправільны час пачатку: ён павінен быць меншым за працягласць аўдыякнігі",
"MessageChapterErrorStartLtPrev": "Няправільны час пачатку: ён павінен быць большым або роўным часу пачатку папярэдняга раздзела",
"MessageConfirmCloseFeed": "Вы ўпэўнены, што жадаеце закрыць гэтую стужку?",
"MessageConfirmDeleteMetadataProvider": "Ці ўпэўненыя вы, што жадаеце выдаліць карыстацкага пастаўшчыка метададзеных \"{0}\"?",
"MessageConfirmEmbedMetadataInAudioFiles": "Ці ўпэўненыя вы, што жадаеце ўбудаваць метададзеныя ў {0} аўдыёфайлаў?",
"MessageConfirmPurgeCache": "Ачышчэнне кэша выдаліць увесь каталог па адрасе <code>/metadata/cache</code>. <br /><br /> Ці сапраўды вы жадаеце выдаліць каталог кэша?",
"MessageConfirmPurgeItemsCache": "Ачышчэнне кэша элементаў выдаліць увесь каталог па адрасе <code>/metadata/cache/items</code>. <br /> Вы ўпэўнены?",
"MessageConfirmQuickMatchEpisodes": "Хуткае супадзенне эпізодаў перазапіша дэталі, калі супадзенне будзе знойдзена. Будуць абноўлены толькі эпізоды, якія не супадаюць. Вы ўпэўнены?",
"MessageConfirmRemoveListeningSessions": "Вы ўпэўнены, што жадаеце выдаліць {0} сеансаў праслухоўвання?",
"MessageConfirmRemoveMetadataFiles": "Ці ўпэўненыя вы, што жадаеце выдаліць усе файлы метададзеных{0} у тэчках элементаў вашай бібліятэкі?",
"MessageConfirmRemovePlaylist": "Вы ўпэўненыя, што жадаеце выдаліць свой спіс прайгравання \"{0}\"?",
"MessageConfirmSendEbookToDevice": "Вы ўпэўнены, што хочаце адправіць {0} электронную кнігу \"{1}\" на прыладу \"{2}\"?",
"MessageDownloadingEpisode": "Спампоўка эпізоду",
"MessageEmbedQueue": "У чарзе на ўбудаванне метададзеных (у чарзе {0})",
"MessageEpisodesQueuedForDownload": "{0} эпізод(аў) у чарзе для спампоўкі",
"MessageEreaderDevices": "Каб забяспечыць дастаўку электронных кніг, вам можа спатрэбіцца дадаць вышэйзгаданы адрас электроннай пошты як дазволенага адпраўніка для кожнай прылады, пералічанай ніжэй.",
"MessageFeedURLWillBe": "URL стужкі будзе {0}",
"MessageFetching": "Атрыманне...",
"MessageInvalidAsin": "Няправільны ASIN",
"MessageItemsUpdated": "{0} элементаў абноўлена",
"MessageLoading": "Загрузка...",
"MessageLogsDescription": "Журналы захоўваюцца ў каталогу <code>/metadata/logs</code> у фармаце JSON. Журналы памылак захоўваюцца ў файле <code>/metadata/logs/crashlogs.txt</code>.",
"MessageMapChapterTitles": "Супаставіць назвы раздзелаў з вашымі існуючымі раздзеламі аўдыякнігі без змянення часовых метак",
"MessageMarkAsFinished": "Пазначыць як скончана",
"MessageNoBookmarks": "Няма закладак",
@@ -536,26 +643,54 @@
"MessageNoMediaProgress": "Няма прагрэсу медыя",
"MessageNoPodcastFeed": "Няправільны падкаст: Няма стужкі",
"MessageNoPodcastsFound": "Падкасты не знойдзены",
"MessageNoTasksRunning": "Няма запушчаных задач",
"MessageNoUpdatesWereNecessary": "Абнаўленні не патрабаваліся",
"MessageNoUserPlaylists": "У вас няма спісаў прайгравання",
"MessageNoUserPlaylistsHelp": "Спісы прайгравання прыватныя. Толькі карыстальнік, які іх стварыў, можа іх бачыць.",
"MessageOpmlPreviewNote": "Заўвага: гэта папярэдні прагляд разабранага OPML-файла. Фактычная назва падкаста будзе ўзятая з RSS-стужкі.",
"MessageOpmlPreviewNote": "Заўвага: гэта папярэдні прагляд разабранага файла OPML. Фактычная назва падкаста будзе ўзятая з RSS-стужкі.",
"MessagePlaylistCreateFromCollection": "Стварыць спіс прайгравання з калекцыі",
"MessagePodcastHasNoRSSFeedForMatching": "У падкаста няма URL RSS-стужкі для супадзення",
"MessagePodcastSearchField": "Увядзіце пошукавы запыт або URL RSS-стужкі",
"MessageQuickMatchDescription": "Запоўніць пустыя дэталі элемента і вокладку першым вынікам супадзення з '{0}'. Не замяняе дэталі, калі опцыя «Аддаваць перавагу супадаючым метададзеным» на серверы не ўключана.",
"MessageReportBugsAndContribute": "Паведамляйце пра памылкі, прапануйце новыя функцыі і ўдзельнічайце на",
"MessageRestoreBackupWarning": "Аднаўленне рэзервовай копіі перазапіша ўсю базу даных, размешчаную ў /config, а таксама выявы вокладкі ў /metadata/items і /metadata/authors. <br /><br /> Рэзервовыя копіі не змяняюць файлы ў вашых тэчках бібліятэкі. Калі вы ўключылі наладкі сервера для захоўвання воклак і метададзеных у тэчках бібліятэкі, гэтыя файлы не будуць захаваныя ў рэзервовых копіях і не зменяцца. <br /><br /> Усе кліенты, якія карыстаюцца вашым серверам, будуць аўтаматычна абноўлены.",
"MessageScheduleRunEveryWeekdayAtTime": "Выконваць кожныя {0} у {1}",
"MessageStartPlaybackAtTime": "Пачаць прайграванне для \"{0}\" з {1}?",
"MessageTaskAudioFileNotWritable": "Аўдыёфайл \"{0}\" недаступны для запісу",
"MessageTaskCanceledByUser": "Задача скасавана карыстальнікам",
"MessageTaskDownloadingEpisodeDescription": "Спампоўка эпізоду \"{0}\"",
"MessageTaskEmbeddingMetadata": "Убудаванне метададзеных",
"MessageTaskEmbeddingMetadataDescription": "Убудаванне метададзеных у аўдыёкнігу \"{0}\"",
"MessageTaskEncodingM4b": "Кадаванне M4B",
"MessageTaskEncodingM4bDescription": "Кадаванне аўдыякнігі \"{0}\" у адзін файл m4b",
"MessageTaskFailed": "Не ўдалося",
"MessageTaskFailedToBackupAudioFile": "Не ўдалося зрабіць рэзервовую копію аўдыёфайла \"{0}\"",
"MessageTaskFailedToCreateCacheDirectory": "Не ўдалося стварыць каталог кэша",
"MessageTaskFailedToEmbedMetadataInFile": "Не ўдалося ўбудаваць метададзеныя ў файл \"{0}\"",
"MessageTaskFailedToMergeAudioFiles": "Не ўдалося аб’яднаць аўдыёфайлы",
"MessageTaskFailedToMoveM4bFile": "Не ўдалося перамясціць файл m4b",
"MessageTaskFailedToWriteMetadataFile": "Не ўдалося захаваць файл метададзеных",
"MessageTaskMatchingBooksInLibrary": "Пошук супадзенняў кніг у бібліятэцы \"{0}\"",
"MessageTaskNoFilesToScan": "Няма файлаў для сканавання",
"MessageTaskOpmlImport": "Імпарт OPML",
"MessageTaskOpmlImportDescription": "Стварэнне падкастаў з {0} RSS-стужак",
"MessageTaskOpmlImportFeed": "Імпарт стужкі з OPML",
"MessageTaskOpmlImportFeed": "Імпарт стужкі OPML",
"MessageTaskOpmlImportFeedDescription": "Імпартаванне RSS-стужкі \"{0}\"",
"MessageTaskOpmlImportFeedFailed": "Не ўдалося атрымаць стужку падкаста",
"MessageTaskOpmlImportFeedPodcastDescription": "Стварэнне падкаста \"{0}\"",
"MessageTaskOpmlImportFeedPodcastExists": "Падкаст ужо існуе па гэтым шляху",
"MessageTaskOpmlImportFeedPodcastFailed": "Не ўдалося стварыць падкаст",
"MessageTaskOpmlParseNoneFound": "У OPML-файле не знойдзена стужак",
"MessageTaskOpmlImportFinished": "Дададзена {0} падкастаў",
"MessageTaskOpmlParseFailed": "Не ўдалося разабраць файл OPML",
"MessageTaskOpmlParseFastFail": "Неправільны файл OPML: тэг <opml> не знойдзены АБО тэг <outline> не знойдзены",
"MessageTaskOpmlParseNoneFound": "У файле OPML не знойдзена стужак",
"MessageTaskScanItemsAdded": "{0} дададзена",
"MessageTaskScanItemsMissing": "{0} адсутнічае",
"MessageTaskScanItemsUpdated": "{0} абноўлена",
"MessageTaskScanNoChangesNeeded": "Змены не патрабуюцца",
"MessageTaskScanningFileChanges": "Сканіраванне змяненняў у файле \"{0}\"",
"MessageTaskScanningLibrary": "Сканіраванне бібліятэкі \"{0}\"",
"MessageTaskTargetDirectoryNotWritable": "Мэтавы каталог недаступны для запісу",
"NoteChapterEditorTimes": "Заўвага: Час пачатку першага раздзела павінен заставацца 0:00, а час пачатку апошняга раздзела не можа перавышаць працягласць гэтай аўдыякнігі.",
"NoteRSSFeedPodcastAppsHttps": "Папярэджанне: большасць праграм для падкастаў патрабуюць, каб URL RSS-стужкі выкарыстоўваў HTTPS",
"NoteRSSFeedPodcastAppsPubDate": "Папярэджанне: адзін ці больш вашых эпізодаў не маюць даты публікацыі. Некаторыя праграмы для падкастаў патрабуюць гэтага.",
@@ -567,6 +702,11 @@
"StatsBooksListenedTo": "кнігі, якія былі праслуханы",
"StatsCollectionGrewTo": "Ваша калекцыя кніг павялічылася да…",
"ToastAccountUpdateSuccess": "Уліковы запіс абноўлены",
"ToastAuthorImageRemoveSuccess": "Выява аўтара выдалена",
"ToastAuthorUpdateSuccess": "Аўтар абноўлены",
"ToastAuthorUpdateSuccessNoImageFound": "Аўтар абноўлены (малюнак не знойдзены)",
"ToastBackupInvalidMaxKeep": "Няправільная колькасць рэзервовых копій для захоўвання",
"ToastBackupInvalidMaxSize": "Няправільны максімальны памер рэзервовай копіі",
"ToastBookmarkCreateFailed": "Не ўдалося стварыць закладку",
"ToastDateTimeInvalidOrIncomplete": "Дата і час указаны некарэктна або не цалкам",
"ToastDeviceTestEmailFailed": "Не ўдалося адправіць тэставае электроннае пісьмо",
@@ -574,6 +714,7 @@
"ToastEncodeCancelSucces": "Кадаванне скасавана",
"ToastEpisodeDownloadQueueClearFailed": "Не ўдалося ачысціць чаргу",
"ToastEpisodeDownloadQueueClearSuccess": "Чарга спампоўкі эпізодаў ачышчана",
"ToastInvalidImageUrl": "Няправільны URL выявы",
"ToastInvalidMaxEpisodesToDownload": "Няправільная максімальная колькасць эпізодаў для спампоўкі",
"ToastItemMarkedAsFinishedFailed": "Не ўдалося пазначыць як Скончана",
"ToastItemMarkedAsFinishedSuccess": "Элемент пазначаны як Завершаны",
@@ -602,6 +743,8 @@
"ToastPlaylistCreateSuccess": "Спіс прайгравання створаны",
"ToastPlaylistRemoveSuccess": "Спіс прайгравання выдалены",
"ToastPlaylistUpdateSuccess": "Спіс прайгравання абноўлены",
"ToastPodcastCreateFailed": "Не ўдалося стварыць падкаст",
"ToastPodcastCreateSuccess": "Падкаст паспяхова створаны",
"ToastPodcastGetFeedFailed": "Не ўдалося атрымаць стужку падкаста",
"ToastPodcastNoEpisodesInFeed": "У RSS-стужцы не знойдзена эпізодаў",
"ToastPodcastNoRssFeed": "У падкаста няма RSS-стужкі",
@@ -610,6 +753,7 @@
"ToastSendEbookToDeviceFailed": "Не ўдалося адправіць электронную кнігу на прыладу",
"ToastSendEbookToDeviceSuccess": "Электронная кніга адпраўлена на прыладу \"{0}\"",
"ToastSleepTimerDone": "Таймер сну скончыўся... Хр-р-р",
"ToastUploaderItemExistsInSubdirectoryError": "Элемент \"{0}\" выкарыстоўвае падкаталог шляху загрузкі.",
"ToastUserPasswordMustChange": "Новы пароль не можа супадаць са старым",
"ToastUserRootRequireName": "Неабходна ўвесці імя карыстальніка адміністратара"
}

View File

@@ -1,5 +1,6 @@
{
"ButtonAdd": "Създай",
"ButtonAddApiKey": "Добави API ключ",
"ButtonAddChapters": "Добави Глави",
"ButtonAddDevice": "Добави Устройство",
"ButtonAddLibrary": "Добави Библиотека",
@@ -20,6 +21,7 @@
"ButtonChooseAFolder": "Избери Папка",
"ButtonChooseFiles": "Избери Файлове",
"ButtonClearFilter": "Изчисти филтър",
"ButtonClose": "Затвори",
"ButtonCloseFeed": "Затвори стената",
"ButtonCloseSession": "Затвори отворената сесия",
"ButtonCollections": "Колекции",
@@ -119,11 +121,13 @@
"HeaderAccount": "Профил",
"HeaderAddCustomMetadataProvider": "Добави персонализиран доставчик на метаданни",
"HeaderAdvanced": "Разширени настройки",
"HeaderApiKeys": "API ключове",
"HeaderAppriseNotificationSettings": "Apprise Notification Опции",
"HeaderAudioTracks": "Песни",
"HeaderAudiobookTools": "Инструмент за Менижиране на Аудиокниги",
"HeaderAuthentication": "Аутентикация",
"HeaderBackups": "Архив",
"HeaderBulkChapterModal": "Добави няколко глави",
"HeaderChangePassword": "Промяна на Парола",
"HeaderChapters": "Глави",
"HeaderChooseAFolder": "Избети Папка",
@@ -162,6 +166,7 @@
"HeaderMetadataOrderOfPrecedence": "Предимство на Метаданни",
"HeaderMetadataToEmbed": "Метаданни за Вграждане",
"HeaderNewAccount": "Нов Профил",
"HeaderNewApiKey": "Нов API ключ",
"HeaderNewLibrary": "Нова Библиотека",
"HeaderNotificationCreate": "Създай нотификация",
"HeaderNotificationUpdate": "Обнови нотификация",
@@ -177,6 +182,7 @@
"HeaderPlaylist": "Плейлист",
"HeaderPlaylistItems": "Елементи от плейлист",
"HeaderPodcastsToAdd": "Подкасти за Добавяне",
"HeaderPresets": "Настройки по подразбиране",
"HeaderPreviewCover": "Преглед на Корица",
"HeaderRSSFeedGeneral": "RSS подробности",
"HeaderRSSFeedIsOpen": "RSS емисията е отворена",
@@ -194,6 +200,7 @@
"HeaderSettingsExperimental": "Експериментални Функции",
"HeaderSettingsGeneral": "Общи",
"HeaderSettingsScanner": "Скенер",
"HeaderSettingsSecurity": "Сигурност",
"HeaderSettingsWebClient": "Уеб клиент",
"HeaderSleepTimer": "Таймер за заспиване",
"HeaderStatsLargestItems": "Най-Големите Елементи",
@@ -205,6 +212,7 @@
"HeaderTableOfContents": "Съдържание",
"HeaderTools": "Инструменти",
"HeaderUpdateAccount": "Обнови Профил",
"HeaderUpdateApiKey": "Обнови API ключ",
"HeaderUpdateAuthor": "Обнови Автор",
"HeaderUpdateDetails": "Обнови Детайли",
"HeaderUpdateLibrary": "Обнови Библиотека",
@@ -219,6 +227,7 @@
"LabelAccountTypeAdmin": "Администратор",
"LabelAccountTypeGuest": "Гост",
"LabelAccountTypeUser": "Потребител",
"LabelActivities": "Дейности",
"LabelActivity": "Дейност",
"LabelAddToCollection": "Добави в Колекция",
"LabelAddToCollectionBatch": "Добави {0} Книги в Колекция",
@@ -228,10 +237,15 @@
"LabelAddedDate": "Добавено",
"LabelAdminUsersOnly": "Само за Администратори",
"LabelAll": "Всичко",
"LabelAllEpisodesDownloaded": "Всички епизоди са изтеглени",
"LabelAllUsers": "Всички Потребители",
"LabelAllUsersExcludingGuests": "Всички потребители без гости",
"LabelAllUsersIncludingGuests": "Всички потребители включително гости",
"LabelAlreadyInYourLibrary": "Вече е в твоята библиотека",
"LabelApiKeyCreated": "API ключ \"{0}\" успешно създатен.",
"LabelApiKeyCreatedDescription": "Погрижете се да копирате API ключът сега, защото повече няма да можете да го виждате онново.",
"LabelApiKeyUser": "Действай от името на потребителя",
"LabelApiKeyUserDescription": "Този API ключ ще има същите права като на потребителя за чието име действа. В логовете ще изглежда все едно потребителя прави заявката.",
"LabelApiToken": "АПИ Токен",
"LabelAppend": "Добави",
"LabelAudioBitrate": "Аудио битрейт (напр. 128k)",
@@ -251,9 +265,9 @@
"LabelBackToUser": "Обратно към Потребител",
"LabelBackupAudioFiles": "Създай резервно копие на аудио файлове",
"LabelBackupLocation": "Местоположение на Архив",
"LabelBackupsEnableAutomaticBackups": "Включи автоматично архивиране",
"LabelBackupsEnableAutomaticBackups": "Автоматично архивиране",
"LabelBackupsEnableAutomaticBackupsHelp": "Архиви запазени в /metadata/backups",
"LabelBackupsMaxBackupSize": "Максимален размер на архива (в GB)",
"LabelBackupsMaxBackupSize": "Максимален размер на архива (в GB) (0 за неограничен)",
"LabelBackupsMaxBackupSizeHelp": "За защита срещу грешки в конфигурацията, архивите ще се провалят ако надхвърлят конфигурирания размер.",
"LabelBackupsNumberToKeep": "Брой архиви за запазване",
"LabelBackupsNumberToKeepHelp": "Само 1 архив ще бъде премахнат на веднъж, така че ако вече имате повече архиви от това трябва да ги премахнете ръчно.",
@@ -270,7 +284,7 @@
"LabelChaptersFound": "намерени глави",
"LabelClickForMoreInfo": "Кликни за повече информация",
"LabelClickToUseCurrentValue": "Натисни да ползваш сегашната стойност",
"LabelClosePlayer": "Затвори",
"LabelClosePlayer": "Затвори плейъра",
"LabelCodec": "Кодек",
"LabelCollapseSeries": "Скрий сериите",
"LabelCollapseSubSeries": "Свий подсерии",
@@ -281,8 +295,10 @@
"LabelContinueListening": "Продължи слушане",
"LabelContinueReading": "Продължи четене",
"LabelContinueSeries": "Продължи серии",
"LabelCorsAllowed": "Разрешени CORS Origins",
"LabelCover": "Корица",
"LabelCoverImageURL": "URL на Корица",
"LabelCoverProvider": "Източник за обложки",
"LabelCreatedAt": "Създадено на",
"LabelCronExpression": "Cron израз",
"LabelCurrent": "Текущо",
@@ -293,6 +309,7 @@
"LabelDeleteFromFileSystemCheckbox": "Изтрий от файловата система (отмени за да бъдат премахни само от базата данни)",
"LabelDescription": "Описание",
"LabelDeselectAll": "Премахни всички",
"LabelDetectedPattern": "Намерен образец:",
"LabelDevice": "Устройство",
"LabelDeviceInfo": "Информация за Устройство",
"LabelDeviceIsAvailableTo": "Устройството е достъпно за ...",
@@ -325,15 +342,28 @@
"LabelEncodingClearItemCache": "Уверете се, че периодично изчиствате кеша на елементите.",
"LabelEncodingFinishedM4B": "Завършеният M4B файл ще бъде поставен в папката на вашите аудиокниги на:",
"LabelEncodingInfoEmbedded": "Метаданните ще бъдат вградени в аудио траковете в папката на вашите аудиокниги.",
"LabelEncodingStartedNavigation": "Когато задачата е стартирана, можете да смените тази страница.",
"LabelEncodingTimeWarning": "Кодирането може да отнеме до 30 минути.",
"LabelEncodingWarningAdvancedSettings": "Внимание: Не променяйте тези настройки, ако не сте запознати с ffmpeg настройките за кодиране.",
"LabelEncodingWatcherDisabled": "Ако сте изключили наблюдението на папки, ще е нужно да сканирате повторно аудио книгата.",
"LabelEnd": "Край",
"LabelEndOfChapter": "Край на глава",
"LabelEpisode": "Епизод",
"LabelEpisodeNotLinkedToRssFeed": "Епизодът не е свързан с RSS канал",
"LabelEpisodeNumber": "Епизод #{0}",
"LabelEpisodeTitle": "Заглавие на Епизод",
"LabelEpisodeType": "Тип на Епизод",
"LabelEpisodeUrlFromRssFeed": "URL адрес на епизод от RSS канал",
"LabelEpisodes": "Епизоди",
"LabelEpisodic": "Епизодичен",
"LabelExample": "Пример",
"LabelExpandSeries": "Покажи сериите",
"LabelExpandSubSeries": "Покажи съб сериите",
"LabelExplicit": "С нецензурно съдържание",
"LabelExpired": "Изтекъл",
"LabelExpiresAt": "Изтича на",
"LabelExpiresInSeconds": "Изтича след (секунди)",
"LabelExpiresNever": "Никога",
"LabelExplicit": "Експлицитно",
"LabelExplicitChecked": "С нецензурно съдържание (проверено)",
"LabelExplicitUnchecked": "Без нецензурно съдържание (непроверено)",
"LabelExportOPML": "Експортирай OPML",
@@ -341,7 +371,9 @@
"LabelFetchingMetadata": "Взимане на Метаданни",
"LabelFile": "Файл",
"LabelFileBirthtime": "Дата на създаване на файла",
"LabelFileBornDate": "Роден {0}",
"LabelFileModified": "Дата на модификация на файла",
"LabelFileModifiedDate": "Променен {0}",
"LabelFilename": "Име на файла",
"LabelFilterByUser": "Филтриране по Потребител",
"LabelFindEpisodes": "Намери Епизоди",
@@ -355,14 +387,17 @@
"LabelFontScale": "Мащаб на шрифта",
"LabelFontStrikethrough": "Зачертан",
"LabelFormat": "Формат",
"LabelFull": "Пълен",
"LabelGenre": "Жанр",
"LabelGenres": "Жанрове",
"LabelHardDeleteFile": "Пълно Изтриване на Файл",
"LabelHasEbook": "Има е-книга",
"LabelHasSupplementaryEbook": "Има допълнителна е-книга",
"LabelHideSubtitles": "Скрий субтитри",
"LabelHighestPriority": "Най-висок Приоритет",
"LabelHost": "Хост",
"LabelHour": "Час",
"LabelHours": "Часа",
"LabelIcon": "Икона",
"LabelImageURLFromTheWeb": "URL на Изображение от Интернет",
"LabelInProgress": "В процес на изпълнение",
@@ -377,13 +412,17 @@
"LabelIntervalEvery6Hours": "Всеки 6 часа",
"LabelIntervalEveryDay": "Всеки ден",
"LabelIntervalEveryHour": "Всеки час",
"LabelIntervalEveryMinute": "Всяка минута",
"LabelInvert": "Обърни",
"LabelItem": "Елемент",
"LabelJumpBackwardAmount": "Количество за прескачане назад",
"LabelJumpForwardAmount": "Количество за прескачане напред",
"LabelLanguage": "Език",
"LabelLanguageDefaultServer": "Език по подразбиране на сървъра",
"LabelLanguages": "Езици",
"LabelLastBookAdded": "Последно Добавена Книга",
"LabelLastBookUpdated": "Последно Обновена Книга",
"LabelLastProgressDate": "Последен прогрес: {0}",
"LabelLastSeen": "Последно Видян",
"LabelLastTime": "Последно Време",
"LabelLastUpdate": "Последно Обновяване",
@@ -393,8 +432,10 @@
"LabelLess": "По-малко",
"LabelLibrariesAccessibleToUser": "Библиотеки Достъпни за Потребителя",
"LabelLibrary": "Библиотека",
"LabelLibraryFilterSublistEmpty": "Не {0}",
"LabelLibraryItem": "Елемент на Библиотека",
"LabelLibraryName": "Име на Библиотека",
"LabelLibrarySortByProgress": "Прогресът е обновен",
"LabelLimit": "Лимит",
"LabelLineSpacing": "Междуредие",
"LabelListenAgain": "Слушай отново",
@@ -403,8 +444,13 @@
"LabelLogLevelWarn": "Предупреждение",
"LabelLookForNewEpisodesAfterDate": "Търси нови епизоди след дата",
"LabelLowestPriority": "Най-нисък Приоритет",
"LabelMatchConfidence": "Увереност",
"LabelMatchExistingUsersBy": "Съпостави съществуващи потребители по",
"LabelMatchExistingUsersByDescription": "Използва се за свързване на съществуващи потребители. След свързване потребителите ще бъдат съпоставени по уникален идентификатор от вашия доставчик на SSO",
"LabelMaxEpisodesToDownload": "Максимален брой епизоди за сваляне. Използвай 0 за неограничен.",
"LabelMaxEpisodesToDownloadPerCheck": "Максимален брой нови епизоди за сваляне за проверка",
"LabelMaxEpisodesToKeep": "Максимален брой епизоди за запазване",
"LabelMaxEpisodesToKeepHelp": "Стойност 0 указва без максимален лимит. След като нов епизод е автоматично свален, най-старият епизод ще бъде изтрит, ако имате повече от X епизода. Само по един епизод ще бъде изтриван за всеки нов свален такъв.",
"LabelMediaPlayer": "Медия Плейър",
"LabelMediaType": "Тип медия",
"LabelMetaTag": "Мета Таг",
@@ -412,6 +458,7 @@
"LabelMetadataOrderOfPrecedenceDescription": "По-високите източници на метаданни ще заменят по-ниските",
"LabelMetadataProvider": "Доставчик на Метаданни",
"LabelMinute": "Минута",
"LabelMinutes": "Минути",
"LabelMissing": "Липсващо",
"LabelMissingEbook": "Няма електронна книга",
"LabelMissingSupplementaryEbook": "Няма допълнителна електронна книга",
@@ -427,7 +474,9 @@
"LabelNewestAuthors": "Най-новите автори",
"LabelNewestEpisodes": "Най-новите епизоди",
"LabelNextBackupDate": "Следваща Дата на Архивиране",
"LabelNextChapters": "Следващите глави ще бъдат:",
"LabelNextScheduledRun": "Следващо Планирано Изпълнение",
"LabelNoApiKeys": "Няма API ключове",
"LabelNoCustomMetadataProviders": "Няма потребителски доставчици на метаданни",
"LabelNoEpisodesSelected": "Няма избрани епизоди",
"LabelNotFinished": "Не е приключено",
@@ -443,17 +492,21 @@
"LabelNotificationsMaxQueueSize": "Максимален размер на опашката за известия",
"LabelNotificationsMaxQueueSizeHelp": "Събитията са ограничени до изстрелване на 1 на секунда. Събитията ще бъдат игнорирани ако опашката е на максимален размер. Това предотвратява спамирането на известия.",
"LabelNumberOfBooks": "Брой на Книги",
"LabelNumberOfChapters": "Брой глави:",
"LabelNumberOfEpisodes": "Брой епизоди",
"LabelOpenIDAdvancedPermsClaimDescription": "Име на OpenID твърдението, което съдържа разширени права за достъп до потребителски действия в приложението, които ще се прилагат за роли, различни от администраторските (<b>ако е конфигурирано</b>). Ако твърдението липсва в отговора, достъпът до ABS ще бъде отказан. Ако липсва една опция, тя ще се третира като <code>false</code>. Уверете се, че твърдението на доставчика на идентичност съответства на очакваната структура:",
"LabelOpenIDClaims": "Оставете следните опции празни, за да деактивирате разширеното присвояване на групи, като автоматично ще бъде присвоена групата 'Потребител'.",
"LabelOpenIDGroupClaimDescription": "Име на OpenID твърдението, което съдържа списък с групите на потребителя. Обикновено се нарича <code>groups</code>. <b>Ако е конфигурирано</b>, приложението автоматично ще присвоява роли въз основа на членството на потребителя в групи, при условие че тези групи са наименувани без чувствителност към регистъра като 'admin', 'user' или 'guest' в твърдението. Твърдението трябва да съдържа списък и ако потребителят принадлежи към множество групи, приложението ще присвои ролята, съответстваща на най-високото ниво на достъп. Ако няма съвпадение с група, достъпът ще бъде отказан.",
"LabelOpenRSSFeed": "Отвори RSS Feed",
"LabelOverwrite": "Презапиши",
"LabelPaginationPageXOfY": "Страница {0} от {1}",
"LabelPassword": "Парола",
"LabelPath": "Път",
"LabelPermanent": "Постоянен",
"LabelPermissionsAccessAllLibraries": "Може да достъпи до всички библиотеки",
"LabelPermissionsAccessAllTags": "Може да достъпи всички тагове",
"LabelPermissionsAccessExplicitContent": "Може да достъпи експлицитно съдържание",
"LabelPermissionsCreateEreader": "Може да създава електронен четец",
"LabelPermissionsDelete": "Може да трие",
"LabelPermissionsDownload": "Може да сваля",
"LabelPermissionsUpdate": "Може да обновява",
@@ -461,6 +514,8 @@
"LabelPersonalYearReview": "Преглед на годината Ви ({0})",
"LabelPhotoPathURL": "Път/URL на Снимка",
"LabelPlayMethod": "Метод на Пускане",
"LabelPlaybackRateIncrementDecrement": "Размер на увеличаване/намаляне при скоростта на възпроизвеждане",
"LabelPlayerChapterNumberMarker": "{0} от {1}",
"LabelPlaylists": "Плейлисти",
"LabelPodcast": "Подкаст",
"LabelPodcastSearchRegion": "Регион за Търсене на Подкасти",
@@ -472,18 +527,22 @@
"LabelPrimaryEbook": "Основна Електронна Книга",
"LabelProgress": "Прогрес",
"LabelProvider": "Доставчик",
"LabelProviderAuthorizationValue": "Стойност на Authorization Header",
"LabelPubDate": "Дата на публикуване",
"LabelPublishYear": "Година на публикуване",
"LabelPublishedDate": "Публикувани {0}",
"LabelPublishedDecade": "Десетилетие на публикуване",
"LabelPublishedDecades": "Десетилетия на публикуване",
"LabelPublisher": "Издател",
"LabelPublishers": "Издателство",
"LabelRSSFeedCustomOwnerEmail": "Персонализиран имейл на собственика",
"LabelRSSFeedCustomOwnerName": "Персонализирано име на собственика",
"LabelRSSFeedOpen": "RSS Feed Оптворен",
"LabelRSSFeedOpen": "RSS Feed е отворен",
"LabelRSSFeedPreventIndexing": "Предотвратете индексиране",
"LabelRSSFeedSlug": "идентификатор на RSS емисия",
"LabelRSSFeedURL": "URL на RSS емисия",
"LabelRandomly": "Случайно",
"LabelReAddSeriesToContinueListening": "Добави отново в \"Продължете да слушате\"",
"LabelRead": "Прочети",
"LabelReadAgain": "Прочети отново",
"LabelReadEbookWithoutProgress": "Прочети електронната книга без записване прогрес",
@@ -493,29 +552,41 @@
"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": "Позволи скриптово съдържание в epub-и",
"LabelSettingsEpubsAllowScriptedContentHelp": "Позволи epub файловете да изпълняват скриптове. Препоръчително е да бъде изключено освен ако не се доверявате на източника на epub файловете.",
@@ -527,10 +598,13 @@
"LabelSettingsHideSingleBookSeriesHelp": "Сериите с една книга ще бъдат скрити от страницата на серията и рафтовете на началната страница.",
"LabelSettingsHomePageBookshelfView": "Начална страница изглед на рафт",
"LabelSettingsLibraryBookshelfView": "Библиотека изглед на рафт",
"LabelSettingsLibraryMarkAsFinishedPercentComplete": "Процент завършеност е по-голям от",
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Оставащо време е по-малко от (секунди)",
"LabelSettingsLibraryMarkAsFinishedWhen": "Отбелязване на мултимедиен елемент като завършен когато",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Пропусни предишни книги в Продължи Поредица",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Рафтът на началната страница 'Продължи поредицата' показва първата книга, която не е започната в поредици, в които има поне една завършена книга и няма книги в процес на четене. Активирането на тази настройка ще продължи поредицата от най-далечната завършена книга вместо от първата незапочната книга.",
"LabelSettingsParseSubtitles": "Извлечи подзаглавия",
"LabelSettingsParseSubtitlesHelp": "Извлича подзаглавия от имената на папките на аудиокнигите.<br>Подзаглавията трябва да бъдат разделени с \" - \"<br>например \"Заглавие на Книга - Тук е Подзаглавито\" има подзаглавие \"Тук е Подзаглавито\"",
"LabelSettingsParseSubtitlesHelp": "Извлича подзаглавия от имената на папките на аудио книгите.<br>Подзаглавията трябва да бъдат разделени с \" - \"<br>например \"Заглавие на Книга - Тук е подзаглавието\" има подзаглавие \"Тук е подзаглавието\"",
"LabelSettingsPreferMatchedMetadata": "Предпочети съвпадащи метаданни",
"LabelSettingsPreferMatchedMetadataHelp": "Съвпадащите данни ще заменят детайлите на елемента при използване на Бързо Съпоставяне. По подразбиране Бързото Съпоставяне ще попълни само липсващите детайли.",
"LabelSettingsSkipMatchingBooksWithASIN": "Пропусни съвпадащи книги, които вече имат ASIN",
@@ -544,11 +618,19 @@
"LabelSettingsStoreMetadataWithItem": "Запази метаданните с елемента",
"LabelSettingsStoreMetadataWithItemHelp": "По подразбиране метаданните се съхраняват в /metadata/items, като активирате тази настройка метаданните ще се съхраняват в папката на елемента на вашата библиотека",
"LabelSettingsTimeFormat": "Формат на Време",
"LabelShare": "Сподели",
"LabelShareDownloadableHelp": "Разреши на потребителите през връзка за споделяне да свалят zip файл с мултимедийния елемент.",
"LabelShareOpen": "Общодостъпно",
"LabelShareURL": "URL за споделяне",
"LabelShowAll": "Покажи всички",
"LabelShowSeconds": "Покажи секунди",
"LabelShowSubtitles": "Показвай подзаглавия",
"LabelSize": "Размер",
"LabelSleepTimer": "Таймер за изключване",
"LabelSlug": "Слъг",
"LabelSortAscending": "Възходящ",
"LabelSortDescending": "Низходящ",
"LabelSortPubDate": "Подреди по дата на публикуване",
"LabelStart": "Старт",
"LabelStartTime": "Начално Време",
"LabelStarted": "Стартирано",
@@ -582,7 +664,13 @@
"LabelTheme": "Тема",
"LabelThemeDark": "Тъмна",
"LabelThemeLight": "Светла",
"LabelThemeSepia": "Сепия",
"LabelTimeBase": "Времева Основа",
"LabelTimeDurationXHours": "{0} часа",
"LabelTimeDurationXMinutes": "{0} минути",
"LabelTimeDurationXSeconds": "{0} секунди",
"LabelTimeInMinutes": "Време в минути",
"LabelTimeLeft": "остава {0}",
"LabelTimeListened": "Време Слушано",
"LabelTimeListenedToday": "Време Слушано Днес",
"LabelTimeRemaining": "{0} оставащи",
@@ -590,6 +678,7 @@
"LabelTitle": "Заглавие",
"LabelToolsEmbedMetadata": "Вграждане на Метаданни",
"LabelToolsEmbedMetadataDescription": "Вграждане на метаданни в аудио файлове, включително корица и глави.",
"LabelToolsM4bEncoder": "M4B кодировчик",
"LabelToolsMakeM4b": "Направи M4B Аудиокнига Файл",
"LabelToolsMakeM4bDescription": "Генериране на .M4B аудиокнига файл с вградени метаданни, корица и глави.",
"LabelToolsSplitM4b": "Раздели M4B на MP3-ки",
@@ -602,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": "Разрешете тези URL-и във вашият OAuth доставчик, за да позволите пренасочването обратно към уеб приложението след вход:",
"LabelWebRedirectURLsSubfolder": "Подпапка за URL адреси за пренасочване",
"LabelWeekdaysToRun": "Делници за изпълнение",
"LabelXBooks": "{0} книги",
"LabelXItems": "{0} елемента",
"LabelYearReviewHide": "Скрий ревю на годината ти",
"LabelYearReviewShow": "Виж ревю на годината ти",
"LabelYourAudiobookDuration": "Продължителност на вашата аудиокнига",
@@ -633,31 +732,51 @@
"LabelYourProgress": "Твоят прогрес",
"MessageAddToPlayerQueue": "Добави към опашката на плейъра",
"MessageAppriseDescription": "За да ползвате тази функция трябва да имате активна инстанция на <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> или на друго АПИ което да обработва тези заявки. <br />The Apprise API Url-а трябва дае пълния URL път за изпращане на известията, например, ако вашето АПИ ве подава от <code>http://192.168.1.1:8337</code> трябва да сложитев <code>http://192.168.1.1:8337/notify</code>.",
"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 feed-ове",
"MessageBookshelfNoResultsForFilter": "Няма резултат за филтер \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Няма резултати от заявката",
"MessageBookshelfNoSeries": "Нямаш сеЗЙ",
"MessageBulkChapterPattern": "Колко глави искате да добавите, използвайки тази схема за номериране?",
"MessageChapterEndIsAfter": "Краят на главата е след края на вашата аудиокнига",
"MessageChapterErrorFirstNotZero": "Първата глава трябва да започва от 0",
"MessageChapterErrorStartGteDuration": "Началото на главата трябва да бъде по-малко от продължителността на аудиокнигата",
"MessageChapterErrorStartLtPrev": "Началото на главата трябва да бъде по-голямо или равно на края на предишната глава",
"MessageChapterStartIsAfter": "Началото на главата е след края на вашата аудиокнига",
"MessageChaptersNotFound": "Главите не са намерени",
"MessageCheckingCron": "Проверяване на cron...",
"MessageConfirmCloseFeed": "Сигурни ли сте, че искате да затворите този feed?",
"MessageConfirmDeleteApiKey": "Сигурни ли сте, че искате да изтриете API ключ \"{0}\"?",
"MessageConfirmDeleteBackup": "Сигурни ли сте, че искате да изтриете този архив {0}?",
"MessageConfirmDeleteDevice": "Сигурни ли сте, че искате да изтриете е-четец \"{0}\"?",
"MessageConfirmDeleteFile": "Това ще изтрие файла от файловата Ви система. Сигурни ли сте?",
"MessageConfirmDeleteLibrary": "Сигурни ли сте, че искате да изтриете за винаги библиотека \"{0}\"?",
"MessageConfirmDeleteLibraryItem": "Това ще изтрие елемента от базата данни и файловата Ви система. Сигурни ли сте?",
"MessageConfirmDeleteLibraryItems": "Това ще изтрие {0} елемента от базата данни и файловата Ви система. Сигурни ли сте?",
"MessageConfirmDeleteMetadataProvider": "Сигурни ли сте, че искате да изтриете доставчика нa метаданни \"{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>Искате ли да продължите?",
@@ -666,6 +785,7 @@
"MessageConfirmRemoveAuthor": "Сигурни ли сте, че искате да премахнете автор \"{0}\"?",
"MessageConfirmRemoveCollection": "Сигурни ли сте, че искате да премахнете колекция \"{0}\"?",
"MessageConfirmRemoveEpisode": "Сигурни ли сте, че искате да премахнете епизод \"{0}\"?",
"MessageConfirmRemoveEpisodeNote": "Забележка: Това няма да доведе до изтриване на аудио файла, освен ако не активирате опцията \"Твърдо изтриване на файла\"",
"MessageConfirmRemoveEpisodes": "Сигурни ли сте, че искате да премахнете {0} епизода?",
"MessageConfirmRemoveListeningSessions": "Сигурни ли сте, че искате да премахнете {0} слушателски сесии?",
"MessageConfirmRemoveNarrator": "Сигурни ли сте, че искате да премахнете разказвач \"{0}\"?",
@@ -676,19 +796,27 @@
"MessageConfirmRenameTag": "Сигурни ли сте, че искате да преименувате таг \"{0}\" на \"{1}\" за всички елементи?",
"MessageConfirmRenameTagMergeNote": "Забележка: Този таг вече съществува и ще бъде слято.",
"MessageConfirmRenameTagWarning": "Внимание! Вече съществува подобен таг с различно писане \"{0}\".",
"MessageConfirmResetProgress": "Сигурни ли сте, че искате да нулирате прогреса си?",
"MessageConfirmSendEbookToDevice": "Сигурни ли сте, че искате да изпратите {0} електронна книга \"{1}\" до устройство \"{2}\"?",
"MessageConfirmUnlinkOpenId": "Сигурни ли сте, че искате да отвържете този потребител от OpenID?",
"MessageDaysListenedInTheLastYear": "{0} дни слушане през последната година",
"MessageDownloadingEpisode": "Сваля епизод",
"MessageDragFilesIntoTrackOrder": "Плъзнете файлове в правилния ред на каналите",
"MessageEmbedFailed": "Вграждането беше неуспешно!",
"MessageEmbedFinished": "Вграждането завърши!",
"MessageEmbedQueue": "Поставено в опашката за вграждане на метаданни ({0} в опашката)",
"MessageEpisodesQueuedForDownload": "{0} Епизод(и) са сложени за сваляне",
"MessageEreaderDevices": "За да осигурите доставката на е-книги, може да се наложи да добавите горепосочения имейл адрес като валиден подател за всяко устройство, изброено по-долу.",
"MessageFeedURLWillBe": "Адресът на емисията ще бъде {0}",
"MessageFetching": "Извличане...",
"MessageForceReScanDescription": "ще сканира всички файлове отново като прясно сканиране. Аудио файлове ID3 тагове, OPF файлове и текстови файлове ще бъдат сканирани като нови.",
"MessageHeatmapListeningTimeTooltip": "<strong>{0} слушане</strong> на {1}",
"MessageHeatmapNoListeningSessions": "Няма сесии за слушане на {0}",
"MessageImportantNotice": "Важно Съобщение!",
"MessageInsertChapterBelow": "Вмъкни глава под",
"MessageItemsSelected": "{0} избрани",
"MessageItemsUpdated": "{0} елемента обновени",
"MessageInvalidAsin": "Невалиден ASIN",
"MessageItemsSelected": "{0} избрани елемента",
"MessageItemsUpdated": "{0} обновени елемента",
"MessageJoinUsOn": "Присъединете се към нас",
"MessageLoading": "Зарежда...",
"MessageLoadingFolders": "Зареждане на Папки...",
@@ -709,6 +837,7 @@
"MessageNoCollections": "Няма колекции",
"MessageNoCoversFound": "Не са намерени корици",
"MessageNoDescription": "Няма описание",
"MessageNoDevices": "Няма устройства",
"MessageNoDownloadsInProgress": "Няма изтегляния в прогрес",
"MessageNoDownloadsQueued": "Няма изтегляния в опашка",
"MessageNoEpisodeMatchesFound": "Няма намерени съвпадения за епизоди",
@@ -722,6 +851,7 @@
"MessageNoLogs": "Няма логове",
"MessageNoMediaProgress": "Няма прогрес на медията",
"MessageNoNotifications": "Няма известия",
"MessageNoPodcastFeed": "Невалиден подкаст: Няма канал",
"MessageNoPodcastsFound": "Няма намерени подкасти",
"MessageNoResults": "Няма резултати",
"MessageNoSearchResultsFor": "Няма резултати за \"{0}\"",
@@ -730,13 +860,17 @@
"MessageNoTasksRunning": "Няма вършещи се задачи",
"MessageNoUpdatesWereNecessary": "Няма нужда от обновяване",
"MessageNoUserPlaylists": "Нямате създадени плейлисти",
"MessageNoUserPlaylistsHelp": "Плейлистите за частни. Само създалият ги потребител ще може да ги вижда.",
"MessageNotYetImplemented": "Още не е изпълнено",
"MessageOr": "или",
"MessagePauseChapter": "Пауза на глава",
"MessagePlayChapter": "Пусни налчалото на глава",
"MessagePlaylistCreateFromCollection": "Създай плейлист от колекция",
"MessagePleaseWait": "Моля изчакайте...",
"MessagePodcastHasNoRSSFeedForMatching": "Подкастът няма URL адрес на RSS feed за използване за съпоставяне",
"MessagePodcastSearchField": "Въведи какво да търся или RSS емисия адрес",
"MessageQuickEmbedInProgress": "Бързото вграждане е в процес на изпълнение",
"MessageQuickEmbedQueue": "Поставено в опашката за бързо вграждане ({0} в опашката)",
"MessageQuickMatchDescription": "Попълни празните детайли и корици с първия резултат от '{0}'. Не презаписва детайлите, освен ако не е активирана настройката 'Предпочети съвпадащи метаданни' на сървъра.",
"MessageRemoveChapter": "Премахни глава",
"MessageRemoveEpisodes": "Премахни {0} епизод(и)",
@@ -746,11 +880,43 @@
"MessageResetChaptersConfirm": "Сигурни ли сте, че искате да нулирате главите и да отмените промените, които сте направили?",
"MessageRestoreBackupConfirm": "Сигурни ли сте, че искате да възстановите архива създаден на",
"MessageRestoreBackupWarning": "Възстановяването на архив ще презапише цялата база данни, намираща се в /config и кориците в /metadata/items & /metadata/authors.<br /><br />Архивите не променят файловете в папките на вашата библиотека. Ако сте активирали настройките на сървъра за съхранение на корици и метаданни в папките на вашата библиотека, те няма да бъдат архивирани или презаписани.<br /><br />Всички клиенти, използващи вашия сървър, ще бъдат автоматично обновени.",
"MessageScheduleRunEveryWeekdayAtTime": "Изпълни всеки {0} в {1}",
"MessageSearchResultsFor": "Резултати от търсенето за",
"MessageSelected": "{0} избрани",
"MessageServerCouldNotBeReached": "Сървърът не може да бъде достигнат",
"MessageSetChaptersFromTracksDescription": "Задайте глави, като използвате всеки аудио файл като глава и заглавие на главата като име на аудио файла",
"MessageShareExpiresIn": "Изтича след {0}",
"MessageStartPlaybackAtTime": "Започни възпроизвеждане на \"{0}\" в {1}?",
"MessageTaskDownloadingEpisodeDescription": "Изтегляне на епизод \"{0}\"",
"MessageTaskEmbeddingMetadata": "Вграждане на метаданни",
"MessageTaskEmbeddingMetadataDescription": "Вграждане на метаданни в аудиокнига \"{0}\"",
"MessageTaskEncodingM4bDescription": "Кодиране на аудиокнига \"{0}\" в единичен m4b файл",
"MessageTaskFailed": "Неуспешно",
"MessageTaskFailedToBackupAudioFile": "Неуспешно създаване на разервно копие на аудио файл \"{0}\"",
"MessageTaskFailedToCreateCacheDirectory": "Неуспешно създаване на директория за кеширане",
"MessageTaskFailedToEmbedMetadataInFile": "Неуспешно вграждане на метаданни във файл \"{0}\"",
"MessageTaskFailedToMergeAudioFiles": "Неуспешно сливане на аудио файловете",
"MessageTaskFailedToMoveM4bFile": "Неуспешно преместване на m4b файл",
"MessageTaskFailedToWriteMetadataFile": "Неуспешно записване на файла за метаданни",
"MessageTaskMatchingBooksInLibrary": "Съответстващи книги в библиотека \"{0}\"",
"MessageTaskNoFilesToScan": "Няма файлове за сканиране",
"MessageTaskOpmlImport": "OPML импортиране",
"MessageTaskOpmlImportDescription": "Създаване на подкасти от {0} RSS хранилки",
"MessageTaskOpmlImportFeedDescription": "Импортиране на RSS хранилка \"{0}\"",
"MessageTaskOpmlImportFeedPodcastDescription": "Създаване на подкаст \"{0}\"",
"MessageTaskOpmlImportFeedPodcastExists": "На този път вече съществува подкаст",
"MessageTaskOpmlImportFeedPodcastFailed": "Неуспешно създаване на подкаст",
"MessageTaskOpmlImportFinished": "Добавени {0} подкаста",
"MessageTaskOpmlParseFailed": "Неуспешно анализиране на OPML файла",
"MessageTaskOpmlParseFastFail": "Невалиден OPML файл, не беше намерен нито <opml> таг нито <outline> таг",
"MessageTaskOpmlParseNoneFound": "Няма намерени канали във OPML файла",
"MessageTaskScanItemsAdded": "{0} добавени",
"MessageTaskScanItemsMissing": "{0} липсващи",
"MessageTaskScanItemsUpdated": "{0} обновени",
"MessageTaskScanNoChangesNeeded": "Не са нужни промени",
"MessageTaskScanningFileChanges": "Проверка за промени във файловете в \"{0}\"",
"MessageTaskScanningLibrary": "Сканиране на \"{0}\" библиотека",
"MessageTaskTargetDirectoryNotWritable": "Целевата директория не е достъпна за запис",
"MessageThinking": "Мисля...",
"MessageUploaderItemFailed": "Неуспешно качване",
"MessageUploaderItemSuccess": "Успешно качване!",
@@ -768,11 +934,18 @@
"NoteUploaderFoldersWithMediaFiles": "Папките с медийни файлове ще бъдат обработени като отделни елементи на библиотеката.",
"NoteUploaderOnlyAudioFiles": "Ако качвате само аудио файлове, то всеки аудио файл ще бъде обработен като отделна аудиокнига.",
"NoteUploaderUnsupportedFiles": "Неподдържаните файлове се игнорират. При избор или пускане на папка, други файлове, които не са в папка на елемент, се игнорират.",
"NotificationOnBackupCompletedDescription": "Изпълнява се при завършване на създаване на резервно копие",
"NotificationOnBackupFailedDescription": "Изпълнява се при неуспешено създаване на резервно копие",
"NotificationOnEpisodeDownloadedDescription": "Изпълнява се при автоматично изтегляне на подкаст епизод",
"NotificationOnRSSFeedDisabledDescription": "Изпълнява се, когато автоматичното изтегляне на епизодите е деактивирано, поради твърде много неуспешни опити",
"PlaceholderNewCollection": "Ново име на колекцията",
"PlaceholderNewFolderPath": "Нов път на папката",
"PlaceholderNewPlaylist": "Ново име на плейлиста",
"PlaceholderSearch": "Търсене...",
"PlaceholderSearchEpisode": "Търсене на Епизоди...",
"StatsAuthorsAdded": "добаврени автори",
"StatsBooksAdded": "добавени книги",
"StatsBooksFinished": "завършени книги",
"ToastAccountUpdateSuccess": "Успешно обновяване на акаунта",
"ToastAuthorImageRemoveSuccess": "Авторската снимка е премахната",
"ToastAuthorUpdateMerged": "Обновяване на автора сливано",

View File

@@ -1,35 +1,35 @@
{
"ButtonAdd": "Afegeix",
"ButtonAddChapters": "Afegeix",
"ButtonAddDevice": "Afegeix Dispositiu",
"ButtonAddLibrary": "Crea Biblioteca",
"ButtonAddChapters": "Afegeix capítols",
"ButtonAddDevice": "Afegeix un aparell",
"ButtonAddLibrary": "Afegeix una biblioteca",
"ButtonAddPodcasts": "Afegeix pòdcasts",
"ButtonAddUser": "Crea Usuari",
"ButtonAddYourFirstLibrary": "Crea la teva Primera Biblioteca",
"ButtonAddUser": "Afegeix un usuari",
"ButtonAddYourFirstLibrary": "Afegiu la vostra primera biblioteca",
"ButtonApply": "Aplica",
"ButtonApplyChapters": "Aplica Capítols",
"ButtonApplyChapters": "Aplica capítols",
"ButtonAuthors": "Autors",
"ButtonBack": "Enrere",
"ButtonBatchEditPopulateFromExisting": "Omplir des d'existent",
"ButtonBatchEditPopulateMapDetails": "Omplir detalls del mapa",
"ButtonBrowseForFolder": "Cerca Carpeta",
"ButtonBatchEditPopulateMapDetails": "Omple els detalls del mapa",
"ButtonBrowseForFolder": "Cerca una carpeta",
"ButtonCancel": "Cancel·la",
"ButtonCancelEncode": "Cancel·la Codificador",
"ButtonCancelEncode": "Cancel·la la codificació",
"ButtonChangeRootPassword": "Canvia Contrasenya Root",
"ButtonCheckAndDownloadNewEpisodes": "Verifica i Descarrega Nous Episodis",
"ButtonChooseAFolder": "Tria una Carpeta",
"ButtonChooseFiles": "Tria un Fitxer",
"ButtonClearFilter": "Elimina Filtres",
"ButtonCloseFeed": "Tanca Font",
"ButtonChooseAFolder": "Trieu una carpeta",
"ButtonChooseFiles": "Trieu fitxers",
"ButtonClearFilter": "Neteja el filtre",
"ButtonCloseFeed": "Tanca el canal",
"ButtonCloseSession": "Tanca la sessió oberta",
"ButtonCollections": "Col·leccions",
"ButtonConfigureScanner": "Configura Escàner",
"ButtonCreate": "Crea",
"ButtonCreateBackup": "Crea Còpia de Seguretat",
"ButtonDelete": "Elimina",
"ButtonDelete": "Suprimeix",
"ButtonDownloadQueue": "Cua",
"ButtonEdit": "Edita",
"ButtonEditChapters": "Edita Capítol",
"ButtonEditChapters": "Edita capítols",
"ButtonEditPodcast": "Edita el pòdcast",
"ButtonEnable": "Habilita",
"ButtonFireAndFail": "Executat i fallat",
@@ -177,6 +177,7 @@
"HeaderPlaylist": "Llista de Reproducció",
"HeaderPlaylistItems": "Elements de la Llista de Reproducció",
"HeaderPodcastsToAdd": "Pòdcasts a afegir",
"HeaderPresets": "Valors predefinits",
"HeaderPreviewCover": "Previsualització de la Portada",
"HeaderRSSFeedGeneral": "Detalls RSS",
"HeaderRSSFeedIsOpen": "La Font RSS està oberta",
@@ -192,7 +193,7 @@
"HeaderSettings": "Paràmetres",
"HeaderSettingsDisplay": "Interfície",
"HeaderSettingsExperimental": "Funcionalitats experimentals",
"HeaderSettingsGeneral": "General",
"HeaderSettingsGeneral": "Generals",
"HeaderSettingsScanner": "Escàner",
"HeaderSettingsWebClient": "Client web",
"HeaderSleepTimer": "Temporitzador de son",
@@ -221,10 +222,10 @@
"LabelAccountTypeUser": "Usuari",
"LabelActivities": "Activitats",
"LabelActivity": "Activitat",
"LabelAddToCollection": "Afegit a la Col·lecció",
"LabelAddToCollectionBatch": "S'han Afegit {0} Llibres a la Col·lecció",
"LabelAddToPlaylist": "Afegit a la llista de reproducció",
"LabelAddToPlaylistBatch": "S'han Afegit {0} Elements a la Llista de Reproducció",
"LabelAddToCollection": "Afegeix a la col·lecció",
"LabelAddToCollectionBatch": "Afegeix {0} llibres a la col·lecció",
"LabelAddToPlaylist": "Afegeix a la llista de reproducció",
"LabelAddToPlaylistBatch": "Afegeix {0} elements a la llista de reproducció",
"LabelAddedAt": "Afegit",
"LabelAddedDate": "{0} Afegit",
"LabelAdminUsersOnly": "Només usuaris administradors",
@@ -233,7 +234,7 @@
"LabelAllUsers": "Tots els usuaris",
"LabelAllUsersExcludingGuests": "Tots els usuaris excepte convidats",
"LabelAllUsersIncludingGuests": "Tots els usuaris i convidats",
"LabelAlreadyInYourLibrary": "Ja existeix a la Biblioteca",
"LabelAlreadyInYourLibrary": "Ja existeix a la biblioteca",
"LabelApiToken": "Testimoni de l'API",
"LabelAppend": "Adjuntar",
"LabelAudioBitrate": "Taxa de bits d'àudio (per exemple, 128k)",
@@ -290,14 +291,14 @@
"LabelCronExpression": "Expressió de Cron",
"LabelCurrent": "Actual",
"LabelCurrently": "En aquest moment:",
"LabelCustomCronExpression": "Expressió de Cron Personalitzada:",
"LabelDatetime": "Hora i Data",
"LabelCustomCronExpression": "Expressió del Cron personalitzada:",
"LabelDatetime": "Data i hora",
"LabelDays": "Dies",
"LabelDeleteFromFileSystemCheckbox": "Suprimeix del sistema de fitxers (desmarqueu per a eliminar de la base de dades només)",
"LabelDescription": "Descripció",
"LabelDeselectAll": "Desseleccionar Tots",
"LabelDevice": "Dispositiu",
"LabelDeviceInfo": "Informació del Dispositiu",
"LabelDeviceInfo": "Informació de l'aparell",
"LabelDeviceIsAvailableTo": "El dispositiu està disponible per a...",
"LabelDirectory": "Directori",
"LabelDiscFromFilename": "Disc a partir del nom de fitxer",
@@ -335,11 +336,11 @@
"LabelEnd": "Fi",
"LabelEndOfChapter": "Fi del capítol",
"LabelEpisode": "Episodi",
"LabelEpisodeNotLinkedToRssFeed": "Episodi no enllaçat al feed RSS",
"LabelEpisodeNotLinkedToRssFeed": "Episodi no enllaçat al canal RSS",
"LabelEpisodeNumber": "Episodi #{0}",
"LabelEpisodeTitle": "Títol de l'Episodi",
"LabelEpisodeType": "Tipus d'Episodi",
"LabelEpisodeUrlFromRssFeed": "URL de l'episodi del feed RSS",
"LabelEpisodeUrlFromRssFeed": "URL de l'episodi del canal RSS",
"LabelEpisodes": "Episodis",
"LabelEpisodic": "Episodis",
"LabelExample": "Exemple",
@@ -352,7 +353,7 @@
"LabelFeedURL": "Font de URL",
"LabelFetchingMetadata": "Obtenció de metadades",
"LabelFile": "Fitxer",
"LabelFileBirthtime": "Arxiu creat a",
"LabelFileBirthtime": "Fitxer creat a",
"LabelFileBornDate": "Creat {0}",
"LabelFileModified": "Fitxer modificat",
"LabelFileModifiedDate": "Modificat {0}",
@@ -439,7 +440,7 @@
"LabelMinute": "Minut",
"LabelMinutes": "Minuts",
"LabelMissing": "Absent",
"LabelMissingEbook": "No té ebook",
"LabelMissingEbook": "No té llibre electrònic",
"LabelMissingSupplementaryEbook": "No té ebook complementari",
"LabelMobileRedirectURIs": "URI de redirecció mòbil permeses",
"LabelMobileRedirectURIsDescription": "Aquesta és una llista blanca d'URI de redirecció vàlides per a aplicacions mòbils. El predeterminat és <code> audiobookshelf</code>, que pots eliminar o complementar amb URI addicionals per a la integració d'aplicacions de tercers. Usant un asterisc (<code> *</code>) com a única entrada que permet qualsevol URI.",
@@ -473,6 +474,7 @@
"LabelOpenIDAdvancedPermsClaimDescription": "Nom de la notificació de OpenID que conté permisos avançats per accions d'usuari dins l'aplicació que s'aplicaran a rols que no siguin d'administrador (<b>si estan configurats</b>). Si el reclam no apareix en la resposta, es denegarà l'accés a ABS. Si manca una sola opció, es tractarà com a <code>falsa</code>. Assegura't que la notificació del proveïdor d'identitats coincideixi amb l'estructura esperada:",
"LabelOpenIDClaims": "Deixa les següents opcions buides per desactivar l'assignació avançada de grups i permisos, el que assignaria automàticament al grup 'Usuari'.",
"LabelOpenIDGroupClaimDescription": "Nom de la declaració OpenID que conté una llista de grups de l'usuari. Comunament coneguts com <code>grups</code>. <b>Si es configura</b>, l'aplicació assignarà automàticament rols basats en la pertinença a grups de l'usuari, sempre que aquests grups es denominen 'admin', 'user' o 'guest' en la notificació. La sol·licitud ha de contenir una llista, i si un usuari pertany a diversos grups, l'aplicació assignarà el rol corresponent al major nivell d'accés. Si cap grup coincideix, es denegarà l'accés.",
"LabelOpenRSSFeed": "Obre el canal RSS",
"LabelOverwrite": "Sobreescriure",
"LabelPaginationPageXOfY": "Pàgina {0} de {1}",
"LabelPassword": "Contrasenya",
@@ -496,25 +498,25 @@
"LabelPodcastType": "Tipus de pòdcast",
"LabelPodcasts": "Pòdcasts",
"LabelPort": "Port",
"LabelPrefixesToIgnore": "Prefixos per Ignorar (no distingeix entre majúscules i minúscules.)",
"LabelPreventIndexing": "Evita que la teva font sigui indexada pels directoris de podcasts d'iTunes i Google",
"LabelPrimaryEbook": "Ebook Principal",
"LabelPrefixesToIgnore": "Prefixos a ignorar (no distingeix entre majúscules i minúscules)",
"LabelPreventIndexing": "Evita que el vostre canal l'indexin els directoris de pòdcasts de l'iTunes i Google",
"LabelPrimaryEbook": "Llibre electrònic principal",
"LabelProgress": "Progrés",
"LabelProvider": "Proveïdor",
"LabelProviderAuthorizationValue": "Valor de l'encapçalament d'autorització",
"LabelPubDate": "Data de Publicació",
"LabelPublishYear": "Any de Publicació",
"LabelPubDate": "Data de publicació",
"LabelPublishYear": "Any de publicació",
"LabelPublishedDate": "Publicat {0}",
"LabelPublishedDecade": "Dècada de Publicació",
"LabelPublishedDecade": "Dècada de publicació",
"LabelPublishedDecades": "Dècades Publicades",
"LabelPublisher": "Editor",
"LabelPublishers": "Editors",
"LabelRSSFeedCustomOwnerEmail": "Correu Electrònic Personalitzat del Propietari",
"LabelRSSFeedCustomOwnerName": "Nom Personalitzat del Propietari",
"LabelRSSFeedOpen": "Font RSS Oberta",
"LabelRSSFeedPreventIndexing": "Evitar l'indexació",
"LabelRSSFeedSlug": "Font RSS Slug",
"LabelRSSFeedURL": "URL de la Font RSS",
"LabelRSSFeedPreventIndexing": "Evita la indexació",
"LabelRSSFeedSlug": "URL semàntic del canal RSS",
"LabelRSSFeedURL": "URL del canal RSS",
"LabelRandomly": "A l'atzar",
"LabelReAddSeriesToContinueListening": "Reafegir la sèrie per continuar escoltant-la",
"LabelRead": "Llegit",
@@ -523,52 +525,61 @@
"LabelRecentSeries": "Sèries recents",
"LabelRecentlyAdded": "Addicions recents",
"LabelRecommended": "Recomanats",
"LabelRedo": "Refer",
"LabelRedo": "Refés",
"LabelRegion": "Regió",
"LabelReleaseDate": "Data d'Estrena",
"LabelRemoveAllMetadataAbs": "Eliminar tots els fitxers metadata.abs",
"LabelRemoveAllMetadataJson": "Eliminar tots els fitxers metadata.json",
"LabelRemoveCover": "Eliminar Coberta",
"LabelReleaseDate": "Data d'estrena",
"LabelRemoveAllMetadataAbs": "Elimina tots els fitxers metadata.abs",
"LabelRemoveAllMetadataJson": "Elimina tots els fitxers metadata.json",
"LabelRemoveAudibleBranding": "Elimina la introducció i el tancament de l'Audible dels capítols",
"LabelRemoveCover": "Elimina la coberta",
"LabelRemoveMetadataFile": "Eliminar fitxers de metadades en carpetes d'elements de biblioteca",
"LabelRemoveMetadataFileHelp": "Elimina tots els fitxers metadata.json i metadata.abs de les teves carpetes {0}.",
"LabelRowsPerPage": "Files per Pàgina",
"LabelSearchTerm": "Cercar Terme",
"LabelSearchTitle": "Cercar Títol",
"LabelSearchTitleOrASIN": "Cercar Títol o ASIN",
"LabelRemoveMetadataFileHelp": "Elimina tots els fitxers metadata.json i metadata.abs de les vostres carpetes {0}.",
"LabelRowsPerPage": "Files per pàgina",
"LabelSearchTerm": "Cerca terme",
"LabelSearchTitle": "Cerca títol",
"LabelSearchTitleOrASIN": "Cerca títol o ASIN",
"LabelSeason": "Temporada",
"LabelSeasonNumber": "Temporada #{0}",
"LabelSelectAll": "Seleccionar tot",
"LabelSelectAllEpisodes": "Seleccionar tots els episodis",
"LabelSeasonNumber": "{0}a temporada",
"LabelSelectAll": "Selecciona-ho tot",
"LabelSelectAllEpisodes": "Selecciona tots els episodis",
"LabelSelectEpisodesShowing": "Seleccionar els {0} episodis visibles",
"LabelSelectUsers": "Seleccionar usuaris",
"LabelSendEbookToDevice": "Enviar Ebook a...",
"LabelSequence": "Seqüència",
"LabelSerial": "En sèrie",
"LabelSeries": "Sèries",
"LabelSeriesName": "Nom de la Sèrie",
"LabelSeriesProgress": "Progrés de la Sèrie",
"LabelSeries": "Sèrie",
"LabelSeriesName": "Nom de la sèrie",
"LabelSeriesProgress": "Progrés de la sèrie",
"LabelServerLogLevel": "Nivell de registre del servidor",
"LabelServerYearReview": "Resum de l'any del servidor ({0})",
"LabelSetEbookAsPrimary": "Establir com a principal",
"LabelSetEbookAsSupplementary": "Establir com a suplementari",
"LabelSettingsAudiobooksOnly": "Només Audiollibres",
"LabelSettingsAudiobooksOnlyHelp": "Activant aquesta opció s'ignoraran els fitxers d'ebook, excepte si estan dins d'una carpeta d'audiollibre, en aquest cas es marcaran com ebooks suplementaris",
"LabelSettingsAudiobooksOnly": "Només audiollibres",
"LabelSettingsAudiobooksOnlyHelp": "En activar aquesta opció s'ignoraran els fitxers de llibre electrònic, excepte si estan dins d'una carpeta d'audiollibre; en aquest cas es marcaran com a llibres suplementaris",
"LabelSettingsBookshelfViewHelp": "Disseny esqueomorf amb prestatgeries de fusta",
"LabelSettingsChromecastSupport": "Compatibilitat amb Chromecast",
"LabelSettingsDateFormat": "Format de Data",
"LabelSettingsDateFormat": "Format de data",
"LabelSettingsEnableWatcherHelp": "Permet afegir/actualitzar elements automàticament quan es detectin canvis en els fitxers. *Requereix reiniciar el servidor",
"LabelSettingsEpubsAllowScriptedContent": "Permetre scripts en epubs",
"LabelSettingsEpubsAllowScriptedContentHelp": "Permetre que els fitxers epub executin scripts. Es recomana mantenir aquesta opció desactivada tret que confiïs en l'origen dels fitxers epub.",
"LabelSettingsExperimentalFeatures": "Funcions Experimentals",
"LabelSettingsExperimentalFeaturesHelp": "Funcions en desenvolupament que es beneficiarien dels teus comentaris i experiències de prova. Feu clic aquí per obrir una conversa a Github.",
"LabelSettingsFindCovers": "Troba cobertes",
"LabelSettingsHideSingleBookSeries": "Amaga les sèries amb un sol llibre",
"LabelSettingsParseSubtitles": "Analitza els subtítols",
"LabelSettingsSortingIgnorePrefixes": "Ignora els prefixos en ordenar",
"LabelSettingsTimeFormat": "Format d'hora",
"LabelShare": "Comparteix",
"LabelShareDownloadableHelp": "Permet els usuaris amb l'enllaç de compartició de baixar un fitxer ZIP amb l'element de la biblioteca.",
"LabelShareURL": "URL de compartició",
"LabelShowAll": "Mostra-ho tot",
"LabelShowSeconds": "Mostra segons",
"LabelShowSubtitles": "Mostra subtítols",
"LabelSize": "Mida",
"LabelSleepTimer": "Temporitzador de repòs",
"LabelSlug": "Slug",
"LabelSortAscending": "Ascendent",
"LabelSortDescending": "Descendent",
"LabelStart": "Inicia",
"LabelStartTime": "Hora d'inici",
"LabelStarted": "Iniciat",
@@ -656,89 +667,98 @@
"LabelViewPlayerSettings": "Mostra els ajustaments del reproductor",
"LabelViewQueue": "Mostra cua del reproductor",
"LabelVolume": "Volum",
"LabelWebRedirectURLsDescription": "Autoritza aquestes URL al teu proveïdor OAuth per permetre redirecció a l'aplicació web després d'iniciar sessió:",
"LabelWebRedirectURLsDescription": "Autoritzeu aquests URL al vostre proveïdor OAuth per a permetre redirigir a laplicació web després d'iniciar sessió:",
"LabelWebRedirectURLsSubfolder": "Subcarpeta per a URL de redirecció",
"LabelWeekdaysToRun": "Executar en dies de la setmana",
"LabelXBooks": "{0} llibres",
"LabelXItems": "{0} elements",
"LabelYearReviewHide": "Oculta resum de l'any",
"LabelYearReviewShow": "Mostra resum de l'any",
"LabelYourAudiobookDuration": "Duració del teu audiollibre",
"LabelYourAudiobookDuration": "Duració del vostre audiollibre",
"LabelYourBookmarks": "Els vostres marcadors",
"LabelYourPlaylists": "Les teves llistes",
"LabelYourPlaylists": "Les vostres llistes",
"LabelYourProgress": "El vostre progrés",
"MessageAddToPlayerQueue": "Afegeix a la cua del reproductor",
"MessageAppriseDescription": "Per utilitzar aquesta funció, hauràs de tenir l'<a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">API d'Apprise</a> en funcionament o una API que gestioni resultats similars. <br/>La URL de l'API d'Apprise ha de tenir la mateixa ruta d'arxius que on s'envien les notificacions. Per exemple: si la teva API és a <code>http://192.168.1.1:8337</code>, llavors posaries <code>http://192.168.1.1:8337/notify</code>.",
"MessageAuthenticationOIDCChangesRestart": "Reengegueu el servidor després de desar perquè s'hi apliquin els canvis d'OIDC.",
"MessageBackupsDescription": "Les còpies de seguretat inclouen: usuaris, progrés dels usuaris, detalls dels elements de la biblioteca, configuració del servidor i imatges a <code>/metadata/items</code> i <code>/metadata/authors</code>. Les còpies de seguretat <strong>NO</strong> inclouen cap fitxer guardat a la carpeta de la teva biblioteca.",
"MessageBackupsLocationEditNote": "Nota: Actualitzar la ubicació de la còpia de seguretat no mourà ni modificarà les còpies existents",
"MessageBackupsLocationNoEditNote": "Nota: La ubicació de la còpia de seguretat es defineix mitjançant una variable d'entorn i no es pot modificar aquí.",
"MessageBackupsLocationPathEmpty": "La ruta de la còpia de seguretat no pot estar buida",
"MessageBatchQuickMatchDescription": "La funció \"Troba Ràpid\" intentarà afegir portades i metadades que falten als elements seleccionats. Activa l'opció següent perquè \"Troba Ràpid\" pugui sobreescriure portades i/o metadades existents.",
"MessageBookshelfNoCollections": "No tens cap col·lecció",
"MessageBookshelfNoCollections": "Encara no heu fet cap col·lecció",
"MessageBookshelfNoCollectionsHelp": "Les col·leccions són públiques. Tots els usuaris amb accés a la biblioteca les podran veure.",
"MessageBookshelfNoRSSFeeds": "Cap font RSS està oberta",
"MessageBookshelfNoResultsForFilter": "Cap resultat per al filtre \"{0}: {1}\"",
"MessageBookshelfNoResultsForFilter": "Cap resultat per al filtre «{0}: {1}»",
"MessageBookshelfNoResultsForQuery": "Cap resultat per a la consulta",
"MessageBookshelfNoSeries": "No tens cap sèrie",
"MessageBookshelfNoSeries": "No teniu cap sèrie",
"MessageChapterEndIsAfter": "El final del capítol és després del final del teu audiollibre",
"MessageChapterErrorFirstNotZero": "El primer capítol ha de començar a 0",
"MessageChapterErrorStartGteDuration": "El temps d'inici no és vàlid: ha de ser inferior a la durada de l'audiollibre",
"MessageChapterErrorStartLtPrev": "El temps d'inici no és vàlid: ha de ser igual o més gran que el temps d'inici del capítol anterior",
"MessageChapterStartIsAfter": "L'inici del capítol és després del final del teu audiollibre",
"MessageChaptersNotFound": "No s'han trobat els capítols",
"MessageCheckingCron": "Comprovant cron...",
"MessageConfirmCloseFeed": "Estàs segur que vols tancar aquesta font?",
"MessageConfirmDeleteBackup": "Estàs segur que vols eliminar la còpia de seguretat {0}?",
"MessageConfirmDeleteDevice": "Estàs segur que vols eliminar el lector electrònic \"{0}\"?",
"MessageConfirmDeleteFile": "Això eliminarà el fitxer del teu sistema. Estàs segur?",
"MessageConfirmDeleteLibrary": "Estàs segur que vols eliminar permanentment la biblioteca \"{0}\"?",
"MessageConfirmDeleteLibraryItem": "Això eliminarà l'element de la base de dades i del sistema. Estàs segur?",
"MessageConfirmDeleteLibraryItems": "Això eliminarà {0} element(s) de la base de dades i del sistema. Estàs segur?",
"MessageConfirmDeleteMetadataProvider": "Estàs segur que vols eliminar el proveïdor de metadades personalitzat \"{0}\"?",
"MessageConfirmDeleteNotification": "Estàs segur que vols eliminar aquesta notificació?",
"MessageConfirmDeleteSession": "Estàs segur que vols eliminar aquesta sessió?",
"MessageConfirmEmbedMetadataInAudioFiles": "Estàs segur que vols incrustar metadades a {0} fitxer(s) d'àudio?",
"MessageConfirmForceReScan": "Estàs segur que vols forçar un reescaneig?",
"MessageConfirmMarkAllEpisodesFinished": "Estàs segur que vols marcar tots els episodis com a acabats?",
"MessageConfirmMarkAllEpisodesNotFinished": "Estàs segur que vols marcar tots els episodis com a no acabats?",
"MessageConfirmMarkItemFinished": "Estàs segur que vols marcar \"{0}\" com a acabat?",
"MessageConfirmMarkItemNotFinished": "Estàs segur que vols marcar \"{0}\" com a no acabat?",
"MessageConfirmMarkSeriesFinished": "Estàs segur que vols marcar tots els llibres d'aquesta sèrie com a acabats?",
"MessageConfirmMarkSeriesNotFinished": "Estàs segur que vols marcar tots els llibres d'aquesta sèrie com a no acabats?",
"MessageConfirmNotificationTestTrigger": "Vols activar aquesta notificació amb dades de prova?",
"MessageConfirmPurgeCache": "Esborrar la memòria cau eliminarà tot el directori localitzat a <code>/metadata/cache</code>. <br /><br />Estàs segur que vols eliminar-lo?",
"MessageConfirmCloseFeed": "Segur que voleu tancar aquest canal?",
"MessageConfirmDeleteBackup": "Segur que voleu suprimir la còpia de seguretat de {0}?",
"MessageConfirmDeleteDevice": "Segur que voleu suprimir el lector electrònic «{0}»?",
"MessageConfirmDeleteFile": "Això suprimirà el fitxer del vostre sistema de fitxers. N'esteu segur?",
"MessageConfirmDeleteLibrary": "Segur que voleu suprimir permanentment la biblioteca «{0}»?",
"MessageConfirmDeleteLibraryItem": "Això suprimirà lelement de la base de dades i del sistema de fitxers. Nesteu segur?",
"MessageConfirmDeleteLibraryItems": "Això suprimirà {0} element(s) de la base de dades i del sistema de fitxers. N'esteu segur?",
"MessageConfirmDeleteMetadataProvider": "Segur que voleu suprimir el proveïdor de metadades personalitzat «{0}»?",
"MessageConfirmDeleteNotification": "Segur que voleu suprimir aquesta notificació?",
"MessageConfirmDeleteSession": "Segur que voleu suprimir aquesta sessió?",
"MessageConfirmEmbedMetadataInAudioFiles": "Segur que voleu incrustar metadades a {0} fitxer(s) d'àudio?",
"MessageConfirmForceReScan": "Segur que voleu forçar un reescaneig?",
"MessageConfirmMarkAllEpisodesFinished": "Segur que voleu marcar tots els episodis com a acabats?",
"MessageConfirmMarkAllEpisodesNotFinished": "Segur que voleu marcar tots els episodis com a no acabats?",
"MessageConfirmMarkItemFinished": "Segur que voleu marcar «{0}» com a acabat?",
"MessageConfirmMarkItemNotFinished": "Segur que voleu marcar «{0}» com a no acabat?",
"MessageConfirmMarkSeriesFinished": "Segur que voleu marcar tots els llibres d'aquesta sèrie com a acabats?",
"MessageConfirmMarkSeriesNotFinished": "Segur que voleu marcar tots els llibres d'aquesta sèrie com a no acabats?",
"MessageConfirmNotificationTestTrigger": "Voleu activar aquesta notificació amb dades de prova?",
"MessageConfirmPurgeCache": "Purgar la memòria cau suprimirà tot el directori localitzat a <code>/metadata/cache</code>. <br /><br />Segur que voleu eliminar-lo?",
"MessageConfirmPurgeItemsCache": "Esborrar la memòria cau dels elements eliminarà el directori <code>/metadata/cache/items</code>.<br />Estàs segur?",
"MessageConfirmQuickEmbed": "Advertència! La integració ràpida no fa còpies de seguretat dels teus fitxers d'àudio. Assegura't d'haver-ne fet una còpia abans. <br><br>Vols continuar?",
"MessageConfirmQuickEmbed": "Avís: la incrustació ràpida no fa còpies de seguretat dels vostres fitxers d'àudio. Assegureu-vos d'haver-ne fet una còpia abans. <br><br>Voleu continuar?",
"MessageConfirmQuickMatchEpisodes": "El reconeixement ràpid sobreescriurà els detalls si es troba una coincidència. Estàs segur?",
"MessageConfirmReScanLibraryItems": "Estàs segur que vols reescanejar {0} element(s)?",
"MessageConfirmRemoveAllChapters": "Estàs segur que vols eliminar tots els capítols?",
"MessageConfirmRemoveAuthor": "Estàs segur que vols eliminar l'autor \"{0}\"?",
"MessageConfirmRemoveCollection": "Estàs segur que vols eliminar la col·lecció \"{0}\"?",
"MessageConfirmRemoveEpisode": "Estàs segur que vols eliminar l'episodi \"{0}\"?",
"MessageConfirmRemoveEpisodes": "Estàs segur que vols eliminar {0} episodis?",
"MessageConfirmRemoveListeningSessions": "Estàs segur que vols eliminar {0} sessions d'escolta?",
"MessageConfirmRemoveMetadataFiles": "Estàs segur que vols eliminar tots els fitxers de metadades.{0} de les carpetes dels elements de la teva biblioteca?",
"MessageConfirmRemoveNarrator": "Estàs segur que vols eliminar el narrador \"{0}\"?",
"MessageConfirmRemovePlaylist": "Estàs segur que vols eliminar la llista de reproducció \"{0}\"?",
"MessageConfirmRenameGenre": "Estàs segur que vols canviar el gènere \"{0}\" a \"{1}\" per a tots els elements?",
"MessageConfirmReScanLibraryItems": "Segur que voleu reescanejar {0} element(s)?",
"MessageConfirmRemoveAllChapters": "Segur que voleu eliminar tots els capítols?",
"MessageConfirmRemoveAuthor": "Segur que voleu eliminar l'autor «{0}»?",
"MessageConfirmRemoveCollection": "Segur que voleu eliminar la col·lecció «{0}»?",
"MessageConfirmRemoveEpisode": "Segur que voleu eliminar l'episodi «{0}»?",
"MessageConfirmRemoveEpisodes": "Segur que voleu eliminar {0} episodis?",
"MessageConfirmRemoveListeningSessions": "Segur que voleu eliminar {0} sessions d'escolta?",
"MessageConfirmRemoveMetadataFiles": "Segur que voleu eliminar tots els fitxers metadata.{0} de les carpetes dels elements de la vostra biblioteca?",
"MessageConfirmRemoveNarrator": "Segur que voleu eliminar el narrador «{0}»?",
"MessageConfirmRemovePlaylist": "Segur que voleu eliminar la llista de reproducció «{0}»?",
"MessageConfirmRenameGenre": "Segur que voleu canviar el nom del gènere «{0}» a «{1}» per a tots els elements?",
"MessageConfirmRenameGenreMergeNote": "Nota: Aquest gènere ja existeix, i es fusionarà.",
"MessageConfirmRenameGenreWarning": "Advertència! Ja existeix un gènere similar \"{0}\".",
"MessageConfirmRenameTag": "Estàs segur que vols canviar l'etiqueta \"{0}\" a \"{1}\" per a tots els elements?",
"MessageConfirmRenameTag": "Segur que voleu canviar el nom de l'etiqueta «{0}» a «{1}» per a tots els elements?",
"MessageConfirmRenameTagMergeNote": "Nota: Aquesta etiqueta ja existeix, i es fusionarà.",
"MessageConfirmRenameTagWarning": "Advertència! Ja existeix una etiqueta similar \"{0}\".",
"MessageConfirmResetProgress": "Estàs segur que vols reiniciar el teu progrés?",
"MessageConfirmSendEbookToDevice": "Estàs segur que vols enviar {0} ebook(s) \"{1}\" al dispositiu \"{2}\"?",
"MessageConfirmUnlinkOpenId": "Estàs segur que vols desvincular aquest usuari d'OpenID?",
"MessageConfirmResetProgress": "Segur que voleu reinicialitzar el vostre progrés?",
"MessageConfirmSendEbookToDevice": "Segur que voleu enviar {0} llibre(s) «{1}» al dispositiu «{2}»?",
"MessageConfirmUnlinkOpenId": "Segur que voleu desenllaçar aquest usuari d'OpenID?",
"MessageDaysListenedInTheLastYear": "{0} dies escoltats l'any passat",
"MessageDownloadingEpisode": "S'està baixant l'episodi",
"MessageDragFilesIntoTrackOrder": "Arrossega els fitxers en l'ordre correcte de les pistes",
"MessageEmbedFailed": "Error en incrustar!",
"MessageEmbedFinished": "Incrustació acabada!",
"MessageEmbedQueue": "En cua per incrustar metadades ({0} en cua)",
"MessageFeedURLWillBe": "L'URL del canal serà {0}",
"MessageFetching": "S'està recuperant...",
"MessageImportantNotice": "Avís important",
"MessageInsertChapterBelow": "Insereix un capítol a sota",
"MessageInvalidAsin": "L'ASIN no és vàlid",
"MessageItemsSelected": "{0} elements seleccionats",
"MessageItemsUpdated": "{0} elements actualitzats",
"MessageJoinUsOn": "Uniu-vos a nosaltres a",
"MessageLoading": "S'està carregant...",
"MessageLoadingFolders": "S'estan carregant les carpetes...",
"MessageMarkAllEpisodesFinished": "Marca tots els episodis com a acabats",
"MessageMarkAllEpisodesNotFinished": "Marca tots els episodis com a inacabats",
"MessageMarkAsFinished": "Marcar com acabat",
"MessageMarkAsNotFinished": "Marcar com no acabat",
"MessageMatchBooksDescription": "S'intentarà fer coincidir els llibres de la biblioteca amb un llibre del proveïdor de cerca seleccionat, i s'ompliran els detalls buits i la portada. No sobreescriu els detalls.",
@@ -779,38 +799,40 @@
"MessagePauseChapter": "Pausar la reproducció del capítol",
"MessagePlayChapter": "Escoltar l'inici del capítol",
"MessagePlaylistCreateFromCollection": "Crear una llista de reproducció a partir d'una col·lecció",
"MessagePleaseWait": "Espera si us plau...",
"MessagePodcastHasNoRSSFeedForMatching": "El podcast no té una URL de font RSS que es pugui utilitzar",
"MessagePodcastSearchField": "Introdueix el terme de cerca o la URL de la font RSS",
"MessagePleaseWait": "Espereu...",
"MessagePodcastHasNoRSSFeedForMatching": "El pòdcast no té un URL de canal RSS que es pugui utilitzar",
"MessagePodcastSearchField": "Introduïu el terme de cerca o l'URL del canal RSS",
"MessageQuickEmbedInProgress": "Integració ràpida en procés",
"MessageQuickEmbedQueue": "En cua per a inserció ràpida ({0} en cua)",
"MessageQuickMatchAllEpisodes": "Combina ràpidament tots els episodis",
"MessageQuickMatchDescription": "Omple detalls d'elements buits i portades amb els primers resultats de '{0}'. No sobreescriu els detalls tret que l'opció \"Preferir metadades trobades\" del servidor estigui habilitada.",
"MessageRemoveChapter": "Eliminar capítols",
"MessageRemoveEpisodes": "Eliminar {0} episodi(s)",
"MessageRemoveFromPlayerQueue": "Eliminar de la cua del reproductor",
"MessageRemoveUserWarning": "Estàs segur que vols eliminar l'usuari \"{0}\"?",
"MessageQuickMatchDescription": "Emplena els detalls i la coberta dels elements buits amb el resultat de la primera coincidència de «{0}». No sobreescriu els detalls tret que s'activi el paràmetre del servidor «Prefereix metadades coincidents».",
"MessageRemoveChapter": "Elimina el capítol",
"MessageRemoveEpisodes": "Elimina {0} episodi(s)",
"MessageRemoveFromPlayerQueue": "Elimina de la cua del reproductor",
"MessageRemoveUserWarning": "Segur que voleu suprimir permanentment l'usuari «{0}»?",
"MessageReportBugsAndContribute": "Informa d'errors, sol·licita funcions i contribueix a",
"MessageResetChaptersConfirm": "Estàs segur que vols desfer els canvis i revertir els capítols al seu estat original?",
"MessageRestoreBackupConfirm": "Estàs segur que vols restaurar la còpia de seguretat creada a",
"MessageResetChaptersConfirm": "Segur que voleu desfer els canvis i revertir els capítols al seu estat original?",
"MessageRestoreBackupConfirm": "Segur que voleu restaurar la còpia de seguretat creada a",
"MessageRestoreBackupWarning": "Restaurar sobreescriurà tota la base de dades situada a /config i les imatges de portades a /metadata/items i /metadata/authors.<br /><br />La còpia de seguretat no modifica cap fitxer a les carpetes de la teva biblioteca. Si has activat l'opció del servidor per guardar portades i metadades a les carpetes de la biblioteca, aquests fitxers no es guarden ni sobreescriuen.<br /><br />Tots els clients que utilitzin el teu servidor s'actualitzaran automàticament.",
"MessageScheduleRunEveryWeekdayAtTime": "Executa cada {0} a les {1}",
"MessageSearchResultsFor": "Resultats de la cerca de",
"MessageSelected": "{0} seleccionat(s)",
"MessageSeriesSequenceCannotContainSpaces": "La seqüència de la sèrie no pot contenir espais",
"MessageServerCouldNotBeReached": "No es va poder establir la connexió amb el servidor",
"MessageSetChaptersFromTracksDescription": "Establir capítols utilitzant cada fitxer d'àudio com un capítol i el títol del capítol com el nom del fitxer d'àudio",
"MessageShareExpirationWillBe": "La caducitat serà <strong>{0}</strong>",
"MessageShareExpiresIn": "Caduca en {0}",
"MessageShareURLWillBe": "La URL per compartir serà <strong>{0}</strong>",
"MessageStartPlaybackAtTime": "Començar la reproducció per a \"{0}\" a {1}?",
"MessageTaskAudioFileNotWritable": "El fitxer d'àudio \"{0}\" no es pot escriure",
"MessageStartPlaybackAtTime": "Voleu començar la reproducció per a «{0}» a {1}?",
"MessageTaskAudioFileNotWritable": "El fitxer d'àudio «{0}» no es pot escriure",
"MessageTaskCanceledByUser": "Tasca cancel·lada per l'usuari",
"MessageTaskDownloadingEpisodeDescription": "Descarregant l'episodi \"{0}\"",
"MessageTaskDownloadingEpisodeDescription": "S'està baixant l'episodi «{0}»",
"MessageTaskEmbeddingMetadata": "Inserint metadades",
"MessageTaskEmbeddingMetadataDescription": "Inserint metadades en l'audiollibre \"{0}\"",
"MessageTaskEncodingM4b": "Codificant M4B",
"MessageTaskEncodingM4bDescription": "Codificant l'audiollibre \"{0}\" en un únic fitxer M4B",
"MessageTaskEncodingM4bDescription": "S'està codificant l'audiollibre «{0}» en un únic fitxer M4B",
"MessageTaskFailed": "Fallada",
"MessageTaskFailedToBackupAudioFile": "Error en fer una còpia de seguretat del fitxer d'àudio \"{0}\"",
"MessageTaskFailedToBackupAudioFile": "No s'ha pogut fer una còpia de seguretat del fitxer d'àudio «{0}»",
"MessageTaskFailedToCreateCacheDirectory": "Error en crear el directori de la memòria cau",
"MessageTaskFailedToEmbedMetadataInFile": "Error en incrustar metadades en el fitxer \"{0}\"",
"MessageTaskFailedToMergeAudioFiles": "Error en fusionar fitxers d'àudio",
@@ -819,14 +841,14 @@
"MessageTaskMatchingBooksInLibrary": "Coincidint llibres a la biblioteca \"{0}\"",
"MessageTaskNoFilesToScan": "Sense fitxers per escanejar",
"MessageTaskOpmlImport": "Importar OPML",
"MessageTaskOpmlImportDescription": "Creant podcasts a partir de {0} fonts RSS",
"MessageTaskOpmlImportFeed": "Importació de feed OPML",
"MessageTaskOpmlImportFeedDescription": "Importació del feed RSS \"{0}\"",
"MessageTaskOpmlImportFeedFailed": "No es pot obtenir el podcast",
"MessageTaskOpmlImportFeedPodcastDescription": "Creant el podcast \"{0}\"",
"MessageTaskOpmlImportFeedPodcastExists": "El podcast ja existeix a la ruta",
"MessageTaskOpmlImportFeedPodcastFailed": "Error en crear el podcast",
"MessageTaskOpmlImportFinished": "Afegit {0} podcasts",
"MessageTaskOpmlImportDescription": "S'estan creant pòdcasts a partir de {0} canals RSS",
"MessageTaskOpmlImportFeed": "Importació d'un canal OPML",
"MessageTaskOpmlImportFeedDescription": "S'està important el canal RSS «{0}»",
"MessageTaskOpmlImportFeedFailed": "No s'ha pogut obtenir el canal del pòdcast",
"MessageTaskOpmlImportFeedPodcastDescription": "S'està creant el pòdcast «{0}»",
"MessageTaskOpmlImportFeedPodcastExists": "El pòdcast ja existeix al camí",
"MessageTaskOpmlImportFeedPodcastFailed": "No s'ha pogut crear el pòdcast",
"MessageTaskOpmlImportFinished": "S'han afegit {0} pòdcasts",
"MessageTaskOpmlParseFailed": "No s'ha pogut analitzar el fitxer OPML",
"MessageTaskOpmlParseFastFail": "No s'ha trobat l'etiqueta <opml> o <outline> al fitxer OPML",
"MessageTaskOpmlParseNoneFound": "No s'han trobat fonts al fitxer OPML",
@@ -844,13 +866,13 @@
"MessageValidCronExpression": "Expressió de cron vàlida",
"MessageWatcherIsDisabledGlobally": "El watcher està desactivat globalment a la configuració del servidor",
"MessageXLibraryIsEmpty": "La biblioteca {0} està buida!",
"MessageYourAudiobookDurationIsLonger": "La durada del teu audiollibre és més llarga que la durada trobada",
"MessageYourAudiobookDurationIsShorter": "La durada del teu audiollibre és més curta que la durada trobada",
"MessageYourAudiobookDurationIsLonger": "La durada del vostre audiollibre és major que la durada trobada",
"MessageYourAudiobookDurationIsShorter": "La durada del vostre audiollibre és menor que la durada trobada",
"NoteChangeRootPassword": "L'usuari Root és l'únic usuari que pot no tenir una contrasenya",
"NoteChapterEditorTimes": "Nota: El temps d'inici del primer capítol ha de romandre a 0:00, i el temps d'inici de l'últim capítol no pot superar la durada de l'audiollibre.",
"NoteFolderPicker": "Nota: Les carpetes ja assignades no es mostraran",
"NoteRSSFeedPodcastAppsHttps": "Advertència: La majoria d'aplicacions de podcast requereixen que la URL de la font RSS utilitzi HTTPS",
"NoteRSSFeedPodcastAppsPubDate": "Advertència: Un o més dels teus episodis no tenen data de publicació. Algunes aplicacions de podcast ho requereixen.",
"NoteChapterEditorTimes": "Nota: el temps d'inici del primer capítol ha de romandre a 0:00, i el temps d'inici de l'últim capítol no pot superar la durada de l'audiollibre.",
"NoteFolderPicker": "Nota: les carpetes ja assignades no es mostraran",
"NoteRSSFeedPodcastAppsHttps": "Avís: la majoria d'aplicacions de pòdcast requereixen que l'URL del canal RSS utilitzi HTTPS",
"NoteRSSFeedPodcastAppsPubDate": "Avís: un o més dels vostres episodis no tenen data de publicació. Algunes aplicacions de pòdcast ho requereixen.",
"NoteUploaderFoldersWithMediaFiles": "Les carpetes amb fitxers multimèdia es gestionaran com a elements separats a la biblioteca.",
"NoteUploaderOnlyAudioFiles": "Si només puges fitxers d'àudio, cada fitxer es gestionarà com un audiollibre separat.",
"NoteUploaderUnsupportedFiles": "Els fitxers no compatibles seran ignorats. Si selecciona o arrossega una carpeta, els fitxers que no estiguin dins d'una subcarpeta seran ignorats.",
@@ -859,7 +881,7 @@
"NotificationOnEpisodeDownloadedDescription": "S'activa quan es descarrega automàticament un episodi d'un podcast",
"NotificationOnTestDescription": "Esdeveniment per provar el sistema de notificacions",
"PlaceholderNewCollection": "Nou nom de la col·lecció",
"PlaceholderNewFolderPath": "Nova ruta de carpeta",
"PlaceholderNewFolderPath": "Camí de carpeta nou",
"PlaceholderNewPlaylist": "Nou nom de la llista de reproducció",
"PlaceholderSearch": "Cerca...",
"PlaceholderSearchEpisode": "Cerca d'episodis...",
@@ -885,7 +907,7 @@
"ToastAppriseUrlRequired": "Cal introduir una URL de Apprise",
"ToastAsinRequired": "ASIN requerit",
"ToastAuthorImageRemoveSuccess": "S'ha eliminat la imatge de l'autor",
"ToastAuthorNotFound": "No s'ha trobat l'autor \"{0}\"",
"ToastAuthorNotFound": "No s'ha trobat l'autor «{0}»",
"ToastAuthorRemoveSuccess": "Autor eliminat",
"ToastAuthorSearchNotFound": "No s'ha trobat l'autor",
"ToastAuthorUpdateMerged": "Autor combinat",
@@ -901,6 +923,7 @@
"ToastBackupRestoreFailed": "Error en restaurar la còpia de seguretat",
"ToastBackupUploadFailed": "Error en carregar la còpia de seguretat",
"ToastBackupUploadSuccess": "Còpia de seguretat carregada",
"ToastBatchApplyDetailsToItemsSuccess": "S'han aplicat els detalls als elements",
"ToastBatchDeleteFailed": "Error en l'eliminació per lots",
"ToastBatchDeleteSuccess": "Eliminació per lots correcte",
"ToastBatchQuickMatchFailed": "Error en la sincronització ràpida per lots!",
@@ -913,6 +936,8 @@
"ToastCachePurgeFailed": "Error en purgar la memòria cau",
"ToastCachePurgeSuccess": "Memòria cau purgada amb èxit",
"ToastChaptersHaveErrors": "Els capítols tenen errors",
"ToastChaptersInvalidShiftAmountLast": "La quantitat de desplaçament no és vàlida. L'hora d'inici de l'últim capítol s'estendria més enllà de la durada d'aquest audiollibre.",
"ToastChaptersInvalidShiftAmountStart": "La quantitat de desplaçament no és vàlida. El primer capítol tindria una durada zero o negativa i el sobreescriuria el segon capítol. Augmenteu la durada inicial del segon capítol.",
"ToastChaptersMustHaveTitles": "Els capítols han de tenir un títol",
"ToastChaptersRemoved": "Capítols eliminats",
"ToastChaptersUpdated": "Capítols actualitzats",
@@ -920,6 +945,7 @@
"ToastCollectionRemoveSuccess": "Col·lecció eliminada",
"ToastCollectionUpdateSuccess": "Col·lecció actualitzada",
"ToastCoverUpdateFailed": "Error en actualitzar la portada",
"ToastDateTimeInvalidOrIncomplete": "La data i hora no és vàlida o està incompleta",
"ToastDeleteFileFailed": "No s'ha pogut suprimir el fitxer",
"ToastDeleteFileSuccess": "Fitxer suprimit",
"ToastDeviceAddFailed": "Error en afegir el dispositiu",
@@ -950,34 +976,35 @@
"ToastItemMarkedAsNotFinishedSuccess": "Element marcat com a no acabat",
"ToastItemUpdateSuccess": "Element actualitzat",
"ToastLibraryCreateFailed": "Error en crear la biblioteca",
"ToastLibraryCreateSuccess": "Biblioteca \"{0}\" creada",
"ToastLibraryCreateSuccess": "S'ha creat la biblioteca «{0}»",
"ToastLibraryDeleteFailed": "Error en eliminar la biblioteca",
"ToastLibraryDeleteSuccess": "Biblioteca eliminada",
"ToastLibraryScanFailedToStart": "Error en iniciar l'escaneig",
"ToastLibraryScanStarted": "S'ha iniciat l'escaneig de la biblioteca",
"ToastLibraryUpdateSuccess": "Biblioteca \"{0}\" actualitzada",
"ToastLibraryUpdateSuccess": "S'ha actualitzat la biblioteca «{0}»",
"ToastMatchAllAuthorsFailed": "No coincideix amb tots els autors",
"ToastMetadataFilesRemovedError": "Error en eliminar metadades de {0} arxius",
"ToastMetadataFilesRemovedNoneFound": "No s'han trobat metadades en {0} arxius",
"ToastMetadataFilesRemovedNoneRemoved": "Cap metadada eliminada en {0} arxius",
"ToastMetadataFilesRemovedError": "Sha produït un error en eliminar els fitxers metadata.{0}",
"ToastMetadataFilesRemovedNoneFound": "No hi ha cap fitxer metadata.{0} a la biblioteca",
"ToastMetadataFilesRemovedNoneRemoved": "No s'ha eliminat cap fitxer metadata.{0}",
"ToastMetadataFilesRemovedSuccess": "{0} metadades eliminades en {1} arxius",
"ToastMustHaveAtLeastOnePath": "Ha de tenir almenys una ruta",
"ToastNameEmailRequired": "El nom i el correu electrònic són obligatoris",
"ToastNameRequired": "Nom obligatori",
"ToastNewEpisodesFound": "{0} episodi(s) nou(s) trobat(s)",
"ToastNewUserCreatedFailed": "Error en crear el compte: \"{0}\"",
"ToastNewUserCreatedFailed": "No s'ha pogut crear el compte: «{0}»",
"ToastNewUserCreatedSuccess": "Nou compte creat",
"ToastNewUserLibraryError": "Ha de seleccionar almenys una biblioteca",
"ToastNewUserPasswordError": "Necessites una contrasenya, només el root pot estar sense contrasenya",
"ToastNewUserTagError": "Selecciona almenys una etiqueta",
"ToastNewUserUsernameError": "Introdueix un nom d'usuari",
"ToastNewUserLibraryError": "S'ha de seleccionar almenys una biblioteca",
"ToastNewUserPasswordError": "Cal una contrasenya; només l'usuari primari pot estar sense contrasenya",
"ToastNewUserTagError": "S'ha de seleccionar almenys una etiqueta",
"ToastNewUserUsernameError": "Introduïu un nom d'usuari",
"ToastNoNewEpisodesFound": "No s'han trobat nous episodis",
"ToastNoRSSFeed": "El pòdcast no té canal RSS",
"ToastNoUpdatesNecessary": "No cal actualitzar",
"ToastNotificationCreateFailed": "Error en crear la notificació",
"ToastNotificationDeleteFailed": "Error en eliminar la notificació",
"ToastNotificationCreateFailed": "No s'ha pogut crear la notificació",
"ToastNotificationDeleteFailed": "No s'ha pogut suprimir la notificació",
"ToastNotificationFailedMaximum": "El nombre màxim d'intents fallits ha de ser ≥ 0",
"ToastNotificationQueueMaximum": "La cua de notificació màxima ha de ser ≥ 0",
"ToastNotificationSettingsUpdateSuccess": "Configuració de notificació actualitzada",
"ToastNotificationSettingsUpdateSuccess": "S'han actualitzat els paràmetres de notificacions",
"ToastNotificationTestTriggerFailed": "No s'ha pogut activar la notificació de prova",
"ToastNotificationTestTriggerSuccess": "Notificació de prova activada",
"ToastNotificationUpdateSuccess": "Notificació actualitzada",
@@ -987,16 +1014,16 @@
"ToastPlaylistUpdateSuccess": "Llista de reproducció actualitzada",
"ToastPodcastCreateFailed": "No s'ha pogut crear el pòdcast",
"ToastPodcastCreateSuccess": "S'ha creat el pòdcast correctament",
"ToastPodcastGetFeedFailed": "No s'ha pogut obtenir el podcast",
"ToastPodcastNoEpisodesInFeed": "No s'han trobat episodis en el feed RSS",
"ToastPodcastNoRssFeed": "El podcast no té un feed RSS",
"ToastPodcastGetFeedFailed": "No s'ha pogut obtenir el canal del pòdcast",
"ToastPodcastNoEpisodesInFeed": "No s'ha trobat cap episodi al canal RSS",
"ToastPodcastNoRssFeed": "El pòdcast no té un canal RSS",
"ToastProgressIsNotBeingSynced": "El progrés no s'està sincronitzant, reinicia la reproducció",
"ToastProviderCreatedFailed": "Error en afegir el proveïdor",
"ToastProviderCreatedSuccess": "Nou proveïdor afegit",
"ToastProviderNameAndUrlRequired": "Nom i URL obligatoris",
"ToastProviderRemoveSuccess": "Proveïdor eliminat",
"ToastRSSFeedCloseFailed": "Error en tancar el feed RSS",
"ToastRSSFeedCloseSuccess": "Feed RSS tancat",
"ToastRSSFeedCloseFailed": "No s'ha pogut tancar el canal RSS",
"ToastRSSFeedCloseSuccess": "Canal RSS tancat",
"ToastRemoveFailed": "Error en eliminar",
"ToastRemoveItemFromCollectionFailed": "Error en eliminar l'element de la col·lecció",
"ToastRemoveItemFromCollectionSuccess": "Element eliminat de la col·lecció",
@@ -1010,7 +1037,8 @@
"ToastScanFailed": "No s'ha pogut escanejar l'element de la biblioteca",
"ToastSelectAtLeastOneUser": "Selecciona almenys un usuari",
"ToastSendEbookToDeviceFailed": "Error en enviar l'ebook al dispositiu",
"ToastSendEbookToDeviceSuccess": "Ebook enviat al dispositiu \"{0}\"",
"ToastSendEbookToDeviceSuccess": "El llibre electrònic s'ha enviat al dispositiu «{0}»",
"ToastSeriesSubmitFailedSameName": "No és possible afegir dues sèries amb el mateix nom",
"ToastSeriesUpdateFailed": "Error en actualitzar la sèrie",
"ToastSeriesUpdateSuccess": "Sèrie actualitzada",
"ToastServerSettingsUpdateSuccess": "Configuració del servidor actualitzada",

View File

@@ -1,5 +1,6 @@
{
"ButtonAdd": "Přidat",
"ButtonAddApiKey": "Přidat API klíč",
"ButtonAddChapters": "Přidat kapitoly",
"ButtonAddDevice": "Přidat zařízení",
"ButtonAddLibrary": "Přidat knihovnu",
@@ -20,6 +21,7 @@
"ButtonChooseAFolder": "Vybrat složku",
"ButtonChooseFiles": "Vybrat soubory",
"ButtonClearFilter": "Vymazat filtr",
"ButtonClose": "Zavřít",
"ButtonCloseFeed": "Zavřít kanál",
"ButtonCloseSession": "Zavřít otevřenou relaci",
"ButtonCollections": "Kolekce",
@@ -119,6 +121,7 @@
"HeaderAccount": "Účet",
"HeaderAddCustomMetadataProvider": "Přidat vlastního poskytovatele metadat",
"HeaderAdvanced": "Pokročilé",
"HeaderApiKeys": "API klíče",
"HeaderAppriseNotificationSettings": "Nastavení oznámení Apprise",
"HeaderAudioTracks": "Zvukové stopy",
"HeaderAudiobookTools": "Nástroje pro správu souborů audioknih",
@@ -154,7 +157,7 @@
"HeaderListeningSessions": "Poslechové relace",
"HeaderListeningStats": "Statistiky poslechu",
"HeaderLogin": "Přihlásit",
"HeaderLogs": "Záznamy",
"HeaderLogs": "Logy",
"HeaderManageGenres": "Spravovat žánry",
"HeaderManageTags": "Spravovat štítky",
"HeaderMapDetails": "Podrobnosti mapování",
@@ -162,6 +165,7 @@
"HeaderMetadataOrderOfPrecedence": "Pořadí priorit metadat",
"HeaderMetadataToEmbed": "Metadata k vložení",
"HeaderNewAccount": "Nový účet",
"HeaderNewApiKey": "Nový API klíč",
"HeaderNewLibrary": "Nová knihovna",
"HeaderNotificationCreate": "Vytvořit notifikaci",
"HeaderNotificationUpdate": "Aktualizovat notifikaci",
@@ -177,6 +181,7 @@
"HeaderPlaylist": "Seznam skladeb",
"HeaderPlaylistItems": "Položky seznamu přehrávání",
"HeaderPodcastsToAdd": "Podcasty k přidání",
"HeaderPresets": "Předvolba",
"HeaderPreviewCover": "Náhled obálky",
"HeaderRSSFeedGeneral": "Podrobnosti o RSS",
"HeaderRSSFeedIsOpen": "Informační kanál RSS je otevřený",
@@ -194,6 +199,7 @@
"HeaderSettingsExperimental": "Experimentální funkce",
"HeaderSettingsGeneral": "Obecné",
"HeaderSettingsScanner": "Skener",
"HeaderSettingsSecurity": "Zabezpečení",
"HeaderSettingsWebClient": "Webový klient",
"HeaderSleepTimer": "Časovač vypnutí",
"HeaderStatsLargestItems": "Největší položky",
@@ -205,6 +211,7 @@
"HeaderTableOfContents": "Obsah",
"HeaderTools": "Nástroje",
"HeaderUpdateAccount": "Aktualizovat účet",
"HeaderUpdateApiKey": "Aktualizovat API klíč",
"HeaderUpdateAuthor": "Aktualizovat autora",
"HeaderUpdateDetails": "Aktualizovat podrobnosti",
"HeaderUpdateLibrary": "Aktualizovat knihovnu",
@@ -234,6 +241,10 @@
"LabelAllUsersExcludingGuests": "Všichni uživatelé kromě hostů",
"LabelAllUsersIncludingGuests": "Všichni uživatelé včetně hostů",
"LabelAlreadyInYourLibrary": "Již ve vaší knihovně",
"LabelApiKeyCreated": "API klíč \"{0}\" byl úspěšně vytvořen.",
"LabelApiKeyCreatedDescription": "Zkopírujte si API klíč nyní, později již nebude možné jej zobrazit.",
"LabelApiKeyUser": "Vydávat se za uživatele",
"LabelApiKeyUserDescription": "Tento API klíč bude mít stejná oprávnění jako uživatel za něhož vystupuje. V protokolech to bude vypadat jako kdyby požadavky vytvářel přímo daný uživatel.",
"LabelApiToken": "API Token",
"LabelAppend": "Připojit",
"LabelAudioBitrate": "Bitový tok zvuku (např. 128k)",
@@ -283,6 +294,7 @@
"LabelContinueListening": "Pokračovat v poslechu",
"LabelContinueReading": "Pokračovat ve čtení",
"LabelContinueSeries": "Pokračovat v sérii",
"LabelCorsAllowed": "Povolené CORS Origins",
"LabelCover": "Obálka",
"LabelCoverImageURL": "URL obrázku obálky",
"LabelCoverProvider": "Poskytovatel obálky",
@@ -345,11 +357,15 @@
"LabelExample": "Příklad",
"LabelExpandSeries": "Rozbalit série",
"LabelExpandSubSeries": "Rozbalit podsérie",
"LabelExplicit": "Explicitní",
"LabelExpired": "Expirovaný",
"LabelExpiresAt": "Expiruje v",
"LabelExpiresInSeconds": "Expiruje za (sekundy)",
"LabelExpiresNever": "Nikdy",
"LabelExplicit": "Explicitně",
"LabelExplicitChecked": "Explicitní (zaškrtnuto)",
"LabelExplicitUnchecked": "Není explicitní (nezaškrtnuto)",
"LabelExportOPML": "Export OPML",
"LabelFeedURL": "URL zdroje",
"LabelFeedURL": "URL kanálu",
"LabelFetchingMetadata": "Získávání metadat",
"LabelFile": "Soubor",
"LabelFileBirthtime": "Čas vzniku souboru",
@@ -404,6 +420,7 @@
"LabelLanguages": "Jazyky",
"LabelLastBookAdded": "Poslední kniha přidána",
"LabelLastBookUpdated": "Poslední kniha aktualizována",
"LabelLastProgressDate": "Poslední pokrok: {0}",
"LabelLastSeen": "Naposledy viděno",
"LabelLastTime": "Naposledy",
"LabelLastUpdate": "Poslední aktualizace",
@@ -416,6 +433,7 @@
"LabelLibraryFilterSublistEmpty": "Žádné {0}",
"LabelLibraryItem": "Položka knihovny",
"LabelLibraryName": "Název knihovny",
"LabelLibrarySortByProgress": "Aktualizace pokroku",
"LabelLimit": "Omezit",
"LabelLineSpacing": "Řádkování",
"LabelListenAgain": "Poslouchat znovu",
@@ -424,6 +442,7 @@
"LabelLogLevelWarn": "Varovat",
"LabelLookForNewEpisodesAfterDate": "Hledat nové epizody po tomto datu",
"LabelLowestPriority": "Nejnižší priorita",
"LabelMatchConfidence": "Jistota",
"LabelMatchExistingUsersBy": "Přiřadit stávající uživatele podle",
"LabelMatchExistingUsersByDescription": "Slouží k propojení stávajících uživatelů. Po propojení budou uživatelé přiřazeni k jedinečnému ID od poskytovatele SSO",
"LabelMaxEpisodesToDownload": "Maximální # epizod pro stažení. Použijte 0 pro bez omezení.",
@@ -454,6 +473,7 @@
"LabelNewestEpisodes": "Nejnovější epizody",
"LabelNextBackupDate": "Datum příští zálohy",
"LabelNextScheduledRun": "Další naplánované spuštění",
"LabelNoApiKeys": "Žádné API klíče",
"LabelNoCustomMetadataProviders": "Žádní vlastní poskytovatelé metadat",
"LabelNoEpisodesSelected": "Nebyly vybrány žádné epizody",
"LabelNotFinished": "Nedokončeno",
@@ -513,9 +533,9 @@
"LabelPublishers": "Vydavatelé",
"LabelRSSFeedCustomOwnerEmail": "Vlastní e-mail vlastníka",
"LabelRSSFeedCustomOwnerName": "Vlastní jméno vlastníka",
"LabelRSSFeedOpen": "Otevření RSS kanálu",
"LabelRSSFeedOpen": "RSS kanál otevřen",
"LabelRSSFeedPreventIndexing": "Zabránit indexování",
"LabelRSSFeedSlug": "RSS kanál Slug",
"LabelRSSFeedSlug": "Klíčové slovo kanálu RSS",
"LabelRSSFeedURL": "URL RSS kanálu",
"LabelRandomly": "Náhodně",
"LabelReAddSeriesToContinueListening": "Znovu přidat sérii k pokračování poslechu",
@@ -530,6 +550,7 @@
"LabelReleaseDate": "Datum vydání",
"LabelRemoveAllMetadataAbs": "Odebrat všechny soubory metadata.abs",
"LabelRemoveAllMetadataJson": "Smazat všechny soubory metadata.json",
"LabelRemoveAudibleBranding": "Odebrat úvod a závěr Audible z kapitol",
"LabelRemoveCover": "Odstranit obálku",
"LabelRemoveMetadataFile": "Odstranit soubory metadat ve složkách položek knihovny",
"LabelRemoveMetadataFileHelp": "Odstraníte všechny soubory metadata.json a metadata.abs ve svých složkách {0}.",
@@ -542,6 +563,7 @@
"LabelSelectAll": "Vybrat vše",
"LabelSelectAllEpisodes": "Vybrat všechny epizody",
"LabelSelectEpisodesShowing": "Vyberte {0} epizody, které se zobrazují",
"LabelSelectUser": "Vybrat uživatele",
"LabelSelectUsers": "Vybrat uživatele",
"LabelSendEbookToDevice": "Odeslat e-knihu do...",
"LabelSequence": "Sekvence",
@@ -549,7 +571,7 @@
"LabelSeries": "Série",
"LabelSeriesName": "Název série",
"LabelSeriesProgress": "Průběh série",
"LabelServerLogLevel": "Úroveň protokolu serveru",
"LabelServerLogLevel": "Úroveň Logování serveru",
"LabelServerYearReview": "Přehled roku na serveru ({0})",
"LabelSetEbookAsPrimary": "Nastavit jako primární",
"LabelSetEbookAsSupplementary": "Nastavit jako doplňkové",
@@ -705,6 +727,10 @@
"LabelYourProgress": "Váš pokrok",
"MessageAddToPlayerQueue": "Přidat do fronty přehrávače",
"MessageAppriseDescription": "Abyste mohli používat tuto funkci, musíte mít spuštěnou instanci <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> nebo API, které bude zpracovávat stejné požadavky. <br />Adresa URL API Apprise by měla být úplná URL cesta pro odeslání oznámení, např. pokud je vaše instance API obsluhována na adrese <code>http://192.168.1.1:8337</code> pak byste měli zadat <code>http://192.168.1.1:8337/notify</code>.",
"MessageAsinCheck": "Ujistěte se, že používáte ASIN ze správného regionu Audible a ne z Amazonu.",
"MessageAuthenticationLegacyTokenWarning": "Zastaralé API tokeny budou v budoucnu odstraněny. Použijte místo nich <a href=\"/config/api-keys\">API klíče</a>.",
"MessageAuthenticationOIDCChangesRestart": "Po uložení restartujte server, aby se změny OIDC použily.",
"MessageAuthenticationSecurityMessage": "Bezpečnost autentizace byla vylepšena. Všichni uživatelé se musí znovu přihlásit.",
"MessageBackupsDescription": "Zálohy zahrnují uživatele, průběh uživatele, podrobnosti o položkách knihovny, nastavení serveru a obrázky uložené v <code>/metadata/items</code> a <code>/metadata/authors</code>. Zálohy <strong>ne</strong> zahrnují všechny soubory uložené ve složkách knihovny.",
"MessageBackupsLocationEditNote": "Poznámka: Změna umístění záloh nepřesune ani nezmění existující zálohy",
"MessageBackupsLocationNoEditNote": "Poznámka: Umístění záloh je nastavené z proměnných prostředí a nelze zde změnit.",
@@ -723,8 +749,10 @@
"MessageChapterErrorStartGteDuration": "Neplatný čas začátku, musí být kratší než doba trvání audioknihy",
"MessageChapterErrorStartLtPrev": "Neplatný čas začátku, musí být větší nebo roven času začátku předchozí kapitoly",
"MessageChapterStartIsAfter": "Začátek kapitoly přesahuje konec audioknihy",
"MessageChaptersNotFound": "Kapitoly nenalezeny",
"MessageCheckingCron": "Kontrola cronu...",
"MessageConfirmCloseFeed": "Opravdu chcete zavřít tento kanál?",
"MessageConfirmDeleteApiKey": "Opravdu chcete vymazat API klíč \"{0}\"?",
"MessageConfirmDeleteBackup": "Opravdu chcete smazat zálohu pro {0}?",
"MessageConfirmDeleteDevice": "Opravdu chcete vymazat zařízení e-reader \"{0}\"?",
"MessageConfirmDeleteFile": "Tento krok smaže soubor ze souborového systému. Jsi si jisti?",
@@ -752,6 +780,7 @@
"MessageConfirmRemoveAuthor": "Opravdu chcete odstranit autora \"{0}\"?",
"MessageConfirmRemoveCollection": "Opravdu chcete odstranit kolekci \"{0}\"?",
"MessageConfirmRemoveEpisode": "Opravdu chcete odstranit epizodu \"{0}\"?",
"MessageConfirmRemoveEpisodeNote": "Poznámka: Tím se zvukový soubor neodstraní, pokud nepřepnete volbu “Tvrdé odstranění souboru“",
"MessageConfirmRemoveEpisodes": "Opravdu chcete odstranit {0} epizody?",
"MessageConfirmRemoveListeningSessions": "Opravdu chcete odebrat {0} poslechových relací?",
"MessageConfirmRemoveMetadataFiles": "Jste si jisti, že chcete odstranit všechny metadata.{0} soubory ve složkách s položkami ve vaší knihovně?",
@@ -777,14 +806,17 @@
"MessageFeedURLWillBe": "URL zdroje bude {0}",
"MessageFetching": "Načítání...",
"MessageForceReScanDescription": "znovu prohledá všechny soubory jako při novém skenování. ID3 tagy zvukových souborů OPF soubory a textové soubory budou skenovány jako nové.",
"MessageHeatmapListeningTimeTooltip": "<strong>{0} poslechnuto</strong> na {1}",
"MessageHeatmapNoListeningSessions": "Žádné relace poslouchání na {0}",
"MessageImportantNotice": "Důležité upozornění!",
"MessageInsertChapterBelow": "Vložit kapitolu níže",
"MessageInvalidAsin": "Neplatný ASIN",
"MessageItemsSelected": "{0} vybraných položek",
"MessageItemsUpdated": "{0} položky byly aktualizovány",
"MessageJoinUsOn": "Přidejte se k nám",
"MessageLoading": "Načítá se...",
"MessageLoadingFolders": "Načítám složky...",
"MessageLogsDescription": "Protokoly se ukládají do souborů JSON v <code>/metadata/logs</code>. Protokoly o pádech jsou uloženy v <code>/metadata/logs/crash_logs.txt</code>.",
"MessageLogsDescription": "Logy se ukládají do souborů JSON v <code>/metadata/logs</code>. Logy o pádech jsou uloženy v <code>/metadata/logs/crash_logs.txt</code>.",
"MessageM4BFailed": "M4B se nezdařil!",
"MessageM4BFinished": "M4B dokončen!",
"MessageMapChapterTitles": "Mapování názvů kapitol ke stávajícím kapitolám audioknihy bez úpravy časových razítek",
@@ -808,11 +840,11 @@
"MessageNoEpisodes": "Žádné epizody",
"MessageNoFoldersAvailable": "Nejsou k dispozici žádné složky",
"MessageNoGenres": "Žádné žánry",
"MessageNoIssues": "Žádné výtisk",
"MessageNoIssues": "Žádné problémy",
"MessageNoItems": "Žádné položky",
"MessageNoItemsFound": "Nebyly nalezeny žádné položky",
"MessageNoListeningSessions": "Žádné poslechové relace",
"MessageNoLogs": "Žádné protokoly",
"MessageNoLogs": "Žádné logy",
"MessageNoMediaProgress": "Žádný průběh médií",
"MessageNoNotifications": "Žádná oznámení",
"MessageNoPodcastFeed": "Neplatný podcast: Žádný kanál",
@@ -850,6 +882,7 @@
"MessageScheduleRunEveryWeekdayAtTime": "Spusť každý {0} v {1}",
"MessageSearchResultsFor": "Výsledky hledání pro",
"MessageSelected": "{0} vybráno",
"MessageSeriesSequenceCannotContainSpaces": "Sekvence série nesmí obsahovat mezery",
"MessageServerCouldNotBeReached": "Server je nedostupný",
"MessageSetChaptersFromTracksDescription": "Nastavit kapitoly jako kapitolu a název kapitoly jako název zvukového souboru",
"MessageShareExpirationWillBe": "Expiruje <strong>{0}</strong>",
@@ -911,6 +944,8 @@
"NotificationOnBackupCompletedDescription": "Spuštěno po dokončení zálohování",
"NotificationOnBackupFailedDescription": "Spuštěno pokud zálohování selže",
"NotificationOnEpisodeDownloadedDescription": "Spuštěno při automatickém stažení epizody podcastu",
"NotificationOnRSSFeedDisabledDescription": "Aktivováno když je automatické stahování pozastaveno z důvodu příliš mnoho neůspěšných pokusů",
"NotificationOnRSSFeedFailedDescription": "Aktivováno když selže RSS kanál pro stahování epizod",
"NotificationOnTestDescription": "Akce pro otestování upozorňovacího systému",
"PlaceholderNewCollection": "Nový název kolekce",
"PlaceholderNewFolderPath": "Nová cesta ke složce",
@@ -955,7 +990,7 @@
"ToastBackupRestoreFailed": "Nepodařilo se obnovit zálohu",
"ToastBackupUploadFailed": "Nepodařilo se nahrát zálohu",
"ToastBackupUploadSuccess": "Záloha nahrána",
"ToastBatchApplyDetailsToItemsSuccess": "Detaily aplikované na položky",
"ToastBatchApplyDetailsToItemsSuccess": "Detaily byly aplikované na položky",
"ToastBatchDeleteFailed": "Hromadné smazání selhalo",
"ToastBatchDeleteSuccess": "Hromadné smazání proběhlo úspěšně",
"ToastBatchQuickMatchFailed": "Rychlá schoda dávky se nezdařila!",
@@ -968,6 +1003,8 @@
"ToastCachePurgeFailed": "Nepodařilo se vyčistit mezipaměť",
"ToastCachePurgeSuccess": "Vyrovnávací paměť úspěšně vyčištěna",
"ToastChaptersHaveErrors": "Kapitoly obsahují chyby",
"ToastChaptersInvalidShiftAmountLast": "Nesprávná délka posunu. Čas začátku poslední kapitoly by přesáhl dobu trvání této audioknihy.",
"ToastChaptersInvalidShiftAmountStart": "Nesprávná délka posunu. První kapitola by měla nulovou nebo zápornou délku a byla by přepsána druhou kapitolou. Zvětšete čas začátku druhé kapitoly.",
"ToastChaptersMustHaveTitles": "Kapitoly musí mít názvy",
"ToastChaptersRemoved": "Kapitoly odstraněny",
"ToastChaptersUpdated": "Kapitola aktualizována",
@@ -989,6 +1026,8 @@
"ToastEpisodeDownloadQueueClearSuccess": "Fronta stahování epizod je prázdná",
"ToastEpisodeUpdateSuccess": "{0} epizod aktualizováno",
"ToastErrorCannotShare": "Na tomto zařízení nelze nativně sdílet",
"ToastFailedToCreate": "Nepodařilo se vytvořit",
"ToastFailedToDelete": "Nepodařilo se odstranit",
"ToastFailedToLoadData": "Nepodařilo se načíst data",
"ToastFailedToMatch": "Nepodařilo se spárovat",
"ToastFailedToShare": "Sdílení selhalo",
@@ -996,6 +1035,7 @@
"ToastInvalidImageUrl": "Neplatná URL obrázku",
"ToastInvalidMaxEpisodesToDownload": "Neplatný maximální počet epizod ke stažení",
"ToastInvalidUrl": "Neplatná URL",
"ToastInvalidUrls": "Alespoň jedna URL je neplatná",
"ToastItemCoverUpdateSuccess": "Obálka předmětu byl aktualizována",
"ToastItemDeletedFailed": "Smazání položky selhalo",
"ToastItemDeletedSuccess": "Položka smazána",
@@ -1020,6 +1060,7 @@
"ToastMustHaveAtLeastOnePath": "Musí mít minimálně jednu cestu",
"ToastNameEmailRequired": "Jméno a email jsou vyžadovány",
"ToastNameRequired": "Jméno je vyžadováno",
"ToastNewApiKeyUserError": "Je nutné vybrat uživatele",
"ToastNewEpisodesFound": "{0} nových epizod bylo nalezeno",
"ToastNewUserCreatedFailed": "Chyba při vytváření účtu: \"{0}\"",
"ToastNewUserCreatedSuccess": "Vytvořen nový účet",
@@ -1088,7 +1129,7 @@
"ToastUnlinkOpenIdFailed": "Chyba při odpárování uživatele z OpenID",
"ToastUnlinkOpenIdSuccess": "Uživatel odpárován z uživatele z OpenID",
"ToastUploaderFilepathExistsError": "Soubor \"{0}\" na serveru již existuje",
"ToastUploaderItemExistsInSubdirectoryError": "Položka \"{0}\" používá podsložku nahrávané cesty.",
"ToastUploaderItemExistsInSubdirectoryError": "Položka \"{0}\" používá podadresář cesty pro nahrání.",
"ToastUserDeleteFailed": "Nepodařilo se smazat uživatele",
"ToastUserDeleteSuccess": "Uživatel smazán",
"ToastUserPasswordChangeSuccess": "Heslo bylo změněno úspěšně",

View File

@@ -1,5 +1,6 @@
{
"ButtonAdd": "Tilføj",
"ButtonAddApiKey": "Tilføj API-nøgle",
"ButtonAddChapters": "Tilføj kapitler",
"ButtonAddDevice": "Tilføj enhed",
"ButtonAddLibrary": "Tilføj Bibliotek",
@@ -20,6 +21,7 @@
"ButtonChooseAFolder": "Vælg en mappe",
"ButtonChooseFiles": "Vælg filer",
"ButtonClearFilter": "Ryd filter",
"ButtonClose": "Luk",
"ButtonCloseFeed": "Luk feed",
"ButtonCloseSession": "Luk Åben Session",
"ButtonCollections": "Samlinger",
@@ -119,6 +121,7 @@
"HeaderAccount": "Konto",
"HeaderAddCustomMetadataProvider": "Tilføj Brugerdefineret Metadataudbyder",
"HeaderAdvanced": "Avanceret",
"HeaderApiKeys": "API-nøgler",
"HeaderAppriseNotificationSettings": "Apprise Notifikationsindstillinger",
"HeaderAudioTracks": "Lydspor",
"HeaderAudiobookTools": "Audiobog Filhåndteringsværktøjer",
@@ -162,6 +165,7 @@
"HeaderMetadataOrderOfPrecedence": "Metadata-prioritet",
"HeaderMetadataToEmbed": "Metadata til indlejring",
"HeaderNewAccount": "Ny Konto",
"HeaderNewApiKey": "Ny API-nøgle",
"HeaderNewLibrary": "Nyt Bibliotek",
"HeaderNotificationCreate": "Opret Notifikation",
"HeaderNotificationUpdate": "Updater Notifikation",
@@ -177,6 +181,7 @@
"HeaderPlaylist": "Afspilningsliste",
"HeaderPlaylistItems": "Afspilningsliste Elementer",
"HeaderPodcastsToAdd": "Podcasts til Tilføjelse",
"HeaderPresets": "Forudindstillinger",
"HeaderPreviewCover": "Forhåndsvis Omslag",
"HeaderRSSFeedGeneral": "RSS Detaljer",
"HeaderRSSFeedIsOpen": "RSS Feed er Åben",
@@ -205,6 +210,7 @@
"HeaderTableOfContents": "Indholdsfortegnelse",
"HeaderTools": "Værktøjer",
"HeaderUpdateAccount": "Opdater Konto",
"HeaderUpdateApiKey": "Opdater API-nøgle",
"HeaderUpdateAuthor": "Opdater Forfatter",
"HeaderUpdateDetails": "Opdater Detaljer",
"HeaderUpdateLibrary": "Opdater Bibliotek",
@@ -234,6 +240,10 @@
"LabelAllUsersExcludingGuests": "Alle bruger eksklusiv gæster",
"LabelAllUsersIncludingGuests": "Alle bruger inklusiv gæster",
"LabelAlreadyInYourLibrary": "Allerede i dit bibliotek",
"LabelApiKeyCreated": "API-nøgle\"{0}\" oprettet succesfuldt.",
"LabelApiKeyCreatedDescription": "Sørg for at kopiere API-nøglen nu, da du ikke vil kunne se den igen.",
"LabelApiKeyUser": "Ret på vegne af brugeren",
"LabelApiKeyUserDescription": "Denne API-nøgle vil have de samme tilladelser som den bruger, den handler på vegne af. Dette vil fremgå på samme måde i logfiler, som hvis brugeren foretog anmodningen.",
"LabelApiToken": "API Token",
"LabelAppend": "Tilføj",
"LabelAudioBitrate": "Lydbitrate (f.eks. 128k)",
@@ -286,7 +296,7 @@
"LabelCover": "Omslag",
"LabelCoverImageURL": "Omslagsbillede URL",
"LabelCoverProvider": "Cover billede udbyder",
"LabelCreatedAt": "Oprettet Kl.",
"LabelCreatedAt": "Oprettet d.",
"LabelCronExpression": "Cron Udtryk",
"LabelCurrent": "Aktuel",
"LabelCurrently": "Aktuelt:",
@@ -345,6 +355,10 @@
"LabelExample": "Eksempel",
"LabelExpandSeries": "Udfold serie",
"LabelExpandSubSeries": "Udfold underserie",
"LabelExpired": "Udløbet",
"LabelExpiresAt": "Udløbsdato",
"LabelExpiresInSeconds": "Udløber om (seconds)",
"LabelExpiresNever": "Aldrig",
"LabelExplicit": "Eksplisit",
"LabelExplicitChecked": "Eksplicit (markeret)",
"LabelExplicitUnchecked": "Ikke eksplicit (ikke markeret)",
@@ -454,6 +468,7 @@
"LabelNewestEpisodes": "Nyeste episoder",
"LabelNextBackupDate": "Næste sikkerhedskopi dato",
"LabelNextScheduledRun": "Næste planlagte kørsel",
"LabelNoApiKeys": "Ingen API-nøgler",
"LabelNoCustomMetadataProviders": "Ingen brugerdefinerede metadata udbydere",
"LabelNoEpisodesSelected": "Ingen episoder valgt",
"LabelNotFinished": "Ikke færdig",
@@ -513,7 +528,7 @@
"LabelPublishers": "Forlag",
"LabelRSSFeedCustomOwnerEmail": "Brugerdefineret ejerens e-mail",
"LabelRSSFeedCustomOwnerName": "Brugerdefineret ejerens navn",
"LabelRSSFeedOpen": "Åben RSS-feed",
"LabelRSSFeedOpen": "RSS-feed åbent",
"LabelRSSFeedPreventIndexing": "Forhindrer indeksering",
"LabelRSSFeedSlug": "RSS-feed-slug",
"LabelRSSFeedURL": "RSS-feed-URL",
@@ -530,6 +545,7 @@
"LabelReleaseDate": "Udgivelsesdato",
"LabelRemoveAllMetadataAbs": "Fjern alle metadata.abs filer",
"LabelRemoveAllMetadataJson": "Fjern alle metadata.json filer",
"LabelRemoveAudibleBranding": "Fjern Audible intro og outro fra kapitler",
"LabelRemoveCover": "Fjern omslag",
"LabelRemoveMetadataFile": "Fjern alle metadata filer i biblioteksmapper",
"LabelRemoveMetadataFileHelp": "Fjern alle metadata.json og metadata.abs filer i dine {0} mapper.",
@@ -542,6 +558,7 @@
"LabelSelectAll": "Vælg alle",
"LabelSelectAllEpisodes": "Vælg alle episoder",
"LabelSelectEpisodesShowing": "Vælg {0} episoder vist",
"LabelSelectUser": "Vælg bruger",
"LabelSelectUsers": "Valgte brugere",
"LabelSendEbookToDevice": "Send e-bog til...",
"LabelSequence": "Sekvens",
@@ -575,14 +592,14 @@
"LabelSettingsLibraryMarkAsFinishedPercentComplete": "Procent gennemført er større end",
"LabelSettingsLibraryMarkAsFinishedTimeRemaining": "Tid tilbage er mindre end (sekunder)",
"LabelSettingsLibraryMarkAsFinishedWhen": "Marker medie indhold som færdigt når",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Spring til tidligere bøger i Fortsæt serie",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Fortsæt Serien siden hylde viser de første bøger som ikke er startet i serier med mindst en bog som ikke er startet og ingen bøger i gang. Aktivering af denne indstilling vil fortsætte serien fra den sidst gennemførte bog modsat den først ikke startede bog.",
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Spring tidligere bøger i Fortsæt serie over",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Fortsæt Serien siden viser den første bog som ikke er startet i serier med mindst en bog som ikke er startet og hvor ingen bøger i gang. Aktivering af denne indstilling vil fortsætte serien fra den sidst gennemførte bog i stedet for fra den første ikke startede bog.",
"LabelSettingsParseSubtitles": "Fortolk undertitler",
"LabelSettingsParseSubtitlesHelp": "Udtræk undertekster fra lydbogsmappenavne.<br>Undertitler skal adskilles af \" - \"<br>f.eks. \"Bogtitel - En undertitel her\" har undertitlen \"En undertitel her\"",
"LabelSettingsPreferMatchedMetadata": "Foretræk matchede metadata",
"LabelSettingsPreferMatchedMetadataHelp": "Matchede data vil tilsidesætte elementdetaljer ved brug af Hurtig Match. Som standard udfylder Hurtig Match kun manglende detaljer.",
"LabelSettingsSkipMatchingBooksWithASIN": "Spring over matchende bøger, der allerede har en ASIN",
"LabelSettingsSkipMatchingBooksWithISBN": "Spring matchende bøger over, som allerede har et ISBN-nummer",
"LabelSettingsSkipMatchingBooksWithASIN": "Ignorer matchende bøger, der allerede har en ASIN",
"LabelSettingsSkipMatchingBooksWithISBN": "Ignorer matchende bøger, som allerede har et ISBN-nummer",
"LabelSettingsSortingIgnorePrefixes": "Ignorer præfikser ved sortering",
"LabelSettingsSortingIgnorePrefixesHelp": "f.eks. for præfikset \"the\" vil bogtitlen \"The Book Title\" blive sorteret som \"Book Title, The\"",
"LabelSettingsSquareBookCovers": "Brug kvadratiske bogomslag",
@@ -604,6 +621,7 @@
"LabelSlug": "Snegl",
"LabelSortAscending": "Stigende",
"LabelSortDescending": "Faldende",
"LabelSortPubDate": "Sortér Pub Dato",
"LabelStart": "Start",
"LabelStartTime": "Starttid",
"LabelStarted": "Startet",
@@ -704,6 +722,10 @@
"LabelYourProgress": "Din fremgang",
"MessageAddToPlayerQueue": "Tilføj til afspilningskø",
"MessageAppriseDescription": "For at bruge denne funktion skal du have en instans af <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> kørende eller en API, der håndterer de samme anmodninger. <br /> Apprise API-webadressen skal være den fulde URL-sti for at sende underretningen, f.eks. hvis din API-instans er tilgængelig på <code>http://192.168.1.1:8337</code>, så skal du bruge <code>http://192.168.1.1:8337/notify</code>.",
"MessageAsinCheck": "Sikr dig at du bruger ASIN fra den korrekte Audible region, ikke Amazon.",
"MessageAuthenticationLegacyTokenWarning": "Ældre API tokens vil blive fjernet i fremtiden. Brug <a href=\"/config/api-keys\">API-nøgler</a> i stedet.",
"MessageAuthenticationOIDCChangesRestart": "Genstart sin server efter du har gemt for at bekræfte OIDC ændringer.",
"MessageAuthenticationSecurityMessage": "Autentificeringen er blevet forbedret af sikkerhedsmæssige årsager. Alle brugere skal logge ind igen.",
"MessageBackupsDescription": "Backups inkluderer brugere, brugerfremskridt, biblioteksvareoplysninger, serverindstillinger og billeder gemt i <code>/metadata/items</code> og <code>/metadata/authors</code>. Backups inkluderer <strong>ikke</strong> nogen filer gemt i dine biblioteksmapper.",
"MessageBackupsLocationEditNote": "Note: Opdatering af backup sti vil ikke fjerne eller modificere eksisterende backups",
"MessageBackupsLocationNoEditNote": "Note: Backup sti er sat igennem miljøvariabel og kan ikke ændres her.",
@@ -722,8 +744,10 @@
"MessageChapterErrorStartGteDuration": "Ugyldig starttid skal være mindre end lydbogens varighed",
"MessageChapterErrorStartLtPrev": "Ugyldig starttid skal være større end eller lig med den foregående kapitels starttid",
"MessageChapterStartIsAfter": "Kapitelstarten er efter slutningen af din lydbog",
"MessageChaptersNotFound": "Kapitler ikke fundet",
"MessageCheckingCron": "Tjekker cron...",
"MessageConfirmCloseFeed": "Er du sikker på, at du vil lukke dette feed?",
"MessageConfirmDeleteApiKey": "Er du sikker på at du vil slette API-nøglen \"{0}\"?",
"MessageConfirmDeleteBackup": "Er du sikker på, at du vil slette backup for {0}?",
"MessageConfirmDeleteDevice": "Er du sikker på at du vil fjerne elæser enhed \"{0}\"?",
"MessageConfirmDeleteFile": "Dette vil slette filen fra dit filsystem. Er du sikker?",
@@ -778,6 +802,7 @@
"MessageForceReScanDescription": "vil scanne alle filer igen som en frisk scanning. Lydfilens ID3-tags, OPF-filer og tekstfiler scannes som nye.",
"MessageImportantNotice": "Vigtig besked!",
"MessageInsertChapterBelow": "Indsæt kapitel nedenfor",
"MessageInvalidAsin": "Ugyldig ASIN",
"MessageItemsSelected": "{0} elementer valgt",
"MessageItemsUpdated": "{0} elementer opdateret",
"MessageJoinUsOn": "Deltag i os på",
@@ -849,6 +874,7 @@
"MessageScheduleRunEveryWeekdayAtTime": "Kør hvert {0} af {1}",
"MessageSearchResultsFor": "Søgeresultater for",
"MessageSelected": "{0} valgt",
"MessageSeriesSequenceCannotContainSpaces": "Serie sekvens kan ikke indeholde mellemrum",
"MessageServerCouldNotBeReached": "Serveren kunne ikke nås",
"MessageSetChaptersFromTracksDescription": "Indstil kapitler ved at bruge hver lydfil som et kapitel og kapiteloverskrift som lydfilnavn",
"MessageShareExpirationWillBe": "Udløb vil være <strong>{0}</strong>",
@@ -910,6 +936,8 @@
"NotificationOnBackupCompletedDescription": "Udløst når backup er færdig",
"NotificationOnBackupFailedDescription": "Udløst når backup fejler",
"NotificationOnEpisodeDownloadedDescription": "Udløst når et podcast afsnit er automatisk downloadet",
"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",
"PlaceholderNewCollection": "Nyt samlingnavn",
"PlaceholderNewFolderPath": "Ny mappes sti",
@@ -954,6 +982,7 @@
"ToastBackupRestoreFailed": "Mislykkedes gendannelse af sikkerhedskopi",
"ToastBackupUploadFailed": "Mislykkedes upload af sikkerhedskopi",
"ToastBackupUploadSuccess": "Sikkerhedskopi uploadet",
"ToastBatchApplyDetailsToItemsSuccess": "Detaljer bekræftet på element",
"ToastBatchDeleteFailed": "Batch slet fejlede",
"ToastBatchDeleteSuccess": "Batch slet succes",
"ToastBatchQuickMatchFailed": "Batch Hurtig Match fejlede!",
@@ -987,6 +1016,7 @@
"ToastEpisodeDownloadQueueClearSuccess": "Afsnit download kø renset",
"ToastEpisodeUpdateSuccess": "{0} afsnit opdateret",
"ToastErrorCannotShare": "Kan ikke dele på denne enhed",
"ToastFailedToCreate": "Oprettelsen mislykkedes",
"ToastFailedToLoadData": "Fejlede at indlæse data",
"ToastFailedToMatch": "Fejlet match",
"ToastFailedToShare": "Fejlet deling",
@@ -1003,11 +1033,11 @@
"ToastItemMarkedAsNotFinishedFailed": "Mislykkedes markering som ikke afsluttet",
"ToastItemMarkedAsNotFinishedSuccess": "Vare markeret som ikke afsluttet",
"ToastItemUpdateSuccess": "Genstand opdateret",
"ToastLibraryCreateFailed": "Mislykkedes oprettelse af bibliotek",
"ToastLibraryCreateFailed": "Oprettelse af bibliotek mislykkedes",
"ToastLibraryCreateSuccess": "Bibliotek \"{0}\" oprettet",
"ToastLibraryDeleteFailed": "Mislykkedes sletning af bibliotek",
"ToastLibraryDeleteFailed": "Sletning af bibliotek mislykkedes",
"ToastLibraryDeleteSuccess": "Bibliotek slettet",
"ToastLibraryScanFailedToStart": "Mislykkedes start af skanning",
"ToastLibraryScanFailedToStart": "Start af skanning mislykkedes",
"ToastLibraryScanStarted": "Biblioteksskanning startet",
"ToastLibraryUpdateSuccess": "Bibliotek \"{0}\" opdateret",
"ToastMatchAllAuthorsFailed": "Fejlede at matche alle forfattere",
@@ -1086,7 +1116,7 @@
"ToastUnlinkOpenIdFailed": "Fejlede i af afkoble bruger fra OpenID",
"ToastUnlinkOpenIdSuccess": "Bruger afkoblet fra OpenID",
"ToastUploaderFilepathExistsError": "Filsti \"{0}\" findes allerede på serveren",
"ToastUploaderItemExistsInSubdirectoryError": "Genstand \"{0}\" benytter en undermappe af upload stien",
"ToastUploaderItemExistsInSubdirectoryError": "Genstand \"{0}\" benytter en undermappe af upload stien.",
"ToastUserDeleteFailed": "Mislykkedes sletning af bruger",
"ToastUserDeleteSuccess": "Bruger slettet",
"ToastUserPasswordChangeSuccess": "Password ændret",

View File

@@ -1,5 +1,6 @@
{
"ButtonAdd": "Hinzufügen",
"ButtonAddApiKey": "API-Schlüssel hinzufügen",
"ButtonAddChapters": "Kapitel hinzufügen",
"ButtonAddDevice": "Gerät hinzufügen",
"ButtonAddLibrary": "Bibliothek hinzufügen",
@@ -14,12 +15,13 @@
"ButtonBatchEditPopulateMapDetails": "Kartendetails auffüllen",
"ButtonBrowseForFolder": "Ordnersuche",
"ButtonCancel": "Abbrechen",
"ButtonCancelEncode": "Codierung abbrechen",
"ButtonCancelEncode": "Konvertierung abbrechen",
"ButtonChangeRootPassword": "Hauptpasswort ändern",
"ButtonCheckAndDownloadNewEpisodes": "Überprüfe & lade neue Episoden herunter",
"ButtonChooseAFolder": "Wähle einen Ordner",
"ButtonChooseFiles": "Wähle eine Datei",
"ButtonClearFilter": "Filter löschen",
"ButtonClose": "Schließen",
"ButtonCloseFeed": "Feed schließen",
"ButtonCloseSession": "Offene Sitzung schließen",
"ButtonCollections": "Sammlungen",
@@ -32,7 +34,7 @@
"ButtonEditChapters": "Kapitel bearbeiten",
"ButtonEditPodcast": "Podcast bearbeiten",
"ButtonEnable": "Aktivieren",
"ButtonFireAndFail": "Abfeuern und versagen",
"ButtonFireAndFail": "Abschicken und fehlschlagen",
"ButtonFireOnTest": "Test-Event abfeuern",
"ButtonForceReScan": "Komplett-Scan (alle Medien)",
"ButtonFullPath": "Vollständiger Pfad",
@@ -53,7 +55,7 @@
"ButtonNext": "Vor",
"ButtonNextChapter": "Nächstes Kapitel",
"ButtonNextItemInQueue": "Das nächste Element in der Warteschlange",
"ButtonOk": "Einverstanden",
"ButtonOk": "Ok",
"ButtonOpenFeed": "Feed öffnen",
"ButtonOpenManager": "Manager öffnen",
"ButtonPause": "Pausieren",
@@ -88,7 +90,7 @@
"ButtonSave": "Speichern",
"ButtonSaveAndClose": "Speichern & Schließen",
"ButtonSaveTracklist": "Speichere die Titelliste",
"ButtonScan": "Partial-Scan (nur geänderte/neue Medien)",
"ButtonScan": "Scannen",
"ButtonScanLibrary": "Bibliothek scannen",
"ButtonScrollLeft": "Nach Links scrollen",
"ButtonScrollRight": "Nach Rechts scrollen",
@@ -119,11 +121,13 @@
"HeaderAccount": "Konto",
"HeaderAddCustomMetadataProvider": "Benutzerdefinierten Metadatenanbieter hinzufügen",
"HeaderAdvanced": "Erweitert",
"HeaderApiKeys": "API-Schlüssel",
"HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen",
"HeaderAudioTracks": "Audiodateien",
"HeaderAudiobookTools": "Hörbuch-Dateiverwaltungswerkzeuge",
"HeaderAuthentication": "Authentifizierung",
"HeaderBackups": "Sicherungen",
"HeaderBulkChapterModal": "Mehrere Kapitel hinzufügen",
"HeaderChangePassword": "Passwort ändern",
"HeaderChapters": "Kapitel",
"HeaderChooseAFolder": "Wähle einen Ordner",
@@ -162,6 +166,7 @@
"HeaderMetadataOrderOfPrecedence": "Metadaten Rangfolge",
"HeaderMetadataToEmbed": "Einzubettende Metadaten",
"HeaderNewAccount": "Neues Konto",
"HeaderNewApiKey": "Neuen API-Schlüssel erstellen",
"HeaderNewLibrary": "Neue Bibliothek",
"HeaderNotificationCreate": "Benachrichtigung erstellen",
"HeaderNotificationUpdate": "Benachrichtigung bearbeiten",
@@ -177,6 +182,7 @@
"HeaderPlaylist": "Wiedergabeliste",
"HeaderPlaylistItems": "Einträge in der Wiedergabeliste",
"HeaderPodcastsToAdd": "Podcasts zum Hinzufügen",
"HeaderPresets": "Voreinstellungen",
"HeaderPreviewCover": "Vorschau Titelbild",
"HeaderRSSFeedGeneral": "RSS Details",
"HeaderRSSFeedIsOpen": "RSS-Feed ist geöffnet",
@@ -194,6 +200,7 @@
"HeaderSettingsExperimental": "Experimentelle Funktionen",
"HeaderSettingsGeneral": "Allgemein",
"HeaderSettingsScanner": "Scanner",
"HeaderSettingsSecurity": "Sicherheit",
"HeaderSettingsWebClient": "Web-Client",
"HeaderSleepTimer": "Sleep-Timer",
"HeaderStatsLargestItems": "Größte Medien",
@@ -205,6 +212,7 @@
"HeaderTableOfContents": "Inhaltsverzeichnis",
"HeaderTools": "Werkzeuge",
"HeaderUpdateAccount": "Konto aktualisieren",
"HeaderUpdateApiKey": "API-Schlüssel aktualisieren",
"HeaderUpdateAuthor": "Autor aktualisieren",
"HeaderUpdateDetails": "Details aktualisieren",
"HeaderUpdateLibrary": "Bibliothek aktualisieren",
@@ -234,6 +242,10 @@
"LabelAllUsersExcludingGuests": "Alle Benutzer außer Gästen",
"LabelAllUsersIncludingGuests": "Alle Benutzer und Gäste",
"LabelAlreadyInYourLibrary": "Bereits in der Bibliothek",
"LabelApiKeyCreated": "API-Schlüssel \"{0}\" erfolgreich erstellt.",
"LabelApiKeyCreatedDescription": "Speichere den API-Schlüssel an einem sicheren Ort, du wirst ihn später nicht mehr abrufen können.",
"LabelApiKeyUser": "Im Kontext eines Nutzers agieren",
"LabelApiKeyUserDescription": "Dieser API-Schlüssel hat die gleichen Berechtigungen wie der Benutzer, in dessen Namen er erstellt wurde .In den Protokollen wird es aussehen, als ob der Benutzer die Anfrage durchführte.",
"LabelApiToken": "API Schlüssel",
"LabelAppend": "Anhängen",
"LabelAudioBitrate": "Audiobitrate (z. B. 128 kbit/s)",
@@ -283,6 +295,7 @@
"LabelContinueListening": "Weiterhören",
"LabelContinueReading": "Weiterlesen",
"LabelContinueSeries": "Serien fortsetzen",
"LabelCorsAllowed": "Erlaubte CORS Quellen",
"LabelCover": "Titelbild",
"LabelCoverImageURL": "URL des Titelbildes",
"LabelCoverProvider": "Titelbildanbieter",
@@ -296,6 +309,7 @@
"LabelDeleteFromFileSystemCheckbox": "Löschen von der Festplatte + Datenbank (deaktivieren um nur aus der Datenbank zu entfernen)",
"LabelDescription": "Beschreibung",
"LabelDeselectAll": "Alles abwählen",
"LabelDetectedPattern": "Erkanntes Muster:",
"LabelDevice": "Gerät",
"LabelDeviceInfo": "Geräteinformationen",
"LabelDeviceIsAvailableTo": "Dem Gerät ist es möglich zu ...",
@@ -345,7 +359,11 @@
"LabelExample": "Beispiel",
"LabelExpandSeries": "Serie ausklappen",
"LabelExpandSubSeries": "Unterserie ausklappen",
"LabelExplicit": "Explizit (Altersbeschränkung)",
"LabelExpired": "Abgelaufen",
"LabelExpiresAt": "Läuft ab am",
"LabelExpiresInSeconds": "Ablauf in (seconds) Sekunden",
"LabelExpiresNever": "Niemals",
"LabelExplicit": "Explizit",
"LabelExplicitChecked": "Explicit (Altersbeschränkung) (angehakt)",
"LabelExplicitUnchecked": "Not Explicit (Altersbeschränkung) (nicht angehakt)",
"LabelExportOPML": "OPML exportieren",
@@ -404,6 +422,7 @@
"LabelLanguages": "Sprachen",
"LabelLastBookAdded": "Zuletzt hinzugefügtes Buch",
"LabelLastBookUpdated": "Zuletzt aktualisiertes Buch",
"LabelLastProgressDate": "Letzter Fortschritt: {0}",
"LabelLastSeen": "Zuletzt gesehen",
"LabelLastTime": "Letztes Mal",
"LabelLastUpdate": "Letzte Aktualisierung",
@@ -416,6 +435,7 @@
"LabelLibraryFilterSublistEmpty": "Keine {0}",
"LabelLibraryItem": "Bibliothekseintrag",
"LabelLibraryName": "Bibliotheksname",
"LabelLibrarySortByProgress": "Fortschritt aktualisiert",
"LabelLimit": "Begrenzung",
"LabelLineSpacing": "Zeilenabstand",
"LabelListenAgain": "Erneut anhören",
@@ -424,6 +444,7 @@
"LabelLogLevelWarn": "Warnungen",
"LabelLookForNewEpisodesAfterDate": "Suche nach neuen Episoden nach diesem Datum",
"LabelLowestPriority": "Niedrigste Priorität",
"LabelMatchConfidence": "Vertrauenswert",
"LabelMatchExistingUsersBy": "Zuordnen existierender Benutzer mit",
"LabelMatchExistingUsersByDescription": "Wird zum Verbinden vorhandener Benutzer verwendet. Sobald die Verbindung hergestellt ist, wird den Benutzern eine eindeutige ID vom SSO-Anbieter zugeordnet",
"LabelMaxEpisodesToDownload": "Max. Anzahl an Episoden zum Herunterladen, 0 für unbegrenzte Episoden.",
@@ -432,7 +453,7 @@
"LabelMaxEpisodesToKeepHelp": "0 setzt keine Begrenzung. Wenn eine neue Episode automatisch heruntergeladen wird, wird die älteste Episode gelöscht, wenn du mehr als X Episoden gespeichert hast. Es wird nur eine Episode pro neuem Download gelöscht.",
"LabelMediaPlayer": "Mediaplayer",
"LabelMediaType": "Medientyp",
"LabelMetaTag": "Meta Schlagwort",
"LabelMetaTag": "Meta Tag",
"LabelMetaTags": "Meta Tags",
"LabelMetadataOrderOfPrecedenceDescription": "Höher priorisierte Quellen für Metadaten überschreiben Metadaten aus Quellen mit niedrigerer Priorität",
"LabelMetadataProvider": "Metadatenanbieter",
@@ -453,7 +474,9 @@
"LabelNewestAuthors": "Neueste Autoren",
"LabelNewestEpisodes": "Neueste Episoden",
"LabelNextBackupDate": "Nächstes Sicherungsdatum",
"LabelNextChapters": "Das nächste Kapitel ist:",
"LabelNextScheduledRun": "Nächster planmäßiger Durchlauf",
"LabelNoApiKeys": "Keine API-Schlüssel vorhanden",
"LabelNoCustomMetadataProviders": "Keine benutzerdefinierten Metadata Anbieter",
"LabelNoEpisodesSelected": "Keine Episoden ausgewählt",
"LabelNotFinished": "Nicht beendet",
@@ -469,6 +492,7 @@
"LabelNotificationsMaxQueueSize": "Maximale Größe der Warteschlange für die Benachrichtigungsereignisse",
"LabelNotificationsMaxQueueSizeHelp": "Es wird nur 1 Ereignis pro Sekunde ausgelöst. Ereignisse werden ignoriert, wenn die Warteschlange die maximale Größe erreicht hat. Dies verhindert Benachrichtigungsspamming.",
"LabelNumberOfBooks": "Anzahl der Hörbücher",
"LabelNumberOfChapters": "Anzahl an Kapiteln:",
"LabelNumberOfEpisodes": "Anzahl der Episoden",
"LabelOpenIDAdvancedPermsClaimDescription": "Name des OpenID-Claims, der erweiterte Berechtigungen für Benutzeraktionen innerhalb der Anwendung enthält, die auf Nicht-Admin-Rollen angewendet werden (<b>wenn konfiguriert</b>). Wenn der Claim in der Antwort fehlt, wird der Zugang zu ABS verweigert. Fehlt eine einzelne Option, wird sie als <code>false</code> behandelt. Stelle sicher, dass der Claim des Identitätsanbieters der erwarteten Struktur entspricht:",
"LabelOpenIDClaims": "Lass die folgenden Optionen leer, um die erweiterte Zuweisung von Gruppen und Berechtigungen zu deaktivieren und automatisch die 'User'-Gruppe zuzuweisen.",
@@ -513,7 +537,7 @@
"LabelPublishers": "Herausgeber",
"LabelRSSFeedCustomOwnerEmail": "Benutzerdefinierte Eigentümer-E-Mail",
"LabelRSSFeedCustomOwnerName": "Benutzerdefinierter Name des Eigentümers",
"LabelRSSFeedOpen": "RSS Feed offen",
"LabelRSSFeedOpen": "RSS-Feed offen",
"LabelRSSFeedPreventIndexing": "Indizierung verhindern",
"LabelRSSFeedSlug": "RSS-Feed-Schlagwort",
"LabelRSSFeedURL": "RSS-Feed-URL",
@@ -530,6 +554,7 @@
"LabelReleaseDate": "Veröffentlichungsdatum",
"LabelRemoveAllMetadataAbs": "Alle metadata.abs Dateien löschen",
"LabelRemoveAllMetadataJson": "Alle metadata.json Dateien löschen",
"LabelRemoveAudibleBranding": "Audible Intro sowie Outro aus Kapiteln entfernen",
"LabelRemoveCover": "Entferne Titelbild",
"LabelRemoveMetadataFile": "Metadaten-Dateien in Bibliotheksordnern löschen",
"LabelRemoveMetadataFileHelp": "Alle metadata.json und metadata.abs Dateien aus den Ordnern {0} löschen.",
@@ -542,6 +567,7 @@
"LabelSelectAll": "Alles auswählen",
"LabelSelectAllEpisodes": "Alle Episoden auswählen",
"LabelSelectEpisodesShowing": "{0} ausgewählte Episoden werden angezeigt",
"LabelSelectUser": "Ausgewählter Benutzer",
"LabelSelectUsers": "Benutzer auswählen",
"LabelSendEbookToDevice": "E-Buch senden an …",
"LabelSequence": "Reihenfolge",
@@ -638,6 +664,7 @@
"LabelTheme": "Farbschema",
"LabelThemeDark": "Dunkel",
"LabelThemeLight": "Hell",
"LabelThemeSepia": "Sepia",
"LabelTimeBase": "Basiszeit",
"LabelTimeDurationXHours": "{0} Stunden",
"LabelTimeDurationXMinutes": "{0} Minuten",
@@ -705,6 +732,10 @@
"LabelYourProgress": "Fortschritt",
"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>.",
"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.",
"MessageBackupsLocationEditNote": "Hinweis: Durch das Aktualisieren des Backup-Speicherorts werden vorhandene Sicherungen nicht verschoben oder geändert",
"MessageBackupsLocationNoEditNote": "Hinweis: Der Sicherungsspeicherort wird über eine Umgebungsvariable festgelegt und kann hier nicht geändert werden.",
@@ -718,13 +749,16 @@
"MessageBookshelfNoResultsForFilter": "Keine Ergebnisse für Filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "Keine Ergebnisse für die Abfrage",
"MessageBookshelfNoSeries": "Keine Serien vorhanden",
"MessageBulkChapterPattern": "Wie viele Kapitel mit diesem Nummerierungs-Muster sollen hinzugefügt werden?",
"MessageChapterEndIsAfter": "Ungültige Kapitelendzeit: Kapitelende > Mediumende (Kapitelende liegt nach dem Ende des Mediums)",
"MessageChapterErrorFirstNotZero": "Ungültige Kapitelstartzeit: Das erste Kapitel muss bei 0 beginnen",
"MessageChapterErrorStartGteDuration": "Ungültige Kapitelstartzeit: Kapitelanfang > Mediumlänge (Kapitelanfang liegt zeitlich nach dem Ende des Mediums -> Lösung: Kapitelanfang < Mediumlänge)",
"MessageChapterErrorStartLtPrev": "Ungültige Kapitelstartzeit: Kapitelanfang < Kapitelanfang vorheriges Kapitel (Kapitelanfang liegt zeitlich vor dem Beginn des vorherigen Kapitels -> Lösung: Kapitelanfang >= Startzeit des vorherigen Kapitels)",
"MessageChapterStartIsAfter": "Ungültige Kapitelstartzeit: Kapitelanfang > Mediumende (Kapitelanfang liegt nach dem Ende des Mediums)",
"MessageChaptersNotFound": "Kapitel gefunden nicht",
"MessageCheckingCron": "Überprüfe Cron...",
"MessageConfirmCloseFeed": "Feed wird geschlossen! Bist du dir sicher?",
"MessageConfirmDeleteApiKey": "Möchtest du den API-Schlüssel \"{0}\" wirklich entfernen ?",
"MessageConfirmDeleteBackup": "Sicherung für {0} wird gelöscht! Bist du dir sicher?",
"MessageConfirmDeleteDevice": "Möchtest du das Lesegerät „{0}“ wirklich löschen?",
"MessageConfirmDeleteFile": "Datei wird vom System gelöscht! Bist du dir sicher?",
@@ -752,6 +786,7 @@
"MessageConfirmRemoveAuthor": "Autor \"{0}\" wird enfernt! Bist du dir sicher?",
"MessageConfirmRemoveCollection": "Sammlung \"{0}\" wird entfernt! Bist du dir sicher?",
"MessageConfirmRemoveEpisode": "Episode \"{0}\" wird entfernt! Bist du dir sicher?",
"MessageConfirmRemoveEpisodeNote": "Hinweis: Die Audiodatei wird nicht gelöscht, es sei denn \"Datei dauerhaft löschen\" ist aktiviert",
"MessageConfirmRemoveEpisodes": "{0} Episoden werden entfernt! Bist du dir sicher?",
"MessageConfirmRemoveListeningSessions": "Bist du dir sicher, dass du {0} Hörsitzungen enfernen möchtest?",
"MessageConfirmRemoveMetadataFiles": "Bist du sicher, dass du alle metadata.{0} Dateien in deinen Bibliotheksordnern löschen willst?",
@@ -777,8 +812,11 @@
"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",
"MessageHeatmapNoListeningSessions": "Keine Hörsitzungen am {0}",
"MessageImportantNotice": "Wichtiger Hinweis!",
"MessageInsertChapterBelow": "Kapitel unten einfügen",
"MessageInvalidAsin": "Ungültige ASIN",
"MessageItemsSelected": "{0} ausgewählte Medien",
"MessageItemsUpdated": "{0} Medien aktualisiert",
"MessageJoinUsOn": "Besuche uns auf",
@@ -846,12 +884,13 @@
"MessageResetChaptersConfirm": "Kapitel und vorgenommenen Änderungen werden zurückgesetzt und rückgängig gemacht! Bist du dir sicher?",
"MessageRestoreBackupConfirm": "Bist du dir sicher, dass du die Sicherung wiederherstellen willst, welche am",
"MessageRestoreBackupWarning": "Bei der Wiederherstellung einer Sicherung wird die gesamte Datenbank unter /config und die Titelbilder in /metadata/items und /metadata/authors überschrieben.<br /><br />Bei der Sicherung werden keine Dateien in deinen Bibliotheksordnern verändert. Wenn du die Servereinstellungen aktiviert hast, um Cover und Metadaten in deinen Bibliotheksordnern zu speichern, werden diese nicht gesichert oder überschrieben.<br /><br />Alle Clients, die Ihren Server nutzen, werden automatisch aktualisiert.",
"MessageScheduleLibraryScanNote": "Für die meisten Nutzer wird empfohlen, diese Funktion deaktiviert zu lassen und stattdessen die Ordnerüberwachung aktiviert zu lassen. Die Ordnerüberwachung erkennt automatisch Änderungen in deinen Bibliotheksordnern. Da die Ordnerüberwachung jedoch nicht mit jedem Dateisystem (z.B. NFS) funktioniert, können alternativ hier geplante Bibliotheks-Scans aktiviert werden.",
"MessageScheduleLibraryScanNote": "Für die meisten Anwender wird empfohlen, diese Funktion deaktiviert und die Ordnerüberwachung aktiviert zu lassen. Die Ordnerüberwachung wird Änderungen in den Bibliotheksordnern automatisch erkennen. Die Ordnerüberwachung funktioniert nicht mit allen Dateisystemen (wie NFS), hier kann stattdessen die automatischen Bibliothekssuchen verwendet werden.",
"MessageScheduleRunEveryWeekdayAtTime": "Immer {0} um {1} ausführen",
"MessageSearchResultsFor": "Suchergebnisse für",
"MessageSelected": "{0} ausgewählt",
"MessageSeriesSequenceCannotContainSpaces": "Serie Abfolge kann keine Leerzeichen enthalten",
"MessageServerCouldNotBeReached": "Server kann nicht erreicht werden",
"MessageSetChaptersFromTracksDescription": "Kaitelerstellung basiert auf den existierenden einzelnen Audiodateien. Pro existierende Audiodatei wird 1 Kapitel erstellt, wobei deren Kapitelname aus dem Audiodateinamen extrahiert wird",
"MessageSetChaptersFromTracksDescription": "Kapitelerstellung basiert auf den existierenden einzelnen Audiodateien. Pro existierende Audiodatei wird 1 Kapitel erstellt, wobei deren Kapitelname aus dem Audiodateinamen extrahiert wird",
"MessageShareExpirationWillBe": "Läuft am <strong>{0}</strong> ab",
"MessageShareExpiresIn": "Läuft in {0} ab",
"MessageShareURLWillBe": "Der Freigabe Link wird <strong>{0}</strong> sein",
@@ -911,7 +950,10 @@
"NotificationOnBackupCompletedDescription": "Wird ausgeführt wenn ein Backup erstellt wurde",
"NotificationOnBackupFailedDescription": "Wird ausgeführt wenn ein Backup fehlgeschlagen ist",
"NotificationOnEpisodeDownloadedDescription": "Wird ausgeführt wenn eine Podcast Folge automatisch heruntergeladen wird",
"NotificationOnRSSFeedDisabledDescription": "Wird ausgeführt wenn automatische Downloads von Episoden wegen zu vielen fehlgeschlagenen Versuchen deaktiviert sind",
"NotificationOnRSSFeedFailedDescription": "Wird ausgelöst, wenn die RSS-Feed-Anforderung für einen automatischen Episoden-Download fehlschlägt",
"NotificationOnTestDescription": "Wird ausgeführt wenn das Benachrichtigungssystem getestet wird",
"PlaceholderBulkChapterInput": "Kapitelbezeichnung eingeben oder Nummerierung verwenden (z.B. 'Episode 1', 'Kapitel 10', '1.')",
"PlaceholderNewCollection": "Neuer Sammlungsname",
"PlaceholderNewFolderPath": "Neuer Ordnerpfad",
"PlaceholderNewPlaylist": "Neuer Wiedergabelistenname",
@@ -965,9 +1007,15 @@
"ToastBookmarkCreateFailed": "Lesezeichen konnte nicht erstellt werden",
"ToastBookmarkCreateSuccess": "Lesezeichen hinzugefügt",
"ToastBookmarkRemoveSuccess": "Lesezeichen entfernt",
"ToastBulkChapterInvalidCount": "Gebe eine Zahl zwischen 1 und 150 ein",
"ToastCachePurgeFailed": "Cache leeren fehlgeschlagen",
"ToastCachePurgeSuccess": "Cache geleert",
"ToastChapterLocked": "Kapitel ist freigegeben.",
"ToastChapterStartTimeAdjusted": "Kapitelbeginn um {0} Sekunden angepasst",
"ToastChaptersAllLocked": "Alle Kapitel sind gesperrt. Gebe einige Kapitel frei um die Zeiten anzupassen.",
"ToastChaptersHaveErrors": "Kapitel sind fehlerhaft",
"ToastChaptersInvalidShiftAmountLast": "Die Verschiebung ist nicht möglich, da die Startzeit des letzten Kapitels über die Gesamtdauer dieses Hörbuchs hinausgehen würde.",
"ToastChaptersInvalidShiftAmountStart": "Ungültige Höhe der Verschiebung. Das erste Kapitel hätte eine Länge von Null oder eine negative Länge und würde vom zweiten Kapitel überschrieben werden. Erhöhe die Startdauer des zweiten Kapitels.",
"ToastChaptersMustHaveTitles": "Kapitel benötigen eindeutige Namen",
"ToastChaptersRemoved": "Kapitel entfernt",
"ToastChaptersUpdated": "Kapitel aktualisiert",
@@ -989,13 +1037,16 @@
"ToastEpisodeDownloadQueueClearSuccess": "Warteschlange für Episoden-Downloads gelöscht",
"ToastEpisodeUpdateSuccess": "{0} Episoden aktualisiert",
"ToastErrorCannotShare": "Das kann nicht nativ auf diesem Gerät freigegeben werden",
"ToastFailedToLoadData": "Daten laden fehlgeschlagen",
"ToastFailedToCreate": "Fehler beim Erzeugen",
"ToastFailedToDelete": "Fehler beim Löschen",
"ToastFailedToLoadData": "Fehler beim laden der Daten",
"ToastFailedToMatch": "Fehler beim Abgleich",
"ToastFailedToShare": "Fehler beim Teilen",
"ToastFailedToUpdate": "Aktualisierung ist fehlgeschlagen",
"ToastInvalidImageUrl": "Ungültiger Bild URL",
"ToastInvalidMaxEpisodesToDownload": "Ungültige Max. Anzahl an Episoden zum Herunterladen",
"ToastInvalidUrl": "Ungültiger URL",
"ToastInvalidUrls": "Eine oder mehrere URLs sind ungültig",
"ToastItemCoverUpdateSuccess": "Titelbild aktualisiert",
"ToastItemDeletedFailed": "Fehler beim löschen des Artikels",
"ToastItemDeletedSuccess": "Artikel gelöscht",
@@ -1020,6 +1071,7 @@
"ToastMustHaveAtLeastOnePath": "Es muss mindestens ein Pfad angegeben werden",
"ToastNameEmailRequired": "Name und E-Mail sind erforderlich",
"ToastNameRequired": "Name ist erforderlich",
"ToastNewApiKeyUserError": "Bitte wähle einen Benutzer aus (Pflichtfeld)",
"ToastNewEpisodesFound": "{0} neue Episoden gefunden",
"ToastNewUserCreatedFailed": "Fehler beim erstellen des Accounts: \"{ 0}\"",
"ToastNewUserCreatedSuccess": "Neuer Account erstellt",
@@ -1094,5 +1146,13 @@
"ToastUserPasswordChangeSuccess": "Passwort erfolgreich verändert",
"ToastUserPasswordMismatch": "Passwörter stimmen nicht überein",
"ToastUserPasswordMustChange": "Neues Passwort muss sich von altem Passwort unterscheiden",
"ToastUserRootRequireName": "Root Benutzername muss angegeben werden"
"ToastUserRootRequireName": "Root Benutzername muss angegeben werden",
"TooltipAddChapters": "Kapitel hinzufügen",
"TooltipAddOneSecond": "1 Sekunde hinzufügen",
"TooltipAdjustChapterStart": "Klicke um die Startzeit anzupassen",
"TooltipLockAllChapters": "Alle Kapitel sperren",
"TooltipLockChapter": "Kapitel sperren (Shift+Klick für mehrere)",
"TooltipSubtractOneSecond": "1 Sekunde abziehen",
"TooltipUnlockAllChapters": "Alle Kapitel freigeben",
"TooltipUnlockChapter": "Kapitel freigeben (Shift+Klick für mehrere)"
}

View File

@@ -1,5 +1,6 @@
{
"ButtonAdd": "Add",
"ButtonAddApiKey": "Add API Key",
"ButtonAddChapters": "Add Chapters",
"ButtonAddDevice": "Add Device",
"ButtonAddLibrary": "Add Library",
@@ -20,6 +21,7 @@
"ButtonChooseAFolder": "Choose a folder",
"ButtonChooseFiles": "Choose files",
"ButtonClearFilter": "Clear Filter",
"ButtonClose": "Close",
"ButtonCloseFeed": "Close Feed",
"ButtonCloseSession": "Close Open Session",
"ButtonCollections": "Collections",
@@ -119,11 +121,13 @@
"HeaderAccount": "Account",
"HeaderAddCustomMetadataProvider": "Add Custom Metadata Provider",
"HeaderAdvanced": "Advanced",
"HeaderApiKeys": "API Keys",
"HeaderAppriseNotificationSettings": "Apprise Notification Settings",
"HeaderAudioTracks": "Audio Tracks",
"HeaderAudiobookTools": "Audiobook File Management Tools",
"HeaderAuthentication": "Authentication",
"HeaderBackups": "Backups",
"HeaderBulkChapterModal": "Add Multiple Chapters",
"HeaderChangePassword": "Change Password",
"HeaderChapters": "Chapters",
"HeaderChooseAFolder": "Choose a Folder",
@@ -162,6 +166,7 @@
"HeaderMetadataOrderOfPrecedence": "Metadata order of precedence",
"HeaderMetadataToEmbed": "Metadata to embed",
"HeaderNewAccount": "New Account",
"HeaderNewApiKey": "New API Key",
"HeaderNewLibrary": "New Library",
"HeaderNotificationCreate": "Create Notification",
"HeaderNotificationUpdate": "Update Notification",
@@ -177,6 +182,7 @@
"HeaderPlaylist": "Playlist",
"HeaderPlaylistItems": "Playlist Items",
"HeaderPodcastsToAdd": "Podcasts to Add",
"HeaderPresets": "Presets",
"HeaderPreviewCover": "Preview Cover",
"HeaderRSSFeedGeneral": "RSS Details",
"HeaderRSSFeedIsOpen": "RSS Feed is Open",
@@ -194,6 +200,7 @@
"HeaderSettingsExperimental": "Experimental Features",
"HeaderSettingsGeneral": "General",
"HeaderSettingsScanner": "Scanner",
"HeaderSettingsSecurity": "Security",
"HeaderSettingsWebClient": "Web Client",
"HeaderSleepTimer": "Sleep Timer",
"HeaderStatsLargestItems": "Largest Items",
@@ -205,6 +212,7 @@
"HeaderTableOfContents": "Table of Contents",
"HeaderTools": "Tools",
"HeaderUpdateAccount": "Update Account",
"HeaderUpdateApiKey": "Update API Key",
"HeaderUpdateAuthor": "Update Author",
"HeaderUpdateDetails": "Update Details",
"HeaderUpdateLibrary": "Update Library",
@@ -234,6 +242,10 @@
"LabelAllUsersExcludingGuests": "All users excluding guests",
"LabelAllUsersIncludingGuests": "All users including guests",
"LabelAlreadyInYourLibrary": "Already in your library",
"LabelApiKeyCreated": "API Key \"{0}\" created successfully.",
"LabelApiKeyCreatedDescription": "Make sure to copy the API key now as you will not be able to see this again.",
"LabelApiKeyUser": "Act on behalf of user",
"LabelApiKeyUserDescription": "This API key will have the same permissions as the user it is acting on behalf of. This will appear the same in logs as if the user was making the request.",
"LabelApiToken": "API Token",
"LabelAppend": "Append",
"LabelAudioBitrate": "Audio Bitrate (e.g. 128k)",
@@ -283,6 +295,7 @@
"LabelContinueListening": "Continue Listening",
"LabelContinueReading": "Continue Reading",
"LabelContinueSeries": "Continue Series",
"LabelCorsAllowed": "Allowed CORS Origins",
"LabelCover": "Cover",
"LabelCoverImageURL": "Cover Image URL",
"LabelCoverProvider": "Cover Provider",
@@ -296,6 +309,7 @@
"LabelDeleteFromFileSystemCheckbox": "Delete from file system (uncheck to only remove from database)",
"LabelDescription": "Description",
"LabelDeselectAll": "Deselect All",
"LabelDetectedPattern": "Detected pattern:",
"LabelDevice": "Device",
"LabelDeviceInfo": "Device Info",
"LabelDeviceIsAvailableTo": "Device is available to...",
@@ -345,6 +359,10 @@
"LabelExample": "Example",
"LabelExpandSeries": "Expand Series",
"LabelExpandSubSeries": "Expand Sub Series",
"LabelExpired": "Expired",
"LabelExpiresAt": "Expires At",
"LabelExpiresInSeconds": "Expires in (seconds)",
"LabelExpiresNever": "Never",
"LabelExplicit": "Explicit",
"LabelExplicitChecked": "Explicit (checked)",
"LabelExplicitUnchecked": "Not Explicit (unchecked)",
@@ -360,6 +378,7 @@
"LabelFilterByUser": "Filter by User",
"LabelFindEpisodes": "Find Episodes",
"LabelFinished": "Finished",
"LabelFinishedDate": "Finished {0}",
"LabelFolder": "Folder",
"LabelFolders": "Folders",
"LabelFontBold": "Bold",
@@ -404,6 +423,7 @@
"LabelLanguages": "Languages",
"LabelLastBookAdded": "Last Book Added",
"LabelLastBookUpdated": "Last Book Updated",
"LabelLastProgressDate": "Last progress: {0}",
"LabelLastSeen": "Last Seen",
"LabelLastTime": "Last Time",
"LabelLastUpdate": "Last Update",
@@ -416,6 +436,9 @@
"LabelLibraryFilterSublistEmpty": "No {0}",
"LabelLibraryItem": "Library Item",
"LabelLibraryName": "Library Name",
"LabelLibrarySortByProgress": "Progress: Last Updated",
"LabelLibrarySortByProgressFinished": "Progress: Finished",
"LabelLibrarySortByProgressStarted": "Progress: Started",
"LabelLimit": "Limit",
"LabelLineSpacing": "Line spacing",
"LabelListenAgain": "Listen Again",
@@ -424,6 +447,7 @@
"LabelLogLevelWarn": "Warn",
"LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date",
"LabelLowestPriority": "Lowest Priority",
"LabelMatchConfidence": "Confidence",
"LabelMatchExistingUsersBy": "Match existing users by",
"LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider",
"LabelMaxEpisodesToDownload": "Max # of episodes to download. Use 0 for unlimited.",
@@ -453,7 +477,9 @@
"LabelNewestAuthors": "Newest Authors",
"LabelNewestEpisodes": "Newest Episodes",
"LabelNextBackupDate": "Next backup date",
"LabelNextChapters": "Next chapters will be:",
"LabelNextScheduledRun": "Next scheduled run",
"LabelNoApiKeys": "No API keys",
"LabelNoCustomMetadataProviders": "No custom metadata providers",
"LabelNoEpisodesSelected": "No episodes selected",
"LabelNotFinished": "Not Finished",
@@ -469,6 +495,7 @@
"LabelNotificationsMaxQueueSize": "Max queue size for notification events",
"LabelNotificationsMaxQueueSizeHelp": "Events are limited to firing 1 per second. Events will be ignored if the queue is at max size. This prevents notification spamming.",
"LabelNumberOfBooks": "Number of Books",
"LabelNumberOfChapters": "Number of chapters:",
"LabelNumberOfEpisodes": "# of Episodes",
"LabelOpenIDAdvancedPermsClaimDescription": "Name of the OpenID claim that contains advanced permissions for user actions within the application which will apply to non-admin roles (<b>if configured</b>). If the claim is missing from the response, access to ABS will be denied. If a single option is missing, it will be treated as <code>false</code>. Ensure the identity provider's claim matches the expected structure:",
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
@@ -530,6 +557,7 @@
"LabelReleaseDate": "Release Date",
"LabelRemoveAllMetadataAbs": "Remove all metadata.abs files",
"LabelRemoveAllMetadataJson": "Remove all metadata.json files",
"LabelRemoveAudibleBranding": "Remove Audible intro and outro from chapters",
"LabelRemoveCover": "Remove cover",
"LabelRemoveMetadataFile": "Remove metadata files in library item folders",
"LabelRemoveMetadataFileHelp": "Remove all metadata.json and metadata.abs files in your {0} folders.",
@@ -542,6 +570,7 @@
"LabelSelectAll": "Select all",
"LabelSelectAllEpisodes": "Select all episodes",
"LabelSelectEpisodesShowing": "Select {0} episodes showing",
"LabelSelectUser": "Select user",
"LabelSelectUsers": "Select users",
"LabelSendEbookToDevice": "Send Ebook to...",
"LabelSequence": "Sequence",
@@ -609,6 +638,7 @@
"LabelStartTime": "Start Time",
"LabelStarted": "Started",
"LabelStartedAt": "Started At",
"LabelStartedDate": "Started {0}",
"LabelStatsAudioTracks": "Audio Tracks",
"LabelStatsAuthors": "Authors",
"LabelStatsBestDay": "Best Day",
@@ -638,6 +668,7 @@
"LabelTheme": "Theme",
"LabelThemeDark": "Dark",
"LabelThemeLight": "Light",
"LabelThemeSepia": "Sepia",
"LabelTimeBase": "Time Base",
"LabelTimeDurationXHours": "{0} hours",
"LabelTimeDurationXMinutes": "{0} minutes",
@@ -706,6 +737,9 @@
"MessageAddToPlayerQueue": "Add to player queue",
"MessageAppriseDescription": "To use this feature you will need to have an instance of <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> running or an api that will handle those same requests. <br />The Apprise API Url should be the full URL path to send the notification, e.g., if your API instance is served at <code>http://192.168.1.1:8337</code> then you would put <code>http://192.168.1.1:8337/notify</code>.",
"MessageAsinCheck": "Ensure you are using the ASIN from the correct Audible region, not Amazon.",
"MessageAuthenticationLegacyTokenWarning": "Legacy API tokens will be removed in the future. Use <a href=\"/config/api-keys\">API Keys</a> instead.",
"MessageAuthenticationOIDCChangesRestart": "Restart your server after saving to apply OIDC changes.",
"MessageAuthenticationSecurityMessage": "Authentication has been improved for security. All users are required to re-login.",
"MessageBackupsDescription": "Backups include users, user progress, library item details, server settings, and images stored in <code>/metadata/items</code> & <code>/metadata/authors</code>. Backups <strong>do not</strong> include any files stored in your library folders.",
"MessageBackupsLocationEditNote": "Note: Updating the backup location will not move or modify existing backups",
"MessageBackupsLocationNoEditNote": "Note: The backup location is set through an environment variable and cannot be changed here.",
@@ -719,6 +753,7 @@
"MessageBookshelfNoResultsForFilter": "No results for filter \"{0}: {1}\"",
"MessageBookshelfNoResultsForQuery": "No results for query",
"MessageBookshelfNoSeries": "You have no series",
"MessageBulkChapterPattern": "How many chapters would you like to add with this numbering pattern?",
"MessageChapterEndIsAfter": "Chapter end is after the end of your audiobook",
"MessageChapterErrorFirstNotZero": "First chapter must start at 0",
"MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration",
@@ -727,6 +762,7 @@
"MessageChaptersNotFound": "Chapters not found",
"MessageCheckingCron": "Checking cron...",
"MessageConfirmCloseFeed": "Are you sure you want to close this feed?",
"MessageConfirmDeleteApiKey": "Are you sure you want to delete API key \"{0}\"?",
"MessageConfirmDeleteBackup": "Are you sure you want to delete backup for {0}?",
"MessageConfirmDeleteDevice": "Are you sure you want to delete e-reader device \"{0}\"?",
"MessageConfirmDeleteFile": "This will delete the file from your file system. Are you sure?",
@@ -754,6 +790,7 @@
"MessageConfirmRemoveAuthor": "Are you sure you want to remove author \"{0}\"?",
"MessageConfirmRemoveCollection": "Are you sure you want to remove collection \"{0}\"?",
"MessageConfirmRemoveEpisode": "Are you sure you want to remove episode \"{0}\"?",
"MessageConfirmRemoveEpisodeNote": "Note: This does not delete the audio file unless toggling \"Hard delete file\"",
"MessageConfirmRemoveEpisodes": "Are you sure you want to remove {0} episodes?",
"MessageConfirmRemoveListeningSessions": "Are you sure you want to remove {0} listening sessions?",
"MessageConfirmRemoveMetadataFiles": "Are you sure you want to remove all metadata.{0} files in your library item folders?",
@@ -779,6 +816,8 @@
"MessageFeedURLWillBe": "Feed URL will be {0}",
"MessageFetching": "Fetching...",
"MessageForceReScanDescription": "will scan all files again like a fresh scan. Audio file ID3 tags, OPF files, and text files will be scanned as new.",
"MessageHeatmapListeningTimeTooltip": "<strong>{0} listening</strong> on {1}",
"MessageHeatmapNoListeningSessions": "No listening sessions on {0}",
"MessageImportantNotice": "Important Notice!",
"MessageInsertChapterBelow": "Insert chapter below",
"MessageInvalidAsin": "Invalid ASIN",
@@ -853,6 +892,7 @@
"MessageScheduleRunEveryWeekdayAtTime": "Run every {0} at {1}",
"MessageSearchResultsFor": "Search results for",
"MessageSelected": "{0} selected",
"MessageSeriesSequenceCannotContainSpaces": "Series sequence cannot contain spaces",
"MessageServerCouldNotBeReached": "Server could not be reached",
"MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
"MessageShareExpirationWillBe": "Expiration will be <strong>{0}</strong>",
@@ -914,7 +954,10 @@
"NotificationOnBackupCompletedDescription": "Triggered when a backup is completed",
"NotificationOnBackupFailedDescription": "Triggered when a backup fails",
"NotificationOnEpisodeDownloadedDescription": "Triggered when a podcast episode is auto-downloaded",
"NotificationOnRSSFeedDisabledDescription": "Triggered when automatic episode downloads are disabled due to too many failed attempts",
"NotificationOnRSSFeedFailedDescription": "Triggered when the RSS feed request fails for an automatic episode download",
"NotificationOnTestDescription": "Event for testing the notification system",
"PlaceholderBulkChapterInput": "Enter chapter title or use numbering (e.g., 'Episode 1', 'Chapter 10', '1.')",
"PlaceholderNewCollection": "New collection name",
"PlaceholderNewFolderPath": "New folder path",
"PlaceholderNewPlaylist": "New playlist name",
@@ -968,9 +1011,15 @@
"ToastBookmarkCreateFailed": "Failed to create bookmark",
"ToastBookmarkCreateSuccess": "Bookmark added",
"ToastBookmarkRemoveSuccess": "Bookmark removed",
"ToastBulkChapterInvalidCount": "Enter a number between 1 and 150",
"ToastCachePurgeFailed": "Failed to purge cache",
"ToastCachePurgeSuccess": "Cache purged successfully",
"ToastChapterLocked": "Chapter is locked.",
"ToastChapterStartTimeAdjusted": "Chapter start time adjusted by {0} seconds",
"ToastChaptersAllLocked": "All chapters are locked. Unlock some chapters to shift their times.",
"ToastChaptersHaveErrors": "Chapters have errors",
"ToastChaptersInvalidShiftAmountLast": "Invalid shift amount. The last chapter start time would extend beyond the duration of this audiobook.",
"ToastChaptersInvalidShiftAmountStart": "Invalid shift amount. The first chapter would have zero or negative length and would be overwritten by the second chapter. Increase the start duration of second chapter.",
"ToastChaptersMustHaveTitles": "Chapters must have titles",
"ToastChaptersRemoved": "Chapters removed",
"ToastChaptersUpdated": "Chapters updated",
@@ -992,6 +1041,8 @@
"ToastEpisodeDownloadQueueClearSuccess": "Episode download queue cleared",
"ToastEpisodeUpdateSuccess": "{0} episodes updated",
"ToastErrorCannotShare": "Cannot share natively on this device",
"ToastFailedToCreate": "Failed to create",
"ToastFailedToDelete": "Failed to delete",
"ToastFailedToLoadData": "Failed to load data",
"ToastFailedToMatch": "Failed to match",
"ToastFailedToShare": "Failed to share",
@@ -999,6 +1050,7 @@
"ToastInvalidImageUrl": "Invalid image URL",
"ToastInvalidMaxEpisodesToDownload": "Invalid max episodes to download",
"ToastInvalidUrl": "Invalid URL",
"ToastInvalidUrls": "One or more URLs are invalid",
"ToastItemCoverUpdateSuccess": "Item cover updated",
"ToastItemDeletedFailed": "Failed to delete item",
"ToastItemDeletedSuccess": "Deleted item",
@@ -1023,6 +1075,7 @@
"ToastMustHaveAtLeastOnePath": "Must have at least one path",
"ToastNameEmailRequired": "Name and email are required",
"ToastNameRequired": "Name is required",
"ToastNewApiKeyUserError": "Must select a user",
"ToastNewEpisodesFound": "{0} new episodes found",
"ToastNewUserCreatedFailed": "Failed to create account: \"{0}\"",
"ToastNewUserCreatedSuccess": "New account created",
@@ -1047,6 +1100,7 @@
"ToastPlaylistUpdateSuccess": "Playlist updated",
"ToastPodcastCreateFailed": "Failed to create podcast",
"ToastPodcastCreateSuccess": "Podcast created successfully",
"ToastPodcastEpisodeUpdated": "Episode updated",
"ToastPodcastGetFeedFailed": "Failed to get podcast feed",
"ToastPodcastNoEpisodesInFeed": "No episodes found in RSS feed",
"ToastPodcastNoRssFeed": "Podcast does not have an RSS feed",
@@ -1097,5 +1151,13 @@
"ToastUserPasswordChangeSuccess": "Password changed successfully",
"ToastUserPasswordMismatch": "Passwords do not match",
"ToastUserPasswordMustChange": "New password cannot match old password",
"ToastUserRootRequireName": "Must enter a root username"
"ToastUserRootRequireName": "Must enter a root username",
"TooltipAddChapters": "Add chapter(s)",
"TooltipAddOneSecond": "Add 1 second",
"TooltipAdjustChapterStart": "Click to adjust start time",
"TooltipLockAllChapters": "Lock all chapters",
"TooltipLockChapter": "Lock chapter (Shift+click for range)",
"TooltipSubtractOneSecond": "Subtract 1 second",
"TooltipUnlockAllChapters": "Unlock all chapters",
"TooltipUnlockChapter": "Unlock chapter (Shift+click for range)"
}

View File

@@ -1,5 +1,6 @@
{
"ButtonAdd": "Añadir",
"ButtonAddApiKey": "Añadir clave API",
"ButtonAddChapters": "Añadir capítulos",
"ButtonAddDevice": "Añadir dispositivo",
"ButtonAddLibrary": "Añadir biblioteca",
@@ -10,14 +11,17 @@
"ButtonApplyChapters": "Aplicar capítulos",
"ButtonAuthors": "Autores",
"ButtonBack": "Atrás",
"ButtonBatchEditPopulateFromExisting": "Rellenar desde existentes",
"ButtonBatchEditPopulateMapDetails": "Rellenar detalles de mapa",
"ButtonBrowseForFolder": "Buscar carpeta",
"ButtonCancel": "Cancelar",
"ButtonCancelEncode": "Cancelar Codificador",
"ButtonCancelEncode": "Cancelar codificación",
"ButtonChangeRootPassword": "Cambiar contraseña administrativa",
"ButtonCheckAndDownloadNewEpisodes": "Comprobar y descargar episodios nuevos",
"ButtonChooseAFolder": "Escoger una Carpeta",
"ButtonChooseFiles": "Escoger un Archivo",
"ButtonChooseAFolder": "Elegir una carpeta",
"ButtonChooseFiles": "Elegir archivos",
"ButtonClearFilter": "Quitar filtros",
"ButtonClose": "Cerrar",
"ButtonCloseFeed": "Cerrar suministro",
"ButtonCloseSession": "Cerrar la sesión abierta",
"ButtonCollections": "Colecciones",
@@ -87,7 +91,7 @@
"ButtonSaveAndClose": "Guardar y cerrar",
"ButtonSaveTracklist": "Guardar lista de pistas",
"ButtonScan": "Escanear",
"ButtonScanLibrary": "Escanear Biblioteca",
"ButtonScanLibrary": "Escanear biblioteca",
"ButtonScrollLeft": "Desplazarse hacia la izquierda",
"ButtonScrollRight": "Desplazarse hacia la derecha",
"ButtonSearch": "Buscar",
@@ -117,11 +121,13 @@
"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",
"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",
"HeaderChapters": "Capítulos",
"HeaderChooseAFolder": "Escoger una Carpeta",
@@ -147,7 +153,7 @@
"HeaderLastListeningSession": "Última sesión de escucha",
"HeaderLatestEpisodes": "Episodios más recientes",
"HeaderLibraries": "Bibliotecas",
"HeaderLibraryFiles": "Archivos de Biblioteca",
"HeaderLibraryFiles": "Archivos de biblioteca",
"HeaderLibraryStats": "Estadísticas de biblioteca",
"HeaderListeningSessions": "Sesión",
"HeaderListeningStats": "Estadísticas de Tiempo Escuchado",
@@ -160,6 +166,7 @@
"HeaderMetadataOrderOfPrecedence": "Orden de precedencia de metadatos",
"HeaderMetadataToEmbed": "Metadatos para Insertar",
"HeaderNewAccount": "Cuenta nueva",
"HeaderNewApiKey": "Nueva clave API",
"HeaderNewLibrary": "Biblioteca nueva",
"HeaderNotificationCreate": "Crear notificación",
"HeaderNotificationUpdate": "Notificación de actualización",
@@ -175,6 +182,7 @@
"HeaderPlaylist": "Lista de reproducción",
"HeaderPlaylistItems": "Elementos de lista de reproducción",
"HeaderPodcastsToAdd": "Pódcast para añadir",
"HeaderPresets": "Preconfiguraciones",
"HeaderPreviewCover": "Previsualizar cubierta",
"HeaderRSSFeedGeneral": "Detalles de RSS",
"HeaderRSSFeedIsOpen": "El suministro RSS está abierto",
@@ -190,8 +198,9 @@
"HeaderSettings": "Configuración",
"HeaderSettingsDisplay": "Interfaz",
"HeaderSettingsExperimental": "Funcionalidades experimentales",
"HeaderSettingsGeneral": "General",
"HeaderSettingsGeneral": "Generales",
"HeaderSettingsScanner": "Escáner",
"HeaderSettingsSecurity": "Seguridad",
"HeaderSettingsWebClient": "Cliente web",
"HeaderSleepTimer": "Temporizador de apagado",
"HeaderStatsLargestItems": "Elementos más grandes",
@@ -203,6 +212,7 @@
"HeaderTableOfContents": "Sumario",
"HeaderTools": "Herramientas",
"HeaderUpdateAccount": "Actualizar cuenta",
"HeaderUpdateApiKey": "Actualizar clave API",
"HeaderUpdateAuthor": "Actualizar autor",
"HeaderUpdateDetails": "Actualizar detalles",
"HeaderUpdateLibrary": "Actualizar biblioteca",
@@ -262,14 +272,14 @@
"LabelBooks": "Libros",
"LabelButtonText": "Texto del botón",
"LabelByAuthor": "por {0}",
"LabelChangePassword": "Cambiar Contraseña",
"LabelChangePassword": "Cambiar contraseña",
"LabelChannels": "Canales",
"LabelChapterCount": "{0} capítulos",
"LabelChapterTitle": "Titulo del Capítulo",
"LabelChapterTitle": "Título del capítulo",
"LabelChapters": "Capítulos",
"LabelChaptersFound": "Capítulo Encontrado",
"LabelClickForMoreInfo": "Click para más información",
"LabelClickToUseCurrentValue": "Haz clic para utilizar el valor actual",
"LabelChaptersFound": "capítulos encontrados",
"LabelClickForMoreInfo": "Pulse para más información",
"LabelClickToUseCurrentValue": "Pulse para utilizar el valor actual",
"LabelClosePlayer": "Cerrar reproductor",
"LabelCodec": "Codec",
"LabelCollapseSeries": "Colapsar serie",
@@ -277,7 +287,7 @@
"LabelCollection": "Colección",
"LabelCollections": "Colecciones",
"LabelComplete": "Completo",
"LabelConfirmPassword": "Confirmar Contraseña",
"LabelConfirmPassword": "Confirmar contraseña",
"LabelContinueListening": "Seguir escuchando",
"LabelContinueReading": "Continuar leyendo",
"LabelContinueSeries": "Continuar series",
@@ -288,12 +298,13 @@
"LabelCronExpression": "Expresión de Cron",
"LabelCurrent": "Actual",
"LabelCurrently": "En este momento:",
"LabelCustomCronExpression": "Expresión de Cron Personalizada:",
"LabelDatetime": "Hora y Fecha",
"LabelCustomCronExpression": "Expresión de Cron personalizada:",
"LabelDatetime": "Hora y fecha",
"LabelDays": "Días",
"LabelDeleteFromFileSystemCheckbox": "Eliminar del sistema de archivos (desmarque para quitar de la base de datos solamente)",
"LabelDescription": "Descripción",
"LabelDeselectAll": "Deseleccionar Todos",
"LabelDetectedPattern": "Patrón detectado:",
"LabelDevice": "Dispositivo",
"LabelDeviceInfo": "Información del dispositivo",
"LabelDeviceIsAvailableTo": "El dispositivo está disponible para...",
@@ -368,7 +379,7 @@
"LabelFontStrikethrough": "Tachado",
"LabelFormat": "Formato",
"LabelFull": "Completo",
"LabelGenre": "Genero",
"LabelGenre": "Género",
"LabelGenres": "Géneros",
"LabelHardDeleteFile": "Eliminar Definitivamente",
"LabelHasEbook": "Tiene un libro",
@@ -451,6 +462,7 @@
"LabelNewestAuthors": "Autores más nuevos",
"LabelNewestEpisodes": "Episodios más nuevos",
"LabelNextBackupDate": "Fecha del siguiente respaldo",
"LabelNextChapters": "Los próximos capítulos serán:",
"LabelNextScheduledRun": "Próxima ejecución programada",
"LabelNoCustomMetadataProviders": "Sin proveedores de metadatos personalizados",
"LabelNoEpisodesSelected": "Ningún Episodio Seleccionado",
@@ -459,17 +471,18 @@
"LabelNotes": "Notas",
"LabelNotificationAppriseURL": "URL(s) de Apprise",
"LabelNotificationAvailableVariables": "Variables disponibles",
"LabelNotificationBodyTemplate": "Plantilla de Cuerpo",
"LabelNotificationEvent": "Evento de Notificación",
"LabelNotificationTitleTemplate": "Plantilla de Titulo",
"LabelNotificationsMaxFailedAttempts": "Máximo de Intentos Fallidos",
"LabelNotificationBodyTemplate": "Plantilla de cuerpo",
"LabelNotificationEvent": "Evento de notificación",
"LabelNotificationTitleTemplate": "Plantilla de tulo",
"LabelNotificationsMaxFailedAttempts": "Máximo de intentos fallidos",
"LabelNotificationsMaxFailedAttemptsHelp": "Las notificaciones se desactivan después de fallar este número de veces",
"LabelNotificationsMaxQueueSize": "Tamaño máximo de la cola de notificaciones",
"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",
"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 deshabilitar la asignación avanzada de grupos y permisos, lo que asignaría de manera automática al grupo 'Usuario'.",
"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.",
"LabelOpenRSSFeed": "Abrir suministro RSS",
"LabelOverwrite": "Sobrescribir",
@@ -496,7 +509,7 @@
"LabelPodcastType": "Tipo de pódcast",
"LabelPodcasts": "Pódcast",
"LabelPort": "Puerto",
"LabelPrefixesToIgnore": "Prefijos para Ignorar (no distingue entre mayúsculas y minúsculas.)",
"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",
"LabelPrimaryEbook": "Libro electrónico principal",
"LabelProgress": "Progreso",
@@ -512,7 +525,7 @@
"LabelRSSFeedCustomOwnerEmail": "Correo electrónico de dueño personalizado",
"LabelRSSFeedCustomOwnerName": "Nombre de dueño personalizado",
"LabelRSSFeedOpen": "Suministro RSS abierto",
"LabelRSSFeedPreventIndexing": "Prevenir indexado",
"LabelRSSFeedPreventIndexing": "Evitar indización",
"LabelRSSFeedSlug": "«Slug» de suministro RSS",
"LabelRSSFeedURL": "URL de suministro RSS",
"LabelRandomly": "Aleatorio",
@@ -528,6 +541,7 @@
"LabelReleaseDate": "Fecha de estreno",
"LabelRemoveAllMetadataAbs": "Eliminar todos los archivos metadata.abs",
"LabelRemoveAllMetadataJson": "Eliminar todos los archivos metadata.json",
"LabelRemoveAudibleBranding": "Quitar introducción y cierre de Audible de los capítulos",
"LabelRemoveCover": "Quitar cubierta",
"LabelRemoveMetadataFile": "Eliminar archivos de metadatos en carpetas de elementos de biblioteca",
"LabelRemoveMetadataFileHelp": "Elimine todos los archivos metadata.json y metadata.abs de sus carpetas {0}.",
@@ -536,7 +550,7 @@
"LabelSearchTitle": "Buscar título",
"LabelSearchTitleOrASIN": "Buscar título o ASIN",
"LabelSeason": "Temporada",
"LabelSeasonNumber": "Sesión #{0}",
"LabelSeasonNumber": "{0}.ª temporada",
"LabelSelectAll": "Seleccionar todo",
"LabelSelectAllEpisodes": "Seleccionar todos los episodios",
"LabelSelectEpisodesShowing": "Seleccionar los {0} episodios visibles",
@@ -576,7 +590,7 @@
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Saltar libros anteriores de la serie Continuada",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "El estante de la página de inicio de Continuar Serie muestra el primer libro no iniciado de una serie que tenga por lo menos un libro finalizado y no tenga libros en progreso. Habilitar esta opción le permitirá continuar series desde el último libro que ha completado en vez del primer libro que no ha empezado.",
"LabelSettingsParseSubtitles": "Extraer Subtítulos",
"LabelSettingsParseSubtitlesHelp": "Extraer subtítulos de los nombres de las carpetas de los audiolibros.<br>Los subtítulos deben estar separados por \" - \"<br>Por ejemplo: \"Ejemplo de Título - Subtítulo Aquí\" tiene el subtítulo \"Subtítulo Aquí\"",
"LabelSettingsParseSubtitlesHelp": "Extraer subtítulos de los nombres de las carpetas de los audiolibros.<br>Los subtítulos deben estar separados por « - »<br>Así, «Título de libro - Un subtítulo aquí» tiene el subtítulo «Un subtítulo aquí»",
"LabelSettingsPreferMatchedMetadata": "Preferir metadatos encontrados",
"LabelSettingsPreferMatchedMetadataHelp": "Los datos encontrados sobreescribirán los detalles del elemento cuando se use \"Encontrar Rápido\". Por defecto, \"Encontrar Rápido\" sólo completará los detalles faltantes.",
"LabelSettingsSkipMatchingBooksWithASIN": "Omitir libros coincidentes que ya tengan un ASIN",
@@ -594,7 +608,7 @@
"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 Todos",
"LabelShowAll": "Mostrar todo",
"LabelShowSeconds": "Mostrar segundos",
"LabelShowSubtitles": "Mostrar subtítulos",
"LabelSize": "Tamaño",
@@ -602,33 +616,34 @@
"LabelSlug": "Slug",
"LabelSortAscending": "Ascendente",
"LabelSortDescending": "Descendente",
"LabelSortPubDate": "Ord. fecha pub.",
"LabelStart": "Iniciar",
"LabelStartTime": "Tiempo de Inicio",
"LabelStarted": "Iniciado",
"LabelStartedAt": "Iniciado En",
"LabelStatsAudioTracks": "Pistas de Audio",
"LabelStatsAuthors": "Autores",
"LabelStatsBestDay": "Mejor Día",
"LabelStatsDailyAverage": "Promedio Diario",
"LabelStatsBestDay": "Mejor día",
"LabelStatsDailyAverage": "Promedio diario",
"LabelStatsDays": "Días",
"LabelStatsDaysListened": "Días Escuchando",
"LabelStatsDaysListened": "Días escuchando",
"LabelStatsHours": "Horas",
"LabelStatsInARow": "seguidos",
"LabelStatsItemsFinished": "Elementos Terminados",
"LabelStatsItemsFinished": "Elementos terminados",
"LabelStatsItemsInLibrary": "Elementos en biblioteca",
"LabelStatsMinutes": "minutos",
"LabelStatsMinutesListening": "Minutos Escuchando",
"LabelStatsOverallDays": "Total de Dias",
"LabelStatsOverallHours": "Total de Horas",
"LabelStatsWeekListening": "Tiempo escuchando en la Semana",
"LabelStatsMinutesListening": "Minutos escuchando",
"LabelStatsOverallDays": "Total de as",
"LabelStatsOverallHours": "Total de horas",
"LabelStatsWeekListening": "Tiempo escuchando en la semana",
"LabelSubtitle": "Subtítulo",
"LabelSupportedFileTypes": "Tipos de archivo admitidos",
"LabelTag": "Etiqueta",
"LabelTags": "Etiquetas",
"LabelTagsAccessibleToUser": "Etiquetas Accessibles al Usuario",
"LabelTagsNotAccessibleToUser": "Etiquetas no Accesibles al Usuario",
"LabelTasks": "Tareas Corriendo",
"LabelTextEditorBulletedList": "Lista con viñetas",
"LabelTagsAccessibleToUser": "Etiquetas accessibles al usuario",
"LabelTagsNotAccessibleToUser": "Etiquetas no accesibles al usuario",
"LabelTasks": "Tareas en ejecución",
"LabelTextEditorBulletedList": "Lista con bolos",
"LabelTextEditorLink": "Enlazar",
"LabelTextEditorNumberedList": "Lista numerada",
"LabelTextEditorUnlink": "Desenlazar",
@@ -681,7 +696,7 @@
"LabelUseFullTrack": "Usar pista completa",
"LabelUseZeroForUnlimited": "Utilice 0 para ilimitado",
"LabelUser": "Usuario",
"LabelUsername": "Nombre de Usuario",
"LabelUsername": "Nombre de usuario",
"LabelValue": "Valor",
"LabelVersion": "Versión",
"LabelViewBookmarks": "Ver Marcadores",
@@ -694,54 +709,60 @@
"LabelWeekdaysToRun": "Correr en Días de la Semana",
"LabelXBooks": "{0} libros",
"LabelXItems": "{0} elementos",
"LabelYearReviewHide": "Ocultar Resumen del año",
"LabelYearReviewShow": "Resumen del año",
"LabelYearReviewHide": "Ocultar resumen del año",
"LabelYearReviewShow": "Ver resumen del año",
"LabelYourAudiobookDuration": "Duración de tu Audiolibro",
"LabelYourBookmarks": "Tus Marcadores",
"LabelYourBookmarks": "Sus marcadores",
"LabelYourPlaylists": "Tus Listas",
"LabelYourProgress": "Su progreso",
"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.",
"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.",
"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",
"MessageBatchEditPopulateMapDetailsAllHelp": "Rellenar campos activados con datos de todos los elementos. Los campos con varios valores se combinarán",
"MessageBatchEditPopulateMapDetailsItemHelp": "Rellenar campos de detalles de mapa con datos de este elemento",
"MessageBatchQuickMatchDescription": "\"Encontrar Rápido\" tratará de agregar portadas y metadatos faltantes de los elementos seleccionados. Habilite la opción de abajo para que \"Encontrar Rápido\" pueda sobrescribir portadas y/o metadatos existentes.",
"MessageBookshelfNoCollections": "Aún no ha hecho ninguna colección",
"MessageBookshelfNoCollectionsHelp": "Las colecciones son públicas. Cualquiera que pueda acceder a la biblioteca las podrá ver.",
"MessageBookshelfNoRSSFeeds": "Ningún suministro RSS está abierto",
"MessageBookshelfNoResultsForFilter": "Ningún Resultado para el filtro \"{0}: {1}\"",
"MessageBookshelfNoResultsForFilter": "El filtro «{0}: {1}» no produjo ningún resultado",
"MessageBookshelfNoResultsForQuery": "No hay resultados para la consulta",
"MessageBookshelfNoSeries": "No tiene ninguna serie",
"MessageBulkChapterPattern": "¿Cuántos capítulos desea añadir con este patrón de numeración?",
"MessageChapterEndIsAfter": "El final del capítulo es después del final de tu audiolibro",
"MessageChapterErrorFirstNotZero": "El primer capítulo debe iniciar en 0",
"MessageChapterErrorStartGteDuration": "El tiempo de inicio no es válido: debe ser inferior a la duración del audiolibro",
"MessageChapterErrorStartLtPrev": "El tiempo de inicio no es válido: debe ser mayor o igual que el tiempo de inicio del capítulo anterior",
"MessageChapterStartIsAfter": "El comienzo del capítulo es después del final de su audiolibro",
"MessageChaptersNotFound": "Capítulos no encontrados",
"MessageCheckingCron": "Revisando cron...",
"MessageConfirmCloseFeed": "¿Confirma que quiere cerrar este suministro?",
"MessageConfirmDeleteBackup": "¿Está seguro de que desea eliminar el respaldo {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 de su sistema de archivos. ¿Está seguro?",
"MessageConfirmDeleteFile": "Esto eliminará el archivo del sistema de archivos. ¿Quiere continuar?",
"MessageConfirmDeleteLibrary": "¿Confirma que quiere eliminar permanentemente la biblioteca «{0}»?",
"MessageConfirmDeleteLibraryItem": "Esto eliminará el elemento de la biblioteca de la base de datos y del sistema de archivos. ¿Confirma que quiere hacerlo?",
"MessageConfirmDeleteLibraryItems": "Esto eliminará {0} elementos de la biblioteca de la base de datos y del sistema de archivos. ¿Confirma que quiere hacerlo?",
"MessageConfirmDeleteMetadataProvider": "¿Confirma que quiere eliminar el proveedor de metadatos personalizado «{0}»?",
"MessageConfirmDeleteNotification": "¿Confirma que quiere eliminar esta notificación?",
"MessageConfirmDeleteSession": "¿Está seguro de que desea eliminar esta sesión?",
"MessageConfirmEmbedMetadataInAudioFiles": "¿Está seguro de que desea incrustar metadatos en {0} archivos de audio?",
"MessageConfirmForceReScan": "¿Está seguro de que desea forzar un re-escaneo?",
"MessageConfirmMarkAllEpisodesFinished": "¿Está seguro de que desea marcar todos los episodios como terminados?",
"MessageConfirmMarkAllEpisodesNotFinished": "¿Está seguro de que desea marcar todos los episodios como no terminados?",
"MessageConfirmMarkItemFinished": "¿Estás seguro de que deseas marcar \"{0}\" como terminado?",
"MessageConfirmMarkItemNotFinished": "¿Estás seguro de que deseas marcar \"{0}\" como no acabado?",
"MessageConfirmMarkSeriesFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como terminados?",
"MessageConfirmMarkSeriesNotFinished": "¿Está seguro de que desea marcar todos los libros en esta serie como no terminados?",
"MessageConfirmDeleteSession": "¿Confirma que quiere eliminar esta sesión?",
"MessageConfirmEmbedMetadataInAudioFiles": "¿Confirma que quiere incrustar metadatos en {0} archivos de audio?",
"MessageConfirmForceReScan": "¿Confirma que quiere forzar un reescaneo?",
"MessageConfirmMarkAllEpisodesFinished": "¿Confirma que quiere marcar todos los episodios como terminados?",
"MessageConfirmMarkAllEpisodesNotFinished": "¿Confirma que quiere marcar todos los episodios como no terminados?",
"MessageConfirmMarkItemFinished": "¿Confirma que quiere marcar «{0}» como terminado?",
"MessageConfirmMarkItemNotFinished": "¿Confirma que quiere marcar «{0}» como no terminado?",
"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 el caché eliminará el directorio completo ubicado en <code>/metadata/cache</code>. <br /><br />¿Está seguro que desea eliminar el directorio del caché?",
"MessageConfirmPurgeItemsCache": "Purgar la caché de los elementos eliminará todo el directorio <code>/metadata/cache/items</code>.<br />¿Estás seguro?",
"MessageConfirmQuickEmbed": "¡Advertencia! La integración rápida no realiza copias de seguridad a ninguno de tus archivos de audio. Asegúrate de haber realizado una copia de los mismos previamente. <br><br>¿Deseas continuar?",
"MessageConfirmQuickMatchEpisodes": "El reconocimiento rápido de extensiones sobrescribirá los detalles si se encuentra una coincidencia. Se actualizarán las extensiones no reconocidas. ¿Está seguro?",
"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?",
"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?",
"MessageConfirmRemoveAllChapters": "¿Confirma que quiere quitar todos los capítulos?",
"MessageConfirmRemoveAuthor": "¿Confirma que quiere quitar el autor «{0}»?",
@@ -749,34 +770,35 @@
"MessageConfirmRemoveEpisode": "¿Confirma que quiere quitar el episodio «{0}»?",
"MessageConfirmRemoveEpisodes": "¿Confirma que quiere quitar {0} episodios?",
"MessageConfirmRemoveListeningSessions": "¿Confirma que quiere quitar {0} sesiones de escucha?",
"MessageConfirmRemoveMetadataFiles": "¿Está seguro de que desea eliminar todos los archivos de metadatos.{0} en las carpetas de elementos de su biblioteca?",
"MessageConfirmRemoveMetadataFiles": "¿Confirma que quiere quitar todos los archivos metadata.{0} en las carpetas de elementos de su biblioteca?",
"MessageConfirmRemoveNarrator": "¿Confirma que quiere quitar el narrador «{0}»?",
"MessageConfirmRemovePlaylist": "¿Confirma que quiere quitar la lista de reproducción «{0}»?",
"MessageConfirmRenameGenre": "¿Está seguro de que desea renombrar el genero \"{0}\" a \"{1}\" de todos los elementos?",
"MessageConfirmRenameGenre": "¿Confirma que quiere cambiar el nombre del género «{0}» a «{1}» en todos los elementos?",
"MessageConfirmRenameGenreMergeNote": "Nota: Este género ya existe, por lo que se fusionarán.",
"MessageConfirmRenameGenreWarning": "Advertencia! Un genero similar ya existe \"{0}\".",
"MessageConfirmRenameTag": "¿Está seguro de que desea renombrar la etiqueta \"{0}\" a \"{1}\" de todos los elementos?",
"MessageConfirmRenameTagMergeNote": "Nota: Esta etiqueta ya existe, por lo que se fusionarán.",
"MessageConfirmRenameTagWarning": "Advertencia! Una etiqueta similar ya existe \"{0}\".",
"MessageConfirmRenameGenreWarning": "¡Atención! Ya existe un género similar con distinta mayusculación, «{0}».",
"MessageConfirmRenameTag": "¿Confirma que quiere cambiar el nombre de la etiqueta «{0}» a «{1}» en todos los elementos?",
"MessageConfirmRenameTagMergeNote": "Nota: esta etiqueta ya existe, por lo que se fusionarán.",
"MessageConfirmRenameTagWarning": "¡Atención! Ya existe una etiqueta similar con distinta mayusculación, «{0}».",
"MessageConfirmResetProgress": "¿Confirma que quiere restablecer su progreso?",
"MessageConfirmSendEbookToDevice": "¿Confirma que quiere enviar el libro electrónico {0} «{1}» al dispositivo «{2}»?",
"MessageConfirmUnlinkOpenId": "¿Estás seguro de que deseas desvincular este usuario de OpenID?",
"MessageConfirmUnlinkOpenId": "¿Confirma que quiere desenlazar este usuario de OpenID?",
"MessageDaysListenedInTheLastYear": "{0} días escuchados el año pasado",
"MessageDownloadingEpisode": "Descargando episodio",
"MessageDragFilesIntoTrackOrder": "Arrastra los archivos al orden correcto de las pistas",
"MessageEmbedFailed": "¡Error al insertar!",
"MessageEmbedFinished": "Incrustación Terminada!",
"MessageDragFilesIntoTrackOrder": "Arrastre los archivos al orden correcto de las pistas",
"MessageEmbedFailed": "Falló la incrustación.",
"MessageEmbedFinished": "Finalizó la incrustación.",
"MessageEmbedQueue": "En cola para incrustar metadatos ({0} en cola)",
"MessageEpisodesQueuedForDownload": "{0} Episodio(s) en cola para descargar",
"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": "Buscando...",
"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.",
"MessageImportantNotice": "¡Notificación importante!",
"MessageInsertChapterBelow": "Insertar Capítulo Abajo",
"MessageInsertChapterBelow": "Insertar capítulo debajo",
"MessageInvalidAsin": "ASIN no válido",
"MessageItemsSelected": "{0} elementos seleccionados",
"MessageItemsUpdated": "{0} elementos actualizados",
"MessageJoinUsOn": "Únetenos en",
"MessageJoinUsOn": "Únase a nosotros en",
"MessageLoading": "Cargando...",
"MessageLoadingFolders": "Cargando archivos...",
"MessageLogsDescription": "Logs son almacenados en <code>/metadata/logs</code> en archivos bajo formato JSON. Logs de fallos son almacenados en <code>/metadata/logs/crash_logs.txt</code>.",
@@ -800,19 +822,19 @@
"MessageNoDownloadsInProgress": "No hay descargas actualmente en curso",
"MessageNoDownloadsQueued": "Sin Lista de Descarga",
"MessageNoEpisodeMatchesFound": "No se encontraron episodios que coinciden",
"MessageNoEpisodes": "Sin Episodios",
"MessageNoFoldersAvailable": "No Hay Carpetas Disponibles",
"MessageNoGenres": "Sin Géneros",
"MessageNoIssues": "Sin Problemas",
"MessageNoItems": "Sin elementos",
"MessageNoEpisodes": "Ningún episodio",
"MessageNoFoldersAvailable": "Ninguna carpeta disponible",
"MessageNoGenres": "Ningún género",
"MessageNoIssues": "Ningún número",
"MessageNoItems": "Ningún elemento",
"MessageNoItemsFound": "Ningún elemento encontrado",
"MessageNoListeningSessions": "Ninguna sesión escuchada",
"MessageNoListeningSessions": "Ninguna sesión de escucha",
"MessageNoLogs": "Ningún registro",
"MessageNoMediaProgress": "Multimedia sin Progreso",
"MessageNoNotifications": "Ninguna notificación",
"MessageNoPodcastFeed": "Podcast no válido: Sin feed",
"MessageNoPodcastFeed": "Pódcast no válido: no hay suministro",
"MessageNoPodcastsFound": "No se encontró ningún pódcast",
"MessageNoResults": "Sin Resultados",
"MessageNoResults": "Ningún resultado",
"MessageNoSearchResultsFor": "La búsqueda «{0}» no produjo ningún resultado",
"MessageNoSeries": "Ninguna serie",
"MessageNoTags": "Ninguna etiqueta",
@@ -820,47 +842,49 @@
"MessageNoUpdatesWereNecessary": "No fue necesario actualizar",
"MessageNoUserPlaylists": "No tiene ninguna lista de reproducción",
"MessageNoUserPlaylistsHelp": "Las listas de reproducción son privadas. Solo quien las cree podrá verlas.",
"MessageNotYetImplemented": "Aun no implementado",
"MessageNotYetImplemented": "Aún no implementado",
"MessageOpmlPreviewNote": "Nota: Esta es una vista previa del archivo OPML analizado. El título real del podcast se obtendrá del canal RSS.",
"MessageOr": "o",
"MessagePauseChapter": "Pausar la reproducción del capítulo",
"MessagePlayChapter": "Escuchar el comienzo del capítulo",
"MessagePlaylistCreateFromCollection": "Crear una lista de reproducción a partir de una colección",
"MessagePleaseWait": "Por favor, espera...",
"MessagePleaseWait": "Espere…",
"MessagePodcastHasNoRSSFeedForMatching": "El pódcast no tiene un URL de suministro RSS que pueda usarse para encontrar correspondencias",
"MessagePodcastSearchField": "Introduzca el término de búsqueda o el URL del suministro RSS",
"MessageQuickEmbedInProgress": "Integración rápida en proceso",
"MessageQuickEmbedQueue": "En cola para inserción rápida ({0} en cola)",
"MessageQuickMatchAllEpisodes": "Combina rápidamente todos los episodios",
"MessageQuickMatchDescription": "Rellenar detalles de elementos vacíos y portada con los primeros resultados de '{0}'. No sobrescribe los detalles a menos que la opción \"Preferir Metadatos Encontrados\" del servidor esté habilitada.",
"MessageQuickMatchDescription": "Rellena los detalles y la cubierta de los elementos vacíos con el primer resultado coincidente de «{0}». No sobrescribe los detalles a menos que se active la opción del servidor «Preferir metadatos coincidentes».",
"MessageRemoveChapter": "Quitar capítulo",
"MessageRemoveEpisodes": "Quitar {0} episodio(s)",
"MessageRemoveFromPlayerQueue": "Quitar de la cola de reproducción",
"MessageRemoveUserWarning": "¿Confirma que quiere eliminar permanentemente el usuario «{0}»?",
"MessageReportBugsAndContribute": "Reporte erres, solicite funciones y contribuya en",
"MessageResetChaptersConfirm": "¿Está seguro de que desea deshacer los cambios y revertir los capítulos a su estado original?",
"MessageRestoreBackupConfirm": "¿Está seguro de que desea para restaurar del respaldo creado en",
"MessageReportBugsAndContribute": "Informe de defectos, solicite funciones y contribuya en",
"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.",
"MessageScheduleRunEveryWeekdayAtTime": "Ejecutar cada {0} a las {1}",
"MessageSearchResultsFor": "Resultados de la búsqueda de",
"MessageSelected": "{0} seleccionado(s)",
"MessageSeriesSequenceCannotContainSpaces": "La secuencia de la serie no puede contener espacios",
"MessageServerCouldNotBeReached": "No se pudo establecer la conexión con el servidor",
"MessageSetChaptersFromTracksDescription": "Establecer capítulos usando cada archivo de audio como un capítulo y el título del capítulo como el nombre del archivo de audio",
"MessageShareExpirationWillBe": "La caducidad será <strong>{0}</strong>",
"MessageShareExpiresIn": "Caduduca en {0}",
"MessageShareURLWillBe": "La URL para compartir será <strong> {0} </strong>",
"MessageStartPlaybackAtTime": "Iniciar reproducción para \"{0}\" en {1}?",
"MessageTaskAudioFileNotWritable": "El archivo de audio \"{0}\" no se puede grabar",
"MessageStartPlaybackAtTime": "¿Iniciar reproducción para «{0}» en {1}?",
"MessageTaskAudioFileNotWritable": "El archivo de audio «{0}» no se puede grabar",
"MessageTaskCanceledByUser": "Tarea cancelada por el usuario",
"MessageTaskDownloadingEpisodeDescription": "Descargando el episodio «{0}»",
"MessageTaskEmbeddingMetadata": "Inserción de metadatos",
"MessageTaskEmbeddingMetadataDescription": "Inserción de metadatos en el audiolibro \"{0}\"",
"MessageTaskEmbeddingMetadataDescription": "Incrustando metadatos en el audiolibro «{0}»",
"MessageTaskEncodingM4b": "Codificación M4B",
"MessageTaskEncodingM4bDescription": "Codificación del audiolibro \"{0}\" en un único archivo m4b",
"MessageTaskEncodingM4bDescription": "Codificando el audiolibro «{0}» en un único archivo m4b",
"MessageTaskFailed": "Fallida",
"MessageTaskFailedToBackupAudioFile": "Error en la copia de seguridad del archivo de audio \"{0}\"",
"MessageTaskFailedToBackupAudioFile": "No se pudo respaldar el archivo de audio «{0}»",
"MessageTaskFailedToCreateCacheDirectory": "Error al crear el directorio de la caché",
"MessageTaskFailedToEmbedMetadataInFile": "Error al incrustar metadatos en el archivo \"{0}\"",
"MessageTaskFailedToEmbedMetadataInFile": "No se pudieron incrustar metadatos en el archivo «{0}»",
"MessageTaskFailedToMergeAudioFiles": "Error al fusionar archivos de audio",
"MessageTaskFailedToMoveM4bFile": "Error al mover el archivo m4b",
"MessageTaskFailedToWriteMetadataFile": "Error al escribir el archivo de metadatos",
@@ -870,9 +894,9 @@
"MessageTaskOpmlImportDescription": "Creando pódcast a partir de {0} suministros RSS",
"MessageTaskOpmlImportFeed": "Feed de importación OPML",
"MessageTaskOpmlImportFeedDescription": "Importando el suministro RSS «{0}»",
"MessageTaskOpmlImportFeedFailed": "No se puede obtener el podcast",
"MessageTaskOpmlImportFeedFailed": "No se pudo obtener el suministro del pódcast",
"MessageTaskOpmlImportFeedPodcastDescription": "Creando pódcast «{0}»",
"MessageTaskOpmlImportFeedPodcastExists": "Podcast ya existe en la ruta",
"MessageTaskOpmlImportFeedPodcastExists": "El pódcast ya existe en la ruta",
"MessageTaskOpmlImportFeedPodcastFailed": "No se pudo crear el pódcast",
"MessageTaskOpmlImportFinished": "Añadido {0} podcasts",
"MessageTaskOpmlParseFailed": "No se pudo analizar el archivo OPML",
@@ -882,8 +906,8 @@
"MessageTaskScanItemsMissing": "Falta {0}",
"MessageTaskScanItemsUpdated": "{0} actualizado",
"MessageTaskScanNoChangesNeeded": "No se necesitan cambios",
"MessageTaskScanningFileChanges": "Escaneando cambios en el archivo en \"{0}\"",
"MessageTaskScanningLibrary": "Escaneando la biblioteca \"{0}\"",
"MessageTaskScanningFileChanges": "Escaneando cambios en archivos en «{0}»",
"MessageTaskScanningLibrary": "Escaneando la biblioteca «{0}»",
"MessageTaskTargetDirectoryNotWritable": "El directorio de destino no se puede escribir",
"MessageThinking": "Pensando...",
"MessageUploaderItemFailed": "Error al Subir",
@@ -906,6 +930,7 @@
"NotificationOnBackupFailedDescription": "Se activa cuando falla una copia de seguridad",
"NotificationOnEpisodeDownloadedDescription": "Se activa cuando se descarga automáticamente un episodio de un podcast",
"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",
"PlaceholderNewFolderPath": "Nueva ruta de carpeta",
"PlaceholderNewPlaylist": "Nuevo nombre de la lista de reproducción",
@@ -933,7 +958,7 @@
"ToastAppriseUrlRequired": "Debes ingresar una URL de Apprise",
"ToastAsinRequired": "Se requiere ASIN",
"ToastAuthorImageRemoveSuccess": "Se eliminó la imagen del autor",
"ToastAuthorNotFound": "No se encontró el autor \"{0}\"",
"ToastAuthorNotFound": "No se encontró el autor «{0}»",
"ToastAuthorRemoveSuccess": "Autor eliminado",
"ToastAuthorSearchNotFound": "No se encontró al autor",
"ToastAuthorUpdateMerged": "Autor combinado",
@@ -944,33 +969,37 @@
"ToastBackupCreateSuccess": "Respaldo creado",
"ToastBackupDeleteFailed": "Error al eliminar respaldo",
"ToastBackupDeleteSuccess": "Respaldo eliminado",
"ToastBackupInvalidMaxKeep": "Número no válido de copias de seguridad a conservar",
"ToastBackupInvalidMaxSize": "Tamaño máximo de copia de seguridad no válido",
"ToastBackupInvalidMaxKeep": "Número no válido de copias de respaldo para conservar",
"ToastBackupInvalidMaxSize": "Tamaño máximo de copia de respaldo no válido",
"ToastBackupRestoreFailed": "Error al restaurar el respaldo",
"ToastBackupUploadFailed": "Error al subir el respaldo",
"ToastBackupUploadFailed": "Error al cargar la copia de respaldo",
"ToastBackupUploadSuccess": "Respaldo cargado",
"ToastBatchApplyDetailsToItemsSuccess": "Detalles aplicados a los elementos",
"ToastBatchDeleteFailed": "Error al eliminar por lotes",
"ToastBatchDeleteSuccess": "Borrado por lotes correcto",
"ToastBatchDeleteFailed": "Falló la eliminación por lotes",
"ToastBatchDeleteSuccess": "Se eliminó por lotes correctamente",
"ToastBatchQuickMatchFailed": "¡Error en la sincronización rápida por lotes!",
"ToastBatchQuickMatchStarted": "¡Se inició el lote de búsqueda rápida de {0} libros!",
"ToastBatchUpdateFailed": "Subida masiva fallida",
"ToastBatchUpdateSuccess": "Subida masiva exitosa",
"ToastBookmarkCreateFailed": "Error al crear marcador",
"ToastBookmarkCreateSuccess": "Marcador Agregado",
"ToastBatchUpdateFailed": "Falló la actualización por lotes",
"ToastBatchUpdateSuccess": "Se actualizó por lotes correctamente",
"ToastBookmarkCreateFailed": "No se pudo crear el marcador",
"ToastBookmarkCreateSuccess": "Marcador añadido",
"ToastBookmarkRemoveSuccess": "Marcador eliminado",
"ToastCachePurgeFailed": "Error al purgar el caché",
"ToastCachePurgeSuccess": "Caché purgado de manera exitosa",
"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",
"ToastChaptersAllLocked": "Todos los capítulos están bloqueados. Desbloquee algunos capítulos para cambiar sus tiempos.",
"ToastChaptersHaveErrors": "Los capítulos tienen errores",
"ToastChaptersMustHaveTitles": "Los capítulos tienen que tener un título",
"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.",
"ToastChaptersInvalidShiftAmountStart": "Cantidad de desplazamiento no válida. El primer capítulo tendría una duración cero o negativa y lo sobrescribiría el segundo capítulo. Aumente la duración inicial del segundo capítulo.",
"ToastChaptersMustHaveTitles": "Los capítulos deben tener título",
"ToastChaptersRemoved": "Capítulos eliminados",
"ToastChaptersUpdated": "Capítulos actualizados",
"ToastCollectionItemsAddFailed": "Artículo(s) añadido(s) a la colección fallido(s)",
"ToastCollectionRemoveSuccess": "Colección quitada",
"ToastCollectionUpdateSuccess": "Colección actualizada",
"ToastCoverUpdateFailed": "Error al actualizar la cubierta",
"ToastDateTimeInvalidOrIncomplete": "Fecha y hora inválidas o incompletas",
"ToastDeleteFileFailed": "Error el eliminar archivo",
"ToastDateTimeInvalidOrIncomplete": "Fecha y hora no válidas o incompletas",
"ToastDeleteFileFailed": "Falló la eliminación del archivo",
"ToastDeleteFileSuccess": "Archivo eliminado",
"ToastDeviceAddFailed": "Error al añadir dispositivo",
"ToastDeviceNameAlreadyExists": "Un libro electrónico ya existe con ese nombre",
@@ -1040,7 +1069,7 @@
"ToastPodcastCreateSuccess": "Se creó el pódcast correctamente",
"ToastPodcastGetFeedFailed": "No se puede obtener el podcast",
"ToastPodcastNoEpisodesInFeed": "No se han encontrado episodios en el feed del RSS",
"ToastPodcastNoRssFeed": "El podcast no tiene feed RSS",
"ToastPodcastNoRssFeed": "El pódcast no tiene suministro RSS",
"ToastProgressIsNotBeingSynced": "El progreso no se sincroniza, reinicia la reproducción",
"ToastProviderCreatedFailed": "Error al añadir el proveedor",
"ToastProviderCreatedSuccess": "Nuevo proveedor añadido",
@@ -1088,5 +1117,12 @@
"ToastUserPasswordChangeSuccess": "Contraseña modificada correctamente",
"ToastUserPasswordMismatch": "No coinciden las contraseñas",
"ToastUserPasswordMustChange": "La nueva contraseña no puede ser igual que la anterior",
"ToastUserRootRequireName": "Debe introducir un nombre de usuario administrativo"
"ToastUserRootRequireName": "Debe introducir un nombre de usuario administrativo",
"TooltipAddChapters": "Añadir capítulo(s)",
"TooltipAddOneSecond": "Añadir 1 segundo",
"TooltipLockAllChapters": "Bloquear todos los capítulos",
"TooltipLockChapter": "Bloquear capítulo (Mayús+clic para rango)",
"TooltipSubtractOneSecond": "Restar 1 segundo",
"TooltipUnlockAllChapters": "Desbloquear todos los capítulos",
"TooltipUnlockChapter": "Desbloquear capítulo (Mayús+clic para rango)"
}

View File

@@ -11,7 +11,7 @@
"ButtonAuthors": "Autorid",
"ButtonBack": "Tagasi",
"ButtonBrowseForFolder": "Sirvi kausta",
"ButtonCancel": "Tühista",
"ButtonCancel": "Katkesta",
"ButtonCancelEncode": "Tühista kodeerimine",
"ButtonChangeRootPassword": "Muuda põhiparooli",
"ButtonCheckAndDownloadNewEpisodes": "Kontrolli ja laadi alla uued episoodid",
@@ -20,9 +20,9 @@
"ButtonClearFilter": "Tühista filter",
"ButtonCloseFeed": "Sulge voog",
"ButtonCloseSession": "Sulge avatud sessioon",
"ButtonCollections": "Kogud",
"ButtonCollections": "Kollektsioonid",
"ButtonConfigureScanner": "Konfigureeri skanner",
"ButtonCreate": "Loo",
"ButtonCreate": "Loo uus",
"ButtonCreateBackup": "Loo varundus",
"ButtonDelete": "Kustuta",
"ButtonDownloadQueue": "Järjekord",
@@ -37,7 +37,7 @@
"ButtonIssues": "Probleemid",
"ButtonJumpBackward": "Hüppa tagasi",
"ButtonJumpForward": "Hüppa edasi",
"ButtonLatest": "Uusim",
"ButtonLatest": "Viimased",
"ButtonLibrary": "Raamatukogu",
"ButtonLogout": "Logi välja",
"ButtonLookup": "Otsi",
@@ -52,11 +52,11 @@
"ButtonOk": "Ok",
"ButtonOpenFeed": "Ava voog",
"ButtonOpenManager": "Ava haldur",
"ButtonPause": "Peata",
"ButtonPlay": "Mängi",
"ButtonPause": "Paus",
"ButtonPlay": "Play",
"ButtonPlayAll": "Mängi kõik",
"ButtonPlaying": "Mängib",
"ButtonPlaylists": "Esitusloendid",
"ButtonPlaylists": "Playlist",
"ButtonPrevious": "Eelmine",
"ButtonPreviousChapter": "Eelmine peatükk",
"ButtonPurgeAllCache": "Tühjenda kogu vahemälu",
@@ -69,7 +69,7 @@
"ButtonReadLess": "Loe vähem",
"ButtonReadMore": "Loe rohkem",
"ButtonRefresh": "Värskenda",
"ButtonRemove": "Eemalda",
"ButtonRemove": "Kustuta",
"ButtonRemoveAll": "Eemalda kõik",
"ButtonRemoveAllLibraryItems": "Eemalda kõik raamatukogu esemed",
"ButtonRemoveFromContinueListening": "Eemalda jätkake kuulamisest",
@@ -120,12 +120,12 @@
"HeaderCustomMetadataProviders": "Kohandatud metaandmete pakkujad",
"HeaderDetails": "Detailid",
"HeaderDownloadQueue": "Allalaadimise järjekord",
"HeaderEbookFiles": "E-raamatute failid",
"HeaderEbookFiles": "E-raamatu failid",
"HeaderEmail": "E-post",
"HeaderEmailSettings": "E-posti seaded",
"HeaderEpisodes": "Episoodid",
"HeaderEreaderDevices": "E-lugerite seadmed",
"HeaderEreaderSettings": "E-lugerite seadistused",
"HeaderEreaderSettings": "E-lugeja sätted",
"HeaderFiles": "Failid",
"HeaderFindChapters": "Leia peatükid",
"HeaderIgnoredFiles": "Ignoreeritud failid",
@@ -155,8 +155,8 @@
"HeaderPasswordAuthentication": "Parooli autentimine",
"HeaderPermissions": "Õigused",
"HeaderPlayerQueue": "Mängija järjekord",
"HeaderPlaylist": "Mänguloend",
"HeaderPlaylistItems": "Mänguloendi esemed",
"HeaderPlaylist": "Playlist",
"HeaderPlaylistItems": "Playlisti esemed",
"HeaderPodcastsToAdd": "Lisatavad podcastid",
"HeaderPreviewCover": "Eelvaate kaas",
"HeaderRSSFeedGeneral": "RSS-i üksikasjad",
@@ -174,7 +174,7 @@
"HeaderSettingsExperimental": "Katsetusfunktsioonid",
"HeaderSettingsGeneral": "Üldised",
"HeaderSettingsScanner": "Skanner",
"HeaderSleepTimer": "Uinaku taimer",
"HeaderSleepTimer": "Unetaimer",
"HeaderStatsLargestItems": "Suurimad esemed",
"HeaderStatsLongestItems": "Kõige pikemad esemed (tunnid)",
"HeaderStatsMinutesListeningChart": "Kuulamise minutid (viimased 7 päeva)",
@@ -197,9 +197,10 @@
"LabelActivity": "Tegevus",
"LabelAddToCollection": "Lisa kogusse",
"LabelAddToCollectionBatch": "Lisa {0} raamatut kogusse",
"LabelAddToPlaylist": "Lisa mänguloendisse",
"LabelAddToPlaylist": "Lisa playlisti",
"LabelAddToPlaylistBatch": "Lisa {0} eset mänguloendisse",
"LabelAddedAt": "Lisatud",
"LabelAddedDate": "Lisatud {0}",
"LabelAdminUsersOnly": "Ainult administraatorid",
"LabelAll": "Kõik",
"LabelAllUsers": "Kõik kasutajad",
@@ -208,10 +209,10 @@
"LabelAlreadyInYourLibrary": "Juba teie raamatukogus",
"LabelAppend": "Lisa",
"LabelAuthor": "Autor",
"LabelAuthorFirstLast": "Autor (Eesnimi Perekonnanimi)",
"LabelAuthorLastFirst": "Autor (Perekonnanimi, Eesnimi)",
"LabelAuthorFirstLast": "Autor (eesnimi perekonnanimi)",
"LabelAuthorLastFirst": "Autor (perekonnanimi, eesnimi)",
"LabelAuthors": "Autorid",
"LabelAutoDownloadEpisodes": "Automaatne episoodide allalaadimine",
"LabelAutoDownloadEpisodes": "Episoodide automaatne allalaadimine",
"LabelAutoFetchMetadata": "Automaatne metaandmete hankimine",
"LabelAutoFetchMetadataHelp": "Toob tiitli, autori ja seeria metaandmed üleslaadimise hõlbustamiseks. Lisametaandmed võivad pärast üleslaadimist vajada vastavust.",
"LabelAutoLaunch": "Automaatne käivitamine",
@@ -265,7 +266,7 @@
"LabelDiscover": "Avasta",
"LabelDownload": "Lae alla",
"LabelDownloadNEpisodes": "Lae alla {0} episoodi",
"LabelDuration": "Kestus",
"LabelDuration": "Kestvus",
"LabelDurationFound": "Leitud kestus:",
"LabelEbook": "E-raamat",
"LabelEbooks": "E-raamatud",
@@ -278,6 +279,7 @@
"LabelEmbeddedCover": "Manustatud kaas",
"LabelEnable": "Luba",
"LabelEnd": "Lõpp",
"LabelEndOfChapter": "Peatükki lõpp",
"LabelEpisode": "Episood",
"LabelEpisodeTitle": "Episoodi pealkiri",
"LabelEpisodeType": "Episoodi tüüp",
@@ -288,13 +290,14 @@
"LabelFile": "Fail",
"LabelFileBirthtime": "Faili sünniaeg",
"LabelFileModified": "Faili muudetud",
"LabelFilename": "Failinimi",
"LabelFilename": "Faili nimi",
"LabelFilterByUser": "Filtri alusel kasutaja järgi",
"LabelFindEpisodes": "Otsi episoodid",
"LabelFinished": "Lõpetatud",
"LabelFolder": "Kaust",
"LabelFolders": "Kataloogid",
"LabelFontBold": "Paks",
"LabelFontBoldness": "Fondi paksus",
"LabelFontFamily": "Fondi pere",
"LabelFontItalic": "Kaldkiri",
"LabelFontScale": "Fondi suurus",
@@ -303,7 +306,7 @@
"LabelGenre": "Žanr",
"LabelGenres": "Žanrid",
"LabelHardDeleteFile": "Faili lõplik kustutamine",
"LabelHasEbook": "On e-raamat",
"LabelHasEbook": "E-raamat olemas",
"LabelHasSupplementaryEbook": "On täiendav e-raamat",
"LabelHighestPriority": "Kõrgeim prioriteet",
"LabelHour": "Tund",
@@ -311,7 +314,7 @@
"LabelImageURLFromTheWeb": "Pildi URL veebist",
"LabelInProgress": "Pooleli",
"LabelIncludeInTracklist": "Kaasa jälgimisloendis",
"LabelIncomplete": "Puudulik",
"LabelIncomplete": "Lõpetamata",
"LabelInterval": "Intervall",
"LabelIntervalCustomDailyWeekly": "Kohandatud päevane/nädalane",
"LabelIntervalEvery12Hours": "Iga 12 tunni tagant",
@@ -365,12 +368,12 @@
"LabelNarrators": "Jutustajad",
"LabelNew": "Uus",
"LabelNewPassword": "Uus parool",
"LabelNewestAuthors": "Uusimad autorid",
"LabelNewestEpisodes": "Uusimad episoodid",
"LabelNewestAuthors": "Uuemad autorid",
"LabelNewestEpisodes": "Uuemad episoodid",
"LabelNextBackupDate": "Järgmine varukoopia kuupäev",
"LabelNextScheduledRun": "Järgmine ajakava järgmine",
"LabelNoEpisodesSelected": "Episoodid pole valitud",
"LabelNotFinished": "Ei ole lõpetatud",
"LabelNotFinished": "Lõpetamata",
"LabelNotStarted": "Pole alustatud",
"LabelNotes": "Märkused",
"LabelNotificationAppriseURL": "Apprise URL-id",
@@ -383,7 +386,7 @@
"LabelNotificationsMaxQueueSize": "Teavituste sündmuste maksimaalne järjekorra suurus",
"LabelNotificationsMaxQueueSizeHelp": "Sündmused on piiratud 1 sekundiga. Sündmusi ignoreeritakse, kui järjekord on maksimumsuuruses. See takistab teavituste rämpsposti.",
"LabelNumberOfBooks": "Raamatute arv",
"LabelNumberOfEpisodes": "Episoodide arv",
"LabelNumberOfEpisodes": "# episoode",
"LabelOpenRSSFeed": "Ava RSS voog",
"LabelOverwrite": "Kirjuta üle",
"LabelPassword": "Parool",
@@ -398,16 +401,18 @@
"LabelPhotoPathURL": "Foto tee/URL",
"LabelPlayMethod": "Esitusmeetod",
"LabelPlaylists": "Mänguloendid",
"LabelPodcast": "Podcast",
"LabelPodcastSearchRegion": "Podcasti otsingu piirkond",
"LabelPodcastType": "Podcasti tüüp",
"LabelPodcasts": "Podcastid",
"LabelPrefixesToIgnore": "Eiramiseks eesliited (tõstutundetu)",
"LabelPreventIndexing": "Vältige oma voogu indekseerimist iTunes'i ja Google podcasti kataloogides",
"LabelPrimaryEbook": "Esmane e-raamat",
"LabelProgress": "Edenemine",
"LabelProgress": "Progress",
"LabelProvider": "Pakkuja",
"LabelPubDate": "Avaldamise kuupäev",
"LabelPublishYear": "Aasta avaldamine",
"LabelPubDate": "Publitseerimise kuupäev",
"LabelPublishYear": "Publitseerimise aasta",
"LabelPublishedDate": "Publitseeritud {0}",
"LabelPublisher": "Kirjastaja",
"LabelRSSFeedCustomOwnerEmail": "Kohandatud omaniku e-post",
"LabelRSSFeedCustomOwnerName": "Kohandatud omaniku nimi",
@@ -415,7 +420,8 @@
"LabelRSSFeedPreventIndexing": "Vältige indekseerimist",
"LabelRSSFeedSlug": "RSS voog Slug",
"LabelRSSFeedURL": "RSS voog URL",
"LabelRead": "Lugenud",
"LabelRandomly": "Juhuslikus järjekorras",
"LabelRead": "Loetud läbi",
"LabelReadAgain": "Loe uuesti",
"LabelReadEbookWithoutProgress": "Lugege e-raamatut ilma edenemist säilitamata",
"LabelRecentSeries": "Hiljutised seeriad",
@@ -469,9 +475,9 @@
"LabelSettingsStoreMetadataWithItem": "Salvesta metaandmed üksusega",
"LabelSettingsStoreMetadataWithItemHelp": "Vaikimisi salvestatakse metaandmed /metadata/items kausta. Selle seadistuse lubamine salvestab metaandmed teie raamatukogu üksuse kaustadesse",
"LabelSettingsTimeFormat": "Kellaaja vorming",
"LabelShowAll": "Näita kõiki",
"LabelShowAll": "Näita kõik",
"LabelSize": "Suurus",
"LabelSleepTimer": "Uinaku taimer",
"LabelSleepTimer": "Unetaimer",
"LabelStart": "Alusta",
"LabelStartTime": "Alustamise aeg",
"LabelStarted": "Alustatud",
@@ -480,13 +486,13 @@
"LabelStatsAuthors": "Autorid",
"LabelStatsBestDay": "Parim päev",
"LabelStatsDailyAverage": "Päevane keskmine",
"LabelStatsDays": "Päevad",
"LabelStatsDays": "Päevi",
"LabelStatsDaysListened": "Kuulatud päevad",
"LabelStatsHours": "Tunnid",
"LabelStatsInARow": "järjest",
"LabelStatsItemsFinished": "Lõpetatud üksused",
"LabelStatsItemsInLibrary": "Üksused raamatukogus",
"LabelStatsMinutes": "minutit",
"LabelStatsMinutes": "minuteid",
"LabelStatsMinutesListening": "Kuulamise minutid",
"LabelStatsOverallDays": "Kokku päevad",
"LabelStatsOverallHours": "Kokku tunnid",
@@ -502,7 +508,7 @@
"LabelTextEditorNumberedList": "Numberloend",
"LabelTextEditorUnlink": "Eemalda link",
"LabelTheme": "Teema",
"LabelThemeDark": "Tume",
"LabelThemeDark": "Pime",
"LabelThemeLight": "Hele",
"LabelTimeBase": "Aja alus",
"LabelTimeListened": "Kuulatud aeg",
@@ -527,7 +533,7 @@
"LabelType": "Tüüp",
"LabelUnabridged": "Täismahus",
"LabelUndo": "Võta tagasi",
"LabelUnknown": "Tundmatu",
"LabelUnknown": "Teadmata",
"LabelUpdateCover": "Uuenda kaant",
"LabelUpdateCoverHelp": "Luba üle kirjutamine olemasolevate kaante jaoks valitud raamatutele, kui leitakse sobivus",
"LabelUpdateDetails": "Uuenda üksikasju",

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