Increase MAX_FONT_FILE_SIZE to 50MB to support large CJK and non-alphabetic fonts. Implement a lightweight binary font metadata parser to replace opentype.js buffer parsing, and update reader custom font composables to lazily manage blob URLs per font family.
Closes#793
* fix(client): remove Hungarian and Korean locales
Both catalogs held nothing but English fallback values, so selecting either language produced a
fully English interface that reads as broken rather than untranslated. Crowdin Translate covers only
24 languages and includes neither, so neither locale had any machine translation route, and no human
translations existed yet.
Drop them from the shared locale list, the Crowdin export configuration, the pull request allowlist,
the catalogs, and the tests, leaving fifteen target languages. Remove both from the Crowdin project
as well, after confirming each export was byte-identical to the English source so no translation work
was discarded.
Re-adding either is cheap once a translation route exists, whether that is a machine translation
engine that supports them or a volunteer translator.
Give the locale tests the same fallback semantics the application runs with. Production always loads
the English catalog and points `fallbackLocale` at it, so a key a target catalog does not carry
renders English. The test instances omitted both, which resolves a missing key to its own name, and
several assertions compared a translated label against the English one without checking that the
language actually translates that key. Both patterns pass only while every catalog is complete, and
would fail confusingly the first time a language lags behind a new key.
Ref #576
* i18n(client): sync French translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Spanish translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Czech translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Danish translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Finnish translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Italian translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Dutch translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Polish translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Russian translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Slovenian translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Swedish translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Ukrainian translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Chinese Simplified translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Portuguese, Brazilian translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Ukrainian translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
Both catalogs held nothing but English fallback values, so selecting either language produced a
fully English interface that reads as broken rather than untranslated. Crowdin Translate covers only
24 languages and includes neither, so neither locale had any machine translation route, and no human
translations existed yet.
Drop them from the shared locale list, the Crowdin export configuration, the pull request allowlist,
the catalogs, and the tests, leaving fifteen target languages. Remove both from the Crowdin project
as well, after confirming each export was byte-identical to the English source so no translation work
was discarded.
Re-adding either is cheap once a translation route exists, whether that is a machine translation
engine that supports them or a volunteer translator.
Give the locale tests the same fallback semantics the application runs with. Production always loads
the English catalog and points `fallbackLocale` at it, so a key a target catalog does not carry
renders English. The test instances omitted both, which resolves a missing key to its own name, and
several assertions compared a translated label against the English one without checking that the
language actually translates that key. Both patterns pass only while every catalog is complete, and
would fail confusingly the first time a language lags behind a new key.
Delete the Crowdin service branch automatically once its pull request closes or merges. Crowdin
appends every export to the same branch and never rebases it, so a branch that outlives its pull
request keeps its original merge base and each later export diverges further from main until every
catalog conflicts. Deleting it forces Crowdin to recreate the branch from the current main.
Ref #576
Register Czech, Danish, Finnish, Hungarian, Korean, Russian, Simplified Chinese, Swedish, and
Ukrainian so the language picker, browser detection, and server preference validation accept them.
Catalogs hold English fallback values; Crowdin supplies translations through the normal export flow.
Give each catalog exactly the CLDR plural categories its locale defines rather than copying the
English branches. Czech, Russian, and Ukrainian gain `few` and `many` seeded from `other` because
the source has no matching text. Korean and Chinese drop `one`, which they have no rule for and
Crowdin cannot store. Those synthesized branches all start from the same English string, so machine
translation returns identical forms where the grammar differs and a human has to resolve them.
Ship Chinese as Simplified only under the bare `zh` locale so every zh-* browser resolves to it.
Traditional Chinese, also requested in #86, would arrive later as a separate zh-Hant catalog.
Use `sv-SE` for Swedish in the Crowdin config, because Crowdin has no bare `sv` language and would
have skipped the export. Tolerate a wrapped SUPPORTED_LOCALES array in validate-locales, which
prettier now produces since the list outgrew the print width, and extend the exhaustive ICU test to
reach Polish `other`, a category its counts never selected.
Add the repository activity embed to the README alongside the existing translation progress chart.
Repair the KOReader plugin source assertions that 40e366a4 left stale after reshaping the
dashboard streak block and the downloaded badge icon lookup, and watch `koreader-plugin/**` in
the server CI path filter so plugin-only changes run the server suite that reads those files.
Closes#86Closes#363Closes#576
* fix(metadata): extract and write back comicvine id for comics
ComicVine references were never read from a comic's embedded
ComicInfo.xml on import, even though every other provider id
(Goodreads, Amazon, Hardcover, Google Books, OpenLibrary, Kobo)
already round-trips through the same <Web>/Notes fields. This
affected Book Dock import, direct upload, and library scanning
alike, since they share the same extractor. Write-back had the
same gap: a comicvineId resolved via search/auto-fill was never
persisted back into the file.
Closes#840
* fix(metadata): keep comicvine id off the contested Web slot and source-check notes
ComicInfo has a single Web element and buildComicInfoXml writes the first
provider id in COMIC_INFO_PROVIDER_ID_KEYS order into it. The other six
providers are also in COMIC_INFO_MANAGED_NOTES_KEYS, so losing that slot costs
them nothing; comicvineId had no second channel, so a write-back for a book
that also carried a goodreads/amazon/hardcover/google/openlibrary/kobo id
dropped the id from the file entirely and clobbered any ComicVine URL that was
already there. Add comicvineId to the managed notes keys, which also makes the
existing managed-note read precedence reachable from our own writes.
ComicTagger emits "[Issue ID N]" for whichever talker produced the tag, naming
the source separately as "using info from <source>". Matching the bare marker
attributed Metron and other non-ComicVine issue ids to ComicVine and wrote them
back out as fabricated comicvine.gamespot.com URLs. Require the Comic Vine
source in the same sentence.
Also anchor the Web URL scan to a path segment so a segment ending in 4000-,
such as "14000-777", is no longer read as an issue id.
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Improve KOReader plugin catalog detail view layout by auto-adjusting widget text heights, fixing series line resolution, and polishing star rating, progress button, and navigation button styling.
Add lastReadAt timestamp to KOReader catalog book items and introduce reading streak and goal statistics widgets to the KOReader plugin dashboard layout.
Extend book sorting to support custom metadata field references (custom:<id>). Join custom metadata values in BookSortBuilder based on field data type, resolve active field types in BookService, and expose custom fields in BookSortBuilder UI.
Closes#829
* i18n(client): sync French translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync German translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync Slovenian translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
KOReader removed Device:unpackArchive in recent builds in favor of ffi/archiver. Update BookOrbitUpdater to extract archives via ffi/archiver when present, fallback to Device:unpackArchive on older builds, and report an error instead of crashing if neither is available.
Closes#827
Books without an explicit user_book_status record are implicitly treated as unread across the app. Update BookQueryBuilder's readStatus filter handling so that querying for unread books also matches books lacking a status row.
Closes#815
Replace hardcoded English filter, operator, and sort field labels with dynamic i18n lookup helpers that resolve against the active locale catalog.
Closes#807
Expose the "Visible to all users" toggle in the smart scope editor panel and allow updating the isPublic flag on existing smart scopes via the backend API.
Closes#805
* feat(koreader): add dashboard row configuration model
* feat(koreader): reuse catalog for dashboard source selection
* feat(koreader): configure dashboard rows on 1.4.0
* chore(koreader): preserve dashboard source line endings
* fix(koreader): keep BookOrbit tools menu loadable
* fix(koreader): keep dashboard requests out of rendering
* revert(koreader): restore stock 1.4.0 dashboard renderer
* revert(koreader): use stock single dashboard row
* fix(koreader): apply selected dashboard row source
* feat(koreader): add second dashboard row setting
* feat(koreader): render second dashboard row
* fix(koreader): fetch configured dashboard rows directly
* fix(koreader): honor configured dashboard row sources
* feat(koreader): support four configurable dashboard sections
* fix(koreader): use real catalog dashboard sources
* fix(koreader): select dashboard shelves from catalog
* fix(koreader): reuse catalog for dashboard source selection
* fix(koreader): close dashboard menu before catalog selection
* test(koreader): split dashboard configuration coverage
* chore(koreader): bump version to 1.5.0
* fix(koreader): address dashboard review feedback
* fix(koreader): address dashboard section review feedback
* test(koreader): cover dashboard behavior at runtime
* test(koreader): use Nest setup for dashboard DTO
* chore(ci): trigger pipeline
* fix(koreader): migrate untagged dashboard section settings
Plugin 1.4.x stored the configurable dashboard rows as a bare ordered
list with no schema marker, so gating the migration on schemaVersion == 1
never matched anything a device had actually written. Every upgrading
user who had ever picked a row was read positionally instead: the Stats
strip disappeared and Discover ended up in two slots, with a stored
SmartScope dropped entirely.
Treat an untagged stored list as the legacy shape and migrate it into the
slots those rows used to occupy. Cover it with the settings shapes 1.4.x
really wrote, rather than a marker nothing ever produced.
Also drop store() and primary(), which no caller uses now that slots are
written through storeAt(), along with the dead grow loop inside storeAt()
that normalize() already makes unreachable.
* fix(koreader): keep dashboard shelves in aspect, inside the page, and deduped
The four-slot renderer handed each shelf a height derived from the
leftover vertical budget while its cover cards kept the width computed
from the natural row, so any page with more than one shelf drew covers
out of aspect. It also had no answer for a page too small for the
configured slots: shelves clamped to a 48px floor and the layout simply
overflowed the available height.
Size shelves from their cards instead. A shelf that does not fit gets
more, narrower cards per row, so its height is always the height those
cards need. When even the narrowest row does not fit, drop slots the way
the pre-four-slot renderer did: the stats strip first, then shelves from
the bottom up.
Fetch each distinct shelf source once per dashboard load. Two slots
showing Discover previously rendered the same list twice, and two slots
on the same catalog filter each paid for their own request; a source that
fails is now also not retried by the next slot that wants it.
Drop what the four-slot rewrite left behind: the unused capabilities
require, the tall-layout helper and its constants, and the section
accessors and request helper that no longer have callers. Name the
per-shelf page size and the minimum cover width instead of repeating the
literals. Add setDashboardSections() so a whole configuration can be
applied in one refresh.
Add a layout spec that drives updateDashboardItems and asserts covers
stay in aspect, the drop order holds, and nothing is laid out past the
available height.
* fix(koreader): close the dashboard slot menu through its own API
Reset to Default wrote section_stale as a boolean where the renderer
reads a per-slot table, so the reset never actually marked anything
pending and stale books stayed on screen under the new headers until the
refresh landed. It also dereferenced current_context without a guard and
wrote the setting behind the catalog's back. Route it through the new
setDashboardSections() so the slots and the cache signature move
together in one refresh.
Close the invoking touch menu with closeMenu() rather than handing the
widget to UIManager:close(), which skips the close_callback that owns its
container.
Drop the section parameter from catalogDashboard() and the unused
catalogDashboardSection() helper: the plugin builds its shelves from the
ordinary catalog endpoints now and sends neither. The server keeps both
routes for devices still on 1.4.x, so its DTO test stays; rename it to
match the file it covers and drop the empty Nest module compile.
* revert(koreader): keep the plugin at 1.4.0
1.4.0 has not shipped yet: 1.3.0 is the last released plugin version, so
the dashboard slot work can land inside 1.4.0 rather than claiming a
version of its own. Devices in the field are on 1.3.0 and see a single
1.3.0 -> 1.4.0 step either way.
* fix(koreader): stop rotating upright read-status badges
buildReadStatusBadge turned every status icon a quarter turn, but only
two of the seven are corner folds. The dogear assets are drawn with their
right angle at the top left and the badge sits in the cover's top-right
corner, so those do need the turn. Bookmark, check and reload are upright
glyphs and were being drawn on their side: a want-to-read cover showed
its bookmark ribbon lying sideways like a chevron.
Rotate only the dogear icons.
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
The bundled binaries embedded golang.org/x/text v0.37.0, where a norm.Iter can enter an
infinite loop on input containing invalid UTF-8 bytes. Trivy flags this as HIGH and fails
the PR image scan on every branch opened since the advisory was published.
Rebuild all six binaries from the pinned upstream commit with x/text v0.40.0. The Go
toolchain was already current at 1.26.5 and is unchanged, so a toolchain-only rebuild would
not have cleared the finding; the vulnerable code lives in a module dependency. x/sync moves
to v0.22.0 as a side effect of the same resolution.
Keep the reported version string at v4-dev so rendition hashes stay stable and existing
cached kepub conversions remain valid.
Project manual book status changes onto Kobo reading states and mark device snapshot rows as unsynced. This ensures manual status updates made in the web UI reach registered Kobo devices on the next sync without overwriting device bookmarks.
Closes#812
Use vue-i18n locale keys for dashboard widget and shelf labels in the dashboard customization sheet and view. This resolves hardcoded English strings when viewing dashboard settings in non-English languages.
Closes#796
Store per-user Kobo sync subscriptions for shared smart scopes in a new database table. This allows non-owner users to independently toggle Kobo sync on public smart scopes without modifying the owner's settings.
Closes#795
* i18n(client): add Spanish, French, Italian, and Polish locales
Add locale catalogs for Spanish, French, Italian, and Polish. Update locale types, Crowdin configuration, documentation, and test suites to support the expanded language options.
* i18n(client): sync Spanish, French, Italian, and Polish locale keys
Update Spanish, French, Italian, and Polish translation catalogs to include key additions for notification preferences, user management, and file naming settings.
* fix(client): correct expanded locale translations
Rework the app shell around a customizable sidebar: reorderable sections, sidebar zones, entity
popovers, and per-user preference persistence, backed by a dedicated browse counts endpoint so
entity totals stay cheap on large libraries.
Redesign the file naming settings with pattern fields, a token helper sheet, and a debounced live
preview. Rebuild the admin users table with server-side pagination, search, and filter DTOs, status
pills, confirmation dialogs, and magic link actions. Group notification preferences by category and
refine the account and privacy settings layouts.
Update theme tokens, icon references, and template event handler bindings across client features for
consistency, extend all locale catalogs, and add unit coverage for the new composables, components,
and server modules.
Add server support for writing metadata, embedded covers, and FictionBook XML structures directly into FB2 format files. Introduce migration 0058 to persist FB2 file write preferences per library, and update library creator UI and locale catalogs.
Add server support for writing metadata, covers, and EXTH records directly into MOBI/AZW format files. Introduce migration 0057 to persist Kindle file write preferences per library, and update the library creation UI and locale catalogs.
Format and render series, publisher, publication date, language, ISBN, page count, and file information rows in the KOReader catalog detail view. Support tappable series navigation and format-aware download button labels with unit test coverage.
Add two-way bookmark sync support between KOReader plugin and server. Include migration 0056 for bookmark sync columns, server endpoints and services for bookmark exchange, and KOReader plugin bookmark management with comprehensive unit and E2E tests.
Add server endpoints and KOReader plugin settings to render configurable dashboard section rows, such as Smart Scopes or custom scrollers, replacing the hardcoded Discover section. Update the plugin capability checks to fall back gracefully on older server versions.
Lifecycle handlers previously blocked on network work, so closing or suspending a book stalled the
device on slow or offline links. Persist snapshots first and drain them through a lifecycle outbox
with connected-state gates and phase-aware retries, so the UI stays responsive and pending work
survives restarts.
Move persistence behind a state manager with atomic flushes, and chunk sweeps, bulk downloads, and
annotation exchange through a background queue with checkpoints and transfer policies, so large
libraries make progress incrementally instead of restarting from scratch after an interruption.
Server side, add bulk manifest and page stats endpoints with batched queries and catalog query
filtering so the plugin can reconcile many books per round trip rather than one request per book.
Add automated Crowdin translation progress SVG chart generator, test, and workflow. Consolidate star history and translation stats generation into unified repo-charts workflow, and update README links.
Ensure metadata score calculations are awaited when book metadata or covers are saved or updated, and add concurrency queuing for on-demand score recalculations.
Expand audit log entries to record detailed resource metadata (such as book titles on deletion), and add rich filtering by action, actor, and resource in the audit log viewer UI.
Closes#747
Allow users to adjust the gap between pages when reading comics in two-page spread mode. Include backend preference validation, shared settings defaults, translation keys, and reader settings UI controls.
Closes#740
Replace pipe-delimited Vue I18n plural messages with ICU MessageFormat syntax across all locale catalogs and Vue components. Configure custom message formatting and validation to enforce ICU syntax and argument parity.
Closes#792
* i18n(client): sync Portuguese, Brazilian translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): sync German translations from Crowdin
Keep translated catalogs synchronized with reviewed Crowdin content.
* i18n(client): restore Portuguese plural fallbacks
Keep the Crowdin sync mergeable while the malformed translations are
corrected at their source.
* fix(reader): fall back to percentage jump when kosync response has no xpointer
The KOReader plugin's progress pull calls syncToProgress(body.progress),
which fires GotoXPointer(nil) or GotoPage(nil) when body.progress is nil.
Both silently no-op, so the reader position does not move and the user
sees 'Progress has been synchronized.' with nothing changing.
This happens whenever the kosync response has percentage-only progress
(e.g. progress written by the BookOrbit web reader, or by an external
audiobook->ebook progress bridge that only knows the percentage, not
an EPUB xpointer). hasUsableRemoteProgress already guards this case in
reconcileProgressBeforeBookSync, but getProgress / applyRemoteProgress
never got the same guard.
Extend syncToProgress to accept an optional percentage_fallback (0-1,
kosync scale). When there is no usable xpointer/page but a fallback
percentage is present, fire GotoPercent (0-100 scale) so the reader
actually jumps.
* fix(reader): apply percentage fallback in pre-book-sync reconcile
Drop the hasUsableRemoteProgress short-circuit so the reconcile that runs before a manual book
sync behaves like the pull paths. Now that syncToProgress falls back to a percentage jump, the
two flows disagreed on identical data: a manual pull moved the reader while the pre-sync
reconcile reported "no exact location" and changed nothing, which also left the percentage
argument passed to applyRemoteProgress unreachable.
An earlier guard already returns when body.percentage is absent, so every response reaching this
point can resolve a target. The strategy branches keep the conservative path intact: DISABLE
still skips the progress push when the remote is newer.
---------
Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Prefer the server account reading streak when provided by the dashboard endpoint, falling back to local KOReader statistics database data when offline or absent. This aligns the KOReader plugin day streak display with the web app.
Closes#711
Update dependency versions across root, server, and client package files and refresh the lockfile.
Upgrade @fastify/static to the patched major release and verify production static-serving compatibility.
Validate smart scope filter rules before constructing query clauses during count computation. Catch validation errors for broken rules so invalid filters return null for bookCount rather than triggering an internal server error when fetching all smart scopes.
Closes#787
Add translate.googleapis.com to the connect-src Content-Security-Policy directive so the built-in eBook reader's translation feature can perform fetch requests without being blocked by CSP.
Closes#786
Add an explicit return type to findBookFilesByBookIds so TypeScript
resolves the bookFiles row shape instead of collapsing it to an index
signature, which broke structural typing against fileStateMatches.
Re-encode uploaded progressive JPEG custom cover images to baseline JPEG format so they render correctly across OPDS clients that lack progressive JPEG support.
Closes#782
Separate transient query filter rules from saved smart scope rules when evaluating series collapsing eligibility so series collapse as expected when requested in smart scopes.
Closes#780
When creating Postgres connection clients or pools, pg-connection-string
parsed SSL configurations did not explicitly set the SSL host matching
the database host, causing hostname verification failures against
localhost when DATABASE_URL specified an external host.
Use a helper to construct postgres client configurations that set the
SSL host from the parsed DATABASE_URL host across the server database
module and CLI scripts.
Closes#778