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
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Configure git

View file

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

View file

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

View file

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

View file

@ -19,36 +19,36 @@ jobs:
fail-fast: false
name: Test server - ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Java 21
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 21
java-package: 'jdk'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@v5
- name: Build
run: ./gradlew build :komga-tray:jar
- name: Upload Unit Test Results
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-results-${{ matrix.os }}
path: komga/build/test-results/
- name: Upload Unit Test Reports
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: test-reports-${{ matrix.os }}
path: komga/build/reports/tests/
- name: Publish Test Report
uses: mikepenz/action-junit-report@v5
uses: mikepenz/action-junit-report@v6
if: always()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
@ -56,7 +56,7 @@ jobs:
- name: Conveyor - compute JDK module list
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:
command: -f conveyor.detect.conf -Kapp.machines=mac.aarch64 make processed-jars
signing_key: ${{ secrets.CONVEYOR_SIGNING_KEY }}
@ -69,7 +69,7 @@ jobs:
- name: Upload JDK required modules
if: steps.conveyor_compare.outcome == 'failure'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: conveyor-required-jdk-modules
path: ./output/required-jdk-modules.txt
@ -78,8 +78,8 @@ jobs:
runs-on: ubuntu-latest
name: Test webui builds
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: '.nvmrc'
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)
## 🐛 Fixes
**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)
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
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> {
version = "1.6.0"
version = "1.7.1"
filter {
exclude("**/generated-src/**")
exclude("**/generated/**")
@ -164,7 +164,7 @@ jreleaser {
packagers {
docker {
active = Active.RELEASE
continueOnError = true
continueOnError = false
templateDirectory = rootDir.resolve("komga/docker")
repository.active = Active.NEVER
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

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("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.kotlin.plugin.compose") version "2.2.0"
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.copy_clipboard=ক্লিপবোর্ডলৈ কপি কৰক
dialog_error.title=Komga আৰম্ভ কৰাত ব্যৰ্থ হ’
error_message.port_in_use={} পৰ্ট ইতিমধ্যে ব্যৱহাৰ কৰা হৈছে।\nKomga চাগে ইতিমধ্যে চলি আছে।\nKomga আইকনৰ বাবে ট্ৰে আইকন বা মেনু বাৰ পৰীক্ষা কৰক।
error_message.unexpected=টা অপ্ৰত্যাশিত ভুল ঘটিল।
menu.open_komga=Komga খুলিব
menu.quit=Komga এৰি দিয়
menu.show_conf_dir=বিন্যাস পঞ্জিকা খোলক
dialog_error.copy_clipboard=ক্লিপব’ৰ্ডলৈ কপি কৰক
dialog_error.title=কমগা আৰম্ভ কৰাত বিফ
error_message.port_in_use={} পৰ্ট ইতিমধ্যে ব্যৱহৃত।\nকমগা সম্ভৱতঃ ইতিমধ্যে চলি আছে।\nকমগা আইকনৰ বাবে ট্ৰে আইকন বা মেনু বাৰ পৰীক্ষা কৰক।
error_message.unexpected=ক অপ্ৰত্যাশিত ত্ৰুটি ঘটিল।
menu.open_komga=কমগা খোলক
menu.quit=কমগা বন্ধ কৰ
menu.show_conf_dir=বিন্যাস ডাইৰেক্টৰি খোলক
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.copy_clipboard=Скопировать в буфер обмена
dialog_error.title=Komga не смог запуститься
error_message.port_in_use=Порт {] уже используется.\nKomga возможно уже работает.\nПроверьте панель задач или меню на наличие иконки Komga.
dialog_error.title=Komga не удалось запустить
error_message.port_in_use=Порт {} уже используется.\nВероятно, Komga уже запущен.\nПроверьте область уведомлений или панель меню на наличие иконки Komga.
error_message.unexpected=Произошла непредвиденная ошибка.
menu.open_komga=Открыть 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.copy_clipboard=Копіювати в буфер обміну
dialog_error.title=Помилка при запуску Komga
error_message.port_in_use=Порт {} вже використовується.\nKomga напевно вже запущена.\nПеревірте панель запущених програм на наявність іконки Komga.
error_message.unexpected=Сталася невідома помилка.

View file

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

View file

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

View file

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

View file

@ -827,7 +827,12 @@
"ERR_1031": "V ComicRack CBL Book chybí série nebo číslo",
"ERR_1032": "Soubor EPUB má chybný typ média",
"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": {
"age_rating": "Věkové hodnocení",

View file

@ -19,7 +19,15 @@
},
"account_settings": {
"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": {
"mark_all_read": "Marcar todo como lido",
@ -27,6 +35,7 @@
"tab_title": "Avisos"
},
"authentication_activity": {
"api_key": "Clave API",
"datetime": "Data e Hora",
"email": "Correo electrónico",
"error": "Erro",
@ -58,9 +67,159 @@
"button_scan": "Escanear",
"button_select_series": "Escolle a serie",
"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": {
"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": {
"colorist": "koloristi",
"cover": "omot",
"cover": "naslovnica",
"editor": "urednici",
"inker": "bojitelj",
"letterer": "tipografi",
@ -109,7 +109,7 @@
"notification_poster_set_series": "Trenutačna stranica je sada postavljena kao poster serije.",
"paged_reader_layout": {
"double": "Dvije stranice",
"double_no_cover": "Dvije stranice (bez omota)",
"double_no_cover": "Dvije stranice (bez naslovnice)",
"single": "Jedna stranica"
},
"reader_settings": "Postavke čitača",
@ -138,7 +138,7 @@
"general": "Općenito",
"gestures": "Geste",
"page_layout": "Raspored stranica",
"page_margin": "Margine stranica",
"page_margin": "Margina stranice",
"paged": "Postavke prikaza stranica",
"reading_mode": "Modus čitanja",
"scale_type": "Vrsta skaliranja",
@ -484,7 +484,7 @@
"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_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_title": "Osnovna mapa biblioteke",
"label_analysis": "Analiza",
@ -497,7 +497,7 @@
"label_scan_directory_exclusions": "Isključivanja mapa",
"label_scan_types": "Traži ove vrste datoteka",
"label_scanner": "Tražilica",
"label_series_cover": "Omot serije",
"label_series_cover": "Naslovnica serije",
"tab_general": "Općenito",
"tab_metadata": "Metapodaci",
"tab_options": "Opcije",
@ -780,7 +780,7 @@
"buttons": "Gumbovi",
"click": "Pritisni / Dodirni"
},
"page_margins": "Margine stranica",
"page_margins": "Margine stranice",
"viewing_theme": "Tema prikaza"
},
"shortcuts": {
@ -827,7 +827,12 @@
"ERR_1031": "ComicRack CBL knjizi nedostaje serija ili broj",
"ERR_1032": "EPUB datoteka sadrži neispravnu vrstu medija",
"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": {
"age_rating": "dobna kategorija",
@ -984,7 +989,7 @@
"btn_confirm": "Da, ali samo ako su veće",
"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_readlists": "Izbriši prazne liste čitanja nakon pretraživanja",
"label_kepubify_path": "Staza do kepubify",

View file

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

View file

@ -4,8 +4,8 @@
"pageText": "{0}-{1} из {2}"
},
"dataIterator": {
"loadingText": "Загрузка объектов...",
"noResultsText": "Подходящих записей не найдено"
"loadingText": "Загрузка элементов...",
"noResultsText": "Совпадений не найдено"
},
"dataTable": {
"itemsPerPageText": "Строк на странице:",
@ -15,30 +15,33 @@
"counter": "Файлов: {0}",
"counterSize": "Файлов: {0} (всего {1})"
},
"noDataText": "Отсутствуют данные"
"noDataText": "Данные отсутствуют"
},
"account_settings": {
"account_settings": "Настройки Аккаунта",
"account_settings": "Настройки аккаунта",
"api_key": {
"created_date": "Дата создания: {date}",
"generate_api_key": "Сгенерировать API ключ",
"force_kobo_sync": "Принудительная синхронизация с Kobo",
"generate_api_key": "Сгенерировать ключ API",
"no_keys": "Ни одного ключа API еще не создано"
},
"change_password": "изменить пароль"
"change_password": "изменить пароль",
"details": "Подробности",
"my_account": "Мой аккаунт"
},
"announcements": {
"mark_all_read": "Пометить всё как прочитанное",
"mark_read": "Пометить как прочитанное",
"mark_all_read": "Отметить всё как прочитанное",
"mark_read": "Отметить как прочитанное",
"tab_title": "Объявления"
},
"authentication_activity": {
"api_key": "API Ключ",
"api_key": "APIлюч",
"datetime": "Дата/Время",
"email": "Эл. почта",
"error": "Ошибка",
"ip": "IP-адрес",
"source": "Источник",
"success": "Статус",
"success": "Успех",
"user_agent": "User Agent"
},
"author_roles": {
@ -54,18 +57,18 @@
"book_card": {
"error": "Ошибка",
"no_release_date": "Дата релиза отсутствует",
"unknown": "Необходимо проанализировать",
"unknown": "Подлежит анализу",
"unread": "Не прочитано",
"unsupported": "Неподдерживаемый"
"unsupported": "Не поддерживается"
},
"book_import": {
"button_browse": "Обзор",
"button_import": "Импортировать",
"button_import": "Импорт",
"button_scan": "Сканировать",
"button_select_series": "Выберите Серию",
"button_select_series": "Выберите серию",
"field_import_path": "Импортировать из каталога",
"info_part1": "Этот раздел позволяет вам импортировать файлы, которые находятся за пределами ваших существующих библиотек. Вы можете импортировать файлы только в существующие Серии, в этом случае Komga переместит или скопирует файлы в каталог выбранной Серии.",
"info_part2": "Если вы выберете номер для книги и книга с таким номером уже существует, то вы сможете сравнить 2 книги. Если вы решите импортировать книгу, Komga обновит существующую книгу, эффективно заменив старый файл новым.",
"info_part1": "Этот раздел позволяет вам импортировать файлы, которые находятся за пределами ваших существующих библиотек. Вы можете импортировать файлы только в существующие серии; в этом случае Komga переместит или скопирует файлы в каталог выбранной серии.",
"info_part2": "Если вы укажете номер для книги, и книга с таким номером уже существует, вы сможете сравнить оба варианта. Если вы решите импортировать книгу, Komga обновит существующий вариант, заменив старый файл новым.",
"no_files_found": "Файлы не найдены",
"notification": {
"go_to_book": "Перейти к книге",
@ -74,9 +77,9 @@
"source_file": "Исходный файл: {file}"
},
"row": {
"error_analyze_first": "Книгу нужно сначала проанализировать",
"error_analyze_first": "Книга нуждается в предварительном анализе",
"error_choose_series": "Выберите серию",
"error_only_import_no_errors": "Можно импортировать только книги без ошибок",
"error_only_import_no_errors": "Можно импортировать только книги без наличия ошибок",
"warning_upgrade": "Существующая книга будет обновлена"
},
"table": {
@ -90,10 +93,11 @@
},
"bookreader": {
"beginning_of_book": "Вы находитесь в начале книги.",
"changing_reading_direction": "Изменение Направления Чтения на",
"cycling_page_layout": "Переключить Формат Страниц",
"cycling_scale": "Переключить Масштабирование",
"cycling_side_padding": "Переключить Боковой Отступ",
"changing_reading_direction": "Изменение направления чтения на",
"cycling_page_layout": "Переключение макета страницы",
"cycling_page_margin": "Переключение полей страницы",
"cycling_scale": "Масштабирование",
"cycling_side_padding": "Переключение боковых отступов",
"download_current_page": "Скачать текущую страницу",
"end_of_book": "Вы достигли конца книги.",
"from_series_metadata": "из метаданных серии",
@ -104,8 +108,8 @@
"notification_poster_set_readlist": "Текущая страница теперь используется в качестве постера списка чтения.",
"notification_poster_set_series": "Текущая страница теперь используется в качестве постера серии.",
"paged_reader_layout": {
"double": "Двойные страницы",
"double_no_cover": "Двойные страницы (без обложки)",
"double": "Две страницы",
"double_no_cover": "Две страницы (без обложки)",
"single": "Одна страница"
},
"reader_settings": "Настройки Ридера",
@ -114,16 +118,16 @@
"continuous_width": "По ширине",
"height": "По высоте",
"original": "Исходное",
"screen": "По экрану",
"screen": "По размеру экрана",
"width": "По ширине",
"width_shrink_only": "По ширине (с отступами)"
},
"set_current_page_as_book_poster": "Установить страницу в качестве постера для книги",
"set_current_page_as_readlist_poster": "Установить страницу в качестве постера для списка чтения",
"set_current_page_as_series_poster": "Установить страницу в качестве постера для серии",
"set_current_page_as_book_poster": "Установить страницу в качестве обложки для книги",
"set_current_page_as_readlist_poster": "Установить страницу в качестве обложки для списка чтения",
"set_current_page_as_series_poster": "Установить страницу в качестве обложки для серии",
"settings": {
"always_fullscreen": "Всегда в полный экран",
"animate_page_transitions": "Анимировать переходы страниц",
"always_fullscreen": "Всегда полноэкранный режим",
"animate_page_transitions": "Анимировать переходы между страницами",
"background_color": "Цвет фона",
"background_colors": {
"black": "Чёрный",
@ -134,54 +138,60 @@
"general": "Общее",
"gestures": "Жесты",
"page_layout": "Формат страницы",
"paged": "Настройки Отображения Страниц",
"page_margin": "Поля страницы",
"paged": "Настройки отображения страниц",
"reading_mode": "Режим чтения",
"scale_type": "Масштабирование",
"side_padding": "Боковой отступ",
"side_padding": "Боковые отступы",
"side_padding_none": "Нет",
"webtoon": "Параметры Режима Webtoon"
"webtoon": "Настройки режима цифрового комикса"
},
"shortcuts": {
"close": "Закрыть",
"cycle_page_layout": "Переключить формат страниц",
"cycle_page_layout": "Переключить макет страницы",
"cycle_page_margin": "Переключить поля страницы",
"cycle_scale": "Переключить масштаб",
"cycle_side_padding": "Переключить боковой отступ",
"cycle_side_padding": "Переключить боковые отступы",
"first_page": "Первая страница",
"fullscreen": "Войти/выйти из полноэкранного режима",
"last_page": "Последняя страница",
"left_to_right": "Слева Направо",
"left_to_right": "Слева направо",
"menus": "Меню",
"next_page": "Следующая страница",
"previous_page": "Предыдущая страница",
"reader_navigation": "Навигация",
"right_to_left": "Справа Налево",
"right_to_left": "Справа налево",
"settings": "Настройки",
"show_hide_help": "Показать/скрыть помощь",
"show_hide_settings": "Показать/скрыть меню настроек",
"show_hide_thumbnails": "Показать/скрыть просмотр эскизов",
"show_hide_thumbnails": "Показать/скрыть обозреватель миниатюр",
"show_hide_toolbars": "Показать/скрыть панели инструментов",
"vertical": "Вертикально",
"webtoon": "Webtoon"
"webtoon": "Цифровой комикс"
},
"tooltip_incognito": "Прогресс чтения не будет сохранен"
},
"browse_book": {
"comment": "КОММЕНТАРИЙ",
"date_created": "СОЗДАНО",
"date_modified": "ПОСЛЕДНЕЕ ИЗМЕНЕНИЕ",
"download_file": "Скачать файл",
"file": "ФАЙЛ",
"format": "ФОРМАТ",
"isbn": "ISBN",
"links": "ССЫЛКИ",
"navigation_within_readlist": "Навигация в пределах списка чтения: {name}",
"outdated_tooltip": "Файл этой книги изменен, книгу необходимо повторно проанализировать",
"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": "ручной порядок"
"manual_ordering": "ручная сортировка"
},
"browse_readlist": {
"edit_elements": "Редактировать элементы",
@ -189,17 +199,19 @@
"manual_ordering": "ручная сортировка"
},
"browse_series": {
"earliest_year_from_release_dates": "Это самая ранняя дата выпуска из всех книг в этой серии",
"series_no_summary": "У серии нет описания, поэтому мы подобрали его для вас!",
"earliest_year_from_release_dates": "Это самый ранний год из дат выхода всех книг серии",
"remove_from_collection": "Удалить серию из коллекции",
"series_no_summary": "У этой серии нет описания, поэтому мы подобрали его для вас!",
"summary_from_book": "Краткое описание из книги {number}:"
},
"collections_expansion_panel": {
"manage_collection": "Управлять коллекцией",
"manage_collection": "Управление коллекцией",
"title": "Коллекция {name}"
},
"common": {
"age": "Возраст",
"all_libraries": "Все Библиотеки",
"all_libraries": "Все библиотеки",
"any_of": "Любой из",
"book": "Книга",
"books": "Книги",
"books_n": "Книг нет | 1 книга | {count} книг",
@ -212,14 +224,16 @@
"copied": "Скопировано!",
"create": "Создать",
"delete": "Удалить",
"dimension": "шир.: {width} выс.: {height}",
"dimension": "шир.: {width}, выс.: {height}",
"discard": "Отмена",
"disk_space": "Дисковое пространство",
"dismiss": "Отклонить",
"download": "Скачать",
"drag_drop": "перетащить",
"duplicate": "Дублировать",
"email": "Эл. почта",
"epub": "Epub",
"error": "Ошибка",
"filename": "Имя файла",
"filter_no_matches": "Нет совпадений по заданному фильтру",
"genre": "Жанр",
@ -227,12 +241,16 @@
"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": "OK",
"outdated": "Устарело",
"page": "Страница",
"page_number": "Номер страницы",
@ -242,17 +260,20 @@
"password": "Пароль",
"pdf": "PDF",
"pending_tasks": "Нет незавершенных задач | 1 незавершенная задача | {count} незавершенных задач",
"pinned_libraries": "Закреплённые библиотеки",
"publisher": "Издатель",
"read": "Читать",
"read_on": "Читать {date}",
"read_on": "Прочитано {date}",
"readlist": "Список чтения",
"readlists": "Списки чтения",
"remember-me": "Запомнить",
"required": "Необходимо",
"remember-me": "Запомнить меня",
"reorder": "Изменить порядок",
"required": "Обязательно",
"reset_filters": "Сбросить фильтры",
"roles": "Роли",
"save_changes": "Сохранить изменения",
"series": "Серии",
"settings": "Настройки",
"tags": "Теги",
"unavailable": "Недоступно",
"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_margin": "Відступи",
"cycling_scale": "Масштабування",
"cycling_side_padding": "Cycling Side Padding",
"cycling_side_padding": "Бічні накладки для велосипедистів",
"download_current_page": "Завантажити поточку сторінку",
"end_of_book": "Ви дійшли до кінця книги.",
"from_series_metadata": "з метаданих серії",
@ -139,6 +139,7 @@
"gestures": "Жести",
"page_layout": "Шаблон сторінки",
"page_margin": "Відступ сторінки",
"paged": "Параметри зчитування з розбивкою на сторінки",
"reading_mode": "Режим читання",
"scale_type": "Тип масштабування",
"side_padding": "Бокові відступи",
@ -147,6 +148,10 @@
},
"shortcuts": {
"close": "Закрити",
"cycle_page_layout": "Перемикання макета сторінки",
"cycle_page_margin": "Перемикання полів сторінки",
"cycle_scale": "Шкала циклу",
"cycle_side_padding": "Бічна підкладка для велосипеда",
"first_page": "Перша сторінка",
"fullscreen": "Увімкнути/вимкнути повноекранний режим",
"last_page": "Остання сторінка",
@ -174,9 +179,914 @@
"file": "ФАЙЛ",
"format": "ФОРМАТ",
"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": {
"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),
downloadCurrentPage() {
new jsFileDownloader({
url: this.currentPage.url,
url: `${this.currentPage.url}?contentNegotiation=false`,
filename: `${this.book.name}-${this.currentPage.number}.${this.currentPage.fileName.split('.').pop()}`,
withCredentials: true,
forceDesktopMode: true,
})
},
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)
switch (type) {
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.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.util.prefixIfNot
import org.springframework.boot.gradle.plugin.SpringBootPlugin
plugins {
kotlin("jvm")
kotlin("plugin.spring")
kotlin("kapt")
id("org.springframework.boot") version "3.5.4"
id("com.gorylenko.gradle-git-properties") version "2.5.2"
id("org.springframework.boot") version libs.versions.springboot.get()
alias(libs.plugins.gradleGitProperties)
id("nu.studer.jooq") version "10.1"
id("org.flywaydb.flyway") version "11.7.2"
id("com.github.johnrengelman.processes") version "0.5.0"
@ -37,7 +38,7 @@ dependencies {
implementation(kotlin("stdlib"))
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")
implementation("org.springframework.boot:spring-boot-starter-webflux")
@ -51,7 +52,7 @@ dependencies {
implementation("com.github.gotson:spring-session-caffeine:2.1.0")
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")
@ -66,14 +67,10 @@ dependencies {
implementation("org.apache.commons:commons-lang3:3.18.0")
implementation("commons-validator:commons-validator:1.10.0")
run {
// v10 requires JDK 21
val luceneVersion = "9.9.1"
implementation("org.apache.lucene:lucene-core:$luceneVersion")
implementation("org.apache.lucene:lucene-analysis-common:$luceneVersion")
implementation("org.apache.lucene:lucene-queryparser:$luceneVersion")
implementation("org.apache.lucene:lucene-backward-codecs:$luceneVersion")
}
implementation("org.apache.lucene:lucene-core:${libs.versions.lucene.get()}")
implementation("org.apache.lucene:lucene-analysis-common:${libs.versions.lucene.get()}")
implementation("org.apache.lucene:lucene-queryparser:${libs.versions.lucene.get()}")
implementation("org.apache.lucene:lucene-backward-codecs:${libs.versions.lucene.get()}")
implementation("com.ibm.icu:icu4j:77.1")
@ -82,18 +79,18 @@ dependencies {
implementation("org.apache.tika:tika-core:2.9.1")
implementation("org.apache.commons:commons-compress:1.27.1")
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("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")
runtimeOnly("com.twelvemonkeys.imageio:imageio-jpeg:3.12.0")
runtimeOnly("com.twelvemonkeys.imageio:imageio-tiff:3.12.0")
runtimeOnly("com.twelvemonkeys.imageio:imageio-webp:3.12.0")
runtimeOnly("com.github.gotson.nightmonkeys:imageio-jxl:1.0.0")
runtimeOnly("com.github.gotson.nightmonkeys:imageio-heif:1.0.0")
runtimeOnly("com.github.gotson.nightmonkeys:imageio-webp:1.0.0")
runtimeOnly("com.twelvemonkeys.imageio:imageio-jpeg:${libs.versions.twelvemonkeys.get()}")
runtimeOnly("com.twelvemonkeys.imageio:imageio-tiff:${libs.versions.twelvemonkeys.get()}")
runtimeOnly("com.twelvemonkeys.imageio:imageio-webp:${libs.versions.twelvemonkeys.get()}")
runtimeOnly("com.github.gotson.nightmonkeys:imageio-jxl:${libs.versions.nightmonkeys.get()}")
runtimeOnly("com.github.gotson.nightmonkeys:imageio-heif:${libs.versions.nightmonkeys.get()}")
runtimeOnly("com.github.gotson.nightmonkeys:imageio-webp:${libs.versions.nightmonkeys.get()}")
// support for jpeg2000
runtimeOnly("com.github.jai-imageio:jai-imageio-jpeg2000:1.4.0")
runtimeOnly("org.apache.pdfbox:jbig2-imageio:3.0.4")
@ -107,8 +104,8 @@ dependencies {
implementation("com.github.ben-manes.caffeine:caffeine")
implementation("org.xerial:sqlite-jdbc:3.50.2.0")
jooqGenerator("org.xerial:sqlite-jdbc:3.50.2.0")
implementation("org.xerial:sqlite-jdbc:${libs.versions.sqliteJdbc.get()}")
jooqGenerator("org.xerial:sqlite-jdbc:${libs.versions.sqliteJdbc.get()}")
if (version.toString().endsWith(".0.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.openjdk.jmh:jmh-core: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 {
@ -306,13 +303,13 @@ tasks.register("flywayMigrateTasks", FlywayMigrateTask::class) {
buildscript {
configurations["classpath"].resolutionStrategy.eachDependency {
if (requested.group.startsWith("org.jooq") && requested.name.startsWith("jooq")) {
useVersion("3.19.24")
useVersion(libs.versions.jooq.get())
}
}
}
jooq {
version = "3.19.24"
version = libs.versions.jooq.get()
configurations {
create("main") {
jooqConfiguration.apply {

View file

@ -9,7 +9,8 @@ FROM ubuntu:24.10 as build-amd64
ENV JAVA_HOME=/opt/java/openjdk
COPY --from=eclipse-temurin:23-jre $JAVA_HOME $JAVA_HOME
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 && \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
locale-gen en_US.UTF-8 && \
@ -23,7 +24,8 @@ FROM ubuntu:24.10 as build-arm64
ENV JAVA_HOME=/opt/java/openjdk
COPY --from=eclipse-temurin:23-jre $JAVA_HOME $JAVA_HOME
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 && \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
locale-gen en_US.UTF-8 && \

View file

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

View file

@ -418,6 +418,7 @@ class BookLifecycle(
val extension =
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." } }
extension.positions[page - 1]
} else {
null
@ -496,6 +497,7 @@ class BookLifecycle(
val extension =
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." } }
// match progression with positions
val matchingPositions = extension.positions.filter { it.href == href }
val matchedPosition =

View file

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

View file

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

View file

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

View file

@ -20,6 +20,8 @@ class Hasher {
return computeHash(path.inputStream())
}
fun computeHash(string: String): String = computeHash(string.byteInputStream())
fun computeHash(stream: InputStream): String {
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.KomgaUser
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.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.AuthenticationActivityRecord
@ -21,9 +22,10 @@ import java.time.LocalDateTime
@Component
class AuthenticationActivityDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : AuthenticationActivityRepository {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
AuthenticationActivityRepository {
private val aa = Tables.AUTHENTICATION_ACTIVITY
private val sorts =

View file

@ -1,6 +1,7 @@
package org.gotson.komga.infrastructure.jooq.main
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.jooq.main.Tables
import org.jooq.DSLContext
@ -19,8 +20,9 @@ import java.time.LocalDateTime
@Component
class BookCommonDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO) {
private val b = Tables.BOOK
private val m = Tables.MEDIA
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.infrastructure.jooq.BookSearchHelper
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.rlbAlias
import org.gotson.komga.infrastructure.jooq.toOrderBy
@ -30,10 +31,11 @@ import java.time.ZoneId
@Component
class BookDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : BookRepository {
) : SplitDslDaoBase(dslRW, dslRO),
BookRepository {
private val b = Tables.BOOK
private val m = Tables.MEDIA
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.jooq.BookSearchHelper
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.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.noCase
@ -51,11 +52,13 @@ import java.net.URL
@Component
class BookDtoDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val bookCommonDao: BookCommonDao,
) : BookDtoRepository {
) : SplitDslDaoBase(dslRW, dslRO),
BookDtoRepository {
private val b = Tables.BOOK
private val m = Tables.MEDIA
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.BookMetadataAggregation
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.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.BookMetadataAggregationAuthorRecord
@ -18,10 +19,11 @@ import java.time.ZoneId
@Component
class BookMetadataAggregationDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : BookMetadataAggregationRepository {
) : SplitDslDaoBase(dslRW, dslRO),
BookMetadataAggregationRepository {
private val d = Tables.BOOK_METADATA_AGGREGATION
private val a = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
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.WebLink
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.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.BookMetadataAuthorRecord
@ -20,10 +21,11 @@ import java.time.ZoneId
@Component
class BookMetadataDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : BookMetadataRepository {
) : SplitDslDaoBase(dslRW, dslRO),
BookMetadataRepository {
private val d = Tables.BOOK_METADATA
private val a = Tables.BOOK_METADATA_AUTHOR
private val bt = Tables.BOOK_METADATA_TAG

View file

@ -1,5 +1,6 @@
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.jooq.main.Tables
import org.jooq.DSLContext
@ -8,9 +9,9 @@ import org.springframework.stereotype.Component
@Component
class ClientSettingsDtoDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO) {
private val g = Tables.CLIENT_SETTINGS_GLOBAL
private val u = Tables.CLIENT_SETTINGS_USER

View file

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

View file

@ -2,6 +2,7 @@ package org.gotson.komga.infrastructure.jooq.main
import com.fasterxml.jackson.databind.ObjectMapper
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.interfaces.api.kobo.dto.ContributorDto
import org.gotson.komga.interfaces.api.kobo.dto.KoboBookMetadataDto
@ -16,9 +17,11 @@ import java.time.ZoneId
@Component
class KoboDtoDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
private val mapper: ObjectMapper,
) : KoboDtoRepository {
) : SplitDslDaoBase(dslRW, dslRO),
KoboDtoRepository {
private val b = Tables.BOOK
private val m = Tables.MEDIA
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.UserRoles
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.records.UserApiKeyRecord
import org.gotson.komga.language.toCurrentTimeZone
@ -21,9 +22,10 @@ import java.time.ZoneId
@Component
class KomgaUserDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : KomgaUserRepository {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
KomgaUserRepository {
private val u = Tables.USER
private val ur = Tables.USER_ROLE
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.persistence.LibraryRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.LibraryRecord
import org.gotson.komga.language.toCurrentTimeZone
@ -17,9 +18,10 @@ import java.time.ZoneId
@Component
class LibraryDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : LibraryRepository {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
LibraryRepository {
private val l = Tables.LIBRARY
private val ul = Tables.USER_LIBRARY_SHARING
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.ProxyExtension
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.deserializeMediaExtension
import org.gotson.komga.infrastructure.jooq.serializeJsonGz
@ -27,11 +28,12 @@ import java.time.ZoneId
@Component
class MediaDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val mapper: ObjectMapper,
) : MediaRepository {
) : SplitDslDaoBase(dslRW, dslRO),
MediaRepository {
private val m = Tables.MEDIA
private val p = Tables.MEDIA_PAGE
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.PageHashUnknown
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.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.PageHashRecord
@ -25,9 +26,10 @@ import java.time.ZoneId
@Component
class PageHashDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : PageHashRepository {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
PageHashRepository {
private val p = Tables.MEDIA_PAGE
private val b = Tables.BOOK
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.persistence.ReadListRepository
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.inOrNoCondition
import org.gotson.komga.infrastructure.jooq.sortByValues
@ -32,11 +33,12 @@ import java.util.SortedMap
@Component
class ReadListDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : ReadListRepository {
) : SplitDslDaoBase(dslRW, dslRO),
ReadListRepository {
private val rl = Tables.READLIST
private val rlb = Tables.READLIST_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.ReadListRequestBookMatches
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.jooq.main.Tables
import org.jooq.DSLContext
@ -18,8 +19,10 @@ import java.time.LocalDate
@Component
class ReadListRequestDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ReadListRequestRepository {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
ReadListRequestRepository {
private val sd = Tables.SERIES_METADATA
private val b = Tables.BOOK
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.ReadProgress
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.Companion.withTempTable
import org.gotson.komga.infrastructure.jooq.deserializeJsonGz
@ -24,11 +25,12 @@ import java.time.ZoneId
@Component
class ReadProgressDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val mapper: ObjectMapper,
) : ReadProgressRepository {
) : SplitDslDaoBase(dslRW, dslRO),
ReadProgressRepository {
private val r = Tables.READ_PROGRESS
private val rs = Tables.READ_PROGRESS_SERIES
private val b = Tables.BOOK

View file

@ -1,5 +1,6 @@
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.rest.dto.TachiyomiReadProgressDto
import org.gotson.komga.interfaces.api.rest.dto.TachiyomiReadProgressV2Dto
@ -16,8 +17,10 @@ import java.math.BigDecimal
@Component
class ReadProgressDtoDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ReadProgressDtoRepository {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
ReadProgressDtoRepository {
private val rlb = Tables.READLIST_BOOK
private val b = Tables.BOOK
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.persistence.ReferentialRepository
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.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.BookMetadataAggregationAuthorRecord
@ -22,8 +23,10 @@ import java.time.LocalDate
@Component
class ReferentialDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ReferentialRepository {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
ReferentialRepository {
private val a = Tables.BOOK_METADATA_AUTHOR
private val sd = Tables.SERIES_METADATA
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.persistence.SeriesCollectionRepository
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.inOrNoCondition
import org.gotson.komga.infrastructure.jooq.sortByValues
@ -31,11 +32,12 @@ import java.time.ZoneId
@Component
class SeriesCollectionDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesCollectionRepository {
) : SplitDslDaoBase(dslRW, dslRO),
SeriesCollectionRepository {
private val c = Tables.COLLECTION
private val cs = Tables.COLLECTION_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.infrastructure.jooq.RequiredJoin
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.csAlias
import org.gotson.komga.jooq.main.Tables
@ -28,10 +29,11 @@ import java.time.ZoneId
@Component
class SeriesDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesRepository {
) : SplitDslDaoBase(dslRW, dslRO),
SeriesRepository {
private val s = Tables.SERIES
private val d = Tables.SERIES_METADATA
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.jooq.RequiredJoin
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.csAlias
import org.gotson.komga.infrastructure.jooq.inOrNoCondition
@ -52,10 +53,12 @@ const val BOOKS_READ_COUNT = "booksReadCount"
@Component
class SeriesDtoDao(
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
private val luceneHelper: LuceneHelper,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesDtoRepository {
) : SplitDslDaoBase(dslRW, dslRO),
SeriesDtoRepository {
private val s = Tables.SERIES
private val d = Tables.SERIES_METADATA
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.WebLink
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.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.SeriesMetadataRecord
@ -19,10 +20,11 @@ import java.time.ZoneId
@Component
class SeriesMetadataDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SeriesMetadataRepository {
) : SplitDslDaoBase(dslRW, dslRO),
SeriesMetadataRepository {
private val d = Tables.SERIES_METADATA
private val g = Tables.SERIES_METADATA_GENRE
private val st = Tables.SERIES_METADATA_TAG

View file

@ -1,5 +1,6 @@
package org.gotson.komga.infrastructure.jooq.main
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.main.Tables
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Qualifier
@ -7,9 +8,9 @@ import org.springframework.stereotype.Component
@Component
class ServerSettingsDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO) {
private val s = Tables.SERVER_SETTINGS
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.SidecarStored
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.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.SidecarRecord
@ -16,10 +17,11 @@ import java.net.URL
@Component
class SidecarDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : SidecarRepository {
) : SplitDslDaoBase(dslRW, dslRO),
SidecarRepository {
private val sc = Tables.SIDECAR
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.infrastructure.jooq.BookSearchHelper
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.language.toZonedDateTime
import org.jooq.DSLContext
@ -28,10 +29,11 @@ import java.time.ZoneId
@Component
class SyncPointDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
private val bookCommonDao: BookCommonDao,
) : SyncPointRepository {
) : SplitDslDaoBase(dslRW, dslRO),
SyncPointRepository {
private val b = Tables.BOOK
private val m = Tables.MEDIA
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.ThumbnailBook
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.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailBookRecord
@ -15,10 +16,11 @@ import java.net.URL
@Component
class ThumbnailBookDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : ThumbnailBookRepository {
) : SplitDslDaoBase(dslRW, dslRO),
ThumbnailBookRepository {
private val tb = Tables.THUMBNAIL_BOOK
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.ThumbnailReadList
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.records.ThumbnailReadlistRecord
import org.jooq.DSLContext
@ -12,9 +13,10 @@ import org.springframework.transaction.annotation.Transactional
@Component
class ThumbnailReadListDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ThumbnailReadListRepository {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
ThumbnailReadListRepository {
private val tr = Tables.THUMBNAIL_READLIST
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.ThumbnailSeriesCollection
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.records.ThumbnailCollectionRecord
import org.jooq.DSLContext
@ -12,9 +13,10 @@ import org.springframework.transaction.annotation.Transactional
@Component
class ThumbnailSeriesCollectionDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
) : ThumbnailSeriesCollectionRepository {
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
) : SplitDslDaoBase(dslRW, dslRO),
ThumbnailSeriesCollectionRepository {
private val tc = Tables.THUMBNAIL_COLLECTION
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.ThumbnailSeries
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.jooq.main.Tables
import org.gotson.komga.jooq.main.tables.records.ThumbnailSeriesRecord
@ -15,10 +16,11 @@ import java.net.URL
@Component
class ThumbnailSeriesDao(
private val dslRW: DSLContext,
@Qualifier("dslContextRO") private val dslRO: DSLContext,
dslRW: DSLContext,
@Qualifier("dslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
) : ThumbnailSeriesRepository {
) : SplitDslDaoBase(dslRW, dslRO),
ThumbnailSeriesRepository {
private val ts = Tables.THUMBNAIL_SERIES
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 org.gotson.komga.application.tasks.Task
import org.gotson.komga.application.tasks.TasksRepository
import org.gotson.komga.infrastructure.jooq.SplitDslDaoBase
import org.gotson.komga.jooq.tasks.Tables
import org.jooq.DSLContext
import org.jooq.Query
@ -22,11 +23,12 @@ private val logger = KotlinLogging.logger {}
@Component
@DependsOn("flywaySecondaryMigrationInitializer")
class TasksDao(
@Qualifier("tasksDslContextRW") private val dslRW: DSLContext,
@Qualifier("tasksDslContextRO") private val dslRO: DSLContext,
@Qualifier("tasksDslContextRW") dslRW: DSLContext,
@Qualifier("tasksDslContextRO") dslRO: DSLContext,
@param:Value("#{@komgaProperties.tasksDb.batchChunkSize}") private val batchSize: Int,
private val objectMapper: ObjectMapper,
) : TasksRepository {
) : SplitDslDaoBase(dslRW, dslRO),
TasksRepository {
private val t = Tables.TASK
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.toEntity
import org.springframework.web.server.ResponseStatusException
import org.springframework.web.util.DefaultUriBuilderFactory
import kotlin.time.Duration.Companion.minutes
import kotlin.time.toJavaDuration
@ -29,11 +30,15 @@ class KoboProxy(
private val komgaSyncTokenGenerator: KomgaSyncTokenGenerator,
private val komgaSettingsProvider: KomgaSettingsProvider,
) {
private val koboApiClient =
private val koboApiClient: RestClient =
RestClient
.builder()
.baseUrl("https://storeapi.kobo.com")
.requestFactory(
.uriBuilderFactory(
DefaultUriBuilderFactory("https://storeapi.kobo.com")
.apply {
this.encodingMode = DefaultUriBuilderFactory.EncodingMode.NONE
},
).requestFactory(
ClientHttpRequestFactoryBuilder.reactor().build(
ClientHttpRequestFactorySettings
.defaults()
@ -42,7 +47,7 @@ class KoboProxy(
),
).build()
private val pathRegex = """\/kobo\/[-\w]*(.*)""".toRegex()
private val pathRegex = """/kobo/[-\w]*(.*)""".toRegex()
private val headersOutInclude =
setOf(
@ -50,6 +55,7 @@ class KoboProxy(
HttpHeaders.USER_AGENT,
HttpHeaders.ACCEPT,
HttpHeaders.ACCEPT_LANGUAGE,
HttpHeaders.CONTENT_TYPE,
)
private val headersOutExclude =
@ -88,7 +94,7 @@ class KoboProxy(
.uri { uriBuilder ->
uriBuilder
.path(path)
.queryParams(LinkedMultiValueMap(request.parameterMap.mapValues { it.value.toList() }))
.query(request.queryString)
.build()
.also { logger.debug { "Proxy URL: $it" } }
}.headers { headersOut ->
@ -110,6 +116,7 @@ class KoboProxy(
}.apply { if (body != null) body(body) }
.retrieve()
.onStatus(HttpStatusCode::isError) { _, response ->
logger.debug { "Kobo response: ${response.statusCode}: ${response.body.bufferedReader().use { it.readText() }}" }
throw ResponseStatusException(response.statusCode, response.statusText)
}.toEntity<JsonNode>()
@ -161,6 +168,7 @@ class KoboProxy(
"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",
"autocomplete": "https://storeapi.kobo.com/v1/products/autocomplete",
"bam": "https://storeapi.kobo.com/v2/activity/bam/success",
"blackstone_header": {
"key": "x-amz-request-payer",
"value": "requester"
@ -173,6 +181,7 @@ class KoboProxy(
"browse_history": "https://storeapi.kobo.com/v1/user/browsehistory",
"categories": "https://storeapi.kobo.com/v1/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_featured_lists": "https://storeapi.kobo.com/v1/categories/{CategoryId}/featured",
"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",
"configuration_data": "https://storeapi.kobo.com/v1/configuration",
"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",
"daily_deal": "https://storeapi.kobo.com/v1/products/dailydeal",
"deals": "https://storeapi.kobo.com/v1/deals",
@ -190,14 +200,19 @@ class KoboProxy(
"device_refresh": "https://storeapi.kobo.com/v1/auth/refresh",
"dictionary_host": "https://ereaderfiles.kobo.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_start": "https://authorize.kobo.com/LinkDropbox/start",
"ereaderdevices": "https://storeapi.kobo.com/v2/products/EReaderDeviceFeeds",
"eula_page": "https://www.kobo.com/termsofuse?style=onestore",
"exchange_auth": "https://storeapi.kobo.com/v1/auth/exchange",
"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_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": {
"EN": "https://www.kobo.com/{region}/{language}/p/free-ebooks",
"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",
"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_link": "https://storeapi.kobo.com/v1/library/downloadlink",
"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",
"googledrive_link_account_start": "https://authorize.kobo.com/{region}/{language}/linkcloudstorage/provider/google_drive",
"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_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",
"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_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_points_redemption_page": "https://www.kobo.com/{region}/{language}/KoboSuperPointsRedemption?productId={ProductId}",
"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",
"notifications_registration_issue": "https://storeapi.kobo.com/v1/notifications/registration",
"oauth_host": "https://oauth.kobo.com",
@ -258,9 +278,13 @@ class KoboProxy(
"product_prices": "https://storeapi.kobo.com/v1/products/{ProductIds}/prices",
"product_recommendations": "https://storeapi.kobo.com/v1/products/{ProductId}/recommendations",
"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",
"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",
"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_templated": "https://www.kobo.com/{region}/{language}/checkoutoption/{ProductId}",
"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_state": "https://storeapi.kobo.com/v1/library/{Ids}/state",
"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",
"remaining_book_series": "https://storeapi.kobo.com/v1/products/books/series/{SeriesId}",
"rename_tag": "https://storeapi.kobo.com/v1/library/tags/{TagId}",
@ -293,14 +319,19 @@ class KoboProxy(
"tags": "https://storeapi.kobo.com/v1/library/tags",
"taste_profile": "https://storeapi.kobo.com/v1/products/tasteprofile",
"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",
"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_platform": "https://storeapi.kobo.com/v1/user/platform",
"user_profile": "https://storeapi.kobo.com/v1/user/profile",
"user_ratings": "https://storeapi.kobo.com/v1/user/ratings",
"user_recommendations": "https://storeapi.kobo.com/v1/user/recommendations",
"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",
"userguide_host": "https://ereaderfiles.kobo.com",
"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 =
getEntryInputStream("META-INF/container.xml")
?.use { Jsoup.parse(it, null, "") }
?.use { Jsoup.parse(it, null, "", Parser.xmlParser()) }
?.getElementsByTag("rootfile")
?.first()
?.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") }
?: // EPUB 2 - get cover from meta element with name="cover"
opfDoc
.selectFirst("metadata > meta[name=cover]")
.selectFirst("*|metadata > *|meta[name=cover]")
?.attr("content")
?.ifBlank { null }
?.let { manifest[it] }
?: // try id="cover-image"
manifest.values.firstOrNull { it.id == "cover-image" }
if (coverManifestItem != null) {
val href = coverManifestItem.href
val href = URLDecoder.decode(coverManifestItem.href, Charsets.UTF_8)
val mediaType = coverManifestItem.mediaType
val coverPath = normalizeHref(opfDir, href)
zip.getEntryBytes(coverPath)?.let { coverBytes ->
@ -84,7 +84,7 @@ class EpubExtractor(
fun getResources(epub: EpubPackage): List<MediaFile> {
val spine =
epub.opfDoc
.select("spine > itemref")
.select("*|spine > *|itemref")
.map { it.attr("idref") }
.mapNotNull { epub.manifest[it] }
@ -126,7 +126,7 @@ class EpubExtractor(
run {
val spine =
epub.opfDoc
.select("spine > itemref")
.select("*|spine > *|itemref")
.map { it.attr("idref") }
.mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } }
@ -137,7 +137,7 @@ class EpubExtractor(
val pagesWithImages =
epub.opfDoc
.select("spine > itemref")
.select("*|spine > *|itemref")
.map { it.attr("idref") }
.mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } }
.map { pagePath ->
@ -219,7 +219,7 @@ class EpubExtractor(
fun computePageCount(epub: EpubPackage): Int {
val spine =
epub.opfDoc
.select("spine > itemref")
.select("*|spine > *|itemref")
.map { it.attr("idref") }
.mapNotNull { idref -> epub.manifest[idref]?.href?.let { normalizeHref(epub.opfDir, it) } }
@ -230,8 +230,8 @@ class EpubExtractor(
}
fun isFixedLayout(epub: EpubPackage) =
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[property=rendition:layout]")?.text() == "pre-paginated" ||
epub.opfDoc.selectFirst("*|metadata > *|meta[name=fixed-layout]")?.attr("content") == "true"
fun computePositions(
epub: EpubPackage,

View file

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

View file

@ -8,7 +8,7 @@ import java.nio.file.Paths
import kotlin.io.path.invariantSeparatorsPathString
fun Document.getManifest() =
select("manifest > item").associate {
select("*|manifest > *|item").associate {
it.attr("id") to
ManifestItem(
it.attr("id"),
@ -36,8 +36,8 @@ fun processOpfGuide(
opf: Document,
opfDir: Path?,
): List<EpubTocEntry> {
val guide = opf.selectFirst("guide") ?: return emptyList()
return guide.select("reference").map { ref ->
val guide = opf.selectFirst("*|guide") ?: return emptyList()
return guide.select("*|reference").map { ref ->
EpubTocEntry(
ref.attr("title"),
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 ->
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 =
opf
.selectFirst("metadata > dc|description")
.selectFirst("*|metadata > *|description")
?.text()
?.let { Jsoup.clean(it, Safelist.none()) }
?.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 =
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() }
val authors =
opf
.select("metadata > dc|creator")
.select("*|metadata > *|creator")
.mapNotNull { el ->
val name = el.text().trim()
if (name.isBlank()) {
@ -81,16 +81,16 @@ class EpubMetadataProvider(
val isbn =
opf
.select("metadata > dc|identifier")
.select("*|metadata > *|identifier")
.map { it.text().lowercase().removePrefix("isbn:") }
.firstNotNullOfOrNull { isbnValidator.validate(it) }
val seriesIndex =
opf
.selectFirst("metadata > *|meta[property=belongs-to-collection]")
.selectFirst("*|metadata > *|meta[property=belongs-to-collection]")
?.attr("id")
?.let { id ->
opf.selectFirst("metadata > *|meta[refines=#$id][property=group-position]")
opf.selectFirst("*|metadata > *|meta[refines=#$id][property=group-position]")
}?.text()
return BookMetadataPatch(
@ -116,18 +116,18 @@ class EpubMetadataProvider(
getPackageFileContent(book.book.path)?.let { packageFile ->
val opf = Jsoup.parse(packageFile, "", Parser.xmlParser())
val series = opf.selectFirst("metadata > *|meta[property=belongs-to-collection]")?.text()?.ifBlank { null }
val publisher = opf.selectFirst("metadata > dc|publisher")?.text()?.ifBlank { null }
val language = opf.selectFirst("metadata > dc|language")?.text()?.ifBlank { null }
val series = opf.selectFirst("*|metadata > *|meta[property=belongs-to-collection]")?.text()?.ifBlank { null }
val publisher = opf.selectFirst("*|metadata > *|publisher")?.text()?.ifBlank { null }
val language = opf.selectFirst("*|metadata > *|language")?.text()?.ifBlank { null }
val genres =
opf
.select("metadata > dc|subject")
.select("*|metadata > *|subject")
.mapNotNull { it.text().trim().ifBlank { null } }
.toSet()
.ifEmpty { null }
val direction =
opf.getElementsByTag("spine").first()?.attr("page-progression-direction")?.let {
opf.selectFirst("*|spine")?.attr("page-progression-direction")?.let {
when (it) {
"rtl" -> SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT
"ltr" -> SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT

View file

@ -28,7 +28,7 @@ class LocalArtworkProvider(
private val imageAnalyzer: ImageAnalyzer,
) : SidecarSeriesConsumer,
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")
fun getBookThumbnails(book: Book): List<ThumbnailBook> {

View file

@ -3,6 +3,7 @@ package org.gotson.komga.infrastructure.security
import jakarta.servlet.Filter
import org.gotson.komga.domain.model.UserRoles
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.ApiKeyAuthenticationProvider
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.WebAuthenticationDetailsSource
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
@Configuration
@ -51,6 +53,7 @@ class SecurityConfiguration(
private val opdsAuthenticationEntryPoint: OpdsAuthenticationEntryPoint,
private val authenticationEventPublisher: AuthenticationEventPublisher,
private val tokenEncoder: TokenEncoder,
private val hasher: Hasher,
clientRegistrationRepository: InMemoryClientRegistrationRepository?,
) {
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()
}
@ -239,19 +242,19 @@ class SecurityConfiguration(
fun koboAuthenticationFilter(): Filter =
ApiKeyAuthenticationFilter(
apiKeyAuthenticationProvider(),
UriRegexApiKeyAuthenticationConverter(Regex("""/kobo/([\w-]+)"""), tokenEncoder, userAgentWebAuthenticationDetailsSource),
UriRegexApiKeyAuthenticationConverter(Regex("""/kobo/([\w-]+)"""), hasher, tokenEncoder, userAgentWebAuthenticationDetailsSource),
)
fun kosyncAuthenticationFilter(): Filter =
ApiKeyAuthenticationFilter(
apiKeyAuthenticationProvider(),
HeaderApiKeyAuthenticationConverter("X-Auth-User", tokenEncoder, userAgentWebAuthenticationDetailsSource),
HeaderApiKeyAuthenticationConverter("X-Auth-User", hasher, tokenEncoder, userAgentWebAuthenticationDetailsSource),
)
fun restAuthenticationFilter(): Filter =
ApiKeyAuthenticationFilter(
apiKeyAuthenticationProvider(),
HeaderApiKeyAuthenticationConverter("X-API-Key", tokenEncoder, userAgentWebAuthenticationDetailsSource),
HeaderApiKeyAuthenticationConverter("X-API-Key", hasher, tokenEncoder, userAgentWebAuthenticationDetailsSource),
)
fun apiKeyAuthenticationProvider(): AuthenticationManager =

View file

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

View file

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

View file

@ -1,6 +1,7 @@
package org.gotson.komga.infrastructure.security.apikey
import jakarta.servlet.http.HttpServletRequest
import org.gotson.komga.infrastructure.hash.Hasher
import org.gotson.komga.infrastructure.security.TokenEncoder
import org.springframework.security.authentication.AuthenticationDetailsSource
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]
*
* @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
*/
class UriRegexApiKeyAuthenticationConverter(
private val tokenRegex: Regex,
private val hasher: Hasher,
private val tokenEncoder: TokenEncoder,
private val authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>,
) : AuthenticationConverter {
@ -24,7 +27,8 @@ class UriRegexApiKeyAuthenticationConverter(
?.let {
tokenRegex.find(it)?.groupValues?.lastOrNull()
}?.let {
val (maskedToken, hashedToken) = it.take(6) + "*".repeat(6) to tokenEncoder.encode(it)
val maskedToken = hasher.computeHash(it)
val hashedToken = tokenEncoder.encode(it)
ApiKeyAuthenticationToken
.unauthenticated(maskedToken, hashedToken)
.apply { details = authenticationDetailsSource.buildDetails(request) }

View file

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

View file

@ -194,6 +194,7 @@ class KoboController(
try {
koboProxy.proxyCurrentRequest().body?.get("Resources")
} 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" }
null
} ?: koboProxy.nativeKoboResources
@ -233,7 +234,7 @@ class KoboController(
): Any {
try {
return koboProxy.proxyCurrentRequest(body)
} catch (e: Exception) {
} catch (_: Exception) {
logger.warn { "Failed to get response from Kobo /v1/auth/device, fallback to noproxy" }
}
@ -395,7 +396,7 @@ class KoboController(
addAll(
// 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 ->
readProgress[book.bookId]?.let { it ->
readProgress[book.bookId]?.let {
ChangedReadingStateDto(
WrappedReadingStateDto(
it.toDto(),
@ -570,7 +571,10 @@ class KoboController(
locator =
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
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()
} else {
R2Locator(

View file

@ -1,5 +1,6 @@
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.MediaProfile
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 java.time.ZonedDateTime
private val logger = KotlinLogging.logger {}
@RestController
@RequestMapping("/koreader", produces = ["application/vnd.koreader.v1+json"])
class KoreaderSyncController(
@ -48,8 +51,14 @@ class KoreaderSyncController(
@PathVariable bookHash: String,
): DocumentProgressDto {
val books = bookRepository.findAllByHashKoreader(bookHash)
if (books.isEmpty()) throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found")
if (books.size > 1) throw ResponseStatusException(HttpStatus.CONFLICT, "More than 1 book found with the same hash")
if (books.isEmpty()) {
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 media = mediaRepository.findById(book.id)
@ -69,7 +78,10 @@ class KoreaderSyncController(
when (media.profile) {
MediaProfile.DIVINA, MediaProfile.PDF -> readProgress.page.toString()
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
val resourceIndex =
@ -81,6 +93,7 @@ class KoreaderSyncController(
// return a progress string that points to the beginning of the resource
"/body/DocFragment[${resourceIndex + 1}].0"
}
null -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book has no media profile")
}
@ -99,8 +112,14 @@ class KoreaderSyncController(
@RequestBody koreaderProgress: DocumentProgressDto,
) {
val books = bookRepository.findAllByHashKoreader(koreaderProgress.document)
if (books.isEmpty()) throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found")
if (books.size > 1) throw ResponseStatusException(HttpStatus.CONFLICT, "More than 1 book found with the same hash")
if (books.isEmpty()) {
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 media = mediaRepository.findById(book.id)
@ -139,8 +158,12 @@ class KoreaderSyncController(
?.value
?.toIntOrNull()
?: 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
val href =

View file

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

View file

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

View file

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

View file

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

View file

@ -11,7 +11,6 @@ import javax.sql.DataSource
class DataSourcesConfigurationTest {
@SpringBootTest
@ActiveProfiles("test", "waltest")
@Nested
inner class WalMode(
@Autowired private val dataSourceRW: DataSource,
@ -27,6 +26,7 @@ class DataSourcesConfigurationTest {
}
@SpringBootTest
@ActiveProfiles("test", "memorydb")
@Nested
inner class MemoryMode(
@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.Nested
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 java.time.LocalDate
@ -37,9 +39,15 @@ class EpubMetadataProviderTest {
@Nested
inner class Book {
@Test
fun `given epub 3 opf when getting book metadata then metadata patch is valid`() {
val opf = ClassPathResource("epub/Panik im Paradies.opf")
@ParameterizedTest
@ValueSource(
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)
every { getPackageFileContent(any()) } returns opf.file.readText()
@ -128,9 +136,15 @@ class EpubMetadataProviderTest {
@Nested
inner class Series {
@Test
fun `given epub 3 opf when getting series metadata then metadata patch is valid`() {
val opf = ClassPathResource("epub/Panik im Paradies.opf")
@ParameterizedTest
@ValueSource(
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)
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:
database:
file: "file:database?mode=memory"
file: "\${java.io.tmpdir}/database\${random.uuid}.sqlite"
journal-mode: WAL
tasks-db:
file: "file:tasks?mode=memory"
file: "\${java.io.tmpdir}/tasks\${random.uuid}.sqlite"
journal-mode: WAL
spring:
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>