Compare commits

...

79 commits

Author SHA1 Message Date
github-actions[bot]
361d20df2c build(webui): update Browserslist db 2025-12-01 11:23:06 +08:00
github-actions
ced89c5c54 chore(release): 1.23.6 [skip ci] 2025-11-28 03:43:03 +00:00
Hosted Weblate
a5548a5429 i18n(komga-tray): translated using Weblate (Arabic)
Currently translated at 100.0% (9 of 9 strings)

i18n(komga-tray): translated using Weblate (Arabic)

Currently translated at 33.3% (3 of 9 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: redaloe <farahks@proton.me>
Co-authored-by: redaloe <redaloe@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/komga/desktop/ar/
Translation: komga/desktop
2025-11-28 11:11:17 +08:00
Hosted Weblate
8f8d20a324 i18n(komga-tray): translated using Weblate (Russian)
Currently translated at 77.7% (7 of 9 strings)

Co-authored-by: Aleksey <mitin_aleksey@mail.ru>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/komga/desktop/ru/
Translation: komga/desktop
2025-11-28 11:11:17 +08:00
Hosted Weblate
0f69a3a4cb i18n(komga-tray): translated using Weblate (Galician)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Francisco José Aquino García <fj.aquino@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/komga/desktop/gl/
Translation: komga/desktop
2025-11-28 11:11:17 +08:00
Hosted Weblate
9d10ed31a7 chore: update translation files
Updated by "Remove blank strings" hook in Weblate.

i18n(webui): translated using Weblate (Arabic)

Currently translated at 91.6% (770 of 840 strings)

i18n(webui): translated using Weblate (Arabic)

Currently translated at 91.6% (770 of 840 strings)

i18n(webui): translated using Weblate (Arabic)

Currently translated at 91.6% (770 of 840 strings)

i18n(webui): translated using Weblate (Arabic)

Currently translated at 91.6% (770 of 840 strings)

i18n(webui): translated using Weblate (Arabic)

Currently translated at 91.6% (770 of 840 strings)

i18n(webui): translated using Weblate (Arabic)

Currently translated at 91.6% (770 of 840 strings)

i18n(webui): translated using Weblate (Arabic)

Currently translated at 91.6% (770 of 840 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Co-authored-by: redaloe <redaloe@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/
Translate-URL: https://hosted.weblate.org/projects/komga/webui/ar/
Translation: komga/webui
2025-11-28 11:10:47 +08:00
Hosted Weblate
dde0169f2a i18n(webui): translated using Weblate (Croatian)
Currently translated at 100.0% (840 of 840 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/hr/
Translation: komga/webui
2025-11-28 11:10:47 +08:00
Hosted Weblate
a2ed7d319d i18n(webui): translated using Weblate (Russian)
Currently translated at 64.6% (543 of 840 strings)

i18n(webui): translated using Weblate (Russian)

Currently translated at 67.0% (563 of 840 strings)

Co-authored-by: Aleksey <mitin_aleksey@mail.ru>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/ru/
Translation: komga/webui
2025-11-28 11:10:47 +08:00
Hosted Weblate
475f026749 i18n(webui): translated using Weblate (Portuguese (Brazil))
Currently translated at 79.5% (668 of 840 strings)

i18n(webui): translated using Weblate (Portuguese (Brazil))

Currently translated at 79.5% (668 of 840 strings)

i18n(webui): translated using Weblate (Portuguese (Brazil))

Currently translated at 79.5% (668 of 840 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Luiz Henrique Moreira de Souza <cloud.5623tumacacori@gmail.com>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/pt_BR/
Translation: komga/webui
2025-11-28 11:10:47 +08:00
Hosted Weblate
a03f1bdf7b i18n(webui): translated using Weblate (Thai)
Currently translated at 100.0% (840 of 840 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: altinat <al@altqx.com>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/th/
Translation: komga/webui
2025-11-28 11:10:47 +08:00
Hosted Weblate
b43046fbeb chore: update translation files
Updated by "Remove blank strings" hook in Weblate.

i18n(webui): translated using Weblate (Galician)

Currently translated at 20.1% (169 of 840 strings)

Co-authored-by: Francisco José Aquino García <fj.aquino@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/
Translate-URL: https://hosted.weblate.org/projects/komga/webui/gl/
Translation: komga/webui
2025-11-28 11:10:47 +08:00
Jason
3739951b36
fix(kobo): proxy 401 errors on initialization
Co-authored-by: Gauthier Roebroeck <gauthier.roebroeck@gmail.com>
2025-11-28 11:10:01 +08:00
dependabot[bot]
0f25453949 deps(webui): bump node-forge from 1.3.1 to 1.3.2 in /komga-webui
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.3.1 to 1.3.2.
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.3.1...v1.3.2)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-version: 1.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-27 11:02:30 +08:00
dependabot[bot]
cd47fc777a deps(webui): bump js-yaml from 3.14.1 to 3.14.2 in /komga-webui
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.14.1 to 3.14.2.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.14.1...3.14.2)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 3.14.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 16:07:21 +08:00
dependabot[bot]
f138fe31e7 deps(ci): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 15:39:11 +08:00
Jason
454c6c7202
refactor(kobo): log error responses 2025-11-13 13:32:16 +08:00
Jason
ce3ad4c1c7
fix(kobo): prevent double URL encoding when proxying
Closes: #2130
2025-11-11 15:52:32 +08:00
Jason
b925f3e19d
fix(kobo): proxy Content-Type headers for kobo
Closes: #2074
2025-11-10 15:26:07 +08:00
Gauthier Roebroeck
9a56b30b6c ci: fix svu install 2025-11-03 16:00:14 +08:00
dependabot[bot]
6b07fda273 deps(ci): bump mikepenz/action-junit-report from 5 to 6
Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 5 to 6.
- [Release notes](https://github.com/mikepenz/action-junit-report/releases)
- [Commits](https://github.com/mikepenz/action-junit-report/compare/v5...v6)

---
updated-dependencies:
- dependency-name: mikepenz/action-junit-report
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 15:12:21 +08:00
github-actions[bot]
727fe39e6d build(webui): update Browserslist db 2025-11-03 10:39:12 +08:00
Gauthier Roebroeck
f8ca936ee7 fix: properly decode cover href when generating epub cover
Closes: #2118
2025-11-03 10:38:43 +08:00
dependabot[bot]
fe40ede34a deps(ci): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-28 10:46:45 +08:00
dependabot[bot]
c23f2d3810 deps(ci): bump actions/setup-node from 5 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-20 15:51:56 +08:00
Gauthier Roebroeck
af66144060 docs(api): fix mediatype 2025-10-14 14:00:34 +08:00
Gauthier Roebroeck
ba7b82631f build(docker): use old-releases apt repo 2025-10-08 16:42:12 +08:00
github-actions
a166f96bdf chore(release): 1.23.5 [skip ci] 2025-10-08 07:21:06 +00:00
Hosted Weblate
2259e4bf1c i18n(komga-tray): translated using Weblate (Portuguese (Brazil))
Currently translated at 55.5% (5 of 9 strings)

Co-authored-by: lucas philippe <lucas.philippe.nunes@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/komga/desktop/pt_BR/
Translation: komga/desktop
2025-10-08 15:09:32 +08:00
Hosted Weblate
f75ad77e85 i18n(webui): translated using Weblate (Slovak)
Currently translated at 100.0% (840 of 840 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: fantastron27 <fantastron27@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/sk/
Translation: komga/webui
2025-10-08 15:08:24 +08:00
Hosted Weblate
f2913d1e83 i18n(webui): translated using Weblate (Croatian)
Currently translated at 100.0% (840 of 840 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/hr/
Translation: komga/webui
2025-10-08 15:08:24 +08:00
Hosted Weblate
0b3307cd70 i18n(webui): translated using Weblate (Czech)
Currently translated at 100.0% (840 of 840 strings)

i18n(webui): translated using Weblate (Czech)

Currently translated at 99.8% (839 of 840 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Petr Šimek <petr.simek@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/cs/
Translation: komga/webui
2025-10-08 15:08:24 +08:00
Hosted Weblate
1213309f35 i18n(webui): translated using Weblate (Portuguese (Brazil))
Currently translated at 77.1% (648 of 840 strings)

i18n(webui): translated using Weblate (Portuguese (Brazil))

Currently translated at 77.1% (648 of 840 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Co-authored-by: lucas philippe <lucas.philippe.nunes@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/pt_BR/
Translation: komga/webui
2025-10-08 15:08:24 +08:00
Gauthier Roebroeck
5a5f8d701e fix(api): empty content when x-api-key is sent alongside session
Closes: #2099
2025-10-08 14:47:10 +08:00
dependabot[bot]
bdca990e82 deps(ci): bump peter-evans/dockerhub-description from 4.0.2 to 5.0.0
Bumps [peter-evans/dockerhub-description](https://github.com/peter-evans/dockerhub-description) from 4.0.2 to 5.0.0.
- [Release notes](https://github.com/peter-evans/dockerhub-description/releases)
- [Commits](https://github.com/peter-evans/dockerhub-description/compare/v4.0.2...v5.0.0)

---
updated-dependencies:
- dependency-name: peter-evans/dockerhub-description
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 15:45:50 +08:00
dependabot[bot]
8081439009 deps(ci): bump gradle/actions from 4 to 5
Bumps [gradle/actions](https://github.com/gradle/actions) from 4 to 5.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v4...v5)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 15:44:31 +08:00
dependabot[bot]
80c604e089 deps(ci): bump peter-evans/repository-dispatch from 3 to 4
Bumps [peter-evans/repository-dispatch](https://github.com/peter-evans/repository-dispatch) from 3 to 4.
- [Release notes](https://github.com/peter-evans/repository-dispatch/releases)
- [Commits](https://github.com/peter-evans/repository-dispatch/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peter-evans/repository-dispatch
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 15:44:07 +08:00
Gauthier Roebroeck
f19d7aac1e feat: support local artwork in gif format
Closes: #1853
2025-10-03 15:07:59 +08:00
Gauthier Roebroeck
43c1018e3e perf(api): remove no-transform cache-control from response header
Closes: #2091
2025-10-03 12:00:48 +08:00
Gauthier Roebroeck
eb8bdfc94c fix(api): relax JSON deserializer 2025-10-03 11:51:50 +08:00
github-actions[bot]
e842a5287f build(webui): update Browserslist db 2025-10-02 10:22:35 +08:00
dependabot[bot]
e0b583ff1d deps(ci): bump hydraulic-software/conveyor from 19.0 to 20.0
Bumps [hydraulic-software/conveyor](https://github.com/hydraulic-software/conveyor) from 19.0 to 20.0.
- [Commits](https://github.com/hydraulic-software/conveyor/compare/v19.0...v20.0)

---
updated-dependencies:
- dependency-name: hydraulic-software/conveyor
  dependency-version: '20.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-23 08:59:10 +08:00
Gauthier Roebroeck
5e3ca4d571 fix(api): add id field in HistoricalEventDto 2025-09-16 14:23:16 +08:00
Gauthier Roebroeck
730b093a5f refactor: add more logs when epub extension is missing 2025-09-16 11:38:14 +08:00
Gauthier Roebroeck
2f9b4e75d2 refactor: add more logs to koreader sync controller 2025-09-16 11:38:00 +08:00
dependabot[bot]
d9657587c4 deps(webui): bump axios from 1.8.2 to 1.12.0 in /komga-webui
Bumps [axios](https://github.com/axios/axios) from 1.8.2 to 1.12.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.2...v1.12.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.12.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-15 09:17:13 +08:00
Gauthier Roebroeck
69ba569b04 refactor: make dslRO transaction aware 2025-09-09 12:32:17 +08:00
github-actions
e850cdcd2f chore(release): 1.23.4 [skip ci] 2025-09-09 02:33:13 +00:00
Gauthier Roebroeck
51bfb353e7 perf: send events outside of db transaction 2025-09-09 10:21:52 +08:00
dependabot[bot]
3f64435afa deps(ci): bump actions/setup-node from 4 to 5
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 5.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 15:15:28 +08:00
Gauthier Roebroeck
166b1ee371 fix(kobo): update default kobo resources
Closes: #2066
2025-09-03 10:32:24 +08:00
github-actions[bot]
0e63e7454b build(webui): update Browserslist db 2025-09-01 11:32:44 +08:00
Gauthier Roebroeck
058af49807 fix(kobo): fail to create proxy url
Closes: #2063
2025-08-28 17:41:44 +08:00
Gauthier Roebroeck
7888a53dbf test: run tests with a WAL database instead of memorydb 2025-08-28 16:49:27 +08:00
github-actions
2ec0e295fa chore(release): 1.23.3 [skip ci] 2025-08-28 02:37:07 +00:00
Gauthier Roebroeck
1776174d3f fix(api): cannot create readlist or collection with database in WAL mode 2025-08-28 09:43:15 +08:00
dependabot[bot]
b837963f0e deps(ci): bump actions/setup-java from 4 to 5
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 10:00:06 +08:00
Gauthier Roebroeck
6b4d81e0ba build(release): fail jreleaser on publish errors 2025-08-26 09:59:37 +08:00
github-actions
caf658a7bf chore(release): 1.23.2 [skip ci] 2025-08-25 09:24:08 +00:00
Hosted Weblate
4a598e3908 i18n(komga-tray): translated using Weblate (Slovak)
Currently translated at 100.0% (9 of 9 strings)

i18n(komga-tray): added translation using Weblate (Slovak)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: fantastron27 <fantastron27@gmail.com>
Co-authored-by: peter cerny <posli.to.semka@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/komga/desktop/sk/
Translation: komga/desktop
2025-08-25 17:13:04 +08:00
Hosted Weblate
9a6f66444d i18n(webui): translated using Weblate (Slovak)
Currently translated at 40.3% (339 of 840 strings)

i18n(webui): translated using Weblate (Slovak)

Currently translated at 28.5% (240 of 840 strings)

i18n(webui): added translation using Weblate (Slovak)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: fantastron27 <fantastron27@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/sk/
Translation: komga/webui
2025-08-25 17:12:25 +08:00
Hosted Weblate
ed271fc485 i18n(webui): translated using Weblate (Ukrainian)
Currently translated at 100.0% (840 of 840 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/komga/webui/uk/
Translation: komga/webui
2025-08-25 17:12:25 +08:00
Hosted Weblate
9ce6258914 i18n(komga-tray): translated using Weblate (Assamese)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Itsmechinmoy <itsmechinmoy@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/komga/desktop/as/
Translation: komga/desktop
2025-08-25 17:10:13 +08:00
Hosted Weblate
c6a424ee92 i18n(komga-tray): added translation using Weblate (Slovak)
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: peter cerny <posli.to.semka@gmail.com>
2025-08-25 17:10:13 +08:00
Hosted Weblate
45a105a26f i18n(komga-tray): translated using Weblate (Ukrainian)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/komga/desktop/uk/
Translation: komga/desktop
2025-08-25 17:10:13 +08:00
Gauthier Roebroeck
0bcf1e4743 docs: update sponsors 2025-08-25 09:34:10 +08:00
Gauthier Roebroeck
e7b56b2bee perf: enable SQLite WAL mode by default 2025-08-22 13:21:56 +08:00
Gauthier Roebroeck
138c0ed464 fix(kobo): NullPointer exception
Closes: #2045
2025-08-22 11:27:21 +08:00
dependabot[bot]
777acbbd68 deps(webui): bump brace-expansion from 1.1.11 to 1.1.12 in /komga-webui
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 14:31:09 +08:00
Gauthier Roebroeck
3ab21ff6aa fix: ignore xml namespace in EPUB opf file
Closes: #2043
2025-08-20 14:30:41 +08:00
Gauthier Roebroeck
4e7c49d5d8 refactor: use Jsoup XmlParser instead of HTML parser where needed 2025-08-20 14:30:41 +08:00
Gauthier Roebroeck
8b629888ff deps: bump jsoup to 1.21.1 2025-08-20 14:30:41 +08:00
dependabot[bot]
30f6d3a862 deps(webui): bump ws in /komga-webui
Bumps  and [ws](https://github.com/websockets/ws). These dependencies needed to be updated together.

Updates `ws` from 7.5.9 to 7.5.10
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10)

Updates `ws` from 8.14.1 to 8.18.3
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-version: 7.5.10
  dependency-type: indirect
- dependency-name: ws
  dependency-version: 8.18.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 13:41:57 +08:00
dependabot[bot]
ea5a4701f2 deps(ci): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 10:41:19 +08:00
Gauthier Roebroeck
85a33d4661 fix(webui): ignore content negotiation when downloading page
Closes: #2042
2025-08-11 12:51:24 +08:00
Gauthier Roebroeck
d1475864af refactor(api): mark kepubifyPath as deprecated 2025-08-05 15:00:53 +08:00
Gauthier Roebroeck
eb8a2df3ea deps: bump nightcompress from 1.1.0 to 1.1.1 2025-08-05 10:21:34 +08:00
dependabot[bot]
a333b75724 deps(ci): bump hydraulic-software/conveyor from 18.1 to 19.0
Bumps [hydraulic-software/conveyor](https://github.com/hydraulic-software/conveyor) from 18.1 to 19.0.
- [Commits](https://github.com/hydraulic-software/conveyor/compare/v18.1...v19.0)

---
updated-dependencies:
- dependency-name: hydraulic-software/conveyor
  dependency-version: '19.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 09:38:03 +08:00
Gauthier Roebroeck
54c818e857 deps: bump ktlint from 1.6.0 to 1.7.1 2025-08-04 10:55:16 +08:00
Gauthier Roebroeck
18ec31f28b build(deps): move redundant versions to gradle version catalog 2025-08-04 10:52:25 +08:00
93 changed files with 4454 additions and 557 deletions

View file

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Configure git - name: Configure git

View file

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Repository Dispatch - name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3 uses: peter-evans/repository-dispatch@v4
with: with:
token: ${{ secrets.REPO_ACCESS_TOKEN }} token: ${{ secrets.REPO_ACCESS_TOKEN }}
repository: gotson/komga-website repository: gotson/komga-website

View file

@ -15,7 +15,7 @@ jobs:
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
- name: DockerHub Description - name: DockerHub Description
uses: peter-evans/dockerhub-description@v4.0.2 uses: peter-evans/dockerhub-description@v5.0.0
env: env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

View file

@ -43,14 +43,14 @@ jobs:
version_next: ${{ steps.versions.outputs.version_next }} version_next: ${{ steps.versions.outputs.version_next }}
should_release: ${{ steps.versions.outputs.should_release }} should_release: ${{ steps.versions.outputs.should_release }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Homebrew - name: Set up Homebrew
id: set-up-homebrew id: set-up-homebrew
uses: Homebrew/actions/setup-homebrew@master uses: Homebrew/actions/setup-homebrew@master
- name: Install svu - name: Install svu
run: brew install caarlos0/tap/svu run: brew install --cask caarlos0/tap/svu
- name: Compute next version for release - name: Compute next version for release
run: | run: |
echo "VERSION_NEXT=`svu ${{ inputs.bump }}`" | tee -a $GITHUB_ENV echo "VERSION_NEXT=`svu ${{ inputs.bump }}`" | tee -a $GITHUB_ENV
@ -72,7 +72,7 @@ jobs:
sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/share/dotnet
sudo rm -rf "$AGENT_TOOLSDIRECTORY" sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- uses: actions/checkout@v4 - uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
@ -84,14 +84,14 @@ jobs:
if: needs.version.outputs.should_release #only redo if the version changed if: needs.version.outputs.should_release #only redo if the version changed
run: sed -i -e "s/version=.*/version=${{ needs.version.outputs.version_next }}/" gradle.properties run: sed -i -e "s/version=.*/version=${{ needs.version.outputs.version_next }}/" gradle.properties
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'npm' cache: 'npm'
cache-dependency-path: komga-webui/package-lock.json cache-dependency-path: komga-webui/package-lock.json
- name: Setup Java 21 - name: Setup Java 21
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
java-version: 21 java-version: 21
java-package: 'jdk' java-package: 'jdk'
@ -117,7 +117,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v5
- name: Build - name: Build
run: ./gradlew :komga:prepareThymeLeaf :komga:bootJar :komga-tray:jar run: ./gradlew :komga:prepareThymeLeaf :komga:bootJar :komga-tray:jar
@ -143,7 +143,7 @@ jobs:
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: JReleaser Changelog output - name: JReleaser Changelog output
if: always() && needs.version.outputs.should_release if: always() && needs.version.outputs.should_release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: jreleaser-changelog name: jreleaser-changelog
path: | path: |
@ -167,7 +167,7 @@ jobs:
echo $APPLE_PRIVATE_KEY | base64 --decode > ./secret/apple_private_key.p8 echo $APPLE_PRIVATE_KEY | base64 --decode > ./secret/apple_private_key.p8
- name: Conveyor make copied-site - name: Conveyor make copied-site
uses: hydraulic-software/conveyor/actions/build@v18.1 uses: hydraulic-software/conveyor/actions/build@v20.0
if: inputs.conveyor-copied-site if: inputs.conveyor-copied-site
with: with:
command: --cache-limit=2.0 -f conveyor.ci.conf make copied-site -o ./output/site command: --cache-limit=2.0 -f conveyor.ci.conf make copied-site -o ./output/site
@ -182,7 +182,7 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.B2_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.B2_SECRET_ACCESS_KEY }}
- name: Upload Conveyor log - name: Upload Conveyor log
if: always() && inputs.conveyor-copied-site if: always() && inputs.conveyor-copied-site
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: conveyor-make-copied-site name: conveyor-make-copied-site
path: ~/.cache/hydraulic/conveyor/logs/log.latest.txt path: ~/.cache/hydraulic/conveyor/logs/log.latest.txt
@ -194,7 +194,7 @@ jobs:
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: JReleaser Release output - name: JReleaser Release output
if: always() && inputs.github_release if: always() && inputs.github_release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: jreleaser-release name: jreleaser-release
path: | path: |
@ -212,7 +212,7 @@ jobs:
JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: JReleaser Publish output - name: JReleaser Publish output
if: always() && inputs.docker_release if: always() && inputs.docker_release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: jreleaser-publish name: jreleaser-publish
path: | path: |
@ -220,7 +220,7 @@ jobs:
build/jreleaser/output.properties build/jreleaser/output.properties
- name: Conveyor - publish to Microsoft Store - name: Conveyor - publish to Microsoft Store
uses: hydraulic-software/conveyor/actions/build@v18.1 uses: hydraulic-software/conveyor/actions/build@v20.0
if: inputs.msstore_release if: inputs.msstore_release
with: with:
command: --cache-limit=2.0 -f conveyor.msstore.ci.conf make ms-store-release -o ./output/msstore command: --cache-limit=2.0 -f conveyor.msstore.ci.conf make ms-store-release -o ./output/msstore
@ -236,7 +236,7 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.B2_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.B2_SECRET_ACCESS_KEY }}
- name: Upload Conveyor log - name: Upload Conveyor log
if: always() && inputs.msstore_release if: always() && inputs.msstore_release
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: conveyor-ms-store-release name: conveyor-ms-store-release
path: ~/.cache/hydraulic/conveyor/logs/log.latest.txt path: ~/.cache/hydraulic/conveyor/logs/log.latest.txt
@ -246,7 +246,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Repository Dispatch - name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3 uses: peter-evans/repository-dispatch@v4
with: with:
token: ${{ secrets.REPO_ACCESS_TOKEN }} token: ${{ secrets.REPO_ACCESS_TOKEN }}
repository: gotson/komga-website repository: gotson/komga-website

View file

@ -19,36 +19,36 @@ jobs:
fail-fast: false fail-fast: false
name: Test server - ${{ matrix.os }} name: Test server - ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Setup Java 21 - name: Setup Java 21
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
java-version: 21 java-version: 21
java-package: 'jdk' java-package: 'jdk'
distribution: 'temurin' distribution: 'temurin'
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v5
- name: Build - name: Build
run: ./gradlew build :komga-tray:jar run: ./gradlew build :komga-tray:jar
- name: Upload Unit Test Results - name: Upload Unit Test Results
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: test-results-${{ matrix.os }} name: test-results-${{ matrix.os }}
path: komga/build/test-results/ path: komga/build/test-results/
- name: Upload Unit Test Reports - name: Upload Unit Test Reports
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: test-reports-${{ matrix.os }} name: test-reports-${{ matrix.os }}
path: komga/build/reports/tests/ path: komga/build/reports/tests/
- name: Publish Test Report - name: Publish Test Report
uses: mikepenz/action-junit-report@v5 uses: mikepenz/action-junit-report@v6
if: always() if: always()
with: with:
report_paths: '**/build/test-results/test/TEST-*.xml' report_paths: '**/build/test-results/test/TEST-*.xml'
@ -56,7 +56,7 @@ jobs:
- name: Conveyor - compute JDK module list - name: Conveyor - compute JDK module list
if: github.event_name == 'push' && github.repository_owner == 'gotson' && contains(matrix.os, 'ubuntu') if: github.event_name == 'push' && github.repository_owner == 'gotson' && contains(matrix.os, 'ubuntu')
uses: hydraulic-software/conveyor/actions/build@v18.1 uses: hydraulic-software/conveyor/actions/build@v20.0
with: with:
command: -f conveyor.detect.conf -Kapp.machines=mac.aarch64 make processed-jars command: -f conveyor.detect.conf -Kapp.machines=mac.aarch64 make processed-jars
signing_key: ${{ secrets.CONVEYOR_SIGNING_KEY }} signing_key: ${{ secrets.CONVEYOR_SIGNING_KEY }}
@ -69,7 +69,7 @@ jobs:
- name: Upload JDK required modules - name: Upload JDK required modules
if: steps.conveyor_compare.outcome == 'failure' if: steps.conveyor_compare.outcome == 'failure'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: conveyor-required-jdk-modules name: conveyor-required-jdk-modules
path: ./output/required-jdk-modules.txt path: ./output/required-jdk-modules.txt
@ -78,8 +78,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Test webui builds name: Test webui builds
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'npm' cache: 'npm'

View file

@ -1,3 +1,189 @@
# [1.23.6](https://github.com/gotson/komga/compare/1.23.5...1.23.6) (2025-11-28)
## 🐛 Fixes
**kobo**
- proxy 401 errors on initialization ([3739951](https://github.com/gotson/komga/commits/3739951))
- prevent double URL encoding when proxying ([ce3ad4c](https://github.com/gotson/komga/commits/ce3ad4c)), closes [#2130](https://github.com/gotson/komga/issues/2130)
- proxy Content-Type headers for kobo ([b925f3e](https://github.com/gotson/komga/commits/b925f3e)), closes [#2074](https://github.com/gotson/komga/issues/2074)
**unscoped**
- properly decode cover href when generating epub cover ([f8ca936](https://github.com/gotson/komga/commits/f8ca936)), closes [#2118](https://github.com/gotson/komga/issues/2118)
## 🔄️ Changes
**kobo**
- log error responses ([454c6c7](https://github.com/gotson/komga/commits/454c6c7))
## 🛠 Build
**docker**
- use old-releases apt repo ([ba7b826](https://github.com/gotson/komga/commits/ba7b826))
**webui**
- update Browserslist db ([727fe39](https://github.com/gotson/komga/commits/727fe39))
**unscoped**
- fix svu install ([9a56b30](https://github.com/gotson/komga/commits/9a56b30))
## 📝 Documentation
**api**
- fix mediatype ([af66144](https://github.com/gotson/komga/commits/af66144))
## 🌐 Translation
**komga-tray**
- translated using Weblate (Arabic) ([a5548a5](https://github.com/gotson/komga/commits/a5548a5))
- translated using Weblate (Russian) ([8f8d20a](https://github.com/gotson/komga/commits/8f8d20a))
- translated using Weblate (Galician) ([0f69a3a](https://github.com/gotson/komga/commits/0f69a3a))
**webui**
- translated using Weblate (Croatian) ([dde0169](https://github.com/gotson/komga/commits/dde0169))
- translated using Weblate (Russian) ([a2ed7d3](https://github.com/gotson/komga/commits/a2ed7d3))
- translated using Weblate (Portuguese (Brazil)) ([475f026](https://github.com/gotson/komga/commits/475f026))
- translated using Weblate (Thai) ([a03f1bd](https://github.com/gotson/komga/commits/a03f1bd))
## ⚙️ Dependencies
**ci**
- bump actions/checkout from 5 to 6 ([f138fe3](https://github.com/gotson/komga/commits/f138fe3))
- bump mikepenz/action-junit-report from 5 to 6 ([6b07fda](https://github.com/gotson/komga/commits/6b07fda))
- bump actions/upload-artifact from 4 to 5 ([fe40ede](https://github.com/gotson/komga/commits/fe40ede))
- bump actions/setup-node from 5 to 6 ([c23f2d3](https://github.com/gotson/komga/commits/c23f2d3))
**webui**
- bump node-forge from 1.3.1 to 1.3.2 in /komga-webui ([0f25453](https://github.com/gotson/komga/commits/0f25453))
- bump js-yaml from 3.14.1 to 3.14.2 in /komga-webui ([cd47fc7](https://github.com/gotson/komga/commits/cd47fc7))
# [1.23.5](https://github.com/gotson/komga/compare/1.23.4...1.23.5) (2025-10-08)
## 🚀 Features
- support local artwork in gif format ([f19d7aa](https://github.com/gotson/komga/commits/f19d7aa)), closes [#1853](https://github.com/gotson/komga/issues/1853)
## 🐛 Fixes
**api**
- empty content when x-api-key is sent alongside session ([5a5f8d7](https://github.com/gotson/komga/commits/5a5f8d7)), closes [#2099](https://github.com/gotson/komga/issues/2099)
- relax JSON deserializer ([eb8bdfc](https://github.com/gotson/komga/commits/eb8bdfc))
- add id field in HistoricalEventDto ([5e3ca4d](https://github.com/gotson/komga/commits/5e3ca4d))
## 🏎 Perf
**api**
- remove no-transform cache-control from response header ([43c1018](https://github.com/gotson/komga/commits/43c1018)), closes [#2091](https://github.com/gotson/komga/issues/2091)
## 🔄️ Changes
- add more logs when epub extension is missing ([730b093](https://github.com/gotson/komga/commits/730b093))
- add more logs to koreader sync controller ([2f9b4e7](https://github.com/gotson/komga/commits/2f9b4e7))
- make dslRO transaction aware ([69ba569](https://github.com/gotson/komga/commits/69ba569))
## 🛠 Build
**webui**
- update Browserslist db ([e842a52](https://github.com/gotson/komga/commits/e842a52))
## 🌐 Translation
**komga-tray**
- translated using Weblate (Portuguese (Brazil)) ([2259e4b](https://github.com/gotson/komga/commits/2259e4b))
**webui**
- translated using Weblate (Slovak) ([f75ad77](https://github.com/gotson/komga/commits/f75ad77))
- translated using Weblate (Croatian) ([f2913d1](https://github.com/gotson/komga/commits/f2913d1))
- translated using Weblate (Czech) ([0b3307c](https://github.com/gotson/komga/commits/0b3307c))
- translated using Weblate (Portuguese (Brazil)) ([1213309](https://github.com/gotson/komga/commits/1213309))
## ⚙️ Dependencies
**ci**
- bump peter-evans/dockerhub-description from 4.0.2 to 5.0.0 ([bdca990](https://github.com/gotson/komga/commits/bdca990))
- bump gradle/actions from 4 to 5 ([8081439](https://github.com/gotson/komga/commits/8081439))
- bump peter-evans/repository-dispatch from 3 to 4 ([80c604e](https://github.com/gotson/komga/commits/80c604e))
- bump hydraulic-software/conveyor from 19.0 to 20.0 ([e0b583f](https://github.com/gotson/komga/commits/e0b583f))
**webui**
- bump axios from 1.8.2 to 1.12.0 in /komga-webui ([d965758](https://github.com/gotson/komga/commits/d965758))
# [1.23.4](https://github.com/gotson/komga/compare/1.23.3...1.23.4) (2025-09-09)
## 🐛 Fixes
**kobo**
- update default kobo resources ([166b1ee](https://github.com/gotson/komga/commits/166b1ee)), closes [#2066](https://github.com/gotson/komga/issues/2066)
- fail to create proxy url ([058af49](https://github.com/gotson/komga/commits/058af49)), closes [#2063](https://github.com/gotson/komga/issues/2063)
## 🏎 Perf
- send events outside of db transaction ([51bfb35](https://github.com/gotson/komga/commits/51bfb35))
## 🧪 Tests
- run tests with a WAL database instead of memorydb ([7888a53](https://github.com/gotson/komga/commits/7888a53))
## 🛠 Build
**webui**
- update Browserslist db ([0e63e74](https://github.com/gotson/komga/commits/0e63e74))
## ⚙️ Dependencies
**ci**
- bump actions/setup-node from 4 to 5 ([3f64435](https://github.com/gotson/komga/commits/3f64435))
# [1.23.3](https://github.com/gotson/komga/compare/1.23.2...1.23.3) (2025-08-28)
## 🐛 Fixes
**api**
- cannot create readlist or collection with database in WAL mode ([1776174](https://github.com/gotson/komga/commits/1776174))
## 🛠 Build
**release**
- fail jreleaser on publish errors ([6b4d81e](https://github.com/gotson/komga/commits/6b4d81e))
## ⚙️ Dependencies
**ci**
- bump actions/setup-java from 4 to 5 ([b837963](https://github.com/gotson/komga/commits/b837963))
# [1.23.2](https://github.com/gotson/komga/compare/1.23.1...1.23.2) (2025-08-25)
## 🐛 Fixes
**kobo**
- NullPointer exception ([138c0ed](https://github.com/gotson/komga/commits/138c0ed)), closes [#2045](https://github.com/gotson/komga/issues/2045)
**webui**
- ignore content negotiation when downloading page ([85a33d4](https://github.com/gotson/komga/commits/85a33d4)), closes [#2042](https://github.com/gotson/komga/issues/2042)
**unscoped**
- ignore xml namespace in EPUB opf file ([3ab21ff](https://github.com/gotson/komga/commits/3ab21ff)), closes [#2043](https://github.com/gotson/komga/issues/2043)
## 🏎 Perf
- enable SQLite WAL mode by default ([e7b56b2](https://github.com/gotson/komga/commits/e7b56b2))
## 🔄️ Changes
**api**
- mark kepubifyPath as deprecated ([d147586](https://github.com/gotson/komga/commits/d147586))
**unscoped**
- use Jsoup XmlParser instead of HTML parser where needed ([4e7c49d](https://github.com/gotson/komga/commits/4e7c49d))
## 🛠 Build
**deps**
- move redundant versions to gradle version catalog ([18ec31f](https://github.com/gotson/komga/commits/18ec31f))
## 📝 Documentation
- update sponsors ([0bcf1e4](https://github.com/gotson/komga/commits/0bcf1e4))
## 🌐 Translation
**komga-tray**
- translated using Weblate (Slovak) ([4a598e3](https://github.com/gotson/komga/commits/4a598e3))
- translated using Weblate (Assamese) ([9ce6258](https://github.com/gotson/komga/commits/9ce6258))
- added translation using Weblate (Slovak) ([c6a424e](https://github.com/gotson/komga/commits/c6a424e))
- translated using Weblate (Ukrainian) ([45a105a](https://github.com/gotson/komga/commits/45a105a))
**webui**
- translated using Weblate (Slovak) ([9a6f664](https://github.com/gotson/komga/commits/9a6f664))
- translated using Weblate (Ukrainian) ([ed271fc](https://github.com/gotson/komga/commits/ed271fc))
## ⚙️ Dependencies
**ci**
- bump actions/checkout from 4 to 5 ([ea5a470](https://github.com/gotson/komga/commits/ea5a470))
- bump hydraulic-software/conveyor from 18.1 to 19.0 ([a333b75](https://github.com/gotson/komga/commits/a333b75))
**webui**
- bump brace-expansion from 1.1.11 to 1.1.12 in /komga-webui ([777acbb](https://github.com/gotson/komga/commits/777acbb))
- bump ws in /komga-webui ([30f6d3a](https://github.com/gotson/komga/commits/30f6d3a))
**unscoped**
- bump jsoup to 1.21.1 ([8b62988](https://github.com/gotson/komga/commits/8b62988))
- bump nightcompress from 1.1.0 to 1.1.1 ([eb8a2df](https://github.com/gotson/komga/commits/eb8a2df))
- bump ktlint from 1.6.0 to 1.7.1 ([54c818e](https://github.com/gotson/komga/commits/54c818e))
# [1.23.1](https://github.com/gotson/komga/compare/1.23.0...1.23.1) (2025-08-01) # [1.23.1](https://github.com/gotson/komga/compare/1.23.0...1.23.1) (2025-08-01)
## 🐛 Fixes ## 🐛 Fixes
**api** **api**

View file

@ -51,6 +51,12 @@ Check the [development guidelines](./DEVELOPING.md).
[![Jetbrains_logo](./.github/readme-images/jetbrains.svg)](https://www.jetbrains.com/?from=Komga) [![Jetbrains_logo](./.github/readme-images/jetbrains.svg)](https://www.jetbrains.com/?from=Komga)
Thanks to [JetBrains](https://www.jetbrains.com/?from=Komga) for providing the development environment that helps us develop Komga.
[![Chromatic logo](https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png)](https://www.chromatic.com)
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
## Credits ## Credits
The Komga icon is based on an icon made by [Freepik](https://www.freepik.com/home) from www.flaticon.com The Komga icon is based on an icon made by [Freepik](https://www.freepik.com/home) from www.flaticon.com

View file

@ -44,7 +44,7 @@ allprojects {
} }
configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> { configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
version = "1.6.0" version = "1.7.1"
filter { filter {
exclude("**/generated-src/**") exclude("**/generated-src/**")
exclude("**/generated/**") exclude("**/generated/**")
@ -164,7 +164,7 @@ jreleaser {
packagers { packagers {
docker { docker {
active = Active.RELEASE active = Active.RELEASE
continueOnError = true continueOnError = false
templateDirectory = rootDir.resolve("komga/docker") templateDirectory = rootDir.resolve("komga/docker")
repository.active = Active.NEVER repository.active = Active.NEVER
buildArgs = listOf("--cache-from", "gotson/komga:latest") buildArgs = listOf("--cache-from", "gotson/komga:latest")

View file

@ -1,2 +1,2 @@
version=1.23.1 version=1.23.6
org.gradle.jvmargs=-Xmx2G org.gradle.jvmargs=-Xmx2G

10
gradle/libs.versions.toml Normal file
View file

@ -0,0 +1,10 @@
[versions]
sqliteJdbc = "3.50.2.0"
nightmonkeys = "1.0.0"
twelvemonkeys = "3.12.0"
springboot = "3.5.4"
lucene = "9.9.1" # v10 requires JDK 21
jooq = "3.19.24" # should be aligned with the version provided by Spring Boot
[plugins]
gradleGitProperties = {id = "com.gorylenko.gradle-git-properties", version = "2.5.2"}

View file

@ -5,7 +5,7 @@ plugins {
kotlin("jvm") kotlin("jvm")
kotlin("plugin.spring") kotlin("plugin.spring")
} }
id("com.gorylenko.gradle-git-properties") version "2.5.2" alias(libs.plugins.gradleGitProperties)
id("org.jetbrains.compose") version "1.8.2" id("org.jetbrains.compose") version "1.8.2"
id("org.jetbrains.kotlin.plugin.compose") version "2.2.0" id("org.jetbrains.kotlin.plugin.compose") version "2.2.0"
id("dev.hydraulic.conveyor") version "1.12" id("dev.hydraulic.conveyor") version "1.12"

View file

@ -0,0 +1,9 @@
dialog_error.close=إغلاق
dialog_error.copy_clipboard=النسخ إلى الحافظة
dialog_error.title=Komga لم يقدر على البدء
error_message.port_in_use=الباب {} على قيد الاستعمال.\nربما Komga جارٍ بالفعل.\nتحقق أيقونة علبة النظام أو القائمة لأيقونة Komga.
error_message.unexpected=حصل خطأ غير متوقع.
menu.open_komga=فتح Komga
menu.quit=إغلاق Komga
menu.show_conf_dir=فتح موقع الإعدادات
menu.show_log=إظهار ملف السجل

View file

@ -1,9 +1,9 @@
dialog_error.close=বন্ধ কৰক dialog_error.close=বন্ধ কৰক
dialog_error.copy_clipboard=ক্লিপবোর্ডলৈ কপি কৰক dialog_error.copy_clipboard=ক্লিপব’ৰ্ডলৈ কপি কৰক
dialog_error.title=Komga আৰম্ভ কৰাত ব্যৰ্থ হ’ dialog_error.title=কমগা আৰম্ভ কৰাত বিফ
error_message.port_in_use={} পৰ্ট ইতিমধ্যে ব্যৱহাৰ কৰা হৈছে।\nKomga চাগে ইতিমধ্যে চলি আছে।\nKomga আইকনৰ বাবে ট্ৰে আইকন বা মেনু বাৰ পৰীক্ষা কৰক। error_message.port_in_use={} পৰ্ট ইতিমধ্যে ব্যৱহৃত।\nকমগা সম্ভৱতঃ ইতিমধ্যে চলি আছে।\nকমগা আইকনৰ বাবে ট্ৰে আইকন বা মেনু বাৰ পৰীক্ষা কৰক।
error_message.unexpected=টা অপ্ৰত্যাশিত ভুল ঘটিল। error_message.unexpected=ক অপ্ৰত্যাশিত ত্ৰুটি ঘটিল।
menu.open_komga=Komga খুলিব menu.open_komga=কমগা খোলক
menu.quit=Komga এৰি দিয় menu.quit=কমগা বন্ধ কৰ
menu.show_conf_dir=বিন্যাস পঞ্জিকা খোলক menu.show_conf_dir=বিন্যাস ডাইৰেক্টৰি খোলক
menu.show_log=লগ ফাইল দেখুৱাওক menu.show_log=লগ ফাইল দেখুৱাওক

View file

@ -0,0 +1,9 @@
dialog_error.close=Pechar
dialog_error.copy_clipboard=Copiar ao portapapeis
dialog_error.title=Komga fallou ao iniciar
error_message.port_in_use=O porto {} está en uso.\nÉ probable que Komga xa esté a executarse.\nBusca a icona de Komga na bandexa de sistema ou a barra de menú.
error_message.unexpected=Aconteceu un erro imprevisto.
menu.open_komga=Abrir Komga
menu.quit=Saír de Komga
menu.show_conf_dir=Abrir o cartafol de configuración
menu.show_log=Amosar o ficheiro de rexistro

View file

@ -0,0 +1,9 @@
dialog_error.close=Fechar
dialog_error.copy_clipboard=Copiar para área de transferência
dialog_error.title=Komga falhou ao inicializar
error_message.port_in_use=A porta {} já está em uso.\nProvavelmente o Komga já está rodando.\nVerifique o ícone na barra de tarefas ou a barra de menu.
error_message.unexpected=Ocorreu um erro inesperado.
menu.open_komga=Abrir Komga
menu.quit=Sair do Komga
menu.show_conf_dir=Abrir configuração de diretório
menu.show_log=Mostrar arquivo de logs

View file

@ -1,7 +1,7 @@
dialog_error.close=Закрыть dialog_error.close=Закрыть
dialog_error.copy_clipboard=Скопировать в буфер обмена dialog_error.copy_clipboard=Скопировать в буфер обмена
dialog_error.title=Komga не смог запуститься dialog_error.title=Komga не удалось запустить
error_message.port_in_use=Порт {] уже используется.\nKomga возможно уже работает.\nПроверьте панель задач или меню на наличие иконки Komga. error_message.port_in_use=Порт {} уже используется.\nВероятно, Komga уже запущен.\nПроверьте область уведомлений или панель меню на наличие иконки Komga.
error_message.unexpected=Произошла непредвиденная ошибка. error_message.unexpected=Произошла непредвиденная ошибка.
menu.open_komga=Открыть Komga menu.open_komga=Открыть Komga
menu.quit=Закрыть Komga menu.quit=Закрыть Komga

View file

@ -0,0 +1,9 @@
dialog_error.close=Zavrieť
dialog_error.copy_clipboard=Skopírovať do schránky
dialog_error.title=Aplikáciu Komga sa nepodarilo spustiť
error_message.port_in_use=Port {} sa aktuálne používa.\nAplikácia Komga je pravdepodobne už spustená.\nSkontrolujte, či ikona Komga už nie je v systémovej lište alebo medzi schovanými ikonami.
error_message.unexpected=Nastala neočakávaná chyba.
menu.open_komga=Otvoriť aplikáciu Komga
menu.quit=Zavrieť aplikáciu Komga
menu.show_conf_dir=Otvoriť konfiguračný priečinok
menu.show_log=Zobraziť logy

View file

@ -1,4 +1,5 @@
dialog_error.close=Закрити dialog_error.close=Закрити
dialog_error.copy_clipboard=Копіювати в буфер обміну
dialog_error.title=Помилка при запуску Komga dialog_error.title=Помилка при запуску Komga
error_message.port_in_use=Порт {} вже використовується.\nKomga напевно вже запущена.\nПеревірте панель запущених програм на наявність іконки Komga. error_message.port_in_use=Порт {} вже використовується.\nKomga напевно вже запущена.\nПеревірте панель запущених програм на наявність іконки Komga.
error_message.unexpected=Сталася невідома помилка. error_message.unexpected=Сталася невідома помилка.

View file

@ -10,7 +10,7 @@
"dependencies": { "dependencies": {
"@d-i-t-a/reader": "github:gotson/R2D2BC#fork", "@d-i-t-a/reader": "github:gotson/R2D2BC#fork",
"@saekitominaga/isbn-verify": "^2.0.1", "@saekitominaga/isbn-verify": "^2.0.1",
"axios": "^1.8.2", "axios": "^1.12.0",
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
@ -5102,13 +5102,12 @@
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.8.2", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
"license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.4",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
@ -5512,9 +5511,9 @@
"integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==" "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA=="
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -5715,6 +5714,18 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/callsite": { "node_modules/callsite": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
@ -5768,9 +5779,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001731", "version": "1.0.30001757",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
"integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==", "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -7288,6 +7299,19 @@
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
"dev": true "dev": true
}, },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/duplexer": { "node_modules/duplexer": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@ -7371,9 +7395,9 @@
} }
}, },
"node_modules/editorconfig/node_modules/brace-expansion": { "node_modules/editorconfig/node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
@ -7614,12 +7638,9 @@
} }
}, },
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dependencies": {
"get-intrinsic": "^1.2.4"
},
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@ -7638,15 +7659,26 @@
"integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==",
"dev": true "dev": true
}, },
"node_modules/es-set-tostringtag": { "node_modules/es-object-atoms": {
"version": "2.0.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"dependencies": { "dependencies": {
"get-intrinsic": "^1.1.3", "es-errors": "^1.3.0"
"has": "^1.0.3", },
"has-tostringtag": "^1.0.0" "engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -8906,9 +8938,9 @@
} }
}, },
"node_modules/filehound/node_modules/brace-expansion": { "node_modules/filehound/node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@ -9240,12 +9272,14 @@
"dev": true "dev": true
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.0", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
}, },
"engines": { "engines": {
@ -9406,15 +9440,20 @@
} }
}, },
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.2.4", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2", "function-bind": "^1.1.2",
"has-proto": "^1.0.1", "get-proto": "^1.0.1",
"has-symbols": "^1.0.3", "gopd": "^1.2.0",
"hasown": "^2.0.0" "has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -9432,6 +9471,18 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/get-stream": { "node_modules/get-stream": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@ -9549,11 +9600,11 @@
} }
}, },
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.0.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dependencies": { "engines": {
"get-intrinsic": "^1.1.3" "node": ">= 0.4"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -9657,6 +9708,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"dev": true,
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@ -9665,9 +9717,9 @@
} }
}, },
"node_modules/has-symbols": { "node_modules/has-symbols": {
"version": "1.0.3", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
}, },
@ -9676,11 +9728,11 @@
} }
}, },
"node_modules/has-tostringtag": { "node_modules/has-tostringtag": {
"version": "1.0.0", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dependencies": { "dependencies": {
"has-symbols": "^1.0.2" "has-symbols": "^1.0.3"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -12915,9 +12967,9 @@
} }
}, },
"node_modules/js-beautify/node_modules/brace-expansion": { "node_modules/js-beautify/node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
@ -12974,9 +13026,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "3.14.1", "version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"argparse": "^1.0.7", "argparse": "^1.0.7",
@ -13683,6 +13735,14 @@
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mdn-data": { "node_modules/mdn-data": {
"version": "2.0.30", "version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@ -14101,9 +14161,9 @@
} }
}, },
"node_modules/node-forge": { "node_modules/node-forge": {
"version": "1.3.1", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">= 6.13.0" "node": ">= 6.13.0"
@ -19404,9 +19464,9 @@
} }
}, },
"node_modules/webpack-dev-server/node_modules/ws": { "node_modules/webpack-dev-server/node_modules/ws": {
"version": "8.14.1", "version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
@ -19657,9 +19717,9 @@
} }
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "7.5.9", "version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=8.3.0" "node": ">=8.3.0"
@ -23587,12 +23647,12 @@
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg=="
}, },
"axios": { "axios": {
"version": "1.8.2", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
"requires": { "requires": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.4",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
@ -23911,9 +23971,9 @@
"integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==" "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA=="
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -24043,6 +24103,15 @@
"set-function-length": "^1.2.1" "set-function-length": "^1.2.1"
} }
}, },
"call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"requires": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
}
},
"callsite": { "callsite": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
@ -24084,9 +24153,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001731", "version": "1.0.30001757",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
"integrity": "sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==" "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="
}, },
"case-sensitive-paths-webpack-plugin": { "case-sensitive-paths-webpack-plugin": {
"version": "2.4.0", "version": "2.4.0",
@ -25214,6 +25283,16 @@
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
"dev": true "dev": true
}, },
"dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"requires": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
}
},
"duplexer": { "duplexer": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@ -25290,9 +25369,9 @@
}, },
"dependencies": { "dependencies": {
"brace-expansion": { "brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
@ -25489,12 +25568,9 @@
} }
}, },
"es-define-property": { "es-define-property": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
"requires": {
"get-intrinsic": "^1.2.4"
}
}, },
"es-errors": { "es-errors": {
"version": "1.3.0", "version": "1.3.0",
@ -25507,15 +25583,23 @@
"integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==",
"dev": true "dev": true
}, },
"es-set-tostringtag": { "es-object-atoms": {
"version": "2.0.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"requires": { "requires": {
"get-intrinsic": "^1.1.3", "es-errors": "^1.3.0"
"has": "^1.0.3", }
"has-tostringtag": "^1.0.0" },
"es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"requires": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
} }
}, },
"es-shim-unscopables": { "es-shim-unscopables": {
@ -26462,9 +26546,9 @@
}, },
"dependencies": { "dependencies": {
"brace-expansion": { "brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"requires": { "requires": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@ -26700,12 +26784,14 @@
} }
}, },
"form-data": { "form-data": {
"version": "4.0.0", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"requires": { "requires": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
} }
}, },
@ -26818,15 +26904,20 @@
"dev": true "dev": true
}, },
"get-intrinsic": { "get-intrinsic": {
"version": "1.2.4", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"requires": { "requires": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2", "function-bind": "^1.1.2",
"has-proto": "^1.0.1", "get-proto": "^1.0.1",
"has-symbols": "^1.0.3", "gopd": "^1.2.0",
"hasown": "^2.0.0" "has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
} }
}, },
"get-package-type": { "get-package-type": {
@ -26835,6 +26926,15 @@
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
"dev": true "dev": true
}, },
"get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"requires": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
}
},
"get-stream": { "get-stream": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@ -26919,12 +27019,9 @@
} }
}, },
"gopd": { "gopd": {
"version": "1.0.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
"requires": {
"get-intrinsic": "^1.1.3"
}
}, },
"graceful-fs": { "graceful-fs": {
"version": "4.2.11", "version": "4.2.11",
@ -26998,19 +27095,20 @@
"has-proto": { "has-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"dev": true
}, },
"has-symbols": { "has-symbols": {
"version": "1.0.3", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
}, },
"has-tostringtag": { "has-tostringtag": {
"version": "1.0.0", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"requires": { "requires": {
"has-symbols": "^1.0.2" "has-symbols": "^1.0.3"
} }
}, },
"hash-sum": { "hash-sum": {
@ -29358,9 +29456,9 @@
}, },
"dependencies": { "dependencies": {
"brace-expansion": { "brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
@ -29407,9 +29505,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
"js-yaml": { "js-yaml": {
"version": "3.14.1", "version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true, "dev": true,
"requires": { "requires": {
"argparse": "^1.0.7", "argparse": "^1.0.7",
@ -29975,6 +30073,11 @@
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.4.tgz", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.4.tgz",
"integrity": "sha512-TCHvDqmb3ZJ4PWG7VEGVgtefA5/euFmsIhxtD0XsBxI39gUSKL81mIRFdt0AiNQozUahd4ke98ZdirExd/vSEw==" "integrity": "sha512-TCHvDqmb3ZJ4PWG7VEGVgtefA5/euFmsIhxtD0XsBxI39gUSKL81mIRFdt0AiNQozUahd4ke98ZdirExd/vSEw=="
}, },
"math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
},
"mdn-data": { "mdn-data": {
"version": "2.0.30", "version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@ -30295,9 +30398,9 @@
} }
}, },
"node-forge": { "node-forge": {
"version": "1.3.1", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
"dev": true "dev": true
}, },
"node-int64": { "node-int64": {
@ -34187,9 +34290,9 @@
} }
}, },
"ws": { "ws": {
"version": "8.14.1", "version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true, "dev": true,
"requires": {} "requires": {}
} }
@ -34354,9 +34457,9 @@
} }
}, },
"ws": { "ws": {
"version": "7.5.9", "version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },

View file

@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@d-i-t-a/reader": "github:gotson/R2D2BC#fork", "@d-i-t-a/reader": "github:gotson/R2D2BC#fork",
"@saekitominaga/isbn-verify": "^2.0.1", "@saekitominaga/isbn-verify": "^2.0.1",
"axios": "^1.8.2", "axios": "^1.12.0",
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",

View file

@ -19,7 +19,15 @@
}, },
"account_settings": { "account_settings": {
"account_settings": "إعدادات الحساب", "account_settings": "إعدادات الحساب",
"change_password": "تغيير كلمة السر" "api_key": {
"created_date": "تاريخ الصنع: {date}",
"force_kobo_sync": "إجبار مزامنة Kobo",
"generate_api_key": "توليد مفتاح API",
"no_keys": "لم يخلق مفتاح API بعد"
},
"change_password": "تغيير كلمة السر",
"details": "تفاصيل",
"my_account": "حسابي"
}, },
"announcements": { "announcements": {
"mark_all_read": "اشر عليها بانها قرأت", "mark_all_read": "اشر عليها بانها قرأت",
@ -27,6 +35,7 @@
"tab_title": "إعلانات" "tab_title": "إعلانات"
}, },
"authentication_activity": { "authentication_activity": {
"api_key": "مفتاح API",
"datetime": "التاريخ والوقت", "datetime": "التاريخ والوقت",
"email": "البريد الإلكتروني", "email": "البريد الإلكتروني",
"error": "خطأ", "error": "خطأ",
@ -85,7 +94,8 @@
"bookreader": { "bookreader": {
"beginning_of_book": "أنت في بداية الكتاب.", "beginning_of_book": "أنت في بداية الكتاب.",
"changing_reading_direction": "تغيير اتجاه القراءة إلى", "changing_reading_direction": "تغيير اتجاه القراءة إلى",
"cycling_page_layout": "تخطيط دوري للصفحة", "cycling_page_layout": "تغيير تخطيط الصفحة",
"cycling_page_margin": "تغيير هامش الصفحة",
"cycling_scale": "تغيير حجم التحرير", "cycling_scale": "تغيير حجم التحرير",
"cycling_side_padding": "تغيير الحدود الجانبية", "cycling_side_padding": "تغيير الحدود الجانبية",
"download_current_page": "تنزيل الصفحة الحالية", "download_current_page": "تنزيل الصفحة الحالية",
@ -128,6 +138,7 @@
"general": "إعدادات عامة", "general": "إعدادات عامة",
"gestures": "الإيماءات", "gestures": "الإيماءات",
"page_layout": "تخطيط الصفحة", "page_layout": "تخطيط الصفحة",
"page_margin": "هامش الصفحة",
"paged": "خيارات القارئ المرقم", "paged": "خيارات القارئ المرقم",
"reading_mode": "وضع القراءة", "reading_mode": "وضع القراءة",
"scale_type": "نوع القياس", "scale_type": "نوع القياس",
@ -137,8 +148,9 @@
}, },
"shortcuts": { "shortcuts": {
"close": "إغلاق", "close": "إغلاق",
"cycle_page_layout": "دورة تخطيط الصفحة", "cycle_page_layout": "تغيير تخطيط الصفحة",
"cycle_scale": "مقياس الدورة", "cycle_page_margin": "تغيير هامش الصفحة",
"cycle_scale": "تغيير الحجم",
"cycle_side_padding": "تغيير الحدود الجانبية", "cycle_side_padding": "تغيير الحدود الجانبية",
"first_page": "الصفحة الأولى", "first_page": "الصفحة الأولى",
"fullscreen": "إدخال/إنهاء ملء الشاشة", "fullscreen": "إدخال/إنهاء ملء الشاشة",
@ -161,6 +173,8 @@
}, },
"browse_book": { "browse_book": {
"comment": "تعليق", "comment": "تعليق",
"date_created": "صُنِع",
"date_modified": "آخر تعديل",
"download_file": "تحميل الملف", "download_file": "تحميل الملف",
"file": "ملف", "file": "ملف",
"format": "صيغة", "format": "صيغة",
@ -170,6 +184,8 @@
"outdated_tooltip": "تم تغيير ملف هذا الكتاب ، يجب إعادة تحليل هذا الكتاب", "outdated_tooltip": "تم تغيير ملف هذا الكتاب ، يجب إعادة تحليل هذا الكتاب",
"read_book": "قراءة كتاب", "read_book": "قراءة كتاب",
"read_incognito": "قراءة التصفح المتخفي", "read_incognito": "قراءة التصفح المتخفي",
"remove_from_collection": "إزالة الكتاب من المجموعة",
"remove_from_readlist": "إزالة الكتاب من قائمة القراءة",
"size": "حجم" "size": "حجم"
}, },
"browse_collection": { "browse_collection": {
@ -184,6 +200,7 @@
}, },
"browse_series": { "browse_series": {
"earliest_year_from_release_dates": "هذه هي السنة الأولى من تواريخ الإصدار لجميع الكتب في السلسلة", "earliest_year_from_release_dates": "هذه هي السنة الأولى من تواريخ الإصدار لجميع الكتب في السلسلة",
"remove_from_collection": "إزالة السلسلة من المجموعة",
"series_no_summary": "هذه السلسلة لا تحتوي على ملخص ، لذلك اخترنا واحدًا لك!", "series_no_summary": "هذه السلسلة لا تحتوي على ملخص ، لذلك اخترنا واحدًا لك!",
"summary_from_book": "ملخص من الكتاب {number}:" "summary_from_book": "ملخص من الكتاب {number}:"
}, },
@ -194,6 +211,8 @@
"common": { "common": {
"age": "العمر", "age": "العمر",
"all_libraries": "كل المكتبات", "all_libraries": "كل المكتبات",
"all_of": "كل من",
"any_of": "أي من",
"book": "كتاب", "book": "كتاب",
"books": "كتب", "books": "كتب",
"books_n": "لا يوجد كتاب | 1 كتاب | {count} كتب", "books_n": "لا يوجد كتاب | 1 كتاب | {count} كتب",
@ -203,15 +222,19 @@
"choose_image": "اختر صورة", "choose_image": "اختر صورة",
"close": "إغلاق", "close": "إغلاق",
"collections": "المجموعات", "collections": "المجموعات",
"copied": "تم النسخ!",
"create": "صنع", "create": "صنع",
"delete": "حذف", "delete": "حذف",
"discard": "تجاهل", "dimension": "ع:{width}, ط:{height}",
"discard": "تخلص",
"disk_space": "مساحة القرص", "disk_space": "مساحة القرص",
"dismiss": "رفض", "dismiss": "رفض",
"download": "تحميل", "download": "تحميل",
"drag_drop": "أمسك واسحب", "drag_drop": "أمسك واسحب",
"duplicate": "مكرر",
"email": "البريد الإلكتروني", "email": "البريد الإلكتروني",
"epub": "Epub", "epub": "EPUB",
"error": "خطأ",
"filename": "اسم الملف", "filename": "اسم الملف",
"filter_no_matches": "الفلتر النشط ليس له تطابق", "filter_no_matches": "الفلتر النشط ليس له تطابق",
"genre": "نوع", "genre": "نوع",
@ -219,13 +242,17 @@
"go_to_library": "اذهب إلى المكتبة", "go_to_library": "اذهب إلى المكتبة",
"go_to_readlist": "انتقل إلى قائمة القراءة", "go_to_readlist": "انتقل إلى قائمة القراءة",
"go_to_series": "الانتقال إلى السلسلة", "go_to_series": "الانتقال إلى السلسلة",
"i_understand": "فهمت",
"library": "مكتبة", "library": "مكتبة",
"locale_name": "العربية", "locale_name": "العربية",
"locale_rtl": "true", "locale_rtl": "true",
"lock_all": "قفل الكل", "lock_all": "قفل الكل",
"media": "الوسائط",
"more": "المزيد",
"n_selected": "{count} مختار", "n_selected": "{count} مختار",
"nothing_to_show": "لا شيء للعرض", "nothing_to_show": "لا شيء للعرض",
"oneshot": "طلقة واحدة", "ok": "حسنا",
"oneshot": "قصة منفردة",
"outdated": "متقادمة", "outdated": "متقادمة",
"page": "صفحة", "page": "صفحة",
"page_number": "رقم الصفحة", "page_number": "رقم الصفحة",
@ -235,19 +262,23 @@
"password": "كلمة السر", "password": "كلمة السر",
"pdf": "PDF", "pdf": "PDF",
"pending_tasks": "لا توجد مهام معلقة | مهمة واحدة معلقة | {count} مهام معلقة", "pending_tasks": "لا توجد مهام معلقة | مهمة واحدة معلقة | {count} مهام معلقة",
"pinned_libraries": "المكتبات المثبتة",
"publisher": "الناشر", "publisher": "الناشر",
"read": "اقرأ", "read": "اقرأ",
"read_on": "قرأ في {date}", "read_on": "قرأ في {date}",
"readlist": "قائمة القراءة", "readlist": "قائمة القراءة",
"readlists": "قوائم القراءة", "readlists": "قوائم القراءة",
"remember-me": "تذكرني", "remember-me": "تذكرني",
"reorder": "تغيير الترتيب",
"required": "مطلوب", "required": "مطلوب",
"reset_filters": "إعادة تعيين فلتر", "reset_filters": "إعادة تعيين فلتر",
"roles": "أدوار", "roles": "أدوار",
"save_changes": "حفظ التغييرات", "save_changes": "حفظ التغييرات",
"series": "سلسلة", "series": "سلسلة",
"settings": "الإعدادات",
"sidecars": "Sidecars", "sidecars": "Sidecars",
"tags": "التصنيف", "tags": "التصنيف",
"ui": "واجهة المستخدم",
"unavailable": "غير متوفر", "unavailable": "غير متوفر",
"unlock_all": "فتح الكل", "unlock_all": "فتح الكل",
"url": "عنوان URL", "url": "عنوان URL",
@ -271,10 +302,11 @@
"comicrack_preambule_html": "يمكنك استيراد قوائم قراءة ComicRack الحالية بتنسيق <code>cbl</code>.<br>سيحاول Komga مطابقة السلسلة ورقم الكتاب المقدمين مع السلسلة والكتب في مكتباتك.", "comicrack_preambule_html": "يمكنك استيراد قوائم قراءة ComicRack الحالية بتنسيق <code>cbl</code>.<br>سيحاول Komga مطابقة السلسلة ورقم الكتاب المقدمين مع السلسلة والكتب في مكتباتك.",
"dialog_confirmation": { "dialog_confirmation": {
"body": "{unmatched} / {total} الكتب غير متطابقة", "body": "{unmatched} / {total} الكتب غير متطابقة",
"body2": "{duplicates} / {total} كتاب مكرر",
"create": "الخلق على أي حال", "create": "الخلق على أي حال",
"title": "بعض الكتب لا تتطابق" "title": "بعض الكتب لا تتطابق"
}, },
"field_file_label": "ComicRack Reading List (.cbl)", "field_file_label": "قائمة قراءة ComicRack (من نوع cbl.)",
"field_files_label": "قوائم القراءة ComicRack (.cbl)", "field_files_label": "قوائم القراءة ComicRack (.cbl)",
"import_read_lists": "استيراد قوائم القراءة", "import_read_lists": "استيراد قوائم القراءة",
"imported_as": "مستورد كـ {name}", "imported_as": "مستورد كـ {name}",
@ -286,6 +318,14 @@
"tab_title": "استيراد البيانات" "tab_title": "استيراد البيانات"
}, },
"dialog": { "dialog": {
"add_api_key": {
"button_confirm": "توليد",
"context": "يمكن استعمال مفاتيح الAPI للتوثيق عبر بروتوكول Kobo Sync.",
"dialog_title": "توليد مفتاح API جديد",
"field_comment": "تعليق",
"field_comment_hint": "ما الغرض من مفتاح الAPI هذا؟",
"info_copy": "يرجى التأكد من نسخ مفتاح الAPI الآن. لا يستطاع النظر عليه مجددا!"
},
"add_to_collection": { "add_to_collection": {
"button_create": "خلق", "button_create": "خلق",
"card_collection_subtitle": "لا توجد سلسلة | 1 سلسلة | {count} سلسلة", "card_collection_subtitle": "لا توجد سلسلة | 1 سلسلة | {count} سلسلة",
@ -319,6 +359,12 @@
"filter": "مصنف حسب رقم الكتاب أو العنوان أو تاريخ الإصدار", "filter": "مصنف حسب رقم الكتاب أو العنوان أو تاريخ الإصدار",
"title": "حدد كتاب" "title": "حدد كتاب"
}, },
"delete_apikey": {
"button_confirm": "حذف",
"confirm_delete": "فهمت, حذف مفتاح الAPI المسمى {name}",
"dialog_title": "حذف مفتاح الAPI",
"warning_html": "كل التطبيقات التي تستعمل مفتاح الAPI هذا ستصبح غير قادرة على الوصول إلى الAPI التابع لKomga. لا يمكن التراجع عن هذا الإجراء."
},
"delete_book": { "delete_book": {
"button_confirm": "حذف", "button_confirm": "حذف",
"confirm_delete": "نعم، حذف كتاب \"{name}\" وملفاته", "confirm_delete": "نعم، حذف كتاب \"{name}\" وملفاته",
@ -389,6 +435,8 @@
"field_summary": "ملخص", "field_summary": "ملخص",
"field_tags": "التصنيف", "field_tags": "التصنيف",
"field_title": "العنوان", "field_title": "العنوان",
"number_sort_decrement": "تقليل الجميع ب1",
"number_sort_increment": "زيادة الجميع ب1",
"tab_authors": "المؤلفون", "tab_authors": "المؤلفون",
"tab_general": "إعدادات عامة", "tab_general": "إعدادات عامة",
"tab_links": "الروابط", "tab_links": "الروابط",
@ -415,6 +463,7 @@
"dialot_title_edit": "تحرير المكتبة", "dialot_title_edit": "تحرير المكتبة",
"field_analysis_analyze_dimensions": "تحليل أبعاد الصفحات", "field_analysis_analyze_dimensions": "تحليل أبعاد الصفحات",
"field_analysis_hash_files": "حساب تجزئة للملفات", "field_analysis_hash_files": "حساب تجزئة للملفات",
"field_analysis_hash_koreader": "حسابة التلبيد لملفات KOReader",
"field_analysis_hash_pages": "حساب تجزئة الصفحات", "field_analysis_hash_pages": "حساب تجزئة الصفحات",
"field_convert_to_cbz": "تحويل تلقائيا إلى CBZ", "field_convert_to_cbz": "تحويل تلقائيا إلى CBZ",
"field_import_barcode_isbn": "الباركود ردمك", "field_import_barcode_isbn": "الباركود ردمك",
@ -422,15 +471,19 @@
"field_import_comicinfo_collections": "المجموعات", "field_import_comicinfo_collections": "المجموعات",
"field_import_comicinfo_readlists": "قوائم القراءة", "field_import_comicinfo_readlists": "قوائم القراءة",
"field_import_comicinfo_series": "سلسلة البيانات الوصفية", "field_import_comicinfo_series": "سلسلة البيانات الوصفية",
"field_import_comicinfo_series_append_volume": "إضافة المجلد إلى عنوان المسلسل",
"field_import_epub_book": "البيانات الوصفية للكتاب", "field_import_epub_book": "البيانات الوصفية للكتاب",
"field_import_epub_series": "سلسلة البيانات الوصفية", "field_import_epub_series": "سلسلة البيانات الوصفية",
"field_import_local_artwork": "الأعمال الفنية المحلية", "field_import_local_artwork": "الأعمال الفنية المحلية",
"field_import_mylar_series": "سلسلة البيانات الوصفية", "field_import_mylar_series": "سلسلة البيانات الوصفية",
"field_name": "الاسم", "field_name": "الاسم",
"field_oneshotsdirectory": "‬موقع القصص المنفردة",
"field_repair_extensions": "إصلاح ملحقات الملفات غير الصحيحة تلقائيًا", "field_repair_extensions": "إصلاح ملحقات الملفات غير الصحيحة تلقائيًا",
"field_root_folder": "المجلد الرئيسي", "field_root_folder": "المجلد الرئيسي",
"field_scan_interval": "تكرار البحث عن الملفات",
"field_scanner_empty_trash_after_scan": "تفريغ المهملات تلقائيا بعد كل فحص", "field_scanner_empty_trash_after_scan": "تفريغ المهملات تلقائيا بعد كل فحص",
"field_scanner_force_directory_modified_time": "فرض وقت تعديل الدليل", "field_scanner_force_directory_modified_time": "فرض وقت تعديل الدليل",
"field_scanner_scan_startup": "البحث عن الملفات عند بدء البرنامج",
"field_series_cover": "غلاف السلسلة", "field_series_cover": "غلاف السلسلة",
"file_browser_dialog_button_confirm": "إختيار", "file_browser_dialog_button_confirm": "إختيار",
"file_browser_dialog_title": "المجلد الجذر للمكتبة", "file_browser_dialog_title": "المجلد الجذر للمكتبة",
@ -441,11 +494,14 @@
"label_import_epub": "استيراد البيانات الوصفية من ملفات EPUB", "label_import_epub": "استيراد البيانات الوصفية من ملفات EPUB",
"label_import_local": "استيراد أصول الوسائط المحلية", "label_import_local": "استيراد أصول الوسائط المحلية",
"label_import_mylar": "استيراد البيانات الوصفية التي تم إنشاؤها بواسطة Mylar", "label_import_mylar": "استيراد البيانات الوصفية التي تم إنشاؤها بواسطة Mylar",
"label_scan_directory_exclusions": "استبعاد المواقع",
"label_scan_types": "البحث عن أنواع الملفات هذه",
"label_scanner": "الماسح الضوئي", "label_scanner": "الماسح الضوئي",
"label_series_cover": "غلاف السلسلة", "label_series_cover": "غلاف السلسلة",
"tab_general": "إعدادات عامة", "tab_general": "إعدادات عامة",
"tab_metadata": "البيانات الوصفية", "tab_metadata": "البيانات الوصفية",
"tab_options": "الخيارات", "tab_options": "الخيارات",
"tooltip_oneshotsdirectory": "يترك فارغا للتعطيل",
"tooltip_scanner_force_modified_time": "تمكين إذا كانت المكتبة على Google Drive", "tooltip_scanner_force_modified_time": "تمكين إذا كانت المكتبة على Google Drive",
"tooltip_use_resources": "يمكن أن تستهلك الكثير من الموارد في مكتبات كبيرة أو أجهزة بطيئة" "tooltip_use_resources": "يمكن أن تستهلك الكثير من الموارد في مكتبات كبيرة أو أجهزة بطيئة"
}, },
@ -453,11 +509,16 @@
"button_cancel": "إلغاء", "button_cancel": "إلغاء",
"button_confirm": "حفظ التغييرات", "button_confirm": "حفظ التغييرات",
"dialog_title": "تحرير قائمة القراءة", "dialog_title": "تحرير قائمة القراءة",
"field_manual_ordering": "ترتيب يدوي",
"field_name": "الاسم", "field_name": "الاسم",
"field_summary": "ملخص", "field_summary": "ملخص",
"tab_general": "عام", "tab_general": "عام",
"tab_poster": "ملصق" "tab_poster": "ملصق"
}, },
"edit_recommended": {
"button_confirm": "حفظ التعديلات",
"button_reset": "الإعادة إلى التعديلات الأصلية"
},
"edit_series": { "edit_series": {
"button_cancel": "إلغاء", "button_cancel": "إلغاء",
"button_confirm": "حفظ التغييرات", "button_confirm": "حفظ التغييرات",
@ -483,6 +544,7 @@
"tab_poster": "ملصق", "tab_poster": "ملصق",
"tab_sharing": "مشاركة", "tab_sharing": "مشاركة",
"tab_tags": "التصنيف", "tab_tags": "التصنيف",
"tab_titles": "العناوين البديلة",
"tags_notice_multiple_edit": "أنت تقوم بتحرير العلامات لسلسلة متعددة. سيؤدي هذا إلى تجاوز العلامات الموجودة في كل سلسلة." "tags_notice_multiple_edit": "أنت تقوم بتحرير العلامات لسلسلة متعددة. سيؤدي هذا إلى تجاوز العلامات الموجودة في كل سلسلة."
}, },
"edit_user": { "edit_user": {
@ -525,6 +587,9 @@
}, },
"title": "اسم الوجهة" "title": "اسم الوجهة"
}, },
"force_kobo_sync": {
"dialog_title": "إجبار مزامنة Kobo"
},
"password_change": { "password_change": {
"button_cancel": "إلغاء", "button_cancel": "إلغاء",
"button_confirm": "تغيير كلمة السر", "button_confirm": "تغيير كلمة السر",
@ -541,6 +606,7 @@
}, },
"series_picker": { "series_picker": {
"label_search_series": "سلسلة البحث", "label_search_series": "سلسلة البحث",
"no_results": "لم يتم العثور على أي سلسلة",
"title": "اختيار سلسلة" "title": "اختيار سلسلة"
}, },
"server_stop": { "server_stop": {
@ -579,21 +645,31 @@
} }
}, },
"duplicate_pages": { "duplicate_pages": {
"action_auto_delete_remaining": "باق إلغاء {count} أوتوماتيكيا",
"action_delete_auto": "حذف تلقائي", "action_delete_auto": "حذف تلقائي",
"action_delete_manual": "حذف يدوي", "action_delete_manual": "حذف يدوي",
"action_delete_matches": "حذف المطابقات", "action_delete_matches": "حذف المطابقات",
"action_ignore": "تجاهل", "action_ignore": "تجاهل",
"action_ignore_remaining": "تجاهل الباقي ({count})",
"action_manual_delete_remaining": "إلغاء الباقي يدويا ({count})",
"delete_to_save": "حذف لحفظ {size}", "delete_to_save": "حذف لحفظ {size}",
"deleted_count": "تم حذف {count} مرة", "deleted_count": "تم حذف {count} مرة",
"empty_title": "لم يتم العثور على صفحات مكررة",
"empty_title_known": "لا توجد نسخ مكررة معروف عنها",
"filter": { "filter": {
"count": "عدد", "count": "عدد",
"date_added": "تاريخ الإضافة",
"date_modified": "تاريخ التعديل",
"delete_count": "عدد الحذف", "delete_count": "عدد الحذف",
"delete_size": "المساحة المحفوظة", "delete_size": "المساحة المحفوظة",
"match_count": "عدد النتائج المتطابقة",
"size": "حجم", "size": "حجم",
"total_size": "الحجم الإجمالي" "total_size": "الحجم الإجمالي"
}, },
"info": "حذف الصفحات المكررة سيغيّر ملفاتك. احتفظ بنسخة احتياطية من ملفاتك واستخدم الحذف اليدوي قبل استخدام الحذف التلقائي.", "info": "حذف الصفحات المكررة سيغيّر ملفاتك. احتفظ بنسخة احتياطية من ملفاتك واستخدم الحذف اليدوي قبل استخدام الحذف التلقائي.",
"known": "المعروفة",
"matches_n": "لا يوجد تطابق | 1 تطابق | {count} التطابق", "matches_n": "لا يوجد تطابق | 1 تطابق | {count} التطابق",
"new": "الجديدة",
"saved_size": "حُفظ {size}", "saved_size": "حُفظ {size}",
"title": "صفحات مكررة", "title": "صفحات مكررة",
"unknown_size": "حجم مجهول" "unknown_size": "حجم مجهول"
@ -609,6 +685,23 @@
"HARDLINK": "ملفات الروابط الثابتة/النسخ", "HARDLINK": "ملفات الروابط الثابتة/النسخ",
"MOVE": "نقل الملفات" "MOVE": "نقل الملفات"
}, },
"epubreader": {
"appearances": {
"day": "النهار",
"night": "الليل",
"sepia": "سيبيا"
},
"column_count": {
"auto": "أوتوماتيكي",
"one": "واحد",
"two": "اثنان"
},
"reading_direction": {
"auto": "أوتوماتيكي",
"ltr": "من اليسار إلى اليمين",
"rtl": "من اليمين إلى اليسار"
}
},
"historical_event_type": { "historical_event_type": {
"BookConverted": "تمّ تحويل الكتاب", "BookConverted": "تمّ تحويل الكتاب",
"BookFileDeleted": "تمّ مسح ملف الكتاب", "BookFileDeleted": "تمّ مسح ملف الكتاب",
@ -616,6 +709,11 @@
"DuplicatePageDeleted": "تمّ حذف الصفحة المكررة", "DuplicatePageDeleted": "تمّ حذف الصفحة المكررة",
"SeriesFolderDeleted": "تمّ حذف مجلد السلسلة" "SeriesFolderDeleted": "تمّ حذف مجلد السلسلة"
}, },
"media_profile": {
"DIVINA": "DIVINA",
"EPUB": "EPUB",
"PDF": "PDF"
},
"media_status": { "media_status": {
"ERROR": "خطأ", "ERROR": "خطأ",
"OUTDATED": "قديمه", "OUTDATED": "قديمه",
@ -634,6 +732,14 @@
"VERTICAL": "عمودي", "VERTICAL": "عمودي",
"WEBTOON": "ويبتون" "WEBTOON": "ويبتون"
}, },
"scan_interval": {
"DAILY": "يوميا",
"DISABLED": "معطل",
"EVERY_12H": "كل 12 ساعة",
"EVERY_6H": "كل 6 ساعات",
"HOURLY": "كل ساعة",
"WEEKLY": "كل أسبوع"
},
"series_cover": { "series_cover": {
"FIRST": "أول", "FIRST": "أول",
"LAST": "أخير" "LAST": "أخير"
@ -645,6 +751,34 @@
"ONGOING": "مستمر" "ONGOING": "مستمر"
} }
}, },
"epubreader": {
"current_chapter": "الفصل الحالي",
"page_of": "الصفحة {page} من {count}",
"publisher_font": "الناشر",
"settings": {
"column_count": "عدد أعمدة",
"font_family": "الخط",
"layout": "تخطيط",
"layout_scroll": "تمرير",
"navigation_mode": "وضع التنقل",
"navigation_options": {
"both": "كلاهما",
"buttons": "أزرار",
"click": "نقر/ضغط"
},
"page_margins": "هوامش الصفحة"
},
"shortcuts": {
"cycle_pagination": "تغيير عدد الأعمدة",
"font_size_decrease": "تصغير الخط",
"font_size_increase": "تكبير الخط",
"menus": "القائمات",
"next": "أمام",
"previous": "خلف",
"settings": "الإعدادات",
"show_hide_toc": "إظهار/إخفاء الفهرس"
}
},
"error_codes": { "error_codes": {
"ERR_1000": "تعذر الوصول إلى الملف أثناء التحليل", "ERR_1000": "تعذر الوصول إلى الملف أثناء التحليل",
"ERR_1001": "نوع الوسائط غير مدعوم", "ERR_1001": "نوع الوسائط غير مدعوم",
@ -667,11 +801,13 @@
"filter": { "filter": {
"age_rating": "التصنيف العمري", "age_rating": "التصنيف العمري",
"age_rating_none": "لا شيء", "age_rating_none": "لا شيء",
"any": "أي",
"complete": "اكتمل", "complete": "اكتمل",
"genre": "نوع", "genre": "نوع",
"in_progress": "قيد التقدم", "in_progress": "قيد التقدم",
"language": "اللغة", "language": "اللغة",
"library": "المكتبة", "library": "المكتبة",
"oneshot": "قصة منفردة",
"publisher": "الناشر", "publisher": "الناشر",
"read": "اقرأ", "read": "اقرأ",
"release_date": "تاريخ الاصدار", "release_date": "تاريخ الاصدار",
@ -699,6 +835,8 @@
}, },
"library_navigation": { "library_navigation": {
"browse": "تصفّح", "browse": "تصفّح",
"browse_books": "كتب",
"browse_series": "سلسلات",
"collections": "المجموعات", "collections": "المجموعات",
"readlists": "قوائم القراءة", "readlists": "قوائم القراءة",
"recommended": "موصى به" "recommended": "موصى به"
@ -730,6 +868,7 @@
"empty_trash": "إفراغ سلة المهملات", "empty_trash": "إفراغ سلة المهملات",
"mark_read": "تحديد كمقروء", "mark_read": "تحديد كمقروء",
"mark_unread": "تحديد كغير مقروء", "mark_unread": "تحديد كغير مقروء",
"pin": "تثبيت",
"refresh_metadata": "تحديث البيانات الوصفية", "refresh_metadata": "تحديث البيانات الوصفية",
"scan_library_files": "فحص ملفات المكتبة", "scan_library_files": "فحص ملفات المكتبة",
"select_all": "تحديد الكل" "select_all": "تحديد الكل"
@ -745,6 +884,9 @@
"libraries": "المكتبات", "libraries": "المكتبات",
"logout": "تسجيل الخروج" "logout": "تسجيل الخروج"
}, },
"no_libraries_pinned": {
"title": "لا مكتبات مثبتة"
},
"page_not_found": { "page_not_found": {
"go_back_to_home_page": "العودة إلى الصفحة الرئيسية", "go_back_to_home_page": "العودة إلى الصفحة الرئيسية",
"page_does_not_exist": "الصفحة التي تبحث عنها غير موجودة.", "page_does_not_exist": "الصفحة التي تبحث عنها غير موجودة.",
@ -754,6 +896,12 @@
"less": "أقرأ أقل", "less": "أقرأ أقل",
"more": "اقرأ أكثر" "more": "اقرأ أكثر"
}, },
"readlist_import": {
"row": {
"duplicate_book": "كتاب مكرر",
"error_choose_book": "اختيار كتاب"
}
},
"readlists_expansion_panel": { "readlists_expansion_panel": {
"manage_readlist": "إدارة قائمة القراءة", "manage_readlist": "إدارة قائمة القراءة",
"title": "{name} قائمة القراءة" "title": "{name} قائمة القراءة"
@ -775,12 +923,21 @@
"button_empty_trash": "إفراغ سلة المهملات لكل المكتبات", "button_empty_trash": "إفراغ سلة المهملات لكل المكتبات",
"button_scan_libraries": "مسح جميع المكتبات", "button_scan_libraries": "مسح جميع المكتبات",
"button_shutdown": "إيقاف التشغيل", "button_shutdown": "إيقاف التشغيل",
"download_log": "تحميل ملف السجل",
"notification_tasks_cancelled": "لا يوجد مهام لإلغائها | تمّ إلغاء 1 مهمة | تمّ إلغاء {count} مهمة", "notification_tasks_cancelled": "لا يوجد مهام لإلغائها | تمّ إلغاء 1 مهمة | تمّ إلغاء {count} مهمة",
"section_title": "إدارة الخادم" "section_title": "إدارة الخادم"
}, },
"tab_title": "خادم" "tab_title": "خادم",
"updates": "تحديثات"
}, },
"server_settings": { "server_settings": {
"dialog_regenerate_thumbnails": {
"btn_alternate": "نعم، جميع الكتب",
"btn_cancel": "لا",
"btn_confirm": "نعم، لكن فقط إن كانت اكبر"
},
"label_kobo_port": "باب مزامنة Kobo الخارجي",
"label_server_port": "باب الخادم",
"server_settings": "إعدادات الخادم" "server_settings": "إعدادات الخادم"
}, },
"settings_user": { "settings_user": {
@ -795,12 +952,15 @@
"sort": { "sort": {
"books_count": "عدد الكتب", "books_count": "عدد الكتب",
"date_added": "تاريخ الإضافة", "date_added": "تاريخ الإضافة",
"date_read": "تاريخ القراءة",
"date_updated": "تاريخ التحديث", "date_updated": "تاريخ التحديث",
"file_name": "اسم الملف", "file_name": "اسم الملف",
"file_size": "حجم الملف", "file_size": "حجم الملف",
"folder_name": "اسم المجلد", "folder_name": "اسم المجلد",
"name": "الاسم", "name": "الاسم",
"number": "رقم", "number": "رقم",
"page_count": "عدد الصفحات",
"random": "عشوائي",
"release_date": "تاريخ الإصدار" "release_date": "تاريخ الإصدار"
}, },
"theme": { "theme": {
@ -818,16 +978,36 @@
"tooltip_too_big": "الملف كبير جدًا!", "tooltip_too_big": "الملف كبير جدًا!",
"tooltip_user_uploaded": "تمّ الرفع بواسطة المستخدم" "tooltip_user_uploaded": "تمّ الرفع بواسطة المستخدم"
}, },
"titles_more": {
"less": "عناوين أقل",
"more": "عناوين أكثر"
},
"ui_settings": {
"general": "عام",
"section_oauth2": "OAuth2",
"series_groups": {
"alpha": "ابجدي",
"japanese": "غوجون (ياباني)"
}
},
"updates": {
"available": "توجد تحديثات"
},
"user_roles": { "user_roles": {
"ADMIN": "مدير", "ADMIN": "مدير",
"FILE_DOWNLOAD": "تحميل الملف", "FILE_DOWNLOAD": "تحميل الملف",
"PAGE_STREAMING": "صفحات البث", "KOBO_SYNC": "مزامنة Kobo",
"KOREADER_SYNC": "مزامنة KOReader",
"PAGE_STREAMING": "بث الصفحات",
"USER": "مستخدم" "USER": "مستخدم"
}, },
"users": { "users": {
"api_keys": "مفاتيح الAPI",
"users": "المستخدمين" "users": "المستخدمين"
}, },
"validation": { "validation": {
"one_or_more": "يجب أن يكون 1 أو أكثر",
"tcp_port": "يجب أن يكون بين 1 و 65535",
"zero_or_more": "يجب أن يكون 0 أو أكثر" "zero_or_more": "يجب أن يكون 0 أو أكثر"
}, },
"welcome": { "welcome": {

View file

@ -827,7 +827,12 @@
"ERR_1031": "V ComicRack CBL Book chybí série nebo číslo", "ERR_1031": "V ComicRack CBL Book chybí série nebo číslo",
"ERR_1032": "Soubor EPUB má chybný typ média", "ERR_1032": "Soubor EPUB má chybný typ média",
"ERR_1033": "Některé položky chybí", "ERR_1033": "Některé položky chybí",
"ERR_1034": "API key s touto poznámkou již existuje" "ERR_1034": "API key s touto poznámkou již existuje",
"ERR_1035": "Chyba při získávání obsahu EPUB",
"ERR_1036": "Chyba při získávání záložek EPUB",
"ERR_1037": "CHyba při získávání seznamu stran EPUB",
"ERR_1038": "Chyba při získávání stran EPUB Divina",
"ERR_1039": "Chyba při získávání pozic EPUB"
}, },
"filter": { "filter": {
"age_rating": "Věkové hodnocení", "age_rating": "Věkové hodnocení",

View file

@ -19,7 +19,15 @@
}, },
"account_settings": { "account_settings": {
"account_settings": "Configuración da conta", "account_settings": "Configuración da conta",
"change_password": "Cambiar o contrasinal" "api_key": {
"created_date": "Data de creación: {date}",
"force_kobo_sync": "Forzar a sincronización con Kobo",
"generate_api_key": "Xerar clave API",
"no_keys": "Aínda non se xerou clave API ningunha"
},
"change_password": "Cambiar o contrasinal",
"details": "Detalles",
"my_account": "A miña conta"
}, },
"announcements": { "announcements": {
"mark_all_read": "Marcar todo como lido", "mark_all_read": "Marcar todo como lido",
@ -27,6 +35,7 @@
"tab_title": "Avisos" "tab_title": "Avisos"
}, },
"authentication_activity": { "authentication_activity": {
"api_key": "Clave API",
"datetime": "Data e Hora", "datetime": "Data e Hora",
"email": "Correo electrónico", "email": "Correo electrónico",
"error": "Erro", "error": "Erro",
@ -58,9 +67,159 @@
"button_scan": "Escanear", "button_scan": "Escanear",
"button_select_series": "Escolle a serie", "button_select_series": "Escolle a serie",
"field_import_path": "Importar dende un cartafol", "field_import_path": "Importar dende un cartafol",
"info_part1": "Esta pantalla permitiralle importar arquivos que están fóra das súas bibliotecas existentes. Só pódese importar arquivos a series que xa existan, nese caso Komga moverá ou copiará os arquivos cara o directorio da serie escollida." "info_part1": "Esta pantalla permitiralle importar arquivos que están fóra das súas bibliotecas existentes. Só pódese importar arquivos a series que xa existan, nese caso Komga moverá ou copiará os arquivos cara o directorio da serie escollida.",
"info_part2": "Se escolles un número para un libro e existe xa algún outro libro con ese número, poderás comparalos. Se decides importar o libro, Komga actualizará o existente co novo, substituindo o ficheiro existente co novo.",
"no_files_found": "Non se atoparon ficheiros",
"notification": {
"go_to_book": "Ir ao libro",
"import_failure": "Fallou a importación do libro: {file}",
"import_successful": "Libro importado con éxito: {book}",
"source_file": "Ficheiro orixe: {file}"
},
"row": {
"error_analyze_first": "É necesario analizar antes o libro",
"error_choose_series": "Escoller serie",
"error_only_import_no_errors": "Só se poden importar libros sen erros",
"warning_upgrade": "Actualizarase o libro existente"
},
"table": {
"destination_name": "Nome de destino",
"file_name": "Nome do ficheiro",
"number": "Número",
"series": "Serie"
},
"title": "Importar",
"try_another_directory": "Tenta atopar outro directorio"
},
"bookreader": {
"beginning_of_book": "Atópaste ao principio do libro.",
"changing_reading_direction": "Trocar o sentido de lectura a",
"download_current_page": "Baixar a páxina actual",
"end_of_book": "Chegaches ao final do libro.",
"from_series_metadata": "dende os metadatos da serie",
"move_next": "Volve pulsar \"Seguinte\" para pasar ao seguinte libro.",
"move_next_exit": "Volve pulser \"Seguinte\" para saír do lector.",
"move_previous": "Volve pulsar \"Anterior\" para pasar ao libro anterior.",
"notification_poster_set_book": "A portada do libro estableceuse á páxina actual.",
"notification_poster_set_readlist": "A portada da lista de lectura estableceuse á páxina actual.",
"notification_poster_set_series": "A portada de serie estableceuse á páxina actual.",
"paged_reader_layout": {
"double": "Dobre páxina",
"double_no_cover": "Dobre páxina (sen portada)",
"single": "Páxina simple"
},
"reader_settings": "Configuración do lector",
"scale_type": {
"continuous_original": "Orixinal",
"continuous_width": "Axuste á anchura",
"height": "Axuste á altura",
"original": "Orixinal",
"screen": "Pantalla"
},
"settings": {
"always_fullscreen": "Sempre a pantalla completa",
"animate_page_transitions": "Transicións de páxina animadas",
"background_color": "Cor de fondo",
"background_colors": {
"black": "Negro",
"gray": "Gris",
"white": "Branco"
},
"general": "Xeral",
"gestures": "Xestos",
"page_layout": "Disposición de páxina",
"page_margin": "Marxe de páxina",
"reading_mode": "Modo de lectura",
"side_padding_none": "Ningún"
},
"shortcuts": {
"close": "Pechar",
"first_page": "Primeira páxina",
"last_page": "Derradeira páxina",
"left_to_right": "De esquerda a dereita",
"menus": "Menús",
"next_page": "Seguinte páxina",
"previous_page": "Páxina anterior",
"right_to_left": "De dereita a esquerda"
},
"tooltip_incognito": "Non se gardará o progreso de lectura"
},
"browse_book": {
"comment": "COMENTARIO",
"date_created": "CREACIÓN",
"date_modified": "ÚLTIMA MODIFICACIÓN",
"download_file": "Baixar ficheiro",
"file": "FICHEIRO",
"format": "FORMATO",
"isbn": "ISBN",
"links": "LIGAZÓNS",
"outdated_tooltip": "Cambiouse o ficheiro deste libro; é necesario volver analizalo",
"read_book": "Ler libro",
"read_incognito": "Ler de incógnito",
"remove_from_collection": "Quitar libro da colección",
"remove_from_readlist": "Quitar libro da lista de lectura",
"size": "TAMAÑO"
},
"browse_collection": {
"edit_collection": "Editar colección",
"edit_elements": "Editar elementos",
"manual_ordering": "Ordenación manual"
},
"browse_readlist": {
"edit_elements": "Editar elementos",
"edit_readlist": "Editar lista de lectura",
"manual_ordering": "Ordenación manual"
},
"browse_series": {
"earliest_year_from_release_dates": "Este é o ano máis temperán entre as datas de lanzamento de tódolos libros da serie",
"remove_from_collection": "Quitar serie da colección",
"series_no_summary": "Esta serie non ten sumario, así que escollemos un por ti!",
"summary_from_book": "Sumario do libro {number}:"
},
"collections_expansion_panel": {
"manage_collection": "Xestionar colección",
"title": "{name} colección"
}, },
"common": { "common": {
"locale_name": "Galego" "age": "Idade",
"all_libraries": "Tódalas bibliotecas",
"all_of": "Todo",
"any_of": "Calquera",
"book": "Libro",
"books": "Libros",
"books_n": "Ningún libro | 1 libro | {count} libros",
"books_total": "{count} / {total} libros",
"cancel": "Cancelar",
"choose_image": "Escoller unha imaxe",
"close": "Pechar",
"collections": "Coleccións",
"copied": "Copiado!",
"create": "Crear",
"delete": "Borrar",
"discard": "Descartar",
"disk_space": "Espazo en disco",
"download": "Baixar",
"duplicate": "Duplicar",
"epub": "EPUB",
"error": "Erro",
"filename": "Nome de ficheiro",
"genre": "Xénero",
"go_to_collection": "Ir á colección",
"go_to_library": "Ir á biblioteca",
"go_to_readlist": "Ir á lista de lectura",
"go_to_series": "Ir á serie",
"i_understand": "Entendido",
"library": "Biblioteca",
"locale_name": "Galego",
"more": "Máis",
"nothing_to_show": "Nada que amosar",
"page": "Páxina",
"page_number": "Número de páxina",
"pages": "páxinas",
"pages_left": "Non quedan páxinas | Queda 1 páxina | Quedan {count} páxinas",
"pages_n": "Ningunha páxina | 1 páxina | {count} páxinas",
"password": "Contrasinal",
"pdf": "PDF",
"pending_tasks": "Sen tarefas pendentes | 1 tarefa pendente | {count} tarefas pendentes"
} }
} }

View file

@ -46,7 +46,7 @@
}, },
"author_roles": { "author_roles": {
"colorist": "koloristi", "colorist": "koloristi",
"cover": "omot", "cover": "naslovnica",
"editor": "urednici", "editor": "urednici",
"inker": "bojitelj", "inker": "bojitelj",
"letterer": "tipografi", "letterer": "tipografi",
@ -109,7 +109,7 @@
"notification_poster_set_series": "Trenutačna stranica je sada postavljena kao poster serije.", "notification_poster_set_series": "Trenutačna stranica je sada postavljena kao poster serije.",
"paged_reader_layout": { "paged_reader_layout": {
"double": "Dvije stranice", "double": "Dvije stranice",
"double_no_cover": "Dvije stranice (bez omota)", "double_no_cover": "Dvije stranice (bez naslovnice)",
"single": "Jedna stranica" "single": "Jedna stranica"
}, },
"reader_settings": "Postavke čitača", "reader_settings": "Postavke čitača",
@ -138,7 +138,7 @@
"general": "Općenito", "general": "Općenito",
"gestures": "Geste", "gestures": "Geste",
"page_layout": "Raspored stranica", "page_layout": "Raspored stranica",
"page_margin": "Margine stranica", "page_margin": "Margina stranice",
"paged": "Postavke prikaza stranica", "paged": "Postavke prikaza stranica",
"reading_mode": "Modus čitanja", "reading_mode": "Modus čitanja",
"scale_type": "Vrsta skaliranja", "scale_type": "Vrsta skaliranja",
@ -484,7 +484,7 @@
"field_scanner_empty_trash_after_scan": "Automatski isprazni smeće nakon svakog pretraživanja", "field_scanner_empty_trash_after_scan": "Automatski isprazni smeće nakon svakog pretraživanja",
"field_scanner_force_directory_modified_time": "Pretraži mape na osnovi vremena promjene", "field_scanner_force_directory_modified_time": "Pretraži mape na osnovi vremena promjene",
"field_scanner_scan_startup": "Pretraži tijekom pokretanja", "field_scanner_scan_startup": "Pretraži tijekom pokretanja",
"field_series_cover": "Omot serije", "field_series_cover": "Naslovnica serije",
"file_browser_dialog_button_confirm": "Odaberi", "file_browser_dialog_button_confirm": "Odaberi",
"file_browser_dialog_title": "Osnovna mapa biblioteke", "file_browser_dialog_title": "Osnovna mapa biblioteke",
"label_analysis": "Analiza", "label_analysis": "Analiza",
@ -497,7 +497,7 @@
"label_scan_directory_exclusions": "Isključivanja mapa", "label_scan_directory_exclusions": "Isključivanja mapa",
"label_scan_types": "Traži ove vrste datoteka", "label_scan_types": "Traži ove vrste datoteka",
"label_scanner": "Tražilica", "label_scanner": "Tražilica",
"label_series_cover": "Omot serije", "label_series_cover": "Naslovnica serije",
"tab_general": "Općenito", "tab_general": "Općenito",
"tab_metadata": "Metapodaci", "tab_metadata": "Metapodaci",
"tab_options": "Opcije", "tab_options": "Opcije",
@ -780,7 +780,7 @@
"buttons": "Gumbovi", "buttons": "Gumbovi",
"click": "Pritisni / Dodirni" "click": "Pritisni / Dodirni"
}, },
"page_margins": "Margine stranica", "page_margins": "Margine stranice",
"viewing_theme": "Tema prikaza" "viewing_theme": "Tema prikaza"
}, },
"shortcuts": { "shortcuts": {
@ -827,7 +827,12 @@
"ERR_1031": "ComicRack CBL knjizi nedostaje serija ili broj", "ERR_1031": "ComicRack CBL knjizi nedostaje serija ili broj",
"ERR_1032": "EPUB datoteka sadrži neispravnu vrstu medija", "ERR_1032": "EPUB datoteka sadrži neispravnu vrstu medija",
"ERR_1033": "Neki unosi nedostaju", "ERR_1033": "Neki unosi nedostaju",
"ERR_1034": "API ključ s tim komentarom već postoji" "ERR_1034": "API ključ s tim komentarom već postoji",
"ERR_1035": "Greška prilikom dohvaćanja tablice sadržaja EPUB-a",
"ERR_1036": "Greška prilikom dohvaćanja straničnika EPUB-a",
"ERR_1037": "Greška prilikom dohvaćanja popisa stranica EPUB-a",
"ERR_1038": "Greška prilikom dohvaćanja divina stranica EPUB-a",
"ERR_1039": "Greška prilikom dohvaćanja pozicija EPUB-a"
}, },
"filter": { "filter": {
"age_rating": "dobna kategorija", "age_rating": "dobna kategorija",
@ -984,7 +989,7 @@
"btn_confirm": "Da, ali samo ako su veće", "btn_confirm": "Da, ali samo ako su veće",
"title": "Nanovo generiraj minijature" "title": "Nanovo generiraj minijature"
}, },
"hint_kobo_port": "Postavi samo u slučaju problema sa sinkronizacijom omota i preuzimanja", "hint_kobo_port": "Postavi samo u slučaju problema sa sinkronizacijom naslovnica i preuzimanja",
"label_delete_empty_collections": "Izbriši prazne zbirke nakon pretraživanja", "label_delete_empty_collections": "Izbriši prazne zbirke nakon pretraživanja",
"label_delete_empty_readlists": "Izbriši prazne liste čitanja nakon pretraživanja", "label_delete_empty_readlists": "Izbriši prazne liste čitanja nakon pretraživanja",
"label_kepubify_path": "Staza do kepubify", "label_kepubify_path": "Staza do kepubify",

View file

@ -19,7 +19,15 @@
}, },
"account_settings": { "account_settings": {
"account_settings": "Configurações de conta", "account_settings": "Configurações de conta",
"change_password": "alterar senha" "api_key": {
"created_date": "Data de criação: {date}",
"force_kobo_sync": "Forçar sincronização com Kobo",
"generate_api_key": "Gerar chave de API",
"no_keys": "Nenhuma Chave de API foi criada"
},
"change_password": "alterar senha",
"details": "Detalhes",
"my_account": "Minha conta"
}, },
"announcements": { "announcements": {
"mark_all_read": "Marque todos como lido", "mark_all_read": "Marque todos como lido",
@ -27,6 +35,7 @@
"tab_title": "Anúncios" "tab_title": "Anúncios"
}, },
"authentication_activity": { "authentication_activity": {
"api_key": "Chave de API",
"datetime": "Data e Hora", "datetime": "Data e Hora",
"email": "E-mail", "email": "E-mail",
"error": "Erro", "error": "Erro",
@ -86,6 +95,7 @@
"beginning_of_book": "Você está no inicio do livro.", "beginning_of_book": "Você está no inicio do livro.",
"changing_reading_direction": "Mudar Direção de Leitura para", "changing_reading_direction": "Mudar Direção de Leitura para",
"cycling_page_layout": "Alterando Layout de Página", "cycling_page_layout": "Alterando Layout de Página",
"cycling_page_margin": "Alterando Margem de Página",
"cycling_scale": "Alterando Escala", "cycling_scale": "Alterando Escala",
"cycling_side_padding": "Alterando Preenchimento Lateral", "cycling_side_padding": "Alterando Preenchimento Lateral",
"download_current_page": "Baixar a pagina atual", "download_current_page": "Baixar a pagina atual",
@ -128,6 +138,7 @@
"general": "Geral", "general": "Geral",
"gestures": "Gestos", "gestures": "Gestos",
"page_layout": "Layout de página", "page_layout": "Layout de página",
"page_margin": "Margem de página",
"paged": "Opções do Reader paginado", "paged": "Opções do Reader paginado",
"reading_mode": "Modo de leitura", "reading_mode": "Modo de leitura",
"scale_type": "Tipo de escala", "scale_type": "Tipo de escala",
@ -138,6 +149,7 @@
"shortcuts": { "shortcuts": {
"close": "Fechar", "close": "Fechar",
"cycle_page_layout": "Alterar layout de página", "cycle_page_layout": "Alterar layout de página",
"cycle_page_margin": "Alterar margem de página",
"cycle_scale": "Alterar escala", "cycle_scale": "Alterar escala",
"cycle_side_padding": "Alterar preenchimento lateral", "cycle_side_padding": "Alterar preenchimento lateral",
"first_page": "Primeira página", "first_page": "Primeira página",
@ -161,6 +173,8 @@
}, },
"browse_book": { "browse_book": {
"comment": "COMENTÁRIO", "comment": "COMENTÁRIO",
"date_created": "CRIADO",
"date_modified": "MODIFICADO POR ÚLTIMO",
"download_file": "Baixar arquivo", "download_file": "Baixar arquivo",
"file": "ARQUIVO", "file": "ARQUIVO",
"format": "FORMATO", "format": "FORMATO",
@ -170,6 +184,8 @@
"outdated_tooltip": "O arquivo para este livro foi alterado, este livro deve ser reanalisado", "outdated_tooltip": "O arquivo para este livro foi alterado, este livro deve ser reanalisado",
"read_book": "Ler livro", "read_book": "Ler livro",
"read_incognito": "Ler incógnito", "read_incognito": "Ler incógnito",
"remove_from_collection": "Remover livro da coleção",
"remove_from_readlist": "Remover livro da lista de leitura",
"size": "TAMANHO" "size": "TAMANHO"
}, },
"browse_collection": { "browse_collection": {
@ -184,6 +200,7 @@
}, },
"browse_series": { "browse_series": {
"earliest_year_from_release_dates": "Este é o ano mais antigo dentre as datas de lançamento de todos os livros na série", "earliest_year_from_release_dates": "Este é o ano mais antigo dentre as datas de lançamento de todos os livros na série",
"remove_from_collection": "Remover série da coleção",
"series_no_summary": "Esta série não contém um resumo, então escolhemos um para você!", "series_no_summary": "Esta série não contém um resumo, então escolhemos um para você!",
"summary_from_book": "Resumo do livro {number}:" "summary_from_book": "Resumo do livro {number}:"
}, },
@ -194,6 +211,8 @@
"common": { "common": {
"age": "Idade", "age": "Idade",
"all_libraries": "Todas as bibliotecas", "all_libraries": "Todas as bibliotecas",
"all_of": "Todos",
"any_of": "Qualquer",
"book": "Livro", "book": "Livro",
"books": "Livros", "books": "Livros",
"books_n": "Nenhum livro | 1 livro | {count} livros", "books_n": "Nenhum livro | 1 livro | {count} livros",
@ -203,6 +222,7 @@
"choose_image": "Escolher uma imagem", "choose_image": "Escolher uma imagem",
"close": "Fechar", "close": "Fechar",
"collections": "Coleções", "collections": "Coleções",
"copied": "Copiado!",
"create": "Criar", "create": "Criar",
"delete": "Excluir", "delete": "Excluir",
"dimension": "l: {width}, a:{height}", "dimension": "l: {width}, a:{height}",
@ -211,8 +231,10 @@
"dismiss": "Ignorar", "dismiss": "Ignorar",
"download": "Baixar", "download": "Baixar",
"drag_drop": "Arrastar e soltar", "drag_drop": "Arrastar e soltar",
"duplicate": "Duplicado",
"email": "Email", "email": "Email",
"epub": "Epub", "epub": "Epub",
"error": "Erro",
"filename": "Nome do arquivo", "filename": "Nome do arquivo",
"filter_no_matches": "O filtro ativo não contém correspondências", "filter_no_matches": "O filtro ativo não contém correspondências",
"genre": "Gênero", "genre": "Gênero",
@ -220,12 +242,17 @@
"go_to_library": "Ir para biblioteca", "go_to_library": "Ir para biblioteca",
"go_to_readlist": "Ir para lista de lidos", "go_to_readlist": "Ir para lista de lidos",
"go_to_series": "Ir para séries", "go_to_series": "Ir para séries",
"i_understand": "Eu compreendo",
"library": "Biblioteca", "library": "Biblioteca",
"locale_name": "Português (Brasil)", "locale_name": "Português (Brasil)",
"locale_rtl": "false", "locale_rtl": "false",
"lock_all": "Bloquear todos", "lock_all": "Bloquear todos",
"media": "Mídia",
"more": "Mais",
"n_selected": "{count} selecionados", "n_selected": "{count} selecionados",
"nothing_to_show": "Nada para exibir", "nothing_to_show": "Nada para exibir",
"ok": "OK",
"oneshot": "One-shot",
"outdated": "Desatualizado", "outdated": "Desatualizado",
"page": "Página", "page": "Página",
"page_number": "Número da página", "page_number": "Número da página",
@ -235,19 +262,23 @@
"password": "Senha", "password": "Senha",
"pdf": "PDF", "pdf": "PDF",
"pending_tasks": "Nenhuma tarefa pendente | 1 tarefa pendente | {count} tarefas pendentes", "pending_tasks": "Nenhuma tarefa pendente | 1 tarefa pendente | {count} tarefas pendentes",
"pinned_libraries": "Bibliotecas Fixadas",
"publisher": "Editora", "publisher": "Editora",
"read": "Ler", "read": "Ler",
"read_on": "Lido em {date}", "read_on": "Lido em {date}",
"readlist": "Lista de Leitura", "readlist": "Lista de Leitura",
"readlists": "Listas de Leitura", "readlists": "Listas de Leitura",
"remember-me": "Me lembre", "remember-me": "Me lembre",
"reorder": "Reordernar",
"required": "Obrigatório", "required": "Obrigatório",
"reset_filters": "Redefinir filtros", "reset_filters": "Redefinir filtros",
"roles": "Funções", "roles": "Funções",
"save_changes": "Salvar mudanças", "save_changes": "Salvar mudanças",
"series": "Séries | Séries", "series": "Séries | Séries",
"settings": "Configurações",
"sidecars": "Carrinho", "sidecars": "Carrinho",
"tags": "Tags", "tags": "Tags",
"ui": "Interface de Usuário",
"unavailable": "Indisponível", "unavailable": "Indisponível",
"unlock_all": "Desbloquear tudo", "unlock_all": "Desbloquear tudo",
"url": "URL", "url": "URL",
@ -271,6 +302,7 @@
"comicrack_preambule_html": "Você pode importar Listas de Leitura do ComicRack existentes no formato <code>.cbl</code><br>Komga tentará combinar a série fornecida e o número do livro com as séries e livros em suas bibliotecas.", "comicrack_preambule_html": "Você pode importar Listas de Leitura do ComicRack existentes no formato <code>.cbl</code><br>Komga tentará combinar a série fornecida e o número do livro com as séries e livros em suas bibliotecas.",
"dialog_confirmation": { "dialog_confirmation": {
"body": "{unmatched} / {total} livro(s) não foram marcados", "body": "{unmatched} / {total} livro(s) não foram marcados",
"body2": "{duplicates} / {total} livros são duplicados",
"create": "Criar de todo modo", "create": "Criar de todo modo",
"title": "Alguns livros não estão combinados" "title": "Alguns livros não estão combinados"
}, },
@ -286,6 +318,14 @@
"tab_title": "Importação de Dados" "tab_title": "Importação de Dados"
}, },
"dialog": { "dialog": {
"add_api_key": {
"button_confirm": "Gerar",
"context": "Chaves API podem ser usadas para autenticar no protocolo Kobo Sync.",
"dialog_title": "Gerar chave de API",
"field_comment": "Comentar",
"field_comment_hint": "Como a chave API será usada?",
"info_copy": "Tenha a certeza de que a chave API foi salva. Você não poderá vê-la novamente!"
},
"add_to_collection": { "add_to_collection": {
"button_create": "Criar", "button_create": "Criar",
"card_collection_subtitle": "Nenhuma série | 1 série | {count} série", "card_collection_subtitle": "Nenhuma série | 1 série | {count} série",
@ -319,6 +359,12 @@
"filter": "Filtrar por número, título ou data de lançamento", "filter": "Filtrar por número, título ou data de lançamento",
"title": "Selecionar livro" "title": "Selecionar livro"
}, },
"delete_apikey": {
"button_confirm": "Excluir",
"confirm_delete": "Eu entendo, exclua a chave API \"{name}\"",
"dialog_title": "Deletar chave API",
"warning_html": "Quaisquer aplicativos ou scripts que utilizem esta chave API não poderão mais acessar a API Komga. Você não poderá desfazer esta ação."
},
"delete_book": { "delete_book": {
"button_confirm": "Excluir", "button_confirm": "Excluir",
"confirm_delete": "Sim, exclua o livro \"{name}\" e seus arquivos", "confirm_delete": "Sim, exclua o livro \"{name}\" e seus arquivos",
@ -468,6 +514,9 @@
"tab_general": "Geral", "tab_general": "Geral",
"tab_poster": "Pôster" "tab_poster": "Pôster"
}, },
"edit_recommended": {
"button_confirm": "Salvar mudanças"
},
"edit_series": { "edit_series": {
"button_cancel": "Cancelar", "button_cancel": "Cancelar",
"button_confirm": "Salvar mudanças", "button_confirm": "Salvar mudanças",
@ -536,6 +585,9 @@
}, },
"title": "Nome do Arquivo de Destino" "title": "Nome do Arquivo de Destino"
}, },
"force_kobo_sync": {
"dialog_title": "Forçar sincronização com Kobo"
},
"password_change": { "password_change": {
"button_cancel": "Cancelar", "button_cancel": "Cancelar",
"button_confirm": "Mudar senha", "button_confirm": "Mudar senha",
@ -603,6 +655,9 @@
"HARDLINK": "Hardlink/Copiar arquivos", "HARDLINK": "Hardlink/Copiar arquivos",
"MOVE": "Mover Arquivos" "MOVE": "Mover Arquivos"
}, },
"media_profile": {
"PDF": "PDF"
},
"media_status": { "media_status": {
"ERROR": "Erro", "ERROR": "Erro",
"OUTDATED": "Desatualizado", "OUTDATED": "Desatualizado",
@ -629,6 +684,14 @@
"ONGOING": "Em andamento" "ONGOING": "Em andamento"
} }
}, },
"epubreader": {
"settings": {
"page_margins": "Margem de Páginas"
},
"shortcuts": {
"settings": "Configurações"
}
},
"error_codes": { "error_codes": {
"ERR_1000": "O arquivo não pôde ser acessado durante a análise", "ERR_1000": "O arquivo não pôde ser acessado durante a análise",
"ERR_1001": "O tipo de mídia não é compatível", "ERR_1001": "O tipo de mídia não é compatível",
@ -660,6 +723,7 @@
"in_progress": "Em progresso", "in_progress": "Em progresso",
"language": "idioma", "language": "idioma",
"library": "biblioteca", "library": "biblioteca",
"oneshot": "One-shot",
"publisher": "editora", "publisher": "editora",
"read": "Lidos", "read": "Lidos",
"release_date": "data de lançamento", "release_date": "data de lançamento",
@ -673,7 +737,8 @@
}, },
"history": { "history": {
"header": { "header": {
"book": "Livro" "book": "Livro",
"details": "Detalhes"
} }
}, },
"home": { "home": {
@ -795,6 +860,9 @@
"tooltip_too_big": "Arquivo muito grande!", "tooltip_too_big": "Arquivo muito grande!",
"tooltip_user_uploaded": "Enviado pelo usuário" "tooltip_user_uploaded": "Enviado pelo usuário"
}, },
"ui_settings": {
"general": "Geral"
},
"user_roles": { "user_roles": {
"ADMIN": "Administrador", "ADMIN": "Administrador",
"FILE_DOWNLOAD": "Baixar arquivos", "FILE_DOWNLOAD": "Baixar arquivos",
@ -802,6 +870,7 @@
"USER": "Usuário" "USER": "Usuário"
}, },
"users": { "users": {
"api_keys": "Chaves API",
"authentication_activity": "Atividade de Autenticação", "authentication_activity": "Atividade de Autenticação",
"users": "Usuários" "users": "Usuários"
}, },

View file

@ -4,8 +4,8 @@
"pageText": "{0}-{1} из {2}" "pageText": "{0}-{1} из {2}"
}, },
"dataIterator": { "dataIterator": {
"loadingText": "Загрузка объектов...", "loadingText": "Загрузка элементов...",
"noResultsText": "Подходящих записей не найдено" "noResultsText": "Совпадений не найдено"
}, },
"dataTable": { "dataTable": {
"itemsPerPageText": "Строк на странице:", "itemsPerPageText": "Строк на странице:",
@ -15,30 +15,33 @@
"counter": "Файлов: {0}", "counter": "Файлов: {0}",
"counterSize": "Файлов: {0} (всего {1})" "counterSize": "Файлов: {0} (всего {1})"
}, },
"noDataText": "Отсутствуют данные" "noDataText": "Данные отсутствуют"
}, },
"account_settings": { "account_settings": {
"account_settings": "Настройки Аккаунта", "account_settings": "Настройки аккаунта",
"api_key": { "api_key": {
"created_date": "Дата создания: {date}", "created_date": "Дата создания: {date}",
"generate_api_key": "Сгенерировать API ключ", "force_kobo_sync": "Принудительная синхронизация с Kobo",
"generate_api_key": "Сгенерировать ключ API",
"no_keys": "Ни одного ключа API еще не создано" "no_keys": "Ни одного ключа API еще не создано"
}, },
"change_password": "изменить пароль" "change_password": "изменить пароль",
"details": "Подробности",
"my_account": "Мой аккаунт"
}, },
"announcements": { "announcements": {
"mark_all_read": "Пометить всё как прочитанное", "mark_all_read": "Отметить всё как прочитанное",
"mark_read": "Пометить как прочитанное", "mark_read": "Отметить как прочитанное",
"tab_title": "Объявления" "tab_title": "Объявления"
}, },
"authentication_activity": { "authentication_activity": {
"api_key": "API Ключ", "api_key": "APIлюч",
"datetime": "Дата / Время", "datetime": "Дата/Время",
"email": "Эл. почта", "email": "Эл. почта",
"error": "Ошибка", "error": "Ошибка",
"ip": "IP-адрес", "ip": "IP-адрес",
"source": "Источник", "source": "Источник",
"success": "Статус", "success": "Успех",
"user_agent": "User Agent" "user_agent": "User Agent"
}, },
"author_roles": { "author_roles": {
@ -54,18 +57,18 @@
"book_card": { "book_card": {
"error": "Ошибка", "error": "Ошибка",
"no_release_date": "Дата релиза отсутствует", "no_release_date": "Дата релиза отсутствует",
"unknown": "Необходимо проанализировать", "unknown": "Подлежит анализу",
"unread": "Не прочитано", "unread": "Не прочитано",
"unsupported": "Неподдерживаемый" "unsupported": "Не поддерживается"
}, },
"book_import": { "book_import": {
"button_browse": "Обзор", "button_browse": "Обзор",
"button_import": "Импортировать", "button_import": "Импорт",
"button_scan": "Сканировать", "button_scan": "Сканировать",
"button_select_series": "Выберите Серию", "button_select_series": "Выберите серию",
"field_import_path": "Импортировать из каталога", "field_import_path": "Импортировать из каталога",
"info_part1": "Этот раздел позволяет вам импортировать файлы, которые находятся за пределами ваших существующих библиотек. Вы можете импортировать файлы только в существующие Серии, в этом случае Komga переместит или скопирует файлы в каталог выбранной Серии.", "info_part1": "Этот раздел позволяет вам импортировать файлы, которые находятся за пределами ваших существующих библиотек. Вы можете импортировать файлы только в существующие серии; в этом случае Komga переместит или скопирует файлы в каталог выбранной серии.",
"info_part2": "Если вы выберете номер для книги и книга с таким номером уже существует, то вы сможете сравнить 2 книги. Если вы решите импортировать книгу, Komga обновит существующую книгу, эффективно заменив старый файл новым.", "info_part2": "Если вы укажете номер для книги, и книга с таким номером уже существует, вы сможете сравнить оба варианта. Если вы решите импортировать книгу, Komga обновит существующий вариант, заменив старый файл новым.",
"no_files_found": "Файлы не найдены", "no_files_found": "Файлы не найдены",
"notification": { "notification": {
"go_to_book": "Перейти к книге", "go_to_book": "Перейти к книге",
@ -74,9 +77,9 @@
"source_file": "Исходный файл: {file}" "source_file": "Исходный файл: {file}"
}, },
"row": { "row": {
"error_analyze_first": "Книгу нужно сначала проанализировать", "error_analyze_first": "Книга нуждается в предварительном анализе",
"error_choose_series": "Выберите серию", "error_choose_series": "Выберите серию",
"error_only_import_no_errors": "Можно импортировать только книги без ошибок", "error_only_import_no_errors": "Можно импортировать только книги без наличия ошибок",
"warning_upgrade": "Существующая книга будет обновлена" "warning_upgrade": "Существующая книга будет обновлена"
}, },
"table": { "table": {
@ -90,10 +93,11 @@
}, },
"bookreader": { "bookreader": {
"beginning_of_book": "Вы находитесь в начале книги.", "beginning_of_book": "Вы находитесь в начале книги.",
"changing_reading_direction": "Изменение Направления Чтения на", "changing_reading_direction": "Изменение направления чтения на",
"cycling_page_layout": "Переключить Формат Страниц", "cycling_page_layout": "Переключение макета страницы",
"cycling_scale": "Переключить Масштабирование", "cycling_page_margin": "Переключение полей страницы",
"cycling_side_padding": "Переключить Боковой Отступ", "cycling_scale": "Масштабирование",
"cycling_side_padding": "Переключение боковых отступов",
"download_current_page": "Скачать текущую страницу", "download_current_page": "Скачать текущую страницу",
"end_of_book": "Вы достигли конца книги.", "end_of_book": "Вы достигли конца книги.",
"from_series_metadata": "из метаданных серии", "from_series_metadata": "из метаданных серии",
@ -104,8 +108,8 @@
"notification_poster_set_readlist": "Текущая страница теперь используется в качестве постера списка чтения.", "notification_poster_set_readlist": "Текущая страница теперь используется в качестве постера списка чтения.",
"notification_poster_set_series": "Текущая страница теперь используется в качестве постера серии.", "notification_poster_set_series": "Текущая страница теперь используется в качестве постера серии.",
"paged_reader_layout": { "paged_reader_layout": {
"double": "Двойные страницы", "double": "Две страницы",
"double_no_cover": "Двойные страницы (без обложки)", "double_no_cover": "Две страницы (без обложки)",
"single": "Одна страница" "single": "Одна страница"
}, },
"reader_settings": "Настройки Ридера", "reader_settings": "Настройки Ридера",
@ -114,16 +118,16 @@
"continuous_width": "По ширине", "continuous_width": "По ширине",
"height": "По высоте", "height": "По высоте",
"original": "Исходное", "original": "Исходное",
"screen": "По экрану", "screen": "По размеру экрана",
"width": "По ширине", "width": "По ширине",
"width_shrink_only": "По ширине (с отступами)" "width_shrink_only": "По ширине (с отступами)"
}, },
"set_current_page_as_book_poster": "Установить страницу в качестве постера для книги", "set_current_page_as_book_poster": "Установить страницу в качестве обложки для книги",
"set_current_page_as_readlist_poster": "Установить страницу в качестве постера для списка чтения", "set_current_page_as_readlist_poster": "Установить страницу в качестве обложки для списка чтения",
"set_current_page_as_series_poster": "Установить страницу в качестве постера для серии", "set_current_page_as_series_poster": "Установить страницу в качестве обложки для серии",
"settings": { "settings": {
"always_fullscreen": "Всегда в полный экран", "always_fullscreen": "Всегда полноэкранный режим",
"animate_page_transitions": "Анимировать переходы страниц", "animate_page_transitions": "Анимировать переходы между страницами",
"background_color": "Цвет фона", "background_color": "Цвет фона",
"background_colors": { "background_colors": {
"black": "Чёрный", "black": "Чёрный",
@ -134,54 +138,60 @@
"general": "Общее", "general": "Общее",
"gestures": "Жесты", "gestures": "Жесты",
"page_layout": "Формат страницы", "page_layout": "Формат страницы",
"paged": "Настройки Отображения Страниц", "page_margin": "Поля страницы",
"paged": "Настройки отображения страниц",
"reading_mode": "Режим чтения", "reading_mode": "Режим чтения",
"scale_type": "Масштабирование", "scale_type": "Масштабирование",
"side_padding": "Боковой отступ", "side_padding": "Боковые отступы",
"side_padding_none": "Нет", "side_padding_none": "Нет",
"webtoon": "Параметры Режима Webtoon" "webtoon": "Настройки режима цифрового комикса"
}, },
"shortcuts": { "shortcuts": {
"close": "Закрыть", "close": "Закрыть",
"cycle_page_layout": "Переключить формат страниц", "cycle_page_layout": "Переключить макет страницы",
"cycle_page_margin": "Переключить поля страницы",
"cycle_scale": "Переключить масштаб", "cycle_scale": "Переключить масштаб",
"cycle_side_padding": "Переключить боковой отступ", "cycle_side_padding": "Переключить боковые отступы",
"first_page": "Первая страница", "first_page": "Первая страница",
"fullscreen": "Войти/выйти из полноэкранного режима", "fullscreen": "Войти/выйти из полноэкранного режима",
"last_page": "Последняя страница", "last_page": "Последняя страница",
"left_to_right": "Слева Направо", "left_to_right": "Слева направо",
"menus": "Меню", "menus": "Меню",
"next_page": "Следующая страница", "next_page": "Следующая страница",
"previous_page": "Предыдущая страница", "previous_page": "Предыдущая страница",
"reader_navigation": "Навигация", "reader_navigation": "Навигация",
"right_to_left": "Справа Налево", "right_to_left": "Справа налево",
"settings": "Настройки", "settings": "Настройки",
"show_hide_help": "Показать/скрыть помощь", "show_hide_help": "Показать/скрыть помощь",
"show_hide_settings": "Показать/скрыть меню настроек", "show_hide_settings": "Показать/скрыть меню настроек",
"show_hide_thumbnails": "Показать/скрыть просмотр эскизов", "show_hide_thumbnails": "Показать/скрыть обозреватель миниатюр",
"show_hide_toolbars": "Показать/скрыть панели инструментов", "show_hide_toolbars": "Показать/скрыть панели инструментов",
"vertical": "Вертикально", "vertical": "Вертикально",
"webtoon": "Webtoon" "webtoon": "Цифровой комикс"
}, },
"tooltip_incognito": "Прогресс чтения не будет сохранен" "tooltip_incognito": "Прогресс чтения не будет сохранен"
}, },
"browse_book": { "browse_book": {
"comment": "КОММЕНТАРИЙ", "comment": "КОММЕНТАРИЙ",
"date_created": "СОЗДАНО",
"date_modified": "ПОСЛЕДНЕЕ ИЗМЕНЕНИЕ",
"download_file": "Скачать файл", "download_file": "Скачать файл",
"file": "ФАЙЛ", "file": "ФАЙЛ",
"format": "ФОРМАТ", "format": "ФОРМАТ",
"isbn": "ISBN", "isbn": "ISBN",
"links": "ССЫЛКИ", "links": "ССЫЛКИ",
"navigation_within_readlist": "Навигация в пределах списка чтения: {name}", "navigation_within_readlist": "Навигация внутри списка чтения: {name}",
"outdated_tooltip": "Файл этой книги изменен, книгу необходимо повторно проанализировать", "outdated_tooltip": "Файл этой книги изменился, книгу необходимо повторно проанализировать",
"read_book": "Читать книгу", "read_book": "Читать книгу",
"read_incognito": "Читать инкогнито", "read_incognito": "Читать инкогнито",
"remove_from_collection": "Удалить книгу из коллекции",
"remove_from_readlist": "Удалить книгу из списка чтения",
"size": "РАЗМЕР" "size": "РАЗМЕР"
}, },
"browse_collection": { "browse_collection": {
"edit_collection": "Редактировать коллекцию", "edit_collection": "Редактировать коллекцию",
"edit_elements": "Редактировать элементы", "edit_elements": "Редактировать элементы",
"manual_ordering": "ручной порядок" "manual_ordering": "ручная сортировка"
}, },
"browse_readlist": { "browse_readlist": {
"edit_elements": "Редактировать элементы", "edit_elements": "Редактировать элементы",
@ -189,17 +199,19 @@
"manual_ordering": "ручная сортировка" "manual_ordering": "ручная сортировка"
}, },
"browse_series": { "browse_series": {
"earliest_year_from_release_dates": "Это самая ранняя дата выпуска из всех книг в этой серии", "earliest_year_from_release_dates": "Это самый ранний год из дат выхода всех книг серии",
"series_no_summary": "У серии нет описания, поэтому мы подобрали его для вас!", "remove_from_collection": "Удалить серию из коллекции",
"series_no_summary": "У этой серии нет описания, поэтому мы подобрали его для вас!",
"summary_from_book": "Краткое описание из книги {number}:" "summary_from_book": "Краткое описание из книги {number}:"
}, },
"collections_expansion_panel": { "collections_expansion_panel": {
"manage_collection": "Управлять коллекцией", "manage_collection": "Управление коллекцией",
"title": "Коллекция {name}" "title": "Коллекция {name}"
}, },
"common": { "common": {
"age": "Возраст", "age": "Возраст",
"all_libraries": "Все Библиотеки", "all_libraries": "Все библиотеки",
"any_of": "Любой из",
"book": "Книга", "book": "Книга",
"books": "Книги", "books": "Книги",
"books_n": "Книг нет | 1 книга | {count} книг", "books_n": "Книг нет | 1 книга | {count} книг",
@ -212,14 +224,16 @@
"copied": "Скопировано!", "copied": "Скопировано!",
"create": "Создать", "create": "Создать",
"delete": "Удалить", "delete": "Удалить",
"dimension": "шир.: {width} выс.: {height}", "dimension": "шир.: {width}, выс.: {height}",
"discard": "Отмена", "discard": "Отмена",
"disk_space": "Дисковое пространство", "disk_space": "Дисковое пространство",
"dismiss": "Отклонить", "dismiss": "Отклонить",
"download": "Скачать", "download": "Скачать",
"drag_drop": "перетащить", "drag_drop": "перетащить",
"duplicate": "Дублировать",
"email": "Эл. почта", "email": "Эл. почта",
"epub": "Epub", "epub": "Epub",
"error": "Ошибка",
"filename": "Имя файла", "filename": "Имя файла",
"filter_no_matches": "Нет совпадений по заданному фильтру", "filter_no_matches": "Нет совпадений по заданному фильтру",
"genre": "Жанр", "genre": "Жанр",
@ -227,12 +241,16 @@
"go_to_library": "Вернуться к библиотеке", "go_to_library": "Вернуться к библиотеке",
"go_to_readlist": "Перейти к списку чтения", "go_to_readlist": "Перейти к списку чтения",
"go_to_series": "Перейти к серии", "go_to_series": "Перейти к серии",
"i_understand": "Я понимаю",
"library": "Библиотека", "library": "Библиотека",
"locale_name": "Русский", "locale_name": "Русский",
"locale_rtl": "false", "locale_rtl": "false",
"lock_all": "Заблокировать все", "lock_all": "Заблокировать все",
"media": "Медиа",
"more": "Ещё",
"n_selected": "{count} выбрано", "n_selected": "{count} выбрано",
"nothing_to_show": "Нет данных для отображения", "nothing_to_show": "Нет данных для отображения",
"ok": "OK",
"outdated": "Устарело", "outdated": "Устарело",
"page": "Страница", "page": "Страница",
"page_number": "Номер страницы", "page_number": "Номер страницы",
@ -242,17 +260,20 @@
"password": "Пароль", "password": "Пароль",
"pdf": "PDF", "pdf": "PDF",
"pending_tasks": "Нет незавершенных задач | 1 незавершенная задача | {count} незавершенных задач", "pending_tasks": "Нет незавершенных задач | 1 незавершенная задача | {count} незавершенных задач",
"pinned_libraries": "Закреплённые библиотеки",
"publisher": "Издатель", "publisher": "Издатель",
"read": "Читать", "read": "Читать",
"read_on": "Читать {date}", "read_on": "Прочитано {date}",
"readlist": "Список чтения", "readlist": "Список чтения",
"readlists": "Списки чтения", "readlists": "Списки чтения",
"remember-me": "Запомнить", "remember-me": "Запомнить меня",
"required": "Необходимо", "reorder": "Изменить порядок",
"required": "Обязательно",
"reset_filters": "Сбросить фильтры", "reset_filters": "Сбросить фильтры",
"roles": "Роли", "roles": "Роли",
"save_changes": "Сохранить изменения", "save_changes": "Сохранить изменения",
"series": "Серии", "series": "Серии",
"settings": "Настройки",
"tags": "Теги", "tags": "Теги",
"unavailable": "Недоступно", "unavailable": "Недоступно",
"unlock_all": "Разблокировать все", "unlock_all": "Разблокировать все",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -97,7 +97,7 @@
"cycling_page_layout": "Шаблон сторінки", "cycling_page_layout": "Шаблон сторінки",
"cycling_page_margin": "Відступи", "cycling_page_margin": "Відступи",
"cycling_scale": "Масштабування", "cycling_scale": "Масштабування",
"cycling_side_padding": "Cycling Side Padding", "cycling_side_padding": "Бічні накладки для велосипедистів",
"download_current_page": "Завантажити поточку сторінку", "download_current_page": "Завантажити поточку сторінку",
"end_of_book": "Ви дійшли до кінця книги.", "end_of_book": "Ви дійшли до кінця книги.",
"from_series_metadata": "з метаданих серії", "from_series_metadata": "з метаданих серії",
@ -139,6 +139,7 @@
"gestures": "Жести", "gestures": "Жести",
"page_layout": "Шаблон сторінки", "page_layout": "Шаблон сторінки",
"page_margin": "Відступ сторінки", "page_margin": "Відступ сторінки",
"paged": "Параметри зчитування з розбивкою на сторінки",
"reading_mode": "Режим читання", "reading_mode": "Режим читання",
"scale_type": "Тип масштабування", "scale_type": "Тип масштабування",
"side_padding": "Бокові відступи", "side_padding": "Бокові відступи",
@ -147,6 +148,10 @@
}, },
"shortcuts": { "shortcuts": {
"close": "Закрити", "close": "Закрити",
"cycle_page_layout": "Перемикання макета сторінки",
"cycle_page_margin": "Перемикання полів сторінки",
"cycle_scale": "Шкала циклу",
"cycle_side_padding": "Бічна підкладка для велосипеда",
"first_page": "Перша сторінка", "first_page": "Перша сторінка",
"fullscreen": "Увімкнути/вимкнути повноекранний режим", "fullscreen": "Увімкнути/вимкнути повноекранний режим",
"last_page": "Остання сторінка", "last_page": "Остання сторінка",
@ -174,9 +179,914 @@
"file": "ФАЙЛ", "file": "ФАЙЛ",
"format": "ФОРМАТ", "format": "ФОРМАТ",
"isbn": "ISBN", "isbn": "ISBN",
"links": "ПОСИЛАННЯ" "links": "ПОСИЛАННЯ",
"navigation_within_readlist": "Навігація у списку для читання: {name}",
"outdated_tooltip": "Файл цієї книги змінився, цю книгу потрібно проаналізувати повторно",
"read_book": "Читати книгу",
"read_incognito": "Читати інкогніто",
"remove_from_collection": "Вилучити книгу з колекції",
"remove_from_readlist": "Вилучити книгу зі списку прочитаних",
"size": "РОЗМІР"
},
"browse_collection": {
"edit_collection": "Редагувати колекцію",
"edit_elements": "Редагувати елементи",
"manual_ordering": "ручне замовлення"
},
"browse_readlist": {
"edit_elements": "Редагувати елементи",
"edit_readlist": "Редагувати список прочитаних",
"manual_ordering": "ручне замовлення"
},
"browse_series": {
"earliest_year_from_release_dates": "Це найдавніший рік з дат виходу всіх книг серії",
"remove_from_collection": "Вилучити серію з колекції",
"series_no_summary": "У цієї серії немає короткого опису, тому ми вибрали його для вас!",
"summary_from_book": "Короткий зміст з книги {number}:"
},
"collections_expansion_panel": {
"manage_collection": "Керування колекцією",
"title": "колекція {name}"
}, },
"common": { "common": {
"locale_name": "Українська" "age": "Вік",
"all_libraries": "Усі бібліотеки",
"all_of": "Усі з",
"any_of": "Будь-який з",
"book": "Книга",
"books": "Книги",
"books_n": "Немає книги | 1 книга | {count} книг",
"books_total": "{count} / {total} книг",
"cancel": "Скасувати",
"cbx": "Архів коміксів",
"choose_image": "Виберіть зображення",
"close": "Закрити",
"collections": "Колекції",
"copied": "Скопійовано!",
"create": "Створити",
"delete": "Видалити",
"dimension": "У: {width}, г: {height}",
"discard": "Відкинути",
"disk_space": "Місце на диску",
"dismiss": "Відхилити",
"download": "Завантажити",
"drag_drop": "перетягування",
"duplicate": "Дублікат",
"email": "Електронна пошта",
"epub": "Електронна книга",
"error": "Помилка",
"filename": "Ім'я файлу",
"filter_no_matches": "Активний фільтр не має збігів",
"genre": "Жанр",
"go_to_collection": "Перейти до колекції",
"go_to_library": "Іди до бібліотеки",
"go_to_readlist": "Перейти до списку прочитання",
"go_to_series": "Перейти до серії",
"i_understand": "Я розумію",
"library": "Бібліотека",
"locale_name": "Українська",
"locale_rtl": "false",
"lock_all": "Заблокувати все",
"media": "Медіа",
"more": "Більше",
"n_selected": "Вибрано {count}",
"nothing_to_show": "Нічого показати",
"ok": "Гаразд",
"oneshot": "Одноразовий",
"outdated": "Застарілий",
"page": "Сторінка",
"page_number": "Номер сторінки",
"pages": "сторінки",
"pages_left": "Не залишилося сторінок | Залишилася 1 сторінка | Залишилося {count} сторінок",
"pages_n": "Немає сторінок | 1 сторінка | {count} сторінок",
"password": "Пароль",
"pdf": "PDF",
"pending_tasks": "Немає завдань, що очікують на розгляд | 1 завдання, що очікує на розгляд | {count} завдань, що очікують на розгляд",
"pinned_libraries": "Закріплені бібліотеки",
"publisher": "Видавець",
"read": "Читати",
"read_on": "Читайте далі {date}",
"readlist": "Список прочитаних",
"readlists": "Списки для читання",
"remember-me": "Запам'ятай мене",
"reorder": "Знову замовити",
"required": "Обов'язково",
"reset_filters": "Скинути фільтри",
"roles": "Ролі",
"save_changes": "Зберегти зміни",
"series": "Серія | Серія",
"settings": "Налаштування",
"sidecars": "Коляски",
"tags": "Теги",
"ui": "Інтерфейс користувача",
"unavailable": "Недоступно",
"unlock_all": "Розблокувати все",
"url": "URL",
"use_filter_panel_to_change_filter": "Використовуйте панель фільтрів, щоб змінити активний фільтр",
"year": "рік"
},
"dashboard": {
"keep_reading": "Продовжуйте читати",
"on_deck": "На палубі",
"recently_added_books": "Нещодавно додані книги",
"recently_added_series": "Нещодавно додані серії",
"recently_read_books": "Нещодавно прочитані книги",
"recently_released_books": "Нещодавно випущені книги",
"recently_updated_series": "Нещодавно оновлені серії"
},
"data_import": {
"book_number": "Номер книги: {name}",
"book_series": "Серія: {name}",
"button_import": "Імпорт",
"button_match": "Матч",
"comicrack_preambule_html": "Ви можете імпортувати існуючі списки читання ComicRack у форматі <code>.cbl</code>.<br>Komga спробує зіставити надану серію та номер книги із серіями та книгами у ваших бібліотеках.",
"dialog_confirmation": {
"body": "{unmatched} / {total} книг не мають збігів",
"body2": "{duplicates} / {total} книг дублюються",
"create": "Створити все одно",
"title": "Деякі книги не збігаються"
},
"field_file_label": "Список для читання ComicRack (.cbl)",
"field_files_label": "Списки для читання ComicRack (.cbl)",
"import_read_lists": "Імпорт списків прочитаного",
"imported_as": "Імпортовано як {name}",
"readlist_created": "Список для читання створено: {name}",
"requested_number": "Запитаний номер",
"requested_series": "Запитана серія",
"results_preambule": "Результат імпорту показано нижче. Ви також можете перевірити незбігані книги для кожного наданого файлу.",
"size_limit": "Розмір має бути меншим за {size} МБ",
"tab_title": "Імпорт даних"
},
"dialog": {
"add_api_key": {
"button_confirm": "Згенерувати",
"context": "Ключі API можна використовувати для автентифікації через протокол Kobo Sync.",
"dialog_title": "Згенерувати новий ключ API",
"field_comment": "Коментар",
"field_comment_hint": "Для чого цей ключ API?",
"info_copy": "Обов’язково скопіюйте свій ключ API зараз. Ви більше його не побачите!"
},
"add_to_collection": {
"button_create": "Створити",
"card_collection_subtitle": "Без серій | 1 серія | {count} серій",
"dialog_title": "Додати до колекції",
"field_search_create": "Пошук або створення колекції",
"field_search_create_error": "Колекція з такою назвою вже існує",
"label_no_matching_collection": "Немає відповідної колекції"
},
"add_to_readlist": {
"button_create": "Створити",
"card_readlist_subtitle": "Немає книги | 1 книга | {count} книг",
"dialog_title": "Додати до списку прочитання",
"field_search_create": "Пошук або створення списку для читання",
"field_search_create_error": "Список для читання з такою назвою вже існує",
"label_no_matching_readlist": "Немає відповідного списку для читання"
},
"add_user": {
"button_cancel": "Скасувати",
"button_confirm": "Додати",
"dialog_title": "Додати користувача",
"field_email": "Електронна пошта",
"field_email_error": "Має бути дійсна адреса електронної пошти",
"field_password": "Пароль"
},
"analyze_library": {
"body": "Аналізує всі медіафайли в бібліотеці. Аналіз збирає інформацію про медіафайли. Залежно від розміру вашої бібліотеки, це може тривати довго.",
"button_confirm": "Аналіз",
"title": "Аналіз бібліотеки"
},
"book_picker": {
"filter": "Фільтрувати за номером книги, назвою або датою виходу",
"title": "Виберіть книгу"
},
"delete_apikey": {
"button_confirm": "Видалити",
"confirm_delete": "Я розумію, видаліть ключ API \"{name}\"",
"dialog_title": "Видалити ключ API",
"warning_html": "Будь-які програми чи скрипти, що використовують цей ключ API, більше не матимуть доступу до API Komga. Ви не можете скасувати цю дію."
},
"delete_book": {
"button_confirm": "Видалити",
"confirm_delete": "Так, видалити книгу «{name}» та її файли",
"confirm_delete_multiple": "Так, видалити {count} книг та їхні файли",
"dialog_title": "Видалити книгу",
"dialog_title_multiple": "Видалити книги",
"warning_html": "Книгу <b>{name}</b> буде видалено з цього сервера разом зі збереженими медіафайлами. Цю дію <b>неможливо</b> скасувати. Продовжити?",
"warning_multiple_html": "{count} Книги будуть вилучені з цього сервера разом зі збереженими медіафайлами. Цю дію <b>неможливо</b> скасувати. Продовжити?"
},
"delete_collection": {
"button_confirm": "Видалити",
"confirm_delete": "Так, видалити колекцію \"{name}\"",
"confirm_delete_multiple": "Так, видалити {count} колекцій",
"dialog_title": "Видалити колекцію",
"dialog_title_multiple": "Видалити колекції",
"warning_html": "Колекцію <b>{name}</b> буде видалено з цього сервера. Ваші медіафайли залишаться в силі. Цю дію <b>неможливо</b> скасувати. Продовжити?",
"warning_multiple_html": "{count} Колекції будуть видалені з цього сервера. Ваші медіафайли залишаться в силі. Цю дію <b>неможливо</b> скасувати. Продовжити?"
},
"delete_library": {
"button_confirm": "Видалити",
"confirm_delete": "Так, видалити бібліотеку \"{name}\"",
"title": "Видалити бібліотеку",
"warning_html": "Бібліотеку <b>{name}</b> буде видалено з цього сервера. Ваші медіафайли залишаться в силі. Цю дію <b>неможливо</b> скасувати. Продовжити?"
},
"delete_readlist": {
"button_confirm": "Видалити",
"confirm_delete": "Так, видалити список прочитаного \"{name}\"",
"confirm_delete_multiple": "Так, видалити {count} списків прочитання",
"dialog_title": "Видалити список прочитаних",
"dialog_title_multiple": "Видалити списки прочитаного",
"warning_html": "Список для читання <b>{name}</b> буде видалено з цього сервера. Ваші медіафайли залишаться в силі. Цю дію <b>неможливо</b> скасувати. Продовжити?",
"warning_multiple_html": "{count} Списки прочитання будуть видалені з цього сервера. Ваші медіафайли залишаться в силі. Цю дію <b>неможливо</b> скасувати. Продовжити?"
},
"delete_series": {
"button_confirm": "Видалити",
"confirm_delete": "Так, видалити серію \"{name}\" та її файли",
"confirm_delete_multiple": "Так, видалити {count} серій та їхні файли",
"dialog_title": "Видалити серію",
"warning_html": "Серіал <b>{name}</b> буде видалено з цього сервера разом зі збереженими медіафайлами. Цю дію <b>неможливо</b> скасувати. Продовжити?",
"warning_multiple_html": "Серії ({count}) будуть видалені з цього сервера разом зі збереженими медіафайлами. Цю дію <b>неможливо</b> скасувати. Продовжити?"
},
"delete_user": {
"button_confirm": "Видалити",
"confirm_delete": "Так, видалити користувача \"{name}\"",
"dialog_title": "Видалити користувача",
"warning_html": "Користувача <b>{name}</b> буде видалено з цього сервера. Цю дію <b>неможливо</b> скасувати. Продовжити?"
},
"edit_books": {
"add_author_role_error_duplicate": "Вже існує",
"authors_notice_multiple_edit": "Ви редагуєте авторів кількох книг. Це замінить існуючих авторів кожної книги.",
"button_cancel": "Скасувати",
"button_confirm": "Зберегти зміни",
"copy_from": "Копіювати з {field}",
"dialog_title_multiple": "Редагувати книгу {count} | Редагувати книги {count}",
"dialog_title_single": "Редагувати {book}",
"field_alternate_title": "Альтернативна назва",
"field_isbn": "ISBN",
"field_isbn_error": "Повинен бути дійсний ISBN 13",
"field_link_label": "Мітка",
"field_link_url": "URL",
"field_link_url_error_protocol": "Має бути http або https",
"field_link_url_error_url": "Має бути дійсна URL-адреса",
"field_number": "Номер",
"field_number_sort": "Номер сортування",
"field_number_sort_hint": "Ви можете використовувати десяткові числа",
"field_release_date": "Дата випуску",
"field_release_date_error": "Має бути дійсна дата у форматі РРРР-ММ-ДД",
"field_summary": "Короткий зміст",
"field_tags": "Теги",
"field_title": "Назва",
"number_sort_decrement": "Зменшити все на 1",
"number_sort_increment": "Збільшити все на 1",
"tab_authors": "Автори",
"tab_general": "Загальне",
"tab_links": "Посилання",
"tab_poster": "Плакат",
"tab_tags": "Теги",
"tags_notice_multiple_edit": "Ви редагуєте теги для кількох книг. Це замінить існуючі теги кожної книги."
},
"edit_collection": {
"button_cancel": "Скасувати",
"button_confirm": "Зберегти зміни",
"dialog_title": "Редагувати колекцію",
"field_manual_ordering": "Ручне замовлення",
"label_ordering": "За замовчуванням серії в колекції будуть упорядковані за назвою. Ви можете ввімкнути ручне впорядкування, щоб визначити свій власний порядок.",
"tab_general": "Загальне",
"tab_poster": "Плакат"
},
"edit_library": {
"button_browse": "Переглянути",
"button_cancel": "Скасувати",
"button_confirm_add": "Додати",
"button_confirm_edit": "Редагувати",
"button_next": "Далі",
"dialog_title_add": "Додати бібліотеку",
"dialot_title_edit": "Редагувати бібліотеку",
"field_analysis_analyze_dimensions": "Аналіз розмірів сторінок",
"field_analysis_hash_files": "Обчислити хеш для файлів",
"field_analysis_hash_koreader": "Обчислити хеш файлів для KOReader",
"field_analysis_hash_pages": "Обчислити хеш для сторінок",
"field_convert_to_cbz": "Автоматично конвертувати в CBZ",
"field_import_barcode_isbn": "Штрих-код ISBN",
"field_import_comicinfo_book": "Метадані книги",
"field_import_comicinfo_collections": "Колекції",
"field_import_comicinfo_readlists": "Списки читання",
"field_import_comicinfo_series": "Метадані серіалу",
"field_import_comicinfo_series_append_volume": "Додати том до назви серії",
"field_import_epub_book": "Метадані книги",
"field_import_epub_series": "Метадані серіалу",
"field_import_local_artwork": "Місцеві витвори мистецтва",
"field_import_mylar_series": "Метадані серіалу",
"field_name": "Ім'я",
"field_oneshotsdirectory": "Довідник One-Shots",
"field_repair_extensions": "Автоматичне виправлення неправильних розширень файлів",
"field_root_folder": "Коренева папка",
"field_scan_interval": "Інтервал сканування",
"field_scanner_empty_trash_after_scan": "Автоматично очищувати кошик після кожного сканування",
"field_scanner_force_directory_modified_time": "Примусово змінений час каталогу",
"field_scanner_scan_startup": "Сканування під час запуску",
"field_series_cover": "Обкладинка серії",
"file_browser_dialog_button_confirm": "Виберіть",
"file_browser_dialog_title": "Коренева папка бібліотеки",
"label_analysis": "Аналізи",
"label_file_management": "Керування файлами",
"label_import_barcode_isbn": "Імпорт ISBN у штрих-код",
"label_import_comicinfo": "Імпорт метаданих для CBR/CBZ, що містять файл ComicInfo.xml",
"label_import_epub": "Імпорт метаданих з файлів EPUB",
"label_import_local": "Імпорт локальних медіаресурсів",
"label_import_mylar": "Імпорт метаданих, згенерованих Mylar",
"label_scan_directory_exclusions": "Виключення з каталогу",
"label_scan_types": "Сканувати ці типи файлів",
"label_scanner": "Сканер",
"label_series_cover": "Обкладинка серії",
"tab_general": "Загальне",
"tab_metadata": "Метадані",
"tab_options": "Опції",
"tooltip_oneshotsdirectory": "Залиште порожнім, щоб вимкнути",
"tooltip_scanner_force_modified_time": "Увімкнути, якщо бібліотека знаходиться на Google Диску",
"tooltip_use_resources": "Може споживати багато ресурсів на великих бібліотеках або повільному обладнанні"
},
"edit_readlist": {
"button_cancel": "Скасувати",
"button_confirm": "Зберегти зміни",
"dialog_title": "Редагувати список прочитаних",
"field_manual_ordering": "Ручне замовлення",
"field_name": "Ім'я",
"field_summary": "Короткий зміст",
"label_ordering": "За замовчуванням книги у списку прочитання впорядковуються вручну. Ви можете вимкнути ручне впорядкування, щоб сортувати книги за датою випуску.",
"tab_general": "Загальне",
"tab_poster": "Плакат"
},
"edit_recommended": {
"button_confirm": "Зберегти зміни",
"button_reset": "Скинути налаштування за замовчуванням",
"dialog_title": "Редагувати рекомендований перегляд"
},
"edit_series": {
"button_cancel": "Скасувати",
"button_confirm": "Зберегти зміни",
"dialog_title_multiple": "Редагувати {count} серій | Редагувати {count} серій",
"dialog_title_single": "Редагувати {series}",
"field_age_rating": "Віковий рейтинг",
"field_age_rating_error": "Віковий рейтинг має бути 0 або більше",
"field_genres": "Жанри",
"field_labels": "Мітки",
"field_language": "Мова",
"field_language_hint": "Тег мови IETF BCP 47",
"field_publisher": "Видавець",
"field_reading_direction": "Напрямок читання",
"field_sort_title": "Сортувати заголовок",
"field_status": "Статус",
"field_summary": "Короткий зміст",
"field_tags": "Теги",
"field_title": "Назва",
"field_total_book_count": "Загальна кількість книг",
"field_total_book_count_error": "Загальна кількість книг має бути 1 або більше",
"mixed": "ЗМІШАНИЙ",
"tab_general": "Загальне",
"tab_poster": "Плакат",
"tab_sharing": "Спільне використання",
"tab_tags": "Теги",
"tab_titles": "Альтернативні назви",
"tags_notice_multiple_edit": "Ви редагуєте теги для кількох серій. Це замінить існуючі теги кожної серії."
},
"edit_user": {
"button_cancel": "Скасувати",
"button_confirm": "Зберегти зміни",
"dialog_title": "Редагувати користувача",
"label_roles_for": "Ролі для {name}"
},
"edit_user_restrictions": {
"age_restriction": {
"allow_under": "Дозволити лише під",
"exclude_over": "Виключити понад",
"none": "Без обмежень"
},
"edit_restrictions_for": "Редагувати обмеження для {name}",
"label_age_restriction": "Вікове обмеження",
"label_allow_only_labels": "Дозволити лише мітки",
"label_exclude_labels": "Виключити мітки",
"tab_content_restrictions": "Обмеження контенту",
"tab_shared_libraries": "Спільні бібліотеки"
},
"empty_trash": {
"body": "За замовчуванням медіасервер не видаляє інформацію про медіафайли одразу. Це допомагає, якщо диск тимчасово відключено. Під час очищення кошика бібліотеки вся інформація про відсутні медіафайли видаляється.",
"button_confirm": "Порожній",
"title": "Очистити кошик для бібліотеки"
},
"file_browser": {
"button_cancel": "Скасувати",
"button_confirm_default": "Виберіть",
"dialog_title_default": "Браузер файлів",
"parent_directory": "Батьківський"
},
"filename_chooser": {
"button_choose": "Виберіть",
"field_destination_filename": "Ім'я файлу призначення",
"label_source_filename": "Ім'я вихідного файлу",
"table": {
"existing_file": "Існуючий файл",
"order": "Замовлення"
},
"title": "Ім'я файлу призначення'"
},
"force_kobo_sync": {
"dialog_title": "Примусова синхронізація Kobo",
"warning_html": "Це видалить всю історію синхронізації для цього ключа API. Ваш Kobo синхронізує все під час наступної синхронізації."
},
"password_change": {
"button_cancel": "Скасувати",
"button_confirm": "Змінити пароль",
"dialog_title": "Змінити пароль",
"field_new_password": "Новий пароль",
"field_new_password_error": "Потрібен новий пароль.",
"field_repeat_password": "Повторіть новий пароль",
"field_repeat_password_error": "Паролі мають бути однаковими."
},
"refresh_library_metadata": {
"body": "Оновлює метадані для всіх медіафайлів у бібліотеці. Залежно від розміру вашої бібліотеки, це може тривати довго.",
"button_confirm": "Оновити",
"title": "Оновити метадані для бібліотеки"
},
"series_picker": {
"label_search_series": "Пошук серій",
"no_results": "Серії не знайдено",
"title": "Виберіть серію"
},
"server_stop": {
"button_confirm": "Стій",
"confirmation_message": "Ви впевнені, що хочете зупинити Komga?",
"dialog_title": "Вимкнути сервер"
},
"shortcut_help": {
"label_description": "Опис",
"label_key": "Ключ"
},
"transient_book_details": {
"label_candidate": "Кандидат",
"label_existing": "Існуючі",
"label_format": "Формат",
"label_name": "Ім'я",
"label_pages": "Сторінки",
"label_size": "Розмір",
"pages_table": {
"filename": "Ім'я Файлу",
"height": "Висота",
"index": "Індекс",
"media_type": "Тип медіа",
"size": "Розмір",
"width": "Ширина"
},
"title": "Деталі книги",
"title_comparison": "Порівняння книг"
},
"transient_book_viewer": {
"label_candidate": "Кандидат",
"label_existing": "Існуючі",
"page_of_pages": "{page} / {pages}",
"title": "Перевірити книгу",
"title_comparison": "Порівняння книг"
}
},
"duplicate_pages": {
"action_auto_delete_remaining": "Залишок автоматичного видалення ({count})",
"action_delete_auto": "Автоматичне видалення",
"action_delete_manual": "Ручне видалення",
"action_delete_matches": "Видалити збіги",
"action_ignore": "Ігнорувати",
"action_ignore_remaining": "Ігнорувати решту ({count})",
"action_manual_delete_remaining": "Залишилося ручного видалення ({count})",
"confirm_auto_delete_remaining": "Усі хеші сторінок, що залишилися на цій сторінці ({count}), будуть позначені для автоматичного видалення.",
"confirm_manual_delete_remaining": "Усі хеші сторінок, що залишилися на цій сторінці ({count}), будуть позначені для ручного видалення.",
"delete_to_save": "Видалити, щоб зберегти {size}",
"deleted_count": "Видалено {count} разів",
"empty_title": "Дублікатів сторінок не знайдено",
"empty_title_known": "Немає відомих дублікатів",
"filter": {
"count": "Кількість",
"date_added": "Дата додавання",
"date_modified": "Дата зміни",
"delete_count": "Кількість видалень",
"delete_size": "Зекономлено місце",
"match_count": "Кількість матчів",
"size": "Розмір",
"total_size": "Загальний розмір"
},
"info": "Видалення дублікатів сторінок призведе до змінення ваших файлів. Створіть резервну копію файлів та видаліть їх вручну, перш ніж використовувати автоматичне видалення.",
"known": "Відомий",
"matches_n": "Немає збігів | 1 збіг | {count} збігів",
"new": "Новий",
"saved_size": "Збережено {size}",
"title": "Дублікати сторінок",
"unknown_size": "Невідомий розмір"
},
"duplicates": {
"file_hash": "Хеш файлу",
"size": "Розмір",
"title": "Дублікати файлів",
"url": "URL"
},
"enums": {
"copy_mode": {
"HARDLINK": "Жорстке посилання/копіювання файлів",
"MOVE": "Переміщення файлів"
},
"epubreader": {
"appearances": {
"day": "День",
"night": "Ніч",
"sepia": "Сепія"
},
"column_count": {
"auto": "Авто",
"one": "Один",
"two": "Два"
},
"reading_direction": {
"auto": "Автоматичний",
"ltr": "Зліва направо",
"rtl": "Справа наліво"
}
},
"historical_event_type": {
"BookConverted": "Книга перетворена",
"BookFileDeleted": "Файл книги видалено",
"BookImported": "Імпортовано книгу",
"DuplicatePageDeleted": "Дублікат сторінки видалено",
"SeriesFolderDeleted": "Папку серіалу видалено"
},
"media_profile": {
"DIVINA": "DIVINA",
"EPUB": "EPUB",
"PDF": "PDF"
},
"media_status": {
"ERROR": "Помилка",
"OUTDATED": "Застарілий",
"READY": "Готовий",
"UNKNOWN": "Невідомо",
"UNSUPPORTED": "Не підтримується"
},
"page_hash_action": {
"DELETE_AUTO": "Автоматичне видалення",
"DELETE_MANUAL": "Ручне видалення",
"IGNORE": "Ігнорувати"
},
"reading_direction": {
"LEFT_TO_RIGHT": "Зліва направо",
"RIGHT_TO_LEFT": "Справа наліво",
"VERTICAL": "Вертикальний",
"WEBTOON": "Вебмультфільм"
},
"scan_interval": {
"DAILY": "Щоденно",
"DISABLED": "Вимкнено",
"EVERY_12H": "Кожні 12 годин",
"EVERY_6H": "Кожні 6 годин",
"HOURLY": "Щогодини",
"WEEKLY": "Щотижнево"
},
"series_cover": {
"FIRST": "Перший",
"FIRST_UNREAD_OR_FIRST": "Спочатку непрочитані, інакше спочатку",
"FIRST_UNREAD_OR_LAST": "Спочатку непрочитані, інакше останні",
"LAST": "Останній"
},
"series_status": {
"ABANDONED": "Покинутий",
"ENDED": "Закінчилося",
"HIATUS": "Перерва",
"ONGOING": "Поточний"
},
"thumbnail_size": {
"DEFAULT": "За замовчуванням (300 пікселів)",
"LARGE": "Великий (900 пікселів)",
"MEDIUM": "Середній (600 пікселів)",
"XLARGE": "Дуже великий (1200 пікселів)"
}
},
"epubreader": {
"current_chapter": "Поточний розділ",
"page_of": "Сторінка {page} з {count}",
"publisher_font": "Видавець",
"settings": {
"column_count": "Кількість стовпців",
"font_family": "Шрифт",
"layout": "Макет",
"layout_paginated": "Пагінована",
"layout_scroll": "Прокрутити",
"navigation_mode": "Режим навігації",
"navigation_options": {
"both": "Обидва",
"buttons": "Ґудзики",
"click": "Клацніть / Торкніться"
},
"page_margins": "Поля сторінки",
"viewing_theme": "Перегляд теми"
},
"shortcuts": {
"cycle_pagination": "Циклічна кількість стовпців",
"cycle_viewing_theme": "Тема перегляду велосипедів",
"font_size_decrease": "Зменшити розмір шрифту",
"font_size_increase": "Збільшити розмір шрифту",
"menus": "Меню",
"next": "Вперед",
"previous": "Назад",
"reader_navigation": "Навігація читача",
"scroll": "Змінити макет на прокручування",
"settings": "Налаштування",
"show_hide_toc": "Показати/приховати зміст"
}
},
"error_codes": {
"ERR_1000": "Не вдалося отримати доступ до файлу під час аналізу",
"ERR_1001": "Тип медіа не підтримується",
"ERR_1002": "Зашифровані RAR-архіви не підтримуються",
"ERR_1003": "Архів Solid RAR не підтримується",
"ERR_1004": "Багатотомні RAR-архіви не підтримуються",
"ERR_1005": "Невідома помилка під час аналізу книги",
"ERR_1006": "Книга не містить жодної сторінки",
"ERR_1007": "Деякі записи не вдалося проаналізувати",
"ERR_1008": "Невідома помилка під час отримання записів книги",
"ERR_1009": "Список для читання з такою назвою вже існує'",
"ERR_1015": "Помилка під час десеріалізації ComicRack CBL",
"ERR_1016": "Каталог недоступний або не є каталогом",
"ERR_1017": "Не вдається сканувати папку, яка є частиною існуючої бібліотеки",
"ERR_1018": "Файл не знайдено",
"ERR_1019": "Неможливо імпортувати файл, який є частиною існуючої бібліотеки",
"ERR_1020": "Книга для оновлення не належить до наданої серії",
"ERR_1021": "Цільовий файл вже існує",
"ERR_1022": "Не вдалося відсканувати нещодавно імпортовану книгу",
"ERR_1023": "Книга вже є у списку для читання",
"ERR_1024": "Помилка входу OAuth2: немає атрибута електронної пошти",
"ERR_1025": "Помилка входу OAuth2: локального користувача з такою електронною адресою не існує",
"ERR_1026": "Помилка входу в OpenID Connect: електронна адреса не підтверджена",
"ERR_1027": "Помилка входу в OpenID Connect: немає атрибута email_verified",
"ERR_1028": "Помилка входу в OpenID Connect: немає атрибута електронної пошти",
"ERR_1029": "ComicRack CBL не містить жодного елемента Book",
"ERR_1030": "ComicRack CBL не має елемента Name",
"ERR_1031": "У книзі ComicRack CBL відсутня серія або номер",
"ERR_1032": "Файл EPUB має неправильний тип медіафайлу",
"ERR_1033": "Деякі записи відсутні",
"ERR_1034": "Ключ API з цим коментарем вже існує",
"ERR_1035": "Помилка під час отримання змісту EPUB",
"ERR_1036": "Помилка під час отримання орієнтирів EPUB",
"ERR_1037": "Помилка під час отримання списку сторінок EPUB",
"ERR_1038": "Помилка під час отримання сторінок EPUB divina",
"ERR_1039": "Помилка під час отримання позицій EPUB"
},
"filter": {
"age_rating": "віковий рейтинг",
"age_rating_none": "Жоден",
"any": "Будь-який",
"complete": "Завершено",
"genre": "жанр",
"in_progress": "У процесі",
"language": "мова",
"library": "бібліотека",
"media_profile": "Медіа-профіль",
"media_status": "Стан медіа",
"oneshot": "Одноразовий",
"publisher": "видавець",
"read": "Читати",
"release_date": "дата випуску",
"sharing_label": "Мітка спільного доступу",
"status": "статус",
"tag": "тег",
"unread": "Непрочитане"
},
"filter_drawer": {
"filter": "фільтр",
"sort": "сортувати"
},
"history": {
"header": {
"book": "Книга",
"date": "Дата",
"details": "Деталі",
"series": "Серія",
"type": "Тип"
},
"title": "Історія"
},
"home": {
"theme": "Тема",
"translation": "Переклад"
},
"library_navigation": {
"browse": "Переглянути",
"browse_books": "Книги",
"browse_series": "Серія",
"collections": "Колекції",
"readlists": "Списки для читання",
"recommended": "Рекомендовано"
},
"login": {
"create_user_account": "Створити обліковий запис користувача",
"login": "Увійти",
"unclaimed_html": "Цей сервер Komga ще не активний, вам потрібно створити обліковий запис користувача, щоб мати до нього доступ.<br><br>Виберіть <strong>електронну пошту</strong> та <strong>пароль</strong> і натисніть <strong>Створити обліковий запис користувача</strong>."
},
"media_analysis": {
"comment": "Коментар",
"media_analysis": "Медіа-аналіз",
"media_type": "Тип носія",
"name": "Ім'я",
"size": "Розмір",
"status": "Статус",
"url": "URL"
},
"menu": {
"add_to_collection": "Додати до колекції",
"add_to_readlist": "Додати до списку прочитання",
"analyze": "Аналіз",
"bulk_edit_metadata": "Масове редагування метаданих",
"delete": "Видалити",
"deselect_all": "Зняти вибір усіх",
"download_readlist": "Завантажити список для читання",
"download_series": "Завантажити серію",
"edit": "Редагувати",
"edit_metadata": "Редагувати метадані",
"empty_trash": "Очистити кошик",
"mark_read": "Позначити як прочитане",
"mark_unread": "Позначити як непрочитане",
"pin": "Закріпити",
"refresh_metadata": "Оновити метадані",
"scan_library_files": "Сканування файлів бібліотеки",
"scan_library_files_deep": "Сканувати файли бібліотеки (глибоко)",
"select_all": "Вибрати все",
"unpin": "Відкріпити"
},
"metrics": {
"library_books": "Книги на бібліотеку",
"library_disk_space": "Місце на диску бібліотеки",
"library_series": "Серії на бібліотеку",
"library_sidecars": "Коляски на бібліотеку",
"tasks_executed": "Виконані завдання",
"tasks_total_time": "Загальний час виконання завдань",
"title": "Метрики"
},
"missing_posters": {
"title": "Відсутні плакати"
},
"navigation": {
"home": "Дім",
"libraries": "Бібліотеки",
"logout": "Вийти"
},
"no_libraries_pinned": {
"subtitle": "Ви можете закріпити бібліотеку з меню з трьома крапками",
"title": "Немає закріплених бібліотек"
},
"page_not_found": {
"go_back_to_home_page": "Повернутися на головну сторінку",
"page_does_not_exist": "Сторінка, яку ви шукаєте, не існує.",
"page_not_found": "Сторінку не знайдено"
},
"read_more": {
"less": "Читати менше",
"more": "Читати більше"
},
"readlist_import": {
"row": {
"duplicate_book": "Дублікат книги",
"error_choose_book": "Виберіть у книгу"
}
},
"readlists_expansion_panel": {
"manage_readlist": "Керування списком прочитаних",
"title": "Список прочитаного {name}"
},
"search": {
"no_results": "Пошук не дав результатів",
"search": "Пошук",
"search_for_something_else": "Спробуйте пошукати щось інше",
"search_results_for": "Результати пошуку для \"{name}\""
},
"searchbox": {
"in_library": "З {library}",
"no_results": "Немає результатів",
"search_all": "Шукати все…"
},
"server": {
"server_management": {
"button_cancel_all_tasks": "Скасувати всі завдання",
"button_empty_trash": "Очистити кошик для всіх бібліотек",
"button_scan_libraries": "Сканувати всі бібліотеки",
"button_scan_libraries_deep": "Сканувати всі бібліотеки (глибоко)",
"button_shutdown": "Вимкнути",
"download_log": "Завантажити файл журналу",
"notification_tasks_cancelled": "Немає завдань для скасування | Одне завдання скасовано | {count} завдань скасовано",
"section_title": "Керування сервером"
},
"tab_title": "Сервер",
"updates": "Оновлення"
},
"server_settings": {
"config_precedence": "Має пріоритет над файлом конфігурації",
"dialog_regenerate_thumbnails": {
"body": "Розмір мініатюр змінився. Бажаєте створити мініатюри книг заново?",
"btn_alternate": "Так, усі книги",
"btn_cancel": "Ні",
"btn_confirm": "Так, але тільки якщо більший",
"title": "Згенерувати мініатюри знову"
},
"hint_kobo_port": "Встановлювати лише у разі проблем із синхронізацією обкладинок та завантажень",
"label_delete_empty_collections": "Видалити порожні колекції після сканування",
"label_delete_empty_readlists": "Видалити порожні списки прочитання після сканування",
"label_kepubify_path": "Шлях до kepubify",
"label_kobo_port": "Зовнішній порт Kobo Sync",
"label_kobo_proxy": "Запити синхронізації проксі-сервера Kobo до магазину Kobo",
"label_rememberme_duration": "Тривалість дії функції «Запам’ятати мене» (у днях)",
"label_server_context_path": "Базова URL-адреса",
"label_server_port": "Порт сервера",
"label_task_pool_size": "Гілки завдань",
"label_thumbnail_size": "Розмір мініатюр",
"requires_restart": "Потрібен перезапуск, щоб набути чинності",
"server_settings": "Налаштування сервера"
},
"settings_user": {
"change_password": "Змінити пароль",
"edit_restrictions": "Редагувати обмеження",
"edit_user": "Редагувати користувача",
"latest_activity": "Остання активність: {date}",
"no_recent_activity": "Немає нещодавньої активності",
"role_administrator": "Адміністратор",
"role_user": "Користувач"
},
"sort": {
"books_count": "Кількість книг",
"date_added": "Дата додавання",
"date_read": "Дата прочитання",
"date_updated": "Дата оновлення",
"file_name": "Ім'я файлу",
"file_size": "Розмір файлу",
"folder_name": "Назва папки",
"name": "Ім'я",
"number": "Номер",
"page_count": "Кількість сторінок",
"random": "Випадкове",
"release_date": "Дата випуску'",
"series": "Серія"
},
"theme": {
"dark": "Темний",
"light": "Світло",
"system": "Система"
},
"thumbnail_card": {
"tooltip_delete": "Видалити",
"tooltip_generated": "Згенеровані ілюстрації",
"tooltip_mark_as_selected": "Позначити як вибране",
"tooltip_selected": "Вибрано",
"tooltip_sidecar": "Місцеві витвори мистецтва",
"tooltip_to_be_deleted": "Буде видалено",
"tooltip_to_be_uploaded": "Для завантаження",
"tooltip_too_big": "Файл занадто великий!",
"tooltip_user_uploaded": "Завантажено користувачем"
},
"titles_more": {
"less": "Менше назв",
"more": "Більше назв"
},
"ui_settings": {
"general": "Загальне",
"label_oauth2_auto_login": "Автоматичний вхід OAuth2",
"label_oauth2_hide_login": "Приховати поля входу, якщо ввімкнено OAuth2",
"label_poster_blur_unread": "Розмиття плаката для непрочитаних книг та серій",
"label_poster_stretch": "Розтягніть плакат, щоб він підходив до листівки",
"label_series_groups": "Групування серій",
"section_oauth2": "OAuth2",
"series_groups": {
"alpha": "Алфавітний",
"japanese": "Годзюон (японська)"
},
"tooltip_oauth2_auto_login": "Потрібен один постачальник OAuth2 та ввімкнена функція «приховувати поля входу»"
},
"updates": {
"available": "Доступні оновлення",
"latest_installed": "Остання версія Komga вже встановлена"
},
"user_roles": {
"ADMIN": "Адміністратор",
"FILE_DOWNLOAD": "Завантаження файлу",
"KOBO_SYNC": "Синхронізація Kobo",
"KOREADER_SYNC": "Синхронізація KOReader",
"PAGE_STREAMING": "Потокове передавання сторінок",
"USER": "Користувач"
},
"users": {
"api_keys": "Ключі API",
"authentication_activity": "Активність автентифікації",
"users": "Користувачі"
},
"validation": {
"context_path": "Повинно починатися з '/', не закінчуватися на '/-_' та містити лише '/-_a-z0-9'",
"one_or_more": "Має бути 1 або більше",
"tcp_port": "Має бути від 1 до 65535",
"zero_or_more": "Має бути 0 або більше"
},
"welcome": {
"add_library": "Додати бібліотеку'",
"no_libraries_yet": "Ще не додано жодної бібліотеки!",
"welcome_message": "Ласкаво просимо до Komga"
} }
} }

View file

@ -906,14 +906,14 @@ export default Vue.extend({
}, 50), }, 50),
downloadCurrentPage() { downloadCurrentPage() {
new jsFileDownloader({ new jsFileDownloader({
url: this.currentPage.url, url: `${this.currentPage.url}?contentNegotiation=false`,
filename: `${this.book.name}-${this.currentPage.number}.${this.currentPage.fileName.split('.').pop()}`, filename: `${this.book.name}-${this.currentPage.number}.${this.currentPage.fileName.split('.').pop()}`,
withCredentials: true, withCredentials: true,
forceDesktopMode: true, forceDesktopMode: true,
}) })
}, },
async setCurrentPageAsPoster(type: ItemTypes) { async setCurrentPageAsPoster(type: ItemTypes) {
const imageFile = await getFileFromUrl(this.currentPage.url, 'poster', 'image/jpeg', {credentials: 'include'}) const imageFile = await getFileFromUrl(`${this.currentPage.url}?contentNegotiation=false`, 'poster', 'image/jpeg', {credentials: 'include'})
const newImageFile = await resizeImageFile(imageFile) const newImageFile = await resizeImageFile(imageFile)
switch (type) { switch (type) {
case ItemTypes.BOOK: case ItemTypes.BOOK:

View file

@ -3,13 +3,14 @@ import org.apache.tools.ant.taskdefs.condition.Os
import org.flywaydb.gradle.task.FlywayMigrateTask import org.flywaydb.gradle.task.FlywayMigrateTask
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.util.prefixIfNot import org.jetbrains.kotlin.util.prefixIfNot
import org.springframework.boot.gradle.plugin.SpringBootPlugin
plugins { plugins {
kotlin("jvm") kotlin("jvm")
kotlin("plugin.spring") kotlin("plugin.spring")
kotlin("kapt") kotlin("kapt")
id("org.springframework.boot") version "3.5.4" id("org.springframework.boot") version libs.versions.springboot.get()
id("com.gorylenko.gradle-git-properties") version "2.5.2" alias(libs.plugins.gradleGitProperties)
id("nu.studer.jooq") version "10.1" id("nu.studer.jooq") version "10.1"
id("org.flywaydb.flyway") version "11.7.2" id("org.flywaydb.flyway") version "11.7.2"
id("com.github.johnrengelman.processes") version "0.5.0" id("com.github.johnrengelman.processes") version "0.5.0"
@ -37,7 +38,7 @@ dependencies {
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
implementation(kotlin("reflect")) implementation(kotlin("reflect"))
api(platform("org.springframework.boot:spring-boot-dependencies:3.5.4")) api(platform(SpringBootPlugin.BOM_COORDINATES))
api("org.springframework.boot:spring-boot-starter-web") api("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.boot:spring-boot-starter-webflux")
@ -51,7 +52,7 @@ dependencies {
implementation("com.github.gotson:spring-session-caffeine:2.1.0") implementation("com.github.gotson:spring-session-caffeine:2.1.0")
implementation("org.springframework.data:spring-data-commons") implementation("org.springframework.data:spring-data-commons")
kapt("org.springframework.boot:spring-boot-configuration-processor:3.5.4") kapt("org.springframework.boot:spring-boot-configuration-processor:${libs.versions.springboot.get()}")
implementation("org.flywaydb:flyway-core") implementation("org.flywaydb:flyway-core")
@ -66,14 +67,10 @@ dependencies {
implementation("org.apache.commons:commons-lang3:3.18.0") implementation("org.apache.commons:commons-lang3:3.18.0")
implementation("commons-validator:commons-validator:1.10.0") implementation("commons-validator:commons-validator:1.10.0")
run { implementation("org.apache.lucene:lucene-core:${libs.versions.lucene.get()}")
// v10 requires JDK 21 implementation("org.apache.lucene:lucene-analysis-common:${libs.versions.lucene.get()}")
val luceneVersion = "9.9.1" implementation("org.apache.lucene:lucene-queryparser:${libs.versions.lucene.get()}")
implementation("org.apache.lucene:lucene-core:$luceneVersion") implementation("org.apache.lucene:lucene-backward-codecs:${libs.versions.lucene.get()}")
implementation("org.apache.lucene:lucene-analysis-common:$luceneVersion")
implementation("org.apache.lucene:lucene-queryparser:$luceneVersion")
implementation("org.apache.lucene:lucene-backward-codecs:$luceneVersion")
}
implementation("com.ibm.icu:icu4j:77.1") implementation("com.ibm.icu:icu4j:77.1")
@ -82,18 +79,18 @@ dependencies {
implementation("org.apache.tika:tika-core:2.9.1") implementation("org.apache.tika:tika-core:2.9.1")
implementation("org.apache.commons:commons-compress:1.27.1") implementation("org.apache.commons:commons-compress:1.27.1")
implementation("com.github.junrar:junrar:7.5.5") implementation("com.github.junrar:junrar:7.5.5")
implementation("com.github.gotson.nightcompress:nightcompress:1.1.0") implementation("com.github.gotson.nightcompress:nightcompress:1.1.1")
implementation("org.apache.pdfbox:pdfbox:3.0.5") implementation("org.apache.pdfbox:pdfbox:3.0.5")
implementation("net.grey-panther:natural-comparator:1.1") implementation("net.grey-panther:natural-comparator:1.1")
implementation("org.jsoup:jsoup:1.18.3") implementation("org.jsoup:jsoup:1.21.1")
implementation("net.coobird:thumbnailator:0.4.20") implementation("net.coobird:thumbnailator:0.4.20")
runtimeOnly("com.twelvemonkeys.imageio:imageio-jpeg:3.12.0") runtimeOnly("com.twelvemonkeys.imageio:imageio-jpeg:${libs.versions.twelvemonkeys.get()}")
runtimeOnly("com.twelvemonkeys.imageio:imageio-tiff:3.12.0") runtimeOnly("com.twelvemonkeys.imageio:imageio-tiff:${libs.versions.twelvemonkeys.get()}")
runtimeOnly("com.twelvemonkeys.imageio:imageio-webp:3.12.0") runtimeOnly("com.twelvemonkeys.imageio:imageio-webp:${libs.versions.twelvemonkeys.get()}")
runtimeOnly("com.github.gotson.nightmonkeys:imageio-jxl:1.0.0") runtimeOnly("com.github.gotson.nightmonkeys:imageio-jxl:${libs.versions.nightmonkeys.get()}")
runtimeOnly("com.github.gotson.nightmonkeys:imageio-heif:1.0.0") runtimeOnly("com.github.gotson.nightmonkeys:imageio-heif:${libs.versions.nightmonkeys.get()}")
runtimeOnly("com.github.gotson.nightmonkeys:imageio-webp:1.0.0") runtimeOnly("com.github.gotson.nightmonkeys:imageio-webp:${libs.versions.nightmonkeys.get()}")
// support for jpeg2000 // support for jpeg2000
runtimeOnly("com.github.jai-imageio:jai-imageio-jpeg2000:1.4.0") runtimeOnly("com.github.jai-imageio:jai-imageio-jpeg2000:1.4.0")
runtimeOnly("org.apache.pdfbox:jbig2-imageio:3.0.4") runtimeOnly("org.apache.pdfbox:jbig2-imageio:3.0.4")
@ -107,8 +104,8 @@ dependencies {
implementation("com.github.ben-manes.caffeine:caffeine") implementation("com.github.ben-manes.caffeine:caffeine")
implementation("org.xerial:sqlite-jdbc:3.50.2.0") implementation("org.xerial:sqlite-jdbc:${libs.versions.sqliteJdbc.get()}")
jooqGenerator("org.xerial:sqlite-jdbc:3.50.2.0") jooqGenerator("org.xerial:sqlite-jdbc:${libs.versions.sqliteJdbc.get()}")
if (version.toString().endsWith(".0.0")) { if (version.toString().endsWith(".0.0")) {
ksp("com.github.gotson.bestbefore:bestbefore-processor-kotlin:0.2.0") ksp("com.github.gotson.bestbefore:bestbefore-processor-kotlin:0.2.0")
@ -127,9 +124,9 @@ dependencies {
benchmarkImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") benchmarkImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
benchmarkImplementation("org.openjdk.jmh:jmh-core:1.37") benchmarkImplementation("org.openjdk.jmh:jmh-core:1.37")
kaptBenchmark("org.openjdk.jmh:jmh-generator-annprocess:1.37") kaptBenchmark("org.openjdk.jmh:jmh-generator-annprocess:1.37")
kaptBenchmark("org.springframework.boot:spring-boot-configuration-processor:3.5.4") kaptBenchmark("org.springframework.boot:spring-boot-configuration-processor:${libs.versions.springboot.get()}")
developmentOnly("org.springframework.boot:spring-boot-devtools:3.5.4") developmentOnly("org.springframework.boot:spring-boot-devtools:${libs.versions.springboot.get()}")
} }
kotlin { kotlin {
@ -306,13 +303,13 @@ tasks.register("flywayMigrateTasks", FlywayMigrateTask::class) {
buildscript { buildscript {
configurations["classpath"].resolutionStrategy.eachDependency { configurations["classpath"].resolutionStrategy.eachDependency {
if (requested.group.startsWith("org.jooq") && requested.name.startsWith("jooq")) { if (requested.group.startsWith("org.jooq") && requested.name.startsWith("jooq")) {
useVersion("3.19.24") useVersion(libs.versions.jooq.get())
} }
} }
} }
jooq { jooq {
version = "3.19.24" version = libs.versions.jooq.get()
configurations { configurations {
create("main") { create("main") {
jooqConfiguration.apply { jooqConfiguration.apply {

View file

@ -9,7 +9,8 @@ FROM ubuntu:24.10 as build-amd64
ENV JAVA_HOME=/opt/java/openjdk ENV JAVA_HOME=/opt/java/openjdk
COPY --from=eclipse-temurin:23-jre $JAVA_HOME $JAVA_HOME COPY --from=eclipse-temurin:23-jre $JAVA_HOME $JAVA_HOME
ENV PATH="${JAVA_HOME}/bin:${PATH}" ENV PATH="${JAVA_HOME}/bin:${PATH}"
RUN apt -y update && \ RUN sed -i -re 's/([a-z]{2}\.)?archive.ubuntu.com|security.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list.d/ubuntu.sources && \
apt -y update && \
apt -y install ca-certificates locales libjxl-dev libheif-dev libwebp-dev libarchive-dev wget curl && \ apt -y install ca-certificates locales libjxl-dev libheif-dev libwebp-dev libarchive-dev wget curl && \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \ echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
locale-gen en_US.UTF-8 && \ locale-gen en_US.UTF-8 && \
@ -23,7 +24,8 @@ FROM ubuntu:24.10 as build-arm64
ENV JAVA_HOME=/opt/java/openjdk ENV JAVA_HOME=/opt/java/openjdk
COPY --from=eclipse-temurin:23-jre $JAVA_HOME $JAVA_HOME COPY --from=eclipse-temurin:23-jre $JAVA_HOME $JAVA_HOME
ENV PATH="${JAVA_HOME}/bin:${PATH}" ENV PATH="${JAVA_HOME}/bin:${PATH}"
RUN apt -y update && \ RUN sed -i -re 's/([a-z]{2}\.)?ports.ubuntu.com\/ubuntu-ports/old-releases.ubuntu.com\/ubuntu/g' /etc/apt/sources.list.d/ubuntu.sources && \
apt -y update && \
apt -y install ca-certificates locales libjxl-dev libheif-dev libwebp-dev libarchive-dev wget curl && \ apt -y install ca-certificates locales libjxl-dev libheif-dev libwebp-dev libarchive-dev wget curl && \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \ echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
locale-gen en_US.UTF-8 && \ locale-gen en_US.UTF-8 && \

View file

@ -7,7 +7,7 @@
"url": "https://github.com/gotson/komga/blob/master/LICENSE" "url": "https://github.com/gotson/komga/blob/master/LICENSE"
}, },
"title": "Komga API", "title": "Komga API",
"version": "1.23.1" "version": "1.23.6"
}, },
"externalDocs": { "externalDocs": {
"description": "Komga documentation", "description": "Komga documentation",
@ -4919,7 +4919,7 @@
"operationId": "matchComicRackList", "operationId": "matchComicRackList",
"requestBody": { "requestBody": {
"content": { "content": {
"application/json": { "multipart/form-data": {
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10313,6 +10313,9 @@
"bookId": { "bookId": {
"type": "string" "type": "string"
}, },
"id": {
"type": "string"
},
"properties": { "properties": {
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
@ -10331,6 +10334,7 @@
} }
}, },
"required": [ "required": [
"id",
"properties", "properties",
"timestamp", "timestamp",
"type" "type"
@ -13082,7 +13086,9 @@
"type": "boolean" "type": "boolean"
}, },
"kepubifyPath": { "kepubifyPath": {
"type": "string" "type": "string",
"deprecated": true,
"description": "Will be removed in a future version"
}, },
"koboPort": { "koboPort": {
"type": "integer", "type": "integer",

View file

@ -418,6 +418,7 @@ class BookLifecycle(
val extension = val extension =
mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub
?: throw IllegalArgumentException("Epub extension not found") ?: throw IllegalArgumentException("Epub extension not found")
.also { logger.error { "Epub extension not found for book ${book.id}. Book should be re-analyzed." } }
extension.positions[page - 1] extension.positions[page - 1]
} else { } else {
null null
@ -496,6 +497,7 @@ class BookLifecycle(
val extension = val extension =
mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub
?: throw IllegalArgumentException("Epub extension not found") ?: throw IllegalArgumentException("Epub extension not found")
.also { logger.error { "Epub extension not found for book ${book.id}. Book should be re-analyzed." } }
// match progression with positions // match progression with positions
val matchingPositions = extension.positions.filter { it.href == href } val matchingPositions = extension.positions.filter { it.href == href }
val matchedPosition = val matchedPosition =

View file

@ -113,13 +113,13 @@ class ReadListLifecycle(
fun deleteEmptyReadLists() { fun deleteEmptyReadLists() {
logger.info { "Deleting empty read lists" } logger.info { "Deleting empty read lists" }
val toDelete = readListRepository.findAllEmpty()
transactionTemplate.executeWithoutResult { transactionTemplate.executeWithoutResult {
val toDelete = readListRepository.findAllEmpty()
thumbnailReadListRepository.deleteByReadListIds(toDelete.map { it.id }) thumbnailReadListRepository.deleteByReadListIds(toDelete.map { it.id })
readListRepository.delete(toDelete.map { it.id }) readListRepository.delete(toDelete.map { it.id })
toDelete.forEach { eventPublisher.publishEvent(DomainEvent.ReadListDeleted(it)) }
} }
toDelete.forEach { eventPublisher.publishEvent(DomainEvent.ReadListDeleted(it)) }
} }
fun addThumbnail(thumbnail: ThumbnailReadList): ThumbnailReadList { fun addThumbnail(thumbnail: ThumbnailReadList): ThumbnailReadList {

View file

@ -99,13 +99,13 @@ class SeriesCollectionLifecycle(
fun deleteEmptyCollections() { fun deleteEmptyCollections() {
logger.info { "Deleting empty collections" } logger.info { "Deleting empty collections" }
val toDelete = collectionRepository.findAllEmpty()
transactionTemplate.executeWithoutResult { transactionTemplate.executeWithoutResult {
val toDelete = collectionRepository.findAllEmpty()
thumbnailSeriesCollectionRepository.deleteByCollectionIds(toDelete.map { it.id }) thumbnailSeriesCollectionRepository.deleteByCollectionIds(toDelete.map { it.id })
collectionRepository.delete(toDelete.map { it.id }) collectionRepository.delete(toDelete.map { it.id })
toDelete.forEach { eventPublisher.publishEvent(DomainEvent.CollectionDeleted(it)) }
} }
toDelete.forEach { eventPublisher.publishEvent(DomainEvent.CollectionDeleted(it)) }
} }
fun addThumbnail(thumbnail: ThumbnailSeriesCollection): ThumbnailSeriesCollection { fun addThumbnail(thumbnail: ThumbnailSeriesCollection): ThumbnailSeriesCollection {

View file

@ -67,7 +67,7 @@ class KomgaProperties {
@get:Positive @get:Positive
var maxPoolSize: Int = 1 var maxPoolSize: Int = 1
var journalMode: JournalMode? = null var journalMode: JournalMode? = JournalMode.WAL
@DurationUnit(ChronoUnit.SECONDS) @DurationUnit(ChronoUnit.SECONDS)
var busyTimeout: Duration? = null var busyTimeout: Duration? = null

View file

@ -20,6 +20,8 @@ class Hasher {
return computeHash(path.inputStream()) return computeHash(path.inputStream())
} }
fun computeHash(string: String): String = computeHash(string.byteInputStream())
fun computeHash(stream: InputStream): String { fun computeHash(stream: InputStream): String {
val hash = Algorithm.XXH3_128.Seeded(SEED.toLong()).createDigest() val hash = Algorithm.XXH3_128.Seeded(SEED.toLong()).createDigest()

View file

@ -0,0 +1,21 @@
package org.gotson.komga.infrastructure.jooq
import org.jooq.DSLContext
import org.springframework.transaction.support.TransactionSynchronizationManager
abstract class SplitDslDaoBase {
val dslRW: DSLContext
private val _dslRO: DSLContext
constructor(dslRW: DSLContext, dslRO: DSLContext) {
this.dslRW = dslRW
this._dslRO = dslRO
}
val dslRO: DSLContext
get() =
if (TransactionSynchronizationManager.isActualTransactionActive() && !TransactionSynchronizationManager.isCurrentTransactionReadOnly())
dslRW
else
_dslRO
}

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.AuthenticationActivity import org.gotson.komga.domain.model.AuthenticationActivity
import org.gotson.komga.domain.model.KomgaUser import org.gotson.komga.domain.model.KomgaUser
import org.gotson.komga.domain.persistence.AuthenticationActivityRepository import org.gotson.komga.domain.persistence.AuthenticationActivityRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.toOrderBy import org.gotson.komga.infrastructure.jooq.toOrderBy
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.AuthenticationActivityRecord import org.gotson.komga.jooq.main.tables.records.AuthenticationActivityRecord
@ -21,9 +22,10 @@ import java.time.LocalDateTime
@Component @Component
class AuthenticationActivityDao( class AuthenticationActivityDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
) : AuthenticationActivityRepository { ) : SplitDslDaoBase(dslRW, dslRO),
AuthenticationActivityRepository {
private val aa = Tables.AUTHENTICATION_ACTIVITY private val aa = Tables.AUTHENTICATION_ACTIVITY
private val sorts = private val sorts =

View file

@ -1,6 +1,7 @@
package org.gotson.komga.infrastructure.jooq.main package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.ContentRestrictions import org.gotson.komga.domain.model.ContentRestrictions
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.toCondition import org.gotson.komga.infrastructure.jooq.toCondition
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.jooq.DSLContext import org.jooq.DSLContext
@ -19,8 +20,9 @@ import java.time.LocalDateTime
@Component @Component
class BookCommonDao( class BookCommonDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext, dslRW: DSLContext,
) { @Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO) {
private val b = Tables.BOOK private val b = Tables.BOOK
private val m = Tables.MEDIA private val m = Tables.MEDIA
private val d = Tables.BOOK_METADATA private val d = Tables.BOOK_METADATA

View file

@ -6,6 +6,7 @@ import org.gotson.komga.domain.model.SearchContext
import org.gotson.komga.domain.persistence.BookRepository import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.infrastructure.jooq.BookSearchHelper import org.gotson.komga.infrastructure.jooq.BookSearchHelper
import org.gotson.komga.infrastructure.jooq.RequiredJoin import org.gotson.komga.infrastructure.jooq.RequiredJoin
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.rlbAlias import org.gotson.komga.infrastructure.jooq.rlbAlias
import org.gotson.komga.infrastructure.jooq.toOrderBy import org.gotson.komga.infrastructure.jooq.toOrderBy
@ -30,10 +31,11 @@ import java.time.ZoneId
@Component @Component
class BookDao( class BookDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : BookRepository { ) : SplitDslDaoBase(dslRW, dslRO),
BookRepository {
private val b = Tables.BOOK private val b = Tables.BOOK
private val m = Tables.MEDIA private val m = Tables.MEDIA
private val d = Tables.BOOK_METADATA private val d = Tables.BOOK_METADATA

View file

@ -7,6 +7,7 @@ import org.gotson.komga.domain.model.SearchContext
import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource
import org.gotson.komga.infrastructure.jooq.BookSearchHelper import org.gotson.komga.infrastructure.jooq.BookSearchHelper
import org.gotson.komga.infrastructure.jooq.RequiredJoin import org.gotson.komga.infrastructure.jooq.RequiredJoin
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable import org.gotson.komga.infrastructure.jooq.TempTable
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.noCase import org.gotson.komga.infrastructure.jooq.noCase
@ -51,11 +52,13 @@ import java.net.URL
@Component @Component
class BookDtoDao( class BookDtoDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
private val luceneHelper: LuceneHelper, private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val bookCommonDao: BookCommonDao, private val bookCommonDao: BookCommonDao,
) : BookDtoRepository { ) : SplitDslDaoBase(dslRW, dslRO),
BookDtoRepository {
private val b = Tables.BOOK private val b = Tables.BOOK
private val m = Tables.MEDIA private val m = Tables.MEDIA
private val d = Tables.BOOK_METADATA private val d = Tables.BOOK_METADATA

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.Author import org.gotson.komga.domain.model.Author
import org.gotson.komga.domain.model.BookMetadataAggregation import org.gotson.komga.domain.model.BookMetadataAggregation
import org.gotson.komga.domain.persistence.BookMetadataAggregationRepository import org.gotson.komga.domain.persistence.BookMetadataAggregationRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.BookMetadataAggregationAuthorRecord import org.gotson.komga.jooq.main.tables.records.BookMetadataAggregationAuthorRecord
@ -18,10 +19,11 @@ import java.time.ZoneId
@Component @Component
class BookMetadataAggregationDao( class BookMetadataAggregationDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : BookMetadataAggregationRepository { ) : SplitDslDaoBase(dslRW, dslRO),
BookMetadataAggregationRepository {
private val d = Tables.BOOK_METADATA_AGGREGATION private val d = Tables.BOOK_METADATA_AGGREGATION
private val a = Tables.BOOK_METADATA_AGGREGATION_AUTHOR private val a = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
private val t = Tables.BOOK_METADATA_AGGREGATION_TAG private val t = Tables.BOOK_METADATA_AGGREGATION_TAG

View file

@ -4,6 +4,7 @@ import org.gotson.komga.domain.model.Author
import org.gotson.komga.domain.model.BookMetadata import org.gotson.komga.domain.model.BookMetadata
import org.gotson.komga.domain.model.WebLink import org.gotson.komga.domain.model.WebLink
import org.gotson.komga.domain.persistence.BookMetadataRepository import org.gotson.komga.domain.persistence.BookMetadataRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.BookMetadataAuthorRecord import org.gotson.komga.jooq.main.tables.records.BookMetadataAuthorRecord
@ -20,10 +21,11 @@ import java.time.ZoneId
@Component @Component
class BookMetadataDao( class BookMetadataDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : BookMetadataRepository { ) : SplitDslDaoBase(dslRW, dslRO),
BookMetadataRepository {
private val d = Tables.BOOK_METADATA private val d = Tables.BOOK_METADATA
private val a = Tables.BOOK_METADATA_AUTHOR private val a = Tables.BOOK_METADATA_AUTHOR
private val bt = Tables.BOOK_METADATA_TAG private val bt = Tables.BOOK_METADATA_TAG

View file

@ -1,5 +1,6 @@
package org.gotson.komga.infrastructure.jooq.main package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.interfaces.api.rest.dto.ClientSettingDto import org.gotson.komga.interfaces.api.rest.dto.ClientSettingDto
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.jooq.DSLContext import org.jooq.DSLContext
@ -8,9 +9,9 @@ import org.springframework.stereotype.Component
@Component @Component
class ClientSettingsDtoDao( class ClientSettingsDtoDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
) { ) : SplitDslDaoBase(dslRW, dslRO) {
private val g = Tables.CLIENT_SETTINGS_GLOBAL private val g = Tables.CLIENT_SETTINGS_GLOBAL
private val u = Tables.CLIENT_SETTINGS_USER private val u = Tables.CLIENT_SETTINGS_USER

View file

@ -1,5 +1,6 @@
package org.gotson.komga.infrastructure.jooq.main package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.toOrderBy import org.gotson.komga.infrastructure.jooq.toOrderBy
import org.gotson.komga.interfaces.api.persistence.HistoricalEventDtoRepository import org.gotson.komga.interfaces.api.persistence.HistoricalEventDtoRepository
import org.gotson.komga.interfaces.api.rest.dto.HistoricalEventDto import org.gotson.komga.interfaces.api.rest.dto.HistoricalEventDto
@ -15,8 +16,10 @@ import org.springframework.stereotype.Component
@Component @Component
class HistoricalEventDtoDao( class HistoricalEventDtoDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext, dslRW: DSLContext,
) : HistoricalEventDtoRepository { @Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
HistoricalEventDtoRepository {
private val e = Tables.HISTORICAL_EVENT private val e = Tables.HISTORICAL_EVENT
private val ep = Tables.HISTORICAL_EVENT_PROPERTIES private val ep = Tables.HISTORICAL_EVENT_PROPERTIES
@ -41,6 +44,7 @@ class HistoricalEventDtoDao(
.map { er -> .map { er ->
val epr = dslRO.selectFrom(ep).where(ep.ID.eq(er.id)).fetch() val epr = dslRO.selectFrom(ep).where(ep.ID.eq(er.id)).fetch()
HistoricalEventDto( HistoricalEventDto(
id = er.id,
type = er.type, type = er.type,
timestamp = er.timestamp, timestamp = er.timestamp,
bookId = er.bookId, bookId = er.bookId,

View file

@ -2,6 +2,7 @@ package org.gotson.komga.infrastructure.jooq.main
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.gotson.komga.domain.model.MediaExtensionEpub import org.gotson.komga.domain.model.MediaExtensionEpub
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.deserializeMediaExtension import org.gotson.komga.infrastructure.jooq.deserializeMediaExtension
import org.gotson.komga.interfaces.api.kobo.dto.ContributorDto import org.gotson.komga.interfaces.api.kobo.dto.ContributorDto
import org.gotson.komga.interfaces.api.kobo.dto.KoboBookMetadataDto import org.gotson.komga.interfaces.api.kobo.dto.KoboBookMetadataDto
@ -16,9 +17,11 @@ import java.time.ZoneId
@Component @Component
class KoboDtoDao( class KoboDtoDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
private val mapper: ObjectMapper, private val mapper: ObjectMapper,
) : KoboDtoRepository { ) : SplitDslDaoBase(dslRW, dslRO),
KoboDtoRepository {
private val b = Tables.BOOK private val b = Tables.BOOK
private val m = Tables.MEDIA private val m = Tables.MEDIA
private val d = Tables.BOOK_METADATA private val d = Tables.BOOK_METADATA

View file

@ -7,6 +7,7 @@ import org.gotson.komga.domain.model.ContentRestrictions
import org.gotson.komga.domain.model.KomgaUser import org.gotson.komga.domain.model.KomgaUser
import org.gotson.komga.domain.model.UserRoles import org.gotson.komga.domain.model.UserRoles
import org.gotson.komga.domain.persistence.KomgaUserRepository import org.gotson.komga.domain.persistence.KomgaUserRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.UserApiKeyRecord import org.gotson.komga.jooq.main.tables.records.UserApiKeyRecord
import org.gotson.komga.language.toCurrentTimeZone import org.gotson.komga.language.toCurrentTimeZone
@ -21,9 +22,10 @@ import java.time.ZoneId
@Component @Component
class KomgaUserDao( class KomgaUserDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
) : KomgaUserRepository { ) : SplitDslDaoBase(dslRW, dslRO),
KomgaUserRepository {
private val u = Tables.USER private val u = Tables.USER
private val ur = Tables.USER_ROLE private val ur = Tables.USER_ROLE
private val ul = Tables.USER_LIBRARY_SHARING private val ul = Tables.USER_LIBRARY_SHARING

View file

@ -2,6 +2,7 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.Library import org.gotson.komga.domain.model.Library
import org.gotson.komga.domain.persistence.LibraryRepository import org.gotson.komga.domain.persistence.LibraryRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.LibraryRecord import org.gotson.komga.jooq.main.tables.records.LibraryRecord
import org.gotson.komga.language.toCurrentTimeZone import org.gotson.komga.language.toCurrentTimeZone
@ -17,9 +18,10 @@ import java.time.ZoneId
@Component @Component
class LibraryDao( class LibraryDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
) : LibraryRepository { ) : SplitDslDaoBase(dslRW, dslRO),
LibraryRepository {
private val l = Tables.LIBRARY private val l = Tables.LIBRARY
private val ul = Tables.USER_LIBRARY_SHARING private val ul = Tables.USER_LIBRARY_SHARING
private val le = Tables.LIBRARY_EXCLUSIONS private val le = Tables.LIBRARY_EXCLUSIONS

View file

@ -8,6 +8,7 @@ import org.gotson.komga.domain.model.MediaExtension
import org.gotson.komga.domain.model.MediaFile import org.gotson.komga.domain.model.MediaFile
import org.gotson.komga.domain.model.ProxyExtension import org.gotson.komga.domain.model.ProxyExtension
import org.gotson.komga.domain.persistence.MediaRepository import org.gotson.komga.domain.persistence.MediaRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.deserializeMediaExtension import org.gotson.komga.infrastructure.jooq.deserializeMediaExtension
import org.gotson.komga.infrastructure.jooq.serializeJsonGz import org.gotson.komga.infrastructure.jooq.serializeJsonGz
@ -27,11 +28,12 @@ import java.time.ZoneId
@Component @Component
class MediaDao( class MediaDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val mapper: ObjectMapper, private val mapper: ObjectMapper,
) : MediaRepository { ) : SplitDslDaoBase(dslRW, dslRO),
MediaRepository {
private val m = Tables.MEDIA private val m = Tables.MEDIA
private val p = Tables.MEDIA_PAGE private val p = Tables.MEDIA_PAGE
private val f = Tables.MEDIA_FILE private val f = Tables.MEDIA_FILE

View file

@ -5,6 +5,7 @@ import org.gotson.komga.domain.model.PageHashKnown
import org.gotson.komga.domain.model.PageHashMatch import org.gotson.komga.domain.model.PageHashMatch
import org.gotson.komga.domain.model.PageHashUnknown import org.gotson.komga.domain.model.PageHashUnknown
import org.gotson.komga.domain.persistence.PageHashRepository import org.gotson.komga.domain.persistence.PageHashRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.toOrderBy import org.gotson.komga.infrastructure.jooq.toOrderBy
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.PageHashRecord import org.gotson.komga.jooq.main.tables.records.PageHashRecord
@ -25,9 +26,10 @@ import java.time.ZoneId
@Component @Component
class PageHashDao( class PageHashDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
) : PageHashRepository { ) : SplitDslDaoBase(dslRW, dslRO),
PageHashRepository {
private val p = Tables.MEDIA_PAGE private val p = Tables.MEDIA_PAGE
private val b = Tables.BOOK private val b = Tables.BOOK
private val ph = Tables.PAGE_HASH private val ph = Tables.PAGE_HASH

View file

@ -4,6 +4,7 @@ import org.gotson.komga.domain.model.ContentRestrictions
import org.gotson.komga.domain.model.ReadList import org.gotson.komga.domain.model.ReadList
import org.gotson.komga.domain.persistence.ReadListRepository import org.gotson.komga.domain.persistence.ReadListRepository
import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.inOrNoCondition import org.gotson.komga.infrastructure.jooq.inOrNoCondition
import org.gotson.komga.infrastructure.jooq.sortByValues import org.gotson.komga.infrastructure.jooq.sortByValues
@ -32,11 +33,12 @@ import java.util.SortedMap
@Component @Component
class ReadListDao( class ReadListDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
private val luceneHelper: LuceneHelper, private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : ReadListRepository { ) : SplitDslDaoBase(dslRW, dslRO),
ReadListRepository {
private val rl = Tables.READLIST private val rl = Tables.READLIST
private val rlb = Tables.READLIST_BOOK private val rlb = Tables.READLIST_BOOK
private val b = Tables.BOOK private val b = Tables.BOOK

View file

@ -5,6 +5,7 @@ import org.gotson.komga.domain.model.ReadListRequestBookMatchBook
import org.gotson.komga.domain.model.ReadListRequestBookMatchSeries import org.gotson.komga.domain.model.ReadListRequestBookMatchSeries
import org.gotson.komga.domain.model.ReadListRequestBookMatches import org.gotson.komga.domain.model.ReadListRequestBookMatches
import org.gotson.komga.domain.persistence.ReadListRequestRepository import org.gotson.komga.domain.persistence.ReadListRequestRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.noCase import org.gotson.komga.infrastructure.jooq.noCase
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.jooq.DSLContext import org.jooq.DSLContext
@ -18,8 +19,10 @@ import java.time.LocalDate
@Component @Component
class ReadListRequestDao( class ReadListRequestDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext, dslRW: DSLContext,
) : ReadListRequestRepository { @Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
ReadListRequestRepository {
private val sd = Tables.SERIES_METADATA private val sd = Tables.SERIES_METADATA
private val b = Tables.BOOK private val b = Tables.BOOK
private val bd = Tables.BOOK_METADATA private val bd = Tables.BOOK_METADATA

View file

@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import org.gotson.komga.domain.model.R2Locator import org.gotson.komga.domain.model.R2Locator
import org.gotson.komga.domain.model.ReadProgress import org.gotson.komga.domain.model.ReadProgress
import org.gotson.komga.domain.persistence.ReadProgressRepository import org.gotson.komga.domain.persistence.ReadProgressRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable import org.gotson.komga.infrastructure.jooq.TempTable
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.deserializeJsonGz import org.gotson.komga.infrastructure.jooq.deserializeJsonGz
@ -24,11 +25,12 @@ import java.time.ZoneId
@Component @Component
class ReadProgressDao( class ReadProgressDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val mapper: ObjectMapper, private val mapper: ObjectMapper,
) : ReadProgressRepository { ) : SplitDslDaoBase(dslRW, dslRO),
ReadProgressRepository {
private val r = Tables.READ_PROGRESS private val r = Tables.READ_PROGRESS
private val rs = Tables.READ_PROGRESS_SERIES private val rs = Tables.READ_PROGRESS_SERIES
private val b = Tables.BOOK private val b = Tables.BOOK

View file

@ -1,5 +1,6 @@
package org.gotson.komga.infrastructure.jooq.main package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.interfaces.api.persistence.ReadProgressDtoRepository import org.gotson.komga.interfaces.api.persistence.ReadProgressDtoRepository
import org.gotson.komga.interfaces.api.rest.dto.TachiyomiReadProgressDto import org.gotson.komga.interfaces.api.rest.dto.TachiyomiReadProgressDto
import org.gotson.komga.interfaces.api.rest.dto.TachiyomiReadProgressV2Dto import org.gotson.komga.interfaces.api.rest.dto.TachiyomiReadProgressV2Dto
@ -16,8 +17,10 @@ import java.math.BigDecimal
@Component @Component
class ReadProgressDtoDao( class ReadProgressDtoDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext, dslRW: DSLContext,
) : ReadProgressDtoRepository { @Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
ReadProgressDtoRepository {
private val rlb = Tables.READLIST_BOOK private val rlb = Tables.READLIST_BOOK
private val b = Tables.BOOK private val b = Tables.BOOK
private val d = Tables.BOOK_METADATA private val d = Tables.BOOK_METADATA

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.Author import org.gotson.komga.domain.model.Author
import org.gotson.komga.domain.persistence.ReferentialRepository import org.gotson.komga.domain.persistence.ReferentialRepository
import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.udfStripAccents import org.gotson.komga.infrastructure.jooq.udfStripAccents
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.BookMetadataAggregationAuthorRecord import org.gotson.komga.jooq.main.tables.records.BookMetadataAggregationAuthorRecord
@ -22,8 +23,10 @@ import java.time.LocalDate
@Component @Component
class ReferentialDao( class ReferentialDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext, dslRW: DSLContext,
) : ReferentialRepository { @Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
ReferentialRepository {
private val a = Tables.BOOK_METADATA_AUTHOR private val a = Tables.BOOK_METADATA_AUTHOR
private val sd = Tables.SERIES_METADATA private val sd = Tables.SERIES_METADATA
private val bma = Tables.BOOK_METADATA_AGGREGATION private val bma = Tables.BOOK_METADATA_AGGREGATION

View file

@ -4,6 +4,7 @@ import org.gotson.komga.domain.model.ContentRestrictions
import org.gotson.komga.domain.model.SeriesCollection import org.gotson.komga.domain.model.SeriesCollection
import org.gotson.komga.domain.persistence.SeriesCollectionRepository import org.gotson.komga.domain.persistence.SeriesCollectionRepository
import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.inOrNoCondition import org.gotson.komga.infrastructure.jooq.inOrNoCondition
import org.gotson.komga.infrastructure.jooq.sortByValues import org.gotson.komga.infrastructure.jooq.sortByValues
@ -31,11 +32,12 @@ import java.time.ZoneId
@Component @Component
class SeriesCollectionDao( class SeriesCollectionDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
private val luceneHelper: LuceneHelper, private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesCollectionRepository { ) : SplitDslDaoBase(dslRW, dslRO),
SeriesCollectionRepository {
private val c = Tables.COLLECTION private val c = Tables.COLLECTION
private val cs = Tables.COLLECTION_SERIES private val cs = Tables.COLLECTION_SERIES
private val s = Tables.SERIES private val s = Tables.SERIES

View file

@ -6,6 +6,7 @@ import org.gotson.komga.domain.model.Series
import org.gotson.komga.domain.persistence.SeriesRepository import org.gotson.komga.domain.persistence.SeriesRepository
import org.gotson.komga.infrastructure.jooq.RequiredJoin import org.gotson.komga.infrastructure.jooq.RequiredJoin
import org.gotson.komga.infrastructure.jooq.SeriesSearchHelper import org.gotson.komga.infrastructure.jooq.SeriesSearchHelper
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.csAlias import org.gotson.komga.infrastructure.jooq.csAlias
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
@ -28,10 +29,11 @@ import java.time.ZoneId
@Component @Component
class SeriesDao( class SeriesDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesRepository { ) : SplitDslDaoBase(dslRW, dslRO),
SeriesRepository {
private val s = Tables.SERIES private val s = Tables.SERIES
private val d = Tables.SERIES_METADATA private val d = Tables.SERIES_METADATA
private val rs = Tables.READ_PROGRESS_SERIES private val rs = Tables.READ_PROGRESS_SERIES

View file

@ -6,6 +6,7 @@ import org.gotson.komga.domain.model.SeriesSearch
import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource import org.gotson.komga.infrastructure.datasource.SqliteUdfDataSource
import org.gotson.komga.infrastructure.jooq.RequiredJoin import org.gotson.komga.infrastructure.jooq.RequiredJoin
import org.gotson.komga.infrastructure.jooq.SeriesSearchHelper import org.gotson.komga.infrastructure.jooq.SeriesSearchHelper
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.csAlias import org.gotson.komga.infrastructure.jooq.csAlias
import org.gotson.komga.infrastructure.jooq.inOrNoCondition import org.gotson.komga.infrastructure.jooq.inOrNoCondition
@ -52,10 +53,12 @@ const val BOOKS_READ_COUNT = "booksReadCount"
@Component @Component
class SeriesDtoDao( class SeriesDtoDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
private val luceneHelper: LuceneHelper, private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesDtoRepository { ) : SplitDslDaoBase(dslRW, dslRO),
SeriesDtoRepository {
private val s = Tables.SERIES private val s = Tables.SERIES
private val d = Tables.SERIES_METADATA private val d = Tables.SERIES_METADATA
private val rs = Tables.READ_PROGRESS_SERIES private val rs = Tables.READ_PROGRESS_SERIES

View file

@ -4,6 +4,7 @@ import org.gotson.komga.domain.model.AlternateTitle
import org.gotson.komga.domain.model.SeriesMetadata import org.gotson.komga.domain.model.SeriesMetadata
import org.gotson.komga.domain.model.WebLink import org.gotson.komga.domain.model.WebLink
import org.gotson.komga.domain.persistence.SeriesMetadataRepository import org.gotson.komga.domain.persistence.SeriesMetadataRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.SeriesMetadataRecord import org.gotson.komga.jooq.main.tables.records.SeriesMetadataRecord
@ -19,10 +20,11 @@ import java.time.ZoneId
@Component @Component
class SeriesMetadataDao( class SeriesMetadataDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesMetadataRepository { ) : SplitDslDaoBase(dslRW, dslRO),
SeriesMetadataRepository {
private val d = Tables.SERIES_METADATA private val d = Tables.SERIES_METADATA
private val g = Tables.SERIES_METADATA_GENRE private val g = Tables.SERIES_METADATA_GENRE
private val st = Tables.SERIES_METADATA_TAG private val st = Tables.SERIES_METADATA_TAG

View file

@ -1,5 +1,6 @@
package org.gotson.komga.infrastructure.jooq.main package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.jooq.DSLContext import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier import org.springframework.beans.factory.annotation.Qualifier
@ -7,9 +8,9 @@ import org.springframework.stereotype.Component
@Component @Component
class ServerSettingsDao( class ServerSettingsDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
) { ) : SplitDslDaoBase(dslRW, dslRO) {
private val s = Tables.SERVER_SETTINGS private val s = Tables.SERVER_SETTINGS
fun <T> getSettingByKey( fun <T> getSettingByKey(

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.Sidecar import org.gotson.komga.domain.model.Sidecar
import org.gotson.komga.domain.model.SidecarStored import org.gotson.komga.domain.model.SidecarStored
import org.gotson.komga.domain.persistence.SidecarRepository import org.gotson.komga.domain.persistence.SidecarRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.SidecarRecord import org.gotson.komga.jooq.main.tables.records.SidecarRecord
@ -16,10 +17,11 @@ import java.net.URL
@Component @Component
class SidecarDao( class SidecarDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SidecarRepository { ) : SplitDslDaoBase(dslRW, dslRO),
SidecarRepository {
private val sc = Tables.SIDECAR private val sc = Tables.SIDECAR
override fun findAll(): Collection<SidecarStored> = dslRO.selectFrom(sc).fetch().map { it.toDomain() } override fun findAll(): Collection<SidecarStored> = dslRO.selectFrom(sc).fetch().map { it.toDomain() }

View file

@ -8,6 +8,7 @@ import org.gotson.komga.domain.model.SyncPoint.ReadList.Companion.ON_DECK_ID
import org.gotson.komga.domain.persistence.SyncPointRepository import org.gotson.komga.domain.persistence.SyncPointRepository
import org.gotson.komga.infrastructure.jooq.BookSearchHelper import org.gotson.komga.infrastructure.jooq.BookSearchHelper
import org.gotson.komga.infrastructure.jooq.RequiredJoin import org.gotson.komga.infrastructure.jooq.RequiredJoin
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.language.toZonedDateTime import org.gotson.komga.language.toZonedDateTime
import org.jooq.DSLContext import org.jooq.DSLContext
@ -28,10 +29,11 @@ import java.time.ZoneId
@Component @Component
class SyncPointDao( class SyncPointDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
private val bookCommonDao: BookCommonDao, private val bookCommonDao: BookCommonDao,
) : SyncPointRepository { ) : SplitDslDaoBase(dslRW, dslRO),
SyncPointRepository {
private val b = Tables.BOOK private val b = Tables.BOOK
private val m = Tables.MEDIA private val m = Tables.MEDIA
private val d = Tables.BOOK_METADATA private val d = Tables.BOOK_METADATA

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.Dimension import org.gotson.komga.domain.model.Dimension
import org.gotson.komga.domain.model.ThumbnailBook import org.gotson.komga.domain.model.ThumbnailBook
import org.gotson.komga.domain.persistence.ThumbnailBookRepository import org.gotson.komga.domain.persistence.ThumbnailBookRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailBookRecord import org.gotson.komga.jooq.main.tables.records.ThumbnailBookRecord
@ -15,10 +16,11 @@ import java.net.URL
@Component @Component
class ThumbnailBookDao( class ThumbnailBookDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : ThumbnailBookRepository { ) : SplitDslDaoBase(dslRW, dslRO),
ThumbnailBookRepository {
private val tb = Tables.THUMBNAIL_BOOK private val tb = Tables.THUMBNAIL_BOOK
override fun findAllByBookId(bookId: String): Collection<ThumbnailBook> = override fun findAllByBookId(bookId: String): Collection<ThumbnailBook> =

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.Dimension import org.gotson.komga.domain.model.Dimension
import org.gotson.komga.domain.model.ThumbnailReadList import org.gotson.komga.domain.model.ThumbnailReadList
import org.gotson.komga.domain.persistence.ThumbnailReadListRepository import org.gotson.komga.domain.persistence.ThumbnailReadListRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailReadlistRecord import org.gotson.komga.jooq.main.tables.records.ThumbnailReadlistRecord
import org.jooq.DSLContext import org.jooq.DSLContext
@ -12,9 +13,10 @@ import org.springframework.transaction.annotation.Transactional
@Component @Component
class ThumbnailReadListDao( class ThumbnailReadListDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
) : ThumbnailReadListRepository { ) : SplitDslDaoBase(dslRW, dslRO),
ThumbnailReadListRepository {
private val tr = Tables.THUMBNAIL_READLIST private val tr = Tables.THUMBNAIL_READLIST
override fun findAllByReadListId(readListId: String): Collection<ThumbnailReadList> = override fun findAllByReadListId(readListId: String): Collection<ThumbnailReadList> =

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.Dimension import org.gotson.komga.domain.model.Dimension
import org.gotson.komga.domain.model.ThumbnailSeriesCollection import org.gotson.komga.domain.model.ThumbnailSeriesCollection
import org.gotson.komga.domain.persistence.ThumbnailSeriesCollectionRepository import org.gotson.komga.domain.persistence.ThumbnailSeriesCollectionRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailCollectionRecord import org.gotson.komga.jooq.main.tables.records.ThumbnailCollectionRecord
import org.jooq.DSLContext import org.jooq.DSLContext
@ -12,9 +13,10 @@ import org.springframework.transaction.annotation.Transactional
@Component @Component
class ThumbnailSeriesCollectionDao( class ThumbnailSeriesCollectionDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
) : ThumbnailSeriesCollectionRepository { ) : SplitDslDaoBase(dslRW, dslRO),
ThumbnailSeriesCollectionRepository {
private val tc = Tables.THUMBNAIL_COLLECTION private val tc = Tables.THUMBNAIL_COLLECTION
override fun findByIdOrNull(thumbnailId: String): ThumbnailSeriesCollection? = override fun findByIdOrNull(thumbnailId: String): ThumbnailSeriesCollection? =

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.domain.model.Dimension import org.gotson.komga.domain.model.Dimension
import org.gotson.komga.domain.model.ThumbnailSeries import org.gotson.komga.domain.model.ThumbnailSeries
import org.gotson.komga.domain.persistence.ThumbnailSeriesRepository import org.gotson.komga.domain.persistence.ThumbnailSeriesRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable import org.gotson.komga.infrastructure.jooq.TempTable.Companion.withTempTable
import org.gotson.komga.jooq.main.Tables import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailSeriesRecord import org.gotson.komga.jooq.main.tables.records.ThumbnailSeriesRecord
@ -15,10 +16,11 @@ import java.net.URL
@Component @Component
class ThumbnailSeriesDao( class ThumbnailSeriesDao(
private val dslRW: DSLContext, dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext, @Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : ThumbnailSeriesRepository { ) : SplitDslDaoBase(dslRW, dslRO),
ThumbnailSeriesRepository {
private val ts = Tables.THUMBNAIL_SERIES private val ts = Tables.THUMBNAIL_SERIES
override fun findByIdOrNull(thumbnailId: String): ThumbnailSeries? = override fun findByIdOrNull(thumbnailId: String): ThumbnailSeries? =

View file

@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import org.gotson.komga.application.tasks.Task import org.gotson.komga.application.tasks.Task
import org.gotson.komga.application.tasks.TasksRepository import org.gotson.komga.application.tasks.TasksRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.tasks.Tables import org.gotson.komga.jooq.tasks.Tables
import org.jooq.DSLContext import org.jooq.DSLContext
import org.jooq.Query import org.jooq.Query
@ -22,11 +23,12 @@ private val logger = KotlinLogging.logger {}
@Component @Component
@DependsOn("flywaySecondaryMigrationInitializer") @DependsOn("flywaySecondaryMigrationInitializer")
class TasksDao( class TasksDao(
@Qualifier("tasksDslContextRW") private val dslRW: DSLContext, @Qualifier("tasksDslContextRW") dslRW: DSLContext,
@Qualifier("tasksDslContextRO") private val dslRO: DSLContext, @Qualifier("tasksDslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.tasksDb.batchChunkSize}") private val batchSize: Int, @param:Value("#{@komgaProperties.tasksDb.batchChunkSize}") private val batchSize: Int,
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
) : TasksRepository { ) : SplitDslDaoBase(dslRW, dslRO),
TasksRepository {
private val t = Tables.TASK private val t = Tables.TASK
private val tasksAvailableCondition = private val tasksAvailableCondition =

View file

@ -18,6 +18,7 @@ import org.springframework.util.LinkedMultiValueMap
import org.springframework.web.client.RestClient import org.springframework.web.client.RestClient
import org.springframework.web.client.toEntity import org.springframework.web.client.toEntity
import org.springframework.web.server.ResponseStatusException import org.springframework.web.server.ResponseStatusException
import org.springframework.web.util.DefaultUriBuilderFactory
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
import kotlin.time.toJavaDuration import kotlin.time.toJavaDuration
@ -29,11 +30,15 @@ class KoboProxy(
private val komgaSyncTokenGenerator: KomgaSyncTokenGenerator, private val komgaSyncTokenGenerator: KomgaSyncTokenGenerator,
private val komgaSettingsProvider: KomgaSettingsProvider, private val komgaSettingsProvider: KomgaSettingsProvider,
) { ) {
private val koboApiClient = private val koboApiClient: RestClient =
RestClient RestClient
.builder() .builder()
.baseUrl("https://storeapi.kobo.com") .uriBuilderFactory(
.requestFactory( DefaultUriBuilderFactory("https://storeapi.kobo.com")
.apply {
this.encodingMode = DefaultUriBuilderFactory.EncodingMode.NONE
},
).requestFactory(
ClientHttpRequestFactoryBuilder.reactor().build( ClientHttpRequestFactoryBuilder.reactor().build(
ClientHttpRequestFactorySettings ClientHttpRequestFactorySettings
.defaults() .defaults()
@ -42,7 +47,7 @@ class KoboProxy(
), ),
).build() ).build()
private val pathRegex = """\/kobo\/[-\w]*(.*)""".toRegex() private val pathRegex = """/kobo/[-\w]*(.*)""".toRegex()
private val headersOutInclude = private val headersOutInclude =
setOf( setOf(
@ -50,6 +55,7 @@ class KoboProxy(
HttpHeaders.USER_AGENT, HttpHeaders.USER_AGENT,
HttpHeaders.ACCEPT, HttpHeaders.ACCEPT,
HttpHeaders.ACCEPT_LANGUAGE, HttpHeaders.ACCEPT_LANGUAGE,
HttpHeaders.CONTENT_TYPE,
) )
private val headersOutExclude = private val headersOutExclude =
@ -88,7 +94,7 @@ class KoboProxy(
.uri { uriBuilder -> .uri { uriBuilder ->
uriBuilder uriBuilder
.path(path) .path(path)
.queryParams(LinkedMultiValueMap(request.parameterMap.mapValues { it.value.toList() })) .query(request.queryString)
.build() .build()
.also { logger.debug { "Proxy URL: $it" } } .also { logger.debug { "Proxy URL: $it" } }
}.headers { headersOut -> }.headers { headersOut ->
@ -110,6 +116,7 @@ class KoboProxy(
}.apply { if (body != null) body(body) } }.apply { if (body != null) body(body) }
.retrieve() .retrieve()
.onStatus(HttpStatusCode::isError) { _, response -> .onStatus(HttpStatusCode::isError) { _, response ->
logger.debug { "Kobo response: ${response.statusCode}: ${response.body.bufferedReader().use { it.readText() }}" }
throw ResponseStatusException(response.statusCode, response.statusText) throw ResponseStatusException(response.statusCode, response.statusText)
}.toEntity<JsonNode>() }.toEntity<JsonNode>()
@ -161,6 +168,7 @@ class KoboProxy(
"audiobook_subscription_tiers": "https://www.kobo.com/{region}/{language}/checkoutoption/21C6D938-934B-4A91-B979-E14D70B2F280", "audiobook_subscription_tiers": "https://www.kobo.com/{region}/{language}/checkoutoption/21C6D938-934B-4A91-B979-E14D70B2F280",
"authorproduct_recommendations": "https://storeapi.kobo.com/v1/products/books/authors/recommendations", "authorproduct_recommendations": "https://storeapi.kobo.com/v1/products/books/authors/recommendations",
"autocomplete": "https://storeapi.kobo.com/v1/products/autocomplete", "autocomplete": "https://storeapi.kobo.com/v1/products/autocomplete",
"bam": "https://storeapi.kobo.com/v2/activity/bam/success",
"blackstone_header": { "blackstone_header": {
"key": "x-amz-request-payer", "key": "x-amz-request-payer",
"value": "requester" "value": "requester"
@ -173,6 +181,7 @@ class KoboProxy(
"browse_history": "https://storeapi.kobo.com/v1/user/browsehistory", "browse_history": "https://storeapi.kobo.com/v1/user/browsehistory",
"categories": "https://storeapi.kobo.com/v1/categories", "categories": "https://storeapi.kobo.com/v1/categories",
"categories_page": "https://www.kobo.com/ebooks/categories", "categories_page": "https://www.kobo.com/ebooks/categories",
"categoriesv2": "https://storeapi.kobo.com/api/v2/Categories/Top",
"category": "https://storeapi.kobo.com/v1/categories/{CategoryId}", "category": "https://storeapi.kobo.com/v1/categories/{CategoryId}",
"category_featured_lists": "https://storeapi.kobo.com/v1/categories/{CategoryId}/featured", "category_featured_lists": "https://storeapi.kobo.com/v1/categories/{CategoryId}/featured",
"category_products": "https://storeapi.kobo.com/v1/categories/{CategoryId}/products", "category_products": "https://storeapi.kobo.com/v1/categories/{CategoryId}/products",
@ -180,6 +189,7 @@ class KoboProxy(
"client_authd_referral": "https://authorize.kobo.com/api/AuthenticatedReferral/client/v1/getLink", "client_authd_referral": "https://authorize.kobo.com/api/AuthenticatedReferral/client/v1/getLink",
"configuration_data": "https://storeapi.kobo.com/v1/configuration", "configuration_data": "https://storeapi.kobo.com/v1/configuration",
"content_access_book": "https://storeapi.kobo.com/v1/products/books/{ProductId}/access", "content_access_book": "https://storeapi.kobo.com/v1/products/books/{ProductId}/access",
"contributorsv2": "https://storeapi.kobo.com/v2/contributors/author",
"customer_care_live_chat": "https://v2.zopim.com/widget/livechat.html?key=Y6gwUmnu4OATxN3Tli4Av9bYN319BTdO", "customer_care_live_chat": "https://v2.zopim.com/widget/livechat.html?key=Y6gwUmnu4OATxN3Tli4Av9bYN319BTdO",
"daily_deal": "https://storeapi.kobo.com/v1/products/dailydeal", "daily_deal": "https://storeapi.kobo.com/v1/products/dailydeal",
"deals": "https://storeapi.kobo.com/v1/deals", "deals": "https://storeapi.kobo.com/v1/deals",
@ -190,14 +200,19 @@ class KoboProxy(
"device_refresh": "https://storeapi.kobo.com/v1/auth/refresh", "device_refresh": "https://storeapi.kobo.com/v1/auth/refresh",
"dictionary_host": "https://ereaderfiles.kobo.com", "dictionary_host": "https://ereaderfiles.kobo.com",
"discovery_host": "https://discovery.kobobooks.com", "discovery_host": "https://discovery.kobobooks.com",
"display_accessibility_enabled": "False",
"display_parental_controls_enabled": "True",
"dropbox_link_account_poll": "https://authorize.kobo.com/{region}/{language}/LinkDropbox", "dropbox_link_account_poll": "https://authorize.kobo.com/{region}/{language}/LinkDropbox",
"dropbox_link_account_start": "https://authorize.kobo.com/LinkDropbox/start", "dropbox_link_account_start": "https://authorize.kobo.com/LinkDropbox/start",
"ereaderdevices": "https://storeapi.kobo.com/v2/products/EReaderDeviceFeeds",
"eula_page": "https://www.kobo.com/termsofuse?style=onestore", "eula_page": "https://www.kobo.com/termsofuse?style=onestore",
"exchange_auth": "https://storeapi.kobo.com/v1/auth/exchange", "exchange_auth": "https://storeapi.kobo.com/v1/auth/exchange",
"external_book": "https://storeapi.kobo.com/v1/products/books/external/{Ids}", "external_book": "https://storeapi.kobo.com/v1/products/books/external/{Ids}",
"facebook_sso_page": "https://authorize.kobo.com/signin/provider/Facebook/login?returnUrl=http://kobo.com/", "facebook_sso_page": "https://authorize.kobo.com/signin/provider/Facebook/login?returnUrl=https://kobo.com/",
"featured_list": "https://storeapi.kobo.com/v1/products/featured/{FeaturedListId}", "featured_list": "https://storeapi.kobo.com/v1/products/featured/{FeaturedListId}",
"featured_lists": "https://storeapi.kobo.com/v1/products/featured", "featured_lists": "https://storeapi.kobo.com/v1/products/featured",
"featuredlist2": "https://storeapi.kobo.com/v2/products/list/featured",
"fixed_layout_page_cache_enabled": "True",
"free_books_page": { "free_books_page": {
"EN": "https://www.kobo.com/{region}/{language}/p/free-ebooks", "EN": "https://www.kobo.com/{region}/{language}/p/free-ebooks",
"FR": "https://www.kobo.com/{region}/{language}/p/livres-gratuits", "FR": "https://www.kobo.com/{region}/{language}/p/livres-gratuits",
@ -207,6 +222,7 @@ class KoboProxy(
}, },
"fte_feedback": "https://storeapi.kobo.com/v1/products/ftefeedback", "fte_feedback": "https://storeapi.kobo.com/v1/products/ftefeedback",
"funnel_metrics": "https://storeapi.kobo.com/v1/funnelmetrics", "funnel_metrics": "https://storeapi.kobo.com/v1/funnelmetrics",
"geography_data": "https://storeapi.kobo.com/v2/configuration/geography/country",
"get_download_keys": "https://storeapi.kobo.com/v1/library/downloadkeys", "get_download_keys": "https://storeapi.kobo.com/v1/library/downloadkeys",
"get_download_link": "https://storeapi.kobo.com/v1/library/downloadlink", "get_download_link": "https://storeapi.kobo.com/v1/library/downloadlink",
"get_tests_request": "https://storeapi.kobo.com/v1/analytics/gettests", "get_tests_request": "https://storeapi.kobo.com/v1/analytics/gettests",
@ -214,10 +230,13 @@ class KoboProxy(
"giftcard_redeem_url": "https://www.kobo.com/{storefront}/{language}/redeem", "giftcard_redeem_url": "https://www.kobo.com/{storefront}/{language}/redeem",
"googledrive_link_account_start": "https://authorize.kobo.com/{region}/{language}/linkcloudstorage/provider/google_drive", "googledrive_link_account_start": "https://authorize.kobo.com/{region}/{language}/linkcloudstorage/provider/google_drive",
"gpb_flow_enabled": "False", "gpb_flow_enabled": "False",
"help_page": "http://www.kobo.com/help", "help_page": "https://www.kobo.com/help",
"image_host": "//cdn.kobo.com/book-images/", "image_host": "//cdn.kobo.com/book-images/",
"image_url_quality_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/{Quality}/{IsGreyscale}/image.jpg", "image_url_quality_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/{Quality}/{IsGreyscale}/image.jpg",
"image_url_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/false/image.jpg", "image_url_template": "https://cdn.kobo.com/book-images/{ImageId}/{Width}/{Height}/false/image.jpg",
"instapaper_enabled": "True",
"instapaper_env_url": "https://www.instapaper.com/api/kobo",
"instapaper_link_account_start": "https://authorize.kobo.com/{region}/{language}/linkinstapaper",
"kobo_audiobooks_credit_redemption": "True", "kobo_audiobooks_credit_redemption": "True",
"kobo_audiobooks_enabled": "True", "kobo_audiobooks_enabled": "True",
"kobo_audiobooks_orange_deal_enabled": "True", "kobo_audiobooks_orange_deal_enabled": "True",
@ -244,7 +263,8 @@ class KoboProxy(
"love_dashboard_page": "https://www.kobo.com/{region}/{language}/kobosuperpoints", "love_dashboard_page": "https://www.kobo.com/{region}/{language}/kobosuperpoints",
"love_points_redemption_page": "https://www.kobo.com/{region}/{language}/KoboSuperPointsRedemption?productId={ProductId}", "love_points_redemption_page": "https://www.kobo.com/{region}/{language}/KoboSuperPointsRedemption?productId={ProductId}",
"magazine_landing_page": "https://www.kobo.com/emagazines", "magazine_landing_page": "https://www.kobo.com/emagazines",
"more_sign_in_options": "https://authorize.kobo.com/signin?returnUrl=http://kobo.com/#allProviders", "more_sign_in_options": "https://authorize.kobo.com/signin?returnUrl=https://kobo.com/#allProviders",
"morebyauthor": "https://storeapi.kobo.com/v2/products/recommendations/morebyauthor",
"notebooks": "https://storeapi.kobo.com/api/internal/notebooks", "notebooks": "https://storeapi.kobo.com/api/internal/notebooks",
"notifications_registration_issue": "https://storeapi.kobo.com/v1/notifications/registration", "notifications_registration_issue": "https://storeapi.kobo.com/v1/notifications/registration",
"oauth_host": "https://oauth.kobo.com", "oauth_host": "https://oauth.kobo.com",
@ -258,9 +278,13 @@ class KoboProxy(
"product_prices": "https://storeapi.kobo.com/v1/products/{ProductIds}/prices", "product_prices": "https://storeapi.kobo.com/v1/products/{ProductIds}/prices",
"product_recommendations": "https://storeapi.kobo.com/v1/products/{ProductId}/recommendations", "product_recommendations": "https://storeapi.kobo.com/v1/products/{ProductId}/recommendations",
"product_reviews": "https://storeapi.kobo.com/v1/products/{ProductIds}/reviews", "product_reviews": "https://storeapi.kobo.com/v1/products/{ProductIds}/reviews",
"productbyid": "https://storeapi.kobo.com/v2/products/itemDetailById/{ProductType}/{Id}",
"productbyslug": "https://storeapi.kobo.com/v2/products/itemDetail/{ProductType}/{Slug}",
"products": "https://storeapi.kobo.com/v1/products", "products": "https://storeapi.kobo.com/v1/products",
"productstatebyid": "https://storeapi.kobo.com/v2/products/itemStateById/{ProductType}/{Id}",
"productstatebyslug": "https://storeapi.kobo.com/v2/products/itemState/{ProductType}/{Slug}",
"productsv2": "https://storeapi.kobo.com/v2/products", "productsv2": "https://storeapi.kobo.com/v2/products",
"provider_external_sign_in_page": "https://authorize.kobo.com/ExternalSignIn/{providerName}?returnUrl=http://kobo.com/", "provider_external_sign_in_page": "https://authorize.kobo.com/ExternalSignIn/{providerName}?returnUrl=https://kobo.com/",
"purchase_buy": "https://www.kobo.com/checkoutoption/", "purchase_buy": "https://www.kobo.com/checkoutoption/",
"purchase_buy_templated": "https://www.kobo.com/{region}/{language}/checkoutoption/{ProductId}", "purchase_buy_templated": "https://www.kobo.com/{region}/{language}/checkoutoption/{ProductId}",
"quickbuy_checkout": "https://storeapi.kobo.com/v1/store/quickbuy/{PurchaseId}/checkout", "quickbuy_checkout": "https://storeapi.kobo.com/v1/store/quickbuy/{PurchaseId}/checkout",
@ -270,7 +294,9 @@ class KoboProxy(
"reading_services_host": "https://readingservices.kobo.com", "reading_services_host": "https://readingservices.kobo.com",
"reading_state": "https://storeapi.kobo.com/v1/library/{Ids}/state", "reading_state": "https://storeapi.kobo.com/v1/library/{Ids}/state",
"redeem_interstitial_page": "https://www.kobo.com", "redeem_interstitial_page": "https://www.kobo.com",
"registration_page": "https://authorize.kobo.com/signup?returnUrl=http://kobo.com/", "reflowable_page_cache_enabled": "True",
"registration_page": "https://authorize.kobo.com/signup?returnUrl=https://kobo.com/",
"related": "https://storeapi.kobo.com/v2/products/recommendations/related",
"related_items": "https://storeapi.kobo.com/v1/products/{Id}/related", "related_items": "https://storeapi.kobo.com/v1/products/{Id}/related",
"remaining_book_series": "https://storeapi.kobo.com/v1/products/books/series/{SeriesId}", "remaining_book_series": "https://storeapi.kobo.com/v1/products/books/series/{SeriesId}",
"rename_tag": "https://storeapi.kobo.com/v1/library/tags/{TagId}", "rename_tag": "https://storeapi.kobo.com/v1/library/tags/{TagId}",
@ -293,14 +319,19 @@ class KoboProxy(
"tags": "https://storeapi.kobo.com/v1/library/tags", "tags": "https://storeapi.kobo.com/v1/library/tags",
"taste_profile": "https://storeapi.kobo.com/v1/products/tasteprofile", "taste_profile": "https://storeapi.kobo.com/v1/products/tasteprofile",
"terms_of_sale_page": "https://authorize.kobo.com/{region}/{language}/terms/termsofsale", "terms_of_sale_page": "https://authorize.kobo.com/{region}/{language}/terms/termsofsale",
"topproducts": "https://storeapi.kobo.com/v2/products/list/topproducts",
"tracking": "https://storeapi.kobo.com/v2/tracking/searchperformed",
"update_accessibility_to_preview": "https://storeapi.kobo.com/v1/library/{EntitlementIds}/preview", "update_accessibility_to_preview": "https://storeapi.kobo.com/v1/library/{EntitlementIds}/preview",
"use_one_store": "True", "use_one_store": "True",
"user_currencyconversion": "https://storeapi.kobo.com/v1/user/currency/convert",
"user_loyalty_benefits": "https://storeapi.kobo.com/v1/user/loyalty/benefits", "user_loyalty_benefits": "https://storeapi.kobo.com/v1/user/loyalty/benefits",
"user_platform": "https://storeapi.kobo.com/v1/user/platform", "user_platform": "https://storeapi.kobo.com/v1/user/platform",
"user_profile": "https://storeapi.kobo.com/v1/user/profile", "user_profile": "https://storeapi.kobo.com/v1/user/profile",
"user_ratings": "https://storeapi.kobo.com/v1/user/ratings", "user_ratings": "https://storeapi.kobo.com/v1/user/ratings",
"user_recommendations": "https://storeapi.kobo.com/v1/user/recommendations", "user_recommendations": "https://storeapi.kobo.com/v1/user/recommendations",
"user_reviews": "https://storeapi.kobo.com/v1/user/reviews", "user_reviews": "https://storeapi.kobo.com/v1/user/reviews",
"user_tasteprofile_complete": "https://storeapi.kobo.com/v2/user/tasteprofile/complete",
"user_tasteprofile_genre": "https://storeapi.kobo.com/v2/user/tasteprofile/genre",
"user_wishlist": "https://storeapi.kobo.com/v1/user/wishlist", "user_wishlist": "https://storeapi.kobo.com/v1/user/wishlist",
"userguide_host": "https://ereaderfiles.kobo.com", "userguide_host": "https://ereaderfiles.kobo.com",
"wishlist_page": "https://www.kobo.com/{region}/{language}/account/wishlist" "wishlist_page": "https://www.kobo.com/{region}/{language}/account/wishlist"

View file

@ -30,7 +30,7 @@ inline fun <R> Path.epub(block: (EpubPackage) -> R): R =
*/ */
fun ZipFile.getPackagePath(): String = fun ZipFile.getPackagePath(): String =
getEntryInputStream("META-INF/container.xml") getEntryInputStream("META-INF/container.xml")
?.use { Jsoup.parse(it, null, "") } ?.use { Jsoup.parse(it, null, "", Parser.xmlParser()) }
?.getElementsByTag("rootfile") ?.getElementsByTag("rootfile")
?.first() ?.first()
?.attr("full-path") ?: throw MediaUnsupportedException("META-INF/container.xml does not contain rootfile tag") ?.attr("full-path") ?: throw MediaUnsupportedException("META-INF/container.xml does not contain rootfile tag")

View file

@ -60,14 +60,14 @@ class EpubExtractor(
manifest.values.firstOrNull { it.properties.contains("cover-image") } manifest.values.firstOrNull { it.properties.contains("cover-image") }
?: // EPUB 2 - get cover from meta element with name="cover" ?: // EPUB 2 - get cover from meta element with name="cover"
opfDoc opfDoc
.selectFirst("metadata > meta[name=cover]") .selectFirst("*|metadata > *|meta[name=cover]")
?.attr("content") ?.attr("content")
?.ifBlank { null } ?.ifBlank { null }
?.let { manifest[it] } ?.let { manifest[it] }
?: // try id="cover-image" ?: // try id="cover-image"
manifest.values.firstOrNull { it.id == "cover-image" } manifest.values.firstOrNull { it.id == "cover-image" }
if (coverManifestItem != null) { if (coverManifestItem != null) {
val href = coverManifestItem.href val href = URLDecoder.decode(coverManifestItem.href, Charsets.UTF_8)
val mediaType = coverManifestItem.mediaType val mediaType = coverManifestItem.mediaType
val coverPath = normalizeHref(opfDir, href) val coverPath = normalizeHref(opfDir, href)
zip.getEntryBytes(coverPath)?.let { coverBytes -> zip.getEntryBytes(coverPath)?.let { coverBytes ->
@ -84,7 +84,7 @@ class EpubExtractor(
fun getResources(epub: EpubPackage): List<MediaFile> { fun getResources(epub: EpubPackage): List<MediaFile> {
val spine = val spine =
epub.opfDoc epub.opfDoc
.select("spine > itemref") .select("*|spine > *|itemref")
.map { it.attr("idref") } .map { it.attr("idref") }
.mapNotNull { epub.manifest[it] } .mapNotNull { epub.manifest[it] }
@ -126,7 +126,7 @@ class EpubExtractor(
run { run {
val spine = val spine =
epub.opfDoc epub.opfDoc
.select("spine > itemref") .select("*|spine > *|itemref")
.map { it.attr("idref") } .map { it.attr("idref") }
.mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } } .mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } }
@ -137,7 +137,7 @@ class EpubExtractor(
val pagesWithImages = val pagesWithImages =
epub.opfDoc epub.opfDoc
.select("spine > itemref") .select("*|spine > *|itemref")
.map { it.attr("idref") } .map { it.attr("idref") }
.mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } } .mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } }
.map { pagePath -> .map { pagePath ->
@ -219,7 +219,7 @@ class EpubExtractor(
fun computePageCount(epub: EpubPackage): Int { fun computePageCount(epub: EpubPackage): Int {
val spine = val spine =
epub.opfDoc epub.opfDoc
.select("spine > itemref") .select("*|spine > *|itemref")
.map { it.attr("idref") } .map { it.attr("idref") }
.mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } } .mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } }
@ -230,8 +230,8 @@ class EpubExtractor(
} }
fun isFixedLayout(epub: EpubPackage) = fun isFixedLayout(epub: EpubPackage) =
epub.opfDoc.selectFirst("metadata > *|meta[property=rendition:layout]")?.text() == "pre-paginated" || epub.opfDoc.selectFirst("*|metadata > *|meta[property=rendition:layout]")?.text() == "pre-paginated" ||
epub.opfDoc.selectFirst("metadata > *|meta[name=fixed-layout]")?.attr("content") == "true" epub.opfDoc.selectFirst("*|metadata > *|meta[name=fixed-layout]")?.attr("content") == "true"
fun computePositions( fun computePositions(
epub: EpubPackage, epub: EpubPackage,

View file

@ -4,6 +4,7 @@ import org.gotson.komga.domain.model.EpubTocEntry
import org.gotson.komga.infrastructure.util.getEntryBytes import org.gotson.komga.infrastructure.util.getEntryBytes
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.parser.Parser
import java.net.URLDecoder import java.net.URLDecoder
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.Path import kotlin.io.path.Path
@ -23,7 +24,7 @@ fun processNcx(
navType: Epub2Nav, navType: Epub2Nav,
): List<EpubTocEntry> = ): List<EpubTocEntry> =
Jsoup Jsoup
.parse(document.content) .parse(document.content, "", Parser.xmlParser())
.select("${navType.level1} > ${navType.level2}") .select("${navType.level1} > ${navType.level2}")
.toList() .toList()
.mapNotNull { ncxElementToTocEntry(navType, it, document.path.parent) } .mapNotNull { ncxElementToTocEntry(navType, it, document.path.parent) }

View file

@ -8,7 +8,7 @@ import java.nio.file.Paths
import kotlin.io.path.invariantSeparatorsPathString import kotlin.io.path.invariantSeparatorsPathString
fun Document.getManifest() = fun Document.getManifest() =
select("manifest > item").associate { select("*|manifest > *|item").associate {
it.attr("id") to it.attr("id") to
ManifestItem( ManifestItem(
it.attr("id"), it.attr("id"),
@ -36,8 +36,8 @@ fun processOpfGuide(
opf: Document, opf: Document,
opfDir: Path?, opfDir: Path?,
): List<EpubTocEntry> { ): List<EpubTocEntry> {
val guide = opf.selectFirst("guide") ?: return emptyList() val guide = opf.selectFirst("*|guide") ?: return emptyList()
return guide.select("reference").map { ref -> return guide.select("*|reference").map { ref ->
EpubTocEntry( EpubTocEntry(
ref.attr("title"), ref.attr("title"),
ref.attr("href").ifBlank { null }?.let { normalizeHref(opfDir, URLDecoder.decode(it, Charsets.UTF_8)) }, ref.attr("href").ifBlank { null }?.let { normalizeHref(opfDir, URLDecoder.decode(it, Charsets.UTF_8)) },

View file

@ -51,22 +51,22 @@ class EpubMetadataProvider(
getPackageFileContent(book.book.path)?.let { packageFile -> getPackageFileContent(book.book.path)?.let { packageFile ->
val opf = Jsoup.parse(packageFile, "", Parser.xmlParser()) val opf = Jsoup.parse(packageFile, "", Parser.xmlParser())
val title = opf.selectFirst("metadata > dc|title")?.text()?.ifBlank { null } val title = opf.selectFirst("*|metadata > *|title")?.text()?.ifBlank { null }
val description = val description =
opf opf
.selectFirst("metadata > dc|description") .selectFirst("*|metadata > *|description")
?.text() ?.text()
?.let { Jsoup.clean(it, Safelist.none()) } ?.let { Jsoup.clean(it, Safelist.none()) }
?.ifBlank { null } ?.ifBlank { null }
val date = opf.selectFirst("metadata > dc|date")?.text()?.let { parseDate(it) } val date = opf.selectFirst("*|metadata > *|date")?.text()?.let { parseDate(it) }
val authorRoles = val authorRoles =
opf opf
.select("metadata > *|meta[property=role][scheme=marc:relators]") .select("*|metadata > *|meta[property=role][scheme=marc:relators]")
.associate { it.attr("refines").removePrefix("#") to it.text() } .associate { it.attr("refines").removePrefix("#") to it.text() }
val authors = val authors =
opf opf
.select("metadata > dc|creator") .select("*|metadata > *|creator")
.mapNotNull { el -> .mapNotNull { el ->
val name = el.text().trim() val name = el.text().trim()
if (name.isBlank()) { if (name.isBlank()) {
@ -81,16 +81,16 @@ class EpubMetadataProvider(
val isbn = val isbn =
opf opf
.select("metadata > dc|identifier") .select("*|metadata > *|identifier")
.map { it.text().lowercase().removePrefix("isbn:") } .map { it.text().lowercase().removePrefix("isbn:") }
.firstNotNullOfOrNull { isbnValidator.validate(it) } .firstNotNullOfOrNull { isbnValidator.validate(it) }
val seriesIndex = val seriesIndex =
opf opf
.selectFirst("metadata > *|meta[property=belongs-to-collection]") .selectFirst("*|metadata > *|meta[property=belongs-to-collection]")
?.attr("id") ?.attr("id")
?.let { id -> ?.let { id ->
opf.selectFirst("metadata > *|meta[refines=#$id][property=group-position]") opf.selectFirst("*|metadata > *|meta[refines=#$id][property=group-position]")
}?.text() }?.text()
return BookMetadataPatch( return BookMetadataPatch(
@ -116,18 +116,18 @@ class EpubMetadataProvider(
getPackageFileContent(book.book.path)?.let { packageFile -> getPackageFileContent(book.book.path)?.let { packageFile ->
val opf = Jsoup.parse(packageFile, "", Parser.xmlParser()) val opf = Jsoup.parse(packageFile, "", Parser.xmlParser())
val series = opf.selectFirst("metadata > *|meta[property=belongs-to-collection]")?.text()?.ifBlank { null } val series = opf.selectFirst("*|metadata > *|meta[property=belongs-to-collection]")?.text()?.ifBlank { null }
val publisher = opf.selectFirst("metadata > dc|publisher")?.text()?.ifBlank { null } val publisher = opf.selectFirst("*|metadata > *|publisher")?.text()?.ifBlank { null }
val language = opf.selectFirst("metadata > dc|language")?.text()?.ifBlank { null } val language = opf.selectFirst("*|metadata > *|language")?.text()?.ifBlank { null }
val genres = val genres =
opf opf
.select("metadata > dc|subject") .select("*|metadata > *|subject")
.mapNotNull { it.text().trim().ifBlank { null } } .mapNotNull { it.text().trim().ifBlank { null } }
.toSet() .toSet()
.ifEmpty { null } .ifEmpty { null }
val direction = val direction =
opf.getElementsByTag("spine").first()?.attr("page-progression-direction")?.let { opf.selectFirst("*|spine")?.attr("page-progression-direction")?.let {
when (it) { when (it) {
"rtl" -> SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT "rtl" -> SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT
"ltr" -> SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT "ltr" -> SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT

View file

@ -28,7 +28,7 @@ class LocalArtworkProvider(
private val imageAnalyzer: ImageAnalyzer, private val imageAnalyzer: ImageAnalyzer,
) : SidecarSeriesConsumer, ) : SidecarSeriesConsumer,
SidecarBookConsumer { SidecarBookConsumer {
val supportedExtensions = listOf("png", "jpeg", "jpg", "tbn", "webp") val supportedExtensions = listOf("png", "jpeg", "jpg", "tbn", "webp", "gif")
val supportedSeriesFiles = listOf("cover", "default", "folder", "poster", "series") val supportedSeriesFiles = listOf("cover", "default", "folder", "poster", "series")
fun getBookThumbnails(book: Book): List<ThumbnailBook> { fun getBookThumbnails(book: Book): List<ThumbnailBook> {

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.security
import jakarta.servlet.Filter import jakarta.servlet.Filter
import org.gotson.komga.domain.model.UserRoles import org.gotson.komga.domain.model.UserRoles
import org.gotson.komga.infrastructure.configuration.KomgaSettingsProvider import org.gotson.komga.infrastructure.configuration.KomgaSettingsProvider
import org.gotson.komga.infrastructure.hash.Hasher
import org.gotson.komga.infrastructure.security.apikey.ApiKeyAuthenticationFilter import org.gotson.komga.infrastructure.security.apikey.ApiKeyAuthenticationFilter
import org.gotson.komga.infrastructure.security.apikey.ApiKeyAuthenticationProvider import org.gotson.komga.infrastructure.security.apikey.ApiKeyAuthenticationProvider
import org.gotson.komga.infrastructure.security.apikey.HeaderApiKeyAuthenticationConverter import org.gotson.komga.infrastructure.security.apikey.HeaderApiKeyAuthenticationConverter
@ -34,6 +35,7 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher
@Configuration @Configuration
@ -51,6 +53,7 @@ class SecurityConfiguration(
private val opdsAuthenticationEntryPoint: OpdsAuthenticationEntryPoint, private val opdsAuthenticationEntryPoint: OpdsAuthenticationEntryPoint,
private val authenticationEventPublisher: AuthenticationEventPublisher, private val authenticationEventPublisher: AuthenticationEventPublisher,
private val tokenEncoder: TokenEncoder, private val tokenEncoder: TokenEncoder,
private val hasher: Hasher,
clientRegistrationRepository: InMemoryClientRegistrationRepository?, clientRegistrationRepository: InMemoryClientRegistrationRepository?,
) { ) {
private val oauth2Enabled = clientRegistrationRepository != null private val oauth2Enabled = clientRegistrationRepository != null
@ -158,7 +161,7 @@ class SecurityConfiguration(
) )
} }
http.addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter::class.java) http.addFilterAfter(restAuthenticationFilter(), BasicAuthenticationFilter::class.java)
return http.build() return http.build()
} }
@ -239,19 +242,19 @@ class SecurityConfiguration(
fun koboAuthenticationFilter(): Filter = fun koboAuthenticationFilter(): Filter =
ApiKeyAuthenticationFilter( ApiKeyAuthenticationFilter(
apiKeyAuthenticationProvider(), apiKeyAuthenticationProvider(),
UriRegexApiKeyAuthenticationConverter(Regex("""/kobo/([\w-]+)"""), tokenEncoder, userAgentWebAuthenticationDetailsSource), UriRegexApiKeyAuthenticationConverter(Regex("""/kobo/([\w-]+)"""), hasher, tokenEncoder, userAgentWebAuthenticationDetailsSource),
) )
fun kosyncAuthenticationFilter(): Filter = fun kosyncAuthenticationFilter(): Filter =
ApiKeyAuthenticationFilter( ApiKeyAuthenticationFilter(
apiKeyAuthenticationProvider(), apiKeyAuthenticationProvider(),
HeaderApiKeyAuthenticationConverter("X-Auth-User", tokenEncoder, userAgentWebAuthenticationDetailsSource), HeaderApiKeyAuthenticationConverter("X-Auth-User", hasher, tokenEncoder, userAgentWebAuthenticationDetailsSource),
) )
fun restAuthenticationFilter(): Filter = fun restAuthenticationFilter(): Filter =
ApiKeyAuthenticationFilter( ApiKeyAuthenticationFilter(
apiKeyAuthenticationProvider(), apiKeyAuthenticationProvider(),
HeaderApiKeyAuthenticationConverter("X-API-Key", tokenEncoder, userAgentWebAuthenticationDetailsSource), HeaderApiKeyAuthenticationConverter("X-API-Key", hasher, tokenEncoder, userAgentWebAuthenticationDetailsSource),
) )
fun apiKeyAuthenticationProvider(): AuthenticationManager = fun apiKeyAuthenticationProvider(): AuthenticationManager =

View file

@ -55,6 +55,8 @@ class ApiKeyAuthenticationFilter(
} catch (ex: AuthenticationException) { } catch (ex: AuthenticationException) {
unsuccessfulAuthentication(request, response, ex) unsuccessfulAuthentication(request, response, ex)
} }
filterChain.doFilter(request, response)
} }
private fun unsuccessfulAuthentication( private fun unsuccessfulAuthentication(
@ -78,7 +80,6 @@ class ApiKeyAuthenticationFilter(
} }
securityContextHolderStrategy.context = context securityContextHolderStrategy.context = context
securityContextRepository.saveContext(context, request, response) securityContextRepository.saveContext(context, request, response)
filterChain.doFilter(request, response)
} }
private fun authenticationIsRequired(username: String): Boolean { private fun authenticationIsRequired(username: String): Boolean {

View file

@ -1,6 +1,7 @@
package org.gotson.komga.infrastructure.security.apikey package org.gotson.komga.infrastructure.security.apikey
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import org.gotson.komga.infrastructure.hash.Hasher
import org.gotson.komga.infrastructure.security.TokenEncoder import org.gotson.komga.infrastructure.security.TokenEncoder
import org.springframework.security.authentication.AuthenticationDetailsSource import org.springframework.security.authentication.AuthenticationDetailsSource
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
@ -11,11 +12,13 @@ import org.springframework.security.web.authentication.AuthenticationConverter
* and convert it to an [ApiKeyAuthenticationToken] * and convert it to an [ApiKeyAuthenticationToken]
* *
* @property headerName the header name from which to retrieve the API key * @property headerName the header name from which to retrieve the API key
* @property hasher the hasher to use to encode the API key as username in the [Authentication] object
* @property tokenEncoder the encoder to use to encode the API key in the [Authentication] object * @property tokenEncoder the encoder to use to encode the API key in the [Authentication] object
* @property authenticationDetailsSource the [AuthenticationDetailsSource] to enrich the [Authentication] details * @property authenticationDetailsSource the [AuthenticationDetailsSource] to enrich the [Authentication] details
*/ */
class HeaderApiKeyAuthenticationConverter( class HeaderApiKeyAuthenticationConverter(
private val headerName: String, private val headerName: String,
private val hasher: Hasher,
private val tokenEncoder: TokenEncoder, private val tokenEncoder: TokenEncoder,
private val authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>, private val authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>,
) : AuthenticationConverter { ) : AuthenticationConverter {
@ -23,7 +26,8 @@ class HeaderApiKeyAuthenticationConverter(
request request
.getHeader(headerName) .getHeader(headerName)
?.let { ?.let {
val (maskedToken, hashedToken) = it.take(6) + "*".repeat(6) to tokenEncoder.encode(it) val maskedToken = hasher.computeHash(it)
val hashedToken = tokenEncoder.encode(it)
ApiKeyAuthenticationToken ApiKeyAuthenticationToken
.unauthenticated(maskedToken, hashedToken) .unauthenticated(maskedToken, hashedToken)
.apply { details = authenticationDetailsSource.buildDetails(request) } .apply { details = authenticationDetailsSource.buildDetails(request) }

View file

@ -1,6 +1,7 @@
package org.gotson.komga.infrastructure.security.apikey package org.gotson.komga.infrastructure.security.apikey
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import org.gotson.komga.infrastructure.hash.Hasher
import org.gotson.komga.infrastructure.security.TokenEncoder import org.gotson.komga.infrastructure.security.TokenEncoder
import org.springframework.security.authentication.AuthenticationDetailsSource import org.springframework.security.authentication.AuthenticationDetailsSource
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
@ -11,11 +12,13 @@ import org.springframework.security.web.authentication.AuthenticationConverter
* request URI, and convert it to an [ApiKeyAuthenticationToken] * request URI, and convert it to an [ApiKeyAuthenticationToken]
* *
* @property tokenRegex the regex used to extract the API key * @property tokenRegex the regex used to extract the API key
* @property tokenEncoder the encoder to use to encode the API key in the [Authentication] object * @property hasher the hasher to use to encode the API key as username in the [Authentication] object
* @property tokenEncoder the encoder to use to encode the API key as credentials in the [Authentication] object
* @property authenticationDetailsSource the [AuthenticationDetailsSource] to enrich the [Authentication] details * @property authenticationDetailsSource the [AuthenticationDetailsSource] to enrich the [Authentication] details
*/ */
class UriRegexApiKeyAuthenticationConverter( class UriRegexApiKeyAuthenticationConverter(
private val tokenRegex: Regex, private val tokenRegex: Regex,
private val hasher: Hasher,
private val tokenEncoder: TokenEncoder, private val tokenEncoder: TokenEncoder,
private val authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>, private val authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>,
) : AuthenticationConverter { ) : AuthenticationConverter {
@ -24,7 +27,8 @@ class UriRegexApiKeyAuthenticationConverter(
?.let { ?.let {
tokenRegex.find(it)?.groupValues?.lastOrNull() tokenRegex.find(it)?.groupValues?.lastOrNull()
}?.let { }?.let {
val (maskedToken, hashedToken) = it.take(6) + "*".repeat(6) to tokenEncoder.encode(it) val maskedToken = hasher.computeHash(it)
val hashedToken = tokenEncoder.encode(it)
ApiKeyAuthenticationToken ApiKeyAuthenticationToken
.unauthenticated(maskedToken, hashedToken) .unauthenticated(maskedToken, hashedToken)
.apply { details = authenticationDetailsSource.buildDetails(request) } .apply { details = authenticationDetailsSource.buildDetails(request) }

View file

@ -21,7 +21,6 @@ fun ResponseEntity.BodyBuilder.setCachePrivate() = this.cacheControl(cachePrivat
val cachePrivate = val cachePrivate =
CacheControl CacheControl
.maxAge(0, TimeUnit.SECONDS) .maxAge(0, TimeUnit.SECONDS)
.noTransform()
.cachePrivate() .cachePrivate()
.mustRevalidate() .mustRevalidate()

View file

@ -194,6 +194,7 @@ class KoboController(
try { try {
koboProxy.proxyCurrentRequest().body?.get("Resources") koboProxy.proxyCurrentRequest().body?.get("Resources")
} catch (e: Exception) { } catch (e: Exception) {
if (e is ResponseStatusException && e.statusCode == HttpStatus.UNAUTHORIZED) throw e
logger.warn { "Failed to get response from Kobo /v1/initialization, fallback to noproxy" } logger.warn { "Failed to get response from Kobo /v1/initialization, fallback to noproxy" }
null null
} ?: koboProxy.nativeKoboResources } ?: koboProxy.nativeKoboResources
@ -233,7 +234,7 @@ class KoboController(
): Any { ): Any {
try { try {
return koboProxy.proxyCurrentRequest(body) return koboProxy.proxyCurrentRequest(body)
} catch (e: Exception) { } catch (_: Exception) {
logger.warn { "Failed to get response from Kobo /v1/auth/device, fallback to noproxy" } logger.warn { "Failed to get response from Kobo /v1/auth/device, fallback to noproxy" }
} }
@ -395,7 +396,7 @@ class KoboController(
addAll( addAll(
// changed books are also passed as changed reading state because Kobo does not process ChangedEntitlement even if it contains a ReadingState // changed books are also passed as changed reading state because Kobo does not process ChangedEntitlement even if it contains a ReadingState
(booksChanged.content + changedReadingState.content).mapNotNull { book -> (booksChanged.content + changedReadingState.content).mapNotNull { book ->
readProgress[book.bookId]?.let { it -> readProgress[book.bookId]?.let {
ChangedReadingStateDto( ChangedReadingStateDto(
WrappedReadingStateDto( WrappedReadingStateDto(
it.toDto(), it.toDto(),
@ -570,7 +571,10 @@ class KoboController(
locator = locator =
if (koboUpdate.statusInfo.status == StatusDto.FINISHED) { if (koboUpdate.statusInfo.status == StatusDto.FINISHED) {
// If the book is finished, Kobo sends the first resource instead of the last, so we can't trust what Kobo sent // If the book is finished, Kobo sends the first resource instead of the last, so we can't trust what Kobo sent
val epubExtension = mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub ?: throw IllegalArgumentException("Epub extension not found") val epubExtension =
mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub
?: throw IllegalArgumentException("Epub extension not found")
.also { logger.error { "Epub extension not found for book ${book.id}. Book should be re-analyzed." } }
epubExtension.positions.last() epubExtension.positions.last()
} else { } else {
R2Locator( R2Locator(

View file

@ -1,5 +1,6 @@
package org.gotson.komga.interfaces.api.kosync package org.gotson.komga.interfaces.api.kosync
import io.github.oshai.kotlinlogging.KotlinLogging
import org.gotson.komga.domain.model.MediaExtensionEpub import org.gotson.komga.domain.model.MediaExtensionEpub
import org.gotson.komga.domain.model.MediaProfile import org.gotson.komga.domain.model.MediaProfile
import org.gotson.komga.domain.model.R2Device import org.gotson.komga.domain.model.R2Device
@ -25,6 +26,8 @@ import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException import org.springframework.web.server.ResponseStatusException
import java.time.ZonedDateTime import java.time.ZonedDateTime
private val logger = KotlinLogging.logger {}
@RestController @RestController
@RequestMapping("/koreader", produces = ["application/vnd.koreader.v1+json"]) @RequestMapping("/koreader", produces = ["application/vnd.koreader.v1+json"])
class KoreaderSyncController( class KoreaderSyncController(
@ -48,8 +51,14 @@ class KoreaderSyncController(
@PathVariable bookHash: String, @PathVariable bookHash: String,
): DocumentProgressDto { ): DocumentProgressDto {
val books = bookRepository.findAllByHashKoreader(bookHash) val books = bookRepository.findAllByHashKoreader(bookHash)
if (books.isEmpty()) throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found") if (books.isEmpty()) {
if (books.size > 1) throw ResponseStatusException(HttpStatus.CONFLICT, "More than 1 book found with the same hash") logger.debug { "No book found with KOReader hash: $bookHash" }
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found")
}
if (books.size > 1) {
logger.debug { "No unique book found with KOReader hash: $bookHash. Found ${books.size} books with the same hash." }
throw ResponseStatusException(HttpStatus.CONFLICT, "More than 1 book found with the same hash")
}
val book = books.first() val book = books.first()
val media = mediaRepository.findById(book.id) val media = mediaRepository.findById(book.id)
@ -69,7 +78,10 @@ class KoreaderSyncController(
when (media.profile) { when (media.profile) {
MediaProfile.DIVINA, MediaProfile.PDF -> readProgress.page.toString() MediaProfile.DIVINA, MediaProfile.PDF -> readProgress.page.toString()
MediaProfile.EPUB -> { MediaProfile.EPUB -> {
val extension = mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Epub extension not found") val extension =
mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Epub extension not found")
.also { logger.error { "Epub extension not found for book ${book.id}. Book should be re-analyzed." } }
// convert the href to its index for KOReader // convert the href to its index for KOReader
val resourceIndex = val resourceIndex =
@ -81,6 +93,7 @@ class KoreaderSyncController(
// return a progress string that points to the beginning of the resource // return a progress string that points to the beginning of the resource
"/body/DocFragment[${resourceIndex + 1}].0" "/body/DocFragment[${resourceIndex + 1}].0"
} }
null -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book has no media profile") null -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book has no media profile")
} }
@ -99,8 +112,14 @@ class KoreaderSyncController(
@RequestBody koreaderProgress: DocumentProgressDto, @RequestBody koreaderProgress: DocumentProgressDto,
) { ) {
val books = bookRepository.findAllByHashKoreader(koreaderProgress.document) val books = bookRepository.findAllByHashKoreader(koreaderProgress.document)
if (books.isEmpty()) throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found") if (books.isEmpty()) {
if (books.size > 1) throw ResponseStatusException(HttpStatus.CONFLICT, "More than 1 book found with the same hash") logger.debug { "No book found with KOReader hash: ${koreaderProgress.document}" }
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found")
}
if (books.size > 1) {
logger.debug { "No unique book found with KOReader hash: ${koreaderProgress.document}. Found ${books.size} books with the same hash." }
throw ResponseStatusException(HttpStatus.CONFLICT, "More than 1 book found with the same hash")
}
val book = books.first() val book = books.first()
val media = mediaRepository.findById(book.id) val media = mediaRepository.findById(book.id)
@ -139,8 +158,12 @@ class KoreaderSyncController(
?.value ?.value
?.toIntOrNull() ?.toIntOrNull()
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not get Epub resource index from progress: ${koreaderProgress.progress}") ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not get Epub resource index from progress: ${koreaderProgress.progress}")
.also { logger.error { "Could not get Epub resource index from progress: ${koreaderProgress.progress}" } }
val extension = mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Epub extension not found") val extension =
mediaRepository.findExtensionByIdOrNull(book.id) as? MediaExtensionEpub
?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Epub extension not found")
.also { logger.error { "Epub extension not found for book ${book.id}. Book should be re-analyzed." } }
// get the href from the index provided by KOReader // get the href from the index provided by KOReader
val href = val href =

View file

@ -268,7 +268,7 @@ class ReadListController(
} }
@Operation(summary = "Match ComicRack list", tags = [OpenApiConfiguration.TagNames.COMICRACK]) @Operation(summary = "Match ComicRack list", tags = [OpenApiConfiguration.TagNames.COMICRACK])
@PostMapping("match/comicrack") @PostMapping("match/comicrack", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
fun matchComicRackList( fun matchComicRackList(
@RequestParam("file") file: MultipartFile, @RequestParam("file") file: MultipartFile,

View file

@ -3,6 +3,7 @@ package org.gotson.komga.interfaces.api.rest.dto
import java.time.LocalDateTime import java.time.LocalDateTime
data class HistoricalEventDto( data class HistoricalEventDto(
val id: String,
val type: String, val type: String,
val timestamp: LocalDateTime, val timestamp: LocalDateTime,
val bookId: String?, val bookId: String?,

View file

@ -46,6 +46,7 @@ class SettingsUpdateDto {
isSet[prop.name] = true isSet[prop.name] = true
} }
@Deprecated("Will be removed in a future version")
var kepubifyPath: String? var kepubifyPath: String?
by Delegates.observable(null) { prop, _, _ -> by Delegates.observable(null) { prop, _, _ ->
isSet[prop.name] = true isSet[prop.name] = true

View file

@ -46,6 +46,9 @@ spring:
jackson: jackson:
deserialization: deserialization:
FAIL_ON_NULL_FOR_PRIMITIVES: true FAIL_ON_NULL_FOR_PRIMITIVES: true
mapper:
accept-case-insensitive-properties: true
accept-case-insensitive-values: true
config: config:
import: import:
- "optional:file:\${komga.config-dir}/application.yml" - "optional:file:\${komga.config-dir}/application.yml"

View file

@ -11,7 +11,6 @@ import javax.sql.DataSource
class DataSourcesConfigurationTest { class DataSourcesConfigurationTest {
@SpringBootTest @SpringBootTest
@ActiveProfiles("test", "waltest")
@Nested @Nested
inner class WalMode( inner class WalMode(
@Autowired private val dataSourceRW: DataSource, @Autowired private val dataSourceRW: DataSource,
@ -27,6 +26,7 @@ class DataSourcesConfigurationTest {
} }
@SpringBootTest @SpringBootTest
@ActiveProfiles("test", "memorydb")
@Nested @Nested
inner class MemoryMode( inner class MemoryMode(
@Autowired private val dataSourceRW: DataSource, @Autowired private val dataSourceRW: DataSource,

View file

@ -14,6 +14,8 @@ import org.gotson.komga.infrastructure.mediacontainer.epub.getPackageFileContent
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.springframework.core.io.ClassPathResource import org.springframework.core.io.ClassPathResource
import java.time.LocalDate import java.time.LocalDate
@ -37,9 +39,15 @@ class EpubMetadataProviderTest {
@Nested @Nested
inner class Book { inner class Book {
@Test @ParameterizedTest
fun `given epub 3 opf when getting book metadata then metadata patch is valid`() { @ValueSource(
val opf = ClassPathResource("epub/Panik im Paradies.opf") strings = [
"epub/Panik im Paradies.opf",
"epub/Panik im Paradies - namespace.opf",
],
)
fun `given epub 3 opf when getting book metadata then metadata patch is valid`(opfFile: String) {
val opf = ClassPathResource(opfFile)
mockkStatic(::getPackageFileContent) mockkStatic(::getPackageFileContent)
every { getPackageFileContent(any()) } returns opf.file.readText() every { getPackageFileContent(any()) } returns opf.file.readText()
@ -128,9 +136,15 @@ class EpubMetadataProviderTest {
@Nested @Nested
inner class Series { inner class Series {
@Test @ParameterizedTest
fun `given epub 3 opf when getting series metadata then metadata patch is valid`() { @ValueSource(
val opf = ClassPathResource("epub/Panik im Paradies.opf") strings = [
"epub/Panik im Paradies.opf",
"epub/Panik im Paradies - namespace.opf",
],
)
fun `given epub 3 opf when getting series metadata then metadata patch is valid`(opfFile: String) {
val opf = ClassPathResource(opfFile)
mockkStatic(::getPackageFileContent) mockkStatic(::getPackageFileContent)
every { getPackageFileContent(any()) } returns opf.file.readText() every { getPackageFileContent(any()) } returns opf.file.readText()

View file

@ -0,0 +1,5 @@
komga:
database:
file: "file:database?mode=memory"
tasks-db:
file: "file:tasks?mode=memory"

View file

@ -2,9 +2,11 @@ application.version: TESTING
komga: komga:
database: database:
file: "file:database?mode=memory" file: "\${java.io.tmpdir}/database\${random.uuid}.sqlite"
journal-mode: WAL
tasks-db: tasks-db:
file: "file:tasks?mode=memory" file: "\${java.io.tmpdir}/tasks\${random.uuid}.sqlite"
journal-mode: WAL
spring: spring:
flyway: flyway:

View file

@ -1,7 +0,0 @@
komga:
database:
file: "\${java.io.tmpdir}/database.sqlite"
journal-mode: WAL
tasks-db:
file: "\${java.io.tmpdir}/tasks.sqlite"
journal-mode: WAL

View file

@ -0,0 +1,128 @@
<myopf:package xmlns:myopf="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uuid_id" prefix="calibre: https://calibre-ebook.com">
<myopf:metadata xmlns:mydc="http://purl.org/dc/elements/1.1/"
xmlns:dcterms="http://purl.org/dc/terms/"
xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">
<mydc:title id="id">Panik im Paradies</mydc:title>
<mydc:creator id="id-1">Ulf Blanck</mydc:creator>
<mydc:creator id="id-3">The Editor</mydc:creator>
<mydc:identifier>goodreads:222735</mydc:identifier>
<mydc:identifier>isbn:9783440077894</mydc:identifier>
<mydc:identifier>calibre:255</mydc:identifier>
<mydc:identifier>uuid:499def46-39dc-4e79-b474-d0ec12ea5dc5</mydc:identifier>
<mydc:identifier id="uuid_id">uuid:499def46-39dc-4e79-b474-d0ec12ea5dc5</mydc:identifier>
<mydc:language>de</mydc:language>
<mydc:date>1999-07-31T16:00:00+00:00</mydc:date>
<mydc:description>&lt;div&gt;
&lt;p&gt;Bereits im ersten Band "Panik im Paradies" machen die drei berühmten Detektive ihrem Namen alle Ehre. Eigentlich haben sie ja gerade Ferien. Doch dann treffen sie auf diesen schrulligen Kapitän Larsson, der sich einen kleinen Privatzoo mit exotischen Tieren hält. Als plötzlich alle Tiere an rätselhaften Infektionen erkranken und die Besucher ausbleiben, werden Justus, Peter und Bob neugierig. Schon bald merken sie, daß da jemand ein düsteres Geheimnis hütet...&lt;/p&gt;&lt;/div&gt;</mydc:description>
<mydc:publisher>Kosmos</mydc:publisher>
<mydc:subject>Kinder- und Jugendbücher</mydc:subject>
<myopf:meta refines="#id" property="title-type">main</myopf:meta>
<myopf:meta refines="#id" property="file-as">Panik im Paradies</myopf:meta>
<myopf:meta name="cover" content="cover"/>
<myopf:meta property="calibre:timestamp" scheme="dcterms:W3CDTF">2020-08-09T08:40:58Z</myopf:meta>
<myopf:meta property="dcterms:modified" scheme="dcterms:W3CDTF">2021-06-19T08:20:33Z</myopf:meta>
<myopf:meta refines="#id-1" property="role" scheme="marc:relators">aut</myopf:meta>
<myopf:meta refines="#id-1" property="file-as">Blanck, Ulf</myopf:meta>
<myopf:meta refines="#id-3" property="role" scheme="marc:relators">edt</myopf:meta>
<myopf:meta refines="#id-3" property="file-as">Editor, The</myopf:meta>
<myopf:meta property="calibre:rating">6</myopf:meta>
<myopf:meta property="belongs-to-collection" id="id-2">Die drei ??? Kids</myopf:meta>
<myopf:meta refines="#id-2" property="collection-type">series</myopf:meta>
<myopf:meta refines="#id-2" property="group-position">1.5</myopf:meta>
<myopf:meta property="calibre:author_link_map">{"Ulf Blanck": ""}</myopf:meta>
</myopf:metadata>
<myopf:manifest>
<myopf:item id="titlepage" href="titlepage.xhtml" media-type="application/xhtml+xml" properties="svg calibre:title-page"/>
<myopf:item id="TableOfContents_html" href="OPS/TableOfContents.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0001_html" href="OPS/section-0001.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0002_html" href="OPS/section-0002.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0003_html" href="OPS/section-0003.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0004_html" href="OPS/section-0004.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0005_html" href="OPS/section-0005.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0006_html" href="OPS/section-0006.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0007_html" href="OPS/section-0007.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0008_html" href="OPS/section-0008.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0009_html" href="OPS/section-0009.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0010_html" href="OPS/section-0010.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0011_html" href="OPS/section-0011.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0012_html" href="OPS/section-0012.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0013_html" href="OPS/section-0013.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0014_html" href="OPS/section-0014.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0015_html" href="OPS/section-0015.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0016_html" href="OPS/section-0016.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0017_html" href="OPS/section-0017.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0018_html" href="OPS/section-0018.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0019_html" href="OPS/section-0019.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0020_html" href="OPS/section-0020.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0021_html" href="OPS/section-0021.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0022_html" href="OPS/section-0022.html" media-type="application/xhtml+xml"/>
<myopf:item id="section-0023_html" href="OPS/section-0023.html" media-type="application/xhtml+xml"/>
<myopf:item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/>
<myopf:item id="page_css" href="page_styles.css" media-type="text/css"/>
<myopf:item id="css" href="stylesheet.css" media-type="text/css"/>
<myopf:item id="cover" href="cover.jpeg" media-type="image/jpeg" properties="cover-image"/>
<myopf:item id="image0_jpg" href="OPS/image0.jpg" media-type="image/jpeg"/>
<myopf:item id="image1_jpg" href="OPS/image1.jpg" media-type="image/jpeg"/>
<myopf:item id="image10_jpg" href="OPS/image10.jpg" media-type="image/jpeg"/>
<myopf:item id="image11_jpg" href="OPS/image11.jpg" media-type="image/jpeg"/>
<myopf:item id="image12_jpg" href="OPS/image12.jpg" media-type="image/jpeg"/>
<myopf:item id="image13_jpg" href="OPS/image13.jpg" media-type="image/jpeg"/>
<myopf:item id="image14_jpg" href="OPS/image14.jpg" media-type="image/jpeg"/>
<myopf:item id="image15_jpg" href="OPS/image15.jpg" media-type="image/jpeg"/>
<myopf:item id="image16_jpg" href="OPS/image16.jpg" media-type="image/jpeg"/>
<myopf:item id="image17_jpg" href="OPS/image17.jpg" media-type="image/jpeg"/>
<myopf:item id="image18_jpg" href="OPS/image18.jpg" media-type="image/jpeg"/>
<myopf:item id="image19_jpg" href="OPS/image19.jpg" media-type="image/jpeg"/>
<myopf:item id="image2_jpg" href="OPS/image2.jpg" media-type="image/jpeg"/>
<myopf:item id="image20_jpg" href="OPS/image20.jpg" media-type="image/jpeg"/>
<myopf:item id="image21_jpg" href="OPS/image21.jpg" media-type="image/jpeg"/>
<myopf:item id="image22_jpg" href="OPS/image22.jpg" media-type="image/jpeg"/>
<myopf:item id="image23_jpg" href="OPS/image23.jpg" media-type="image/jpeg"/>
<myopf:item id="image24_jpg" href="OPS/image24.jpg" media-type="image/jpeg"/>
<myopf:item id="image25_jpg" href="OPS/image25.jpg" media-type="image/jpeg"/>
<myopf:item id="image26_jpg" href="OPS/image26.jpg" media-type="image/jpeg"/>
<myopf:item id="image27_jpg" href="OPS/image27.jpg" media-type="image/jpeg"/>
<myopf:item id="image28_jpg" href="OPS/image28.jpg" media-type="image/jpeg"/>
<myopf:item id="image29_jpg" href="OPS/image29.jpg" media-type="image/jpeg"/>
<myopf:item id="image3_jpg" href="OPS/image3.jpg" media-type="image/jpeg"/>
<myopf:item id="image30_jpg" href="OPS/image30.jpg" media-type="image/jpeg"/>
<myopf:item id="image31_jpg" href="OPS/image31.jpg" media-type="image/jpeg"/>
<myopf:item id="image32_jpg" href="OPS/image32.jpg" media-type="image/jpeg"/>
<myopf:item id="image33_jpg" href="OPS/image33.jpg" media-type="image/jpeg"/>
<myopf:item id="image34_jpg" href="OPS/image34.jpg" media-type="image/jpeg"/>
<myopf:item id="image35_jpg" href="OPS/image35.jpg" media-type="image/jpeg"/>
<myopf:item id="image4_jpg" href="OPS/image4.jpg" media-type="image/jpeg"/>
<myopf:item id="image5_jpg" href="OPS/image5.jpg" media-type="image/jpeg"/>
<myopf:item id="image6_jpg" href="OPS/image6.jpg" media-type="image/jpeg"/>
<myopf:item id="image7_jpg" href="OPS/image7.jpg" media-type="image/jpeg"/>
<myopf:item id="image8_jpg" href="OPS/image8.jpg" media-type="image/jpeg"/>
<myopf:item id="image9_jpg" href="OPS/image9.jpg" media-type="image/jpeg"/>
</myopf:manifest>
<myopf:spine page-progression-direction="rtl">
<myopf:itemref idref="titlepage"/>
<myopf:itemref idref="TableOfContents_html"/>
<myopf:itemref idref="section-0001_html"/>
<myopf:itemref idref="section-0002_html"/>
<myopf:itemref idref="section-0003_html"/>
<myopf:itemref idref="section-0004_html"/>
<myopf:itemref idref="section-0005_html"/>
<myopf:itemref idref="section-0006_html"/>
<myopf:itemref idref="section-0007_html"/>
<myopf:itemref idref="section-0008_html"/>
<myopf:itemref idref="section-0009_html"/>
<myopf:itemref idref="section-0010_html"/>
<myopf:itemref idref="section-0011_html"/>
<myopf:itemref idref="section-0012_html"/>
<myopf:itemref idref="section-0013_html"/>
<myopf:itemref idref="section-0014_html"/>
<myopf:itemref idref="section-0015_html"/>
<myopf:itemref idref="section-0016_html"/>
<myopf:itemref idref="section-0017_html"/>
<myopf:itemref idref="section-0018_html"/>
<myopf:itemref idref="section-0019_html"/>
<myopf:itemref idref="section-0020_html"/>
<myopf:itemref idref="section-0021_html"/>
<myopf:itemref idref="section-0022_html"/>
<myopf:itemref idref="section-0023_html"/>
</myopf:spine>
</myopf:package>