Commit Graph

49 Commits

Author SHA1 Message Date
neon ce118e3890 i18n(client): sync translations from Crowdin (#1843)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-16 23:47:46 -06:00
neon 2855dbb8a3 i18n(client): sync translations from Crowdin (#1611)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-30 22:11:38 -06:00
neon 2d9af5170b i18n(client): sync translations from Crowdin (#1565)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-09-26 11:59:15 -06:00
neon c239cfa2dd feat(books): rebuild duplicate books as a ledger
Sort groups by reclaimable space, pre-pick a keeper with a rule that states
its reasoning, and expand any row into a field-by-field comparison that shows
only what differs. Persist "not duplicates" so a judgement survives a rescan,
and review every copy before deleting. Reading progress no longer renders 0%.
2026-09-20 20:11:34 +00:00
neon e0d3d42150 feat: add read-aloud and media playback support
Add EPUB narration, shared media controls, and native device session integration.

Apply the generated migrations and update all first-party clients alongside the server.

BREAKING CHANGE: introduce incompatible database and API contracts for media playback
2026-09-19 02:27:05 +00:00
neon 6583fd696e i18n(client): sync translations from Crowdin (#1225)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-31 11:07:55 -06:00
neon f8ce9f58f1 i18n(client): sync translations from Crowdin (#1185)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-28 12:26:31 -06:00
neon a51122237d feat(library): rebuild the bulk rename tool
Redesign the bulk rename UI with change-type grouping, inline character diffs, and tree previews. Add two-mode selection for large libraries, virtualize the queue, and move search to the server. Optimize execution by pausing the library watcher instead of tearing it down, and improve SSE progress reporting and run error handling.

Share one path-resolution routine between the preview and the rename so the preview cannot offer a rename the executor refuses: multi-track audiobooks whose parts collapse onto one filename, and renumbering that would overwrite the book's own files.
2026-08-28 04:11:06 +00:00
neon b41675c608 feat(authors): rebuild the page as an index and a gallery on one view toggle
The two layouts are the viewMode the app already persists, so search, filters
and sort carry across them. Only 19% of a library's authors have a portrait,
so the gallery leans on monograms and the index treats the name as the content;
a sort name now prints only when it says something the display name does not.
Jump buckets, a cover fallback and two filters come from the server so the A-Z
rail and the chips cover the whole result set rather than the loaded pages.

Reconciles authors.has_photo against the image store on boot: the list resolves
portraits from disk while the filter queried the column, so "no portrait" was
over-reporting by 64.
2026-08-27 04:04:18 +00:00
neon d4718cbe68 i18n(client): sync translations from Crowdin (#1105)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-25 09:09:53 -06:00
neon 5ec34aef23 feat(settings): redesign users, field rules, file naming and libraries
Rebuilds the users roster, metadata field rules, file naming and metadata
score panels, and replaces the libraries list with an expandable ledger.
Libraries now batch their counts and last scan into one request instead of a
per-library fan-out, and surface scan history from scan_jobs on demand.
2026-08-23 23:57:17 +00:00
neon 42f175f1ef feat(authors): add Goodreads as an author metadata source
Audnexus was the only author provider, and it returns just an ASIN, name,
description and image. Goodreads carries a far richer author record, so add it
as a second provider and let each field choose where it comes from.

The Goodreads API was retired in 2020, so the provider scrapes: a name resolves
to an author id through the WAF-free /book/auto_complete endpoint, falling back
to the author search page for names that only differ by a middle initial, and
the author page yields the biography, photo, birth and death dates, website,
genres and influences. Two rendering quirks are worth knowing about, since both
silently return the wrong value: free-text fields appear twice, truncated and
whole, so the parser takes whichever copy carries the most content; and the
birthplace sits in a bare text node while only the date is wrapped in a
dataItem, so authors with a place but no date correctly store nothing.

Preferences are now per field, mirroring the book-side FieldPreference model,
with an ordered provider list and a merge strategy each. They are global,
because an author row is shared across every library and cannot hold a
different biography per library. Providers declare which fields they can return
and both the resolver and the settings UI filter on that, so Audnexus can never
be assigned to a field it has no way to fill. The single global write mode is
migrated into each field's merge strategy on first read, keeping an existing
install's overwrite behaviour instead of silently adopting the default.

The executor now queries every listed provider and resolves each field
independently, first non-empty value winning, and records which provider
supplied the record so a re-run can pin the match instead of searching by name
again. overwriteIfProvided skips empty values, which is what lets a matched
author with an empty Audnexus biography fall through to Goodreads.
2026-08-18 03:48:20 +00:00
neon 6a0a9a3d53 i18n(client): sync translations from Crowdin (#901)
Keep sparse translated catalogs synchronized with reviewed Crowdin content.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-05 17:33:40 -06:00
neon 854b1a75d1 feat(reader): redesign comic settings as one panel in two containers
Replace the three-tab settings dropdown with a single scrollable column
rendered in a desktop popover and a mobile bottom sheet. Group spread
controls contextually under two-page layout, render backgrounds as color
swatches, move spread gap onto a labelled slider, and disable the header
reset while the book carries no overrides. Persist through the panel
update handler so reset no longer re-records defaults as overrides.
Restructure reader.cbz locale keys across all catalogs.
2026-08-04 00:02:08 +00:00
neon 563086328d feat(series): track expected series length and surface library completeness
Record how many books a series holds from Hardcover, ComicVine, RanobeDB,
Amazon and embedded ComicInfo metadata, storing it on the series row. Show
an ownership meter beside read progress on series detail, and extend gap
detection to report entries past the highest owned index instead of stopping
there. Allow the total to be set by hand in the book metadata editor.

Automated sources only ever raise the stored total so the result does not
depend on scan order; the manual field is the way to lower or clear it.

Closes #801
2026-08-03 00:54:26 +00:00
neon 04ac778645 feat(reader): redesign ebook settings as one panel in two containers
Replace the three-tab dropdown with a single scrollable column ordered by how often each
setting is used. The tabs gave no clue where anything lived, so finding hyphenation or the
font picker meant hunting; one column makes the whole set discoverable in a single scan.

Move continuous values onto sliders. Stepping page width across its range took thirty
clicks, and the labels named the variable rather than the outcome, so page width now reads
Narrow through Full while still storing pixels. Text size keeps discrete buttons because it
is nudged constantly and steps are what that control wants.

Render each theme as a miniature page in its own colors. Thirteen near-identical dots gave
no way to tell Sepia from Ember without selecting one and looking at the result.

Swap the desktop container from DropdownMenu to Popover, since menu semantics capture the
arrow keys the new sliders need, and add a bottom sheet below the sm breakpoint so the page
stays visible while it is being tuned and the controls sit within thumb reach. Both render
the same panel component; only the wrapper changes.

Wire up resetBookSettings, which already existed with no caller, and disable the control
when the book carries no overrides.

Trim the built-in fonts to Book default, Serif, Sans-serif and Monospace. The named serifs
duplicated what the generic keyword already resolves to, and Palatino fell back
unpredictably away from macOS. Saved selections keep rendering because the settings
sanitizer accepts any font string; they simply no longer show a selected chip.

Move the theme names and built-in font labels into the locale catalogs. Both were hardcoded
English and so stayed untranslated in every language.
2026-08-03 00:31:19 +00:00
neon 4be84710bd feat(fonts): add admin-managed server fonts for the web reader
Adds a server_fonts collection an administrator curates under Settings >
Admin, offered to every reader alongside their own uploads. Scoped CSS
family prefixes keep a user font and a server font of the same name
independent. Readers can hide individual server families from their own
picker via a new server-fonts user preference.

Validation, metadata parsing and storage are shared with per-user fonts
through an extracted upload pipeline rather than duplicated.
2026-08-02 21:07:50 +00:00
neon 1f6d9527aa feat(smart-scope): allow filtering by custom metadata fields
Address custom metadata fields as custom:<id> rules, mirroring the
existing custom sort fields, and resolve the value column from the
operator so buildWhere stays synchronous for every caller.

Closes #855
2026-08-02 18:13:32 +00:00
neon f4cfc3ca5d feat(books): rework cross-library moves with preview and progress
Replaces the implementation from #818 with a job-backed flow. Moves are
planned up front and surfaced as a preview the user confirms, then run
through a planner/executor split against a book_move_jobs table, so
progress is trackable and partial failures roll back.

The dialog-based flow from #818 is superseded: its module, service, and
repository are removed here in favour of the job-based equivalents. Its
e2e coverage is replaced by a book-move suite that exercises preview
classification, the collision policies, file relocation, preserved user
state, and the books-before-book_files update ordering that the
composite folder foreign key constrains.

Also adds dashboard shelf layout configuration.

Ref #257
2026-08-02 17:24:18 +00:00
Oliver 41fd6f1881 feat(book): move books between libraries (#818)
* feat(books): add moving books between libraries

Allow moving books to another library without re-uploading, so a staging
library (e.g. book-dock imports) can feed curated genre libraries. Re-parent
the existing book row instead of delete+recreate so reading progress,
annotations, collections, and Kobo sync state survive the move. Relocate
files by rebasing library-relative paths onto the target folder with
rollback on failure and a copy+delete fallback for cross-filesystem moves,
then schedule the target library's rename pattern. Exposed as a bulk
endpoint plus UI actions in the selection bar and book detail page.

Closes #257

* ci: allow container image dispatch from any branch

Enable building a GHCR image from a feature branch via workflow_dispatch
to test changes in a container before they land on main. The main tag is
still only updated by the CI-gated workflow_run path.

Ref #257

* fix(books): update book row before files when moving between libraries

book_files_book_folder_consistency_fk references books(id, library_folder_id)
with ON UPDATE CASCADE, so updating a file's library_folder_id while the book
still points at the source folder violates the constraint and the move fails
with a 500. Update the books row first so the new folder id cascades to the
files, then rewrite the file paths. Verified against a real Postgres instance
in addition to the reordered unit test expectations.

Ref #257

* test(books): add book move e2e suite

Unit tests mock the database and cannot catch constraint-order bugs like
the book_files_book_folder_consistency_fk violation fixed in b1c430df.
Cover the move endpoint against a real Postgres and filesystem: successful
cross-library move with row re-parenting, file relocation, and preserved
user state, plus disk-collision skip, foreign-folder rejection, and
format-restriction skip. Reuses the metadata-write e2e harness.

Ref #257

* fix(books): emit transfer event so library views refresh after a move

Moving books via the API updated the database and files but never told
connected clients, so the source and target library views kept stale book
lists and counts until a manual reload. Emit the existing book:transferred
websocket event (grouped per source library) after successful moves; the
library and book detail views already subscribe to it and refresh their
lists, counts, and buckets.

Ref #257

* fix(books): refresh views deterministically after moving books

The initiating library view only removed rows locally and relied on the
book:transferred websocket round-trip for counts, so deployments where
websockets are unavailable kept stale headers and sidebar badges until a
reload. Refresh the view's list and buckets directly after a successful
move, refresh the sidebar library counts on transfer events like scans
and uploads already do, and surface an error toast when a move is
confirmed with an empty selection instead of silently doing nothing.
Promote the move action from the overflow menu to a dedicated,
count-gated button in the selection action bar.

Ref #257

* fix(books): show move action for demo-restricted accounts like delete

The move button was additionally gated on the demo-restriction flag, so
accounts carrying the explicit demo_restricted permission saw the delete
button but no way to move books. Gate it on library_delete_books alone,
mirroring the delete action next to it; demo instances withhold that
permission anyway.

Ref #257

* style(server): align union type formatting with upstream main

The criss-cross merges of two upstream states left seven files with the
older prettier union-type layout, which fails lint against the current
formatter output. Restore the files byte-identical to upstream main.

* i18n(client): translate the move-to-library UI

The move action, dialog, and detail menu entries carried hardcoded
English strings from before the vue-i18n migration. Move them to
translation keys following the existing catalog structure and add
entries to all locale catalogs so the parity validator passes; Crowdin
can refine the non-English translations later.

Ref #257

* security(deps): update brace-expansion and engine.io past new advisories

Trivy fails the container build on CVE-2026-13149 (brace-expansion DoS)
and CVE-2026-59725 (engine.io DoS). Both fixes are semver-compatible
patch releases, so refreshing the lockfile resolves them without
manifest changes.

* security(deps): patch fastify static and find-my-way CVEs

Trivy blocks image builds on CVE-2026-15074 (route guard bypass in
fastify/static 9.x) and CVE-2026-47219 (HTTP2 DDoS in find-my-way
9.6.0). Bump the direct fastify/static dependency to 10.1.2 and add a
find-my-way override because nestjs/platform-fastify pins it to an
exact vulnerable version. The static plugin usage (register with root
and prefix plus sendFile fallback) is unchanged in v10; the peer range
warning from NestJS is expected until it allows v10.

* i18n(client): add BookBulkMove audit action label to all catalogs

Upstream's audit filtering feature derives its action options from the
AuditAction enum and requires a label for every member. Add the label
for the move feature's BookBulkMove action to all nine locale
catalogs.

* ci: restore main-only guard for container image dispatch

Revert the fork-local change that allowed workflow_dispatch container
builds from any branch. The guard matches upstream again so the branch
only carries the move feature. Fork builds continue from the
fork-build branch which keeps the relaxed dispatch.

This reverts commit b13d276a4becbba7be80723ff6708df23132b3fb.

* style(client): restore upstream formatting in untouched files

Merge conflict resolution left four files with stale prettier
formatting that differs from upstream while carrying no functional
change. Restore them byte-identical to upstream/main so the move
feature diff stays clean.

* fix(book-move): harden error paths raised in PR review

Address review feedback on bookorbit#818:

- Guard the applyMove call so a database error fails only that book
  instead of aborting the batch, which discarded collected results and
  skipped transfer events for books that had already moved
- Replace the bare catch around the source access check with one that
  maps ForbiddenException to the access reason and logs unexpected
  errors with a distinct failure reason
- Report an incomplete rollback in the outcome reason so callers know
  a book may need manual verification
- Ignore backdrop and cancel clicks in the move dialog while a move is
  running
- Catch thrown move requests in handleBulkMove, surface an error
  toast, and keep the dialog open on transport errors so the user can
  retry
- Wire onBooksMoved in SmartScopeView so scope totals and jump buckets
  refresh deterministically after a move
- Extend the book-move e2e suite changedPaths with the library and
  scanner modules and the metadata-write harness it depends on

* i18n(client): fill move keys with English source for Crowdin

Crowdin owns the target catalogs, so hand-written translations get
overwritten or conflict on the next export. Replace the translated
move dialog, menu, tooltip, and audit label values in all fifteen
target catalogs with the English source strings, keeping the
locale-specific ICU plural categories the parity validator requires.
Real translations happen in Crowdin.

* fix(book-move): harden access, filesystem, and audit behavior from review

Address the maintainer review on bookorbit#818, server side:

- Require editor-level access on the target library via a new
  LibraryService.verifyUserAccessLevel backed by a repository lookup,
  mirroring the LibraryAccessGuard ranking; viewer access could
  previously write files into a library through this endpoint
- Skip books whose files are missing before touching the database
- Assert resolved target paths stay inside the target folder so a bad
  relPath cannot escape the library via mkdir and rename
- Bound empty-directory cleanup at the library folder root and walk
  nested directories upward so flat libraries keep their configured
  folder and Author/Series trees do not orphan
- Bump books.updatedAt in applyMove so Kobo sync sees moved books, and
  cancel the source library's pending unavailable-books notification
  like the scanner's transfer path does
- Suppress file-watcher events for all touched paths through
  SelfWriteRegistry while files move, closing the race against
  missing-marking and duplicate detection
- Replace rename with link+unlink (COPYFILE_EXCL on cross-device
  fallback) so concurrent moves resolving the same target fail with
  EEXIST instead of silently clobbering each other
- Record the actual moved/skipped/failed outcome in the audit entry
  instead of the requested count

* fix(book-move): use stable outcome codes and an accessible move dialog

Address the client-facing maintainer review items on bookorbit#818:

- Replace the free-form English outcome reasons with a stable code
  union in MoveBookOutcome; the prose stays in server log lines and
  the client maps codes to translated copy, so the UI no longer
  renders raw server strings
- Localize the single-book move toasts on the detail page through the
  new outcome keys (English source in all catalogs, translation via
  Crowdin)
- Rebuild MoveBooksDialog on reka-ui DialogRoot/Content/Title like
  ConfirmDialog, gaining role=dialog, focus trap and restore, Escape
  handling, and aria wiring; closing stays blocked while a move runs
- Filter libraries without folders out of the target picker since
  confirming one is a guaranteed 400

* test(book-move): assert database and disk state in no-op scenarios

The rejected and skipped e2e cases only checked the response before;
a partial move could have passed. Assert the 400 error message, the
unchanged libraryId and libraryFolderId, the source file remaining,
the destination staying absent, and that a colliding target file
keeps its original content.

* feat(book-move): stream bulk moves with per-book progress

The move endpoint processed an unbounded selection serially inside one
request, which cannot finish within proxy timeouts on target-scale
libraries and gave no progress. Convert it to the same SSE pattern as
bulk metadata refresh:

- The endpoint streams one event per book plus a final done summary
  and stops processing when the client disconnects
- Target validation runs before headers are written so user errors
  stay regular JSON error responses
- Audit is emitted manually with the actual outcome because the
  interceptor never sees a response body on streaming handlers
- The bulk composable consumes the stream, reports progress through
  the shared in-flight indicator, and removes moved books from the
  view as their events arrive; the detail page reads the single
  outcome from the stream
- The e2e suite parses the event stream and additionally asserts the
  done summary and content type

* fix(book-move): explain skipped books in multi-library selections

Collection and smart-scope selections can span libraries, so the
books' own library legitimately appears as a move target and picking
it skips them with already_in_target. Surface that in the dialog with
a hint that is only shown when the view has no single current library
to filter out. Exact target filtering would need a library id on every
book card, which collapsed series cards cannot provide unambiguously.

---------

Co-authored-by: ohoeltke <hoeltke@oho-dt.de>
2026-08-02 10:41:57 -06:00
neon d26f6999b4 i18n(client): sync translations from Crowdin (#848)
* 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.
2026-07-31 17:07:27 -06:00
neon 50ba5f151b feat(books): allow sorting by custom metadata fields
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
2026-07-31 00:25:09 +00:00
neon e7ccf6648c i18n(client): translate book filter, operator, and sort field labels
Replace hardcoded English filter, operator, and sort field labels with dynamic i18n lookup helpers that resolve against the active locale catalog.

Closes #807
2026-07-30 19:51:13 +00:00
neon 624dce34a8 feat(smart-scope): allow sharing smart scope after creation
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
2026-07-30 19:43:34 +00:00
neon d6fad39edd i18n(client): sync translations from Crowdin (#808)
* i18n(client): sync Dutch 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 Portuguese, Brazilian 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 Slovenian 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 German translations from Crowdin

Keep translated catalogs synchronized with reviewed Crowdin content.

* 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 German 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 Slovenian 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 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 German 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 Slovenian 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.
2026-07-30 11:45:23 -06:00
neon 648a6cacf2 fix(client): translate dashboard widget names in customization modal
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
2026-07-28 21:43:09 +00:00
neon 75a7cd58b2 fix(smart-scope): enable per-user kobo sync for shared smart scopes
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
2026-07-28 21:28:12 +00:00
Chris Mansell 12ca966a79 i18n(client): add Spanish, French, Italian, and Polish locales (#811)
* 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
2026-07-28 11:37:43 -06:00
neon 515f7b89e9 feat(client): redesign sidebar navigation, settings, and user management
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.
2026-07-28 16:12:36 +00:00
neon d0047bbf18 feat(library): add FB2 file metadata writing and library settings
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.
2026-07-28 06:50:37 +00:00
neon f7716bdbdb feat(library): add MOBI file metadata writing and Kindle library settings
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.
2026-07-28 05:48:15 +00:00
neon 20561c28df feat(audit): add detailed entry metadata and filtering to audit logs
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
2026-07-27 05:42:38 +00:00
neon e850e929d9 feat(reader): add configurable spread gap for two-page comic view
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
2026-07-27 04:28:22 +00:00
neon 92ade7dbba i18n(client): migrate plural messages to ICU MessageFormat
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
2026-07-27 01:28:52 +00:00
neon 58c5b422d3 feat(scanner): align bookdock duplicate detection with upload behavior
Replace metadata-based duplicate checks (ISBN, title/author) during BookDock finalization with destination path lookup. This allows books with shared or incorrect metadata to be imported whenever their resolved library target paths differ, matching the standard upload workflow.

Closes #736
2026-07-22 20:06:49 +00:00
neon 596284c389 feat(settings): expand accent color palette options
Add vivid and pastel accent color choices across client themes, user preferences, and shared types.
2026-07-22 16:47:06 +00:00
neon 095b347aff feat(books): add jump rail display preference
Let readers disable jump rails from grid display controls and skip bucket queries while the preference is off.
2026-07-15 22:32:59 -06:00
neon 504c9ce0e7 feat(books): expand and refine library jump navigation
Extend jump rails across alphabetical, temporal, and categorical sorts.

Keep large libraries responsive with bounded database buckets and exact offsets.

Refine active-state tracking, Unicode initials, localized labels, accessibility, adaptive sizing, and flat or collapsed-series navigation.
2026-07-15 22:32:59 -06:00
neon 5923ea95a5 feat(books): add adaptive temporal jump rail
Add bounded, user-scoped temporal buckets for supported date sorts and adapt resolution to populated dates. Keep rail navigation responsive with viewport sizing, sparse-gap compression, and virtual-grid synchronization.
2026-07-15 22:32:58 -06:00
neon 014c0af456 i18n(client): sync translations from Crowdin (#706)
* i18n(client): sync German 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 Slovenian 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.
2026-07-15 13:54:21 -06:00
neon 876e32654a feat(client): support system theme mode
Add system preference option for appearance theme with auto-switching based on OS color scheme settings. Update default theme preference to system and listen for OS dark mode changes dynamically.

Closes #695
2026-07-15 15:22:15 +00:00
neon ed5dbc9507 feat(books): add duplicate book detection and resolution tools
Introduce backend duplicate scanning services, persistence schemas, and frontend view components to allow users to scan their library for duplicate books by title/author similarity, group potential matches, and bulk delete redundant duplicates safely.
2026-07-15 05:20:16 +00:00
neon 04d2ce0732 i18n(client): sync translations from Crowdin (#692)
* i18n(client): sync German 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 Slovenian 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.
2026-07-14 13:29:44 -06:00
zanivann 6544d23002 feat(client): add Portuguese language localization (#688)
Expose Portuguese through shared locale preferences and keep the catalog synchronized with current English messages. Use keyed interpolation so series headings follow each language’s natural word order.

Closes #673

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-07-14 11:53:16 -06:00
Jadehawk ec84703cef feat(koreader): enhance settings and plugin behavior (#669)
* feat(koreader): add device file naming and improve plugin downloads (BO-658)

* fix(koreader): secure download paths and test collisions

* fix(koreader): preserve bulk collision identities

* fix(koreader): improve device file naming validation and path generation

* fix(koreader): remove duplicate settings lookup and audit file naming changes

* fix(koreader): resolve remaining CodeRabbit review issues

* fix(koreader): validate book detail query and finalize file naming release

validate book detail deviceId via DTO and ValidationPipe

localize new KOReader file naming settings (de, nl, sl)

bump KOReader plugin version to 1.3.0

* chore: rerun CI

* chore: normalized Closes #658 to not have embedded URL to issue. This is failing PR Conventions test

* test(koreader): align source and device removal assertions

* test(koreader): cover file naming settings flows

* test(koreader): cover file naming sync requests

* test(koreader): close remaining patch coverage gaps

* test(koreader): cover file naming server paths

* docs: add fixed2 modifier to file naming reference

* feat(koreader): support custom file naming patterns for KOReader sync

Add support for configurable KOReader file naming patterns, allowing users to define dynamic path templates for sync targets. Include UI draft settings, preview updates, and server path resolution.

Closes #658

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
Co-authored-by: jadehawk <jadehawk@hotmail.com>
2026-07-14 11:26:27 -06:00
neon 214d887dd1 fix(authors): allow closing author enrichment progress notification
Ensure author enrichment progress notifications can be dismissed and resolve stuck status indicators that persist on screen after completion or matching failures.

Closes #678
2026-07-13 19:39:18 +00:00
neon 4c88c15365 db(library): add missing foreign key indexes for deletion
Add indexes on foreign key columns referencing books, book files, and libraries across user activity and integration tables to prevent table scans during cascade deletes.

Closes #663
2026-07-13 18:50:44 +00:00
Chris Mansell a7c281955a feat(stats): expose privacy-aware reading insights (#662)
* build(deps): synchronize i18n lockfile

Record the locale dependencies so frozen installs can resolve the client workspace.

* feat(stats): expose privacy-aware reading insights

- Give administrators permission-gated access to bounded account activity.
- Let readers opt into auditable shared reading insights.

Ref #501
2026-07-12 23:11:35 -06:00
Dennis van der Stelt 31bda18c9a feat(i18n): add multi-language support with Dutch translation (#500)
* feat(i18n): add vue-i18n foundation with language switcher and Dutch locale

Introduce multi-language support for the client using vue-i18n and
@intlify/unplugin-vue-i18n. Locale is detected from a stored preference
or the browser language and persisted client-first, with optional server
sync riding the existing theme-sync mechanism (new locale category on the
generic user_preferences store, no migration needed).

Adds a language selector in Appearance > Behavior settings and seeds
en/nl message catalogs. Extraction of existing hardcoded strings follows
in subsequent commits.

* feat(i18n): add full en/nl message catalogs and test/build wiring

Populate the English and Dutch message catalogs (~4300 keys) covering the
whole UI, register a vitest setup that installs vue-i18n globally so
component tests resolve real messages, and allow inline HTML in messages
(strictMessage/escapeHtml off) for notes rendered via v-html / <i18n-t>.

* feat(i18n): localize settings and book UI strings

Replace hardcoded strings with t() calls across the settings and book
feature areas (including book detail, table, metadata edit, reading log,
highlights). Label collections captured at setup are wrapped in computed()
so they react to locale changes.

* feat(i18n): localize remaining feature UI strings

Replace hardcoded strings with t() across reader, email, library, tools,
migration, dashboard, auth, hardcover, author, series, book-dock,
book-metadata-fetch, annotations, admin, statistics, collection,
smart-scope, audit, whats-new, achievements, notifications, custom-icons,
scanner and metadata-score. Reactive label maps use computed().

* feat(i18n): localize shared components and view wrappers

Replace hardcoded strings with t() across shared components (app header,
sidebar, view header, pickers, selection bar) and top-level views.

* feat(i18n): localize settings tab bars and browser page titles

Make settings tab labels reactive via t() in the consuming components and
slim the *-tabs.ts helpers to ids/order/permission only. Route the browser
page titles in the router through the app i18n instance so document.title
follows the active locale. Adds settings.<area>.tabs.* and titles.* keys.

* feat(i18n): localize KOReader hash-linking and reader sidebar pin

Localize the strings from the KOReader manual hash-linking feature and the
reader sidebar pin/close controls that were merged from main during the
rebase, adding the corresponding en/nl keys.

* feat(i18n): localize upstream features merged during rebase onto main

Add en/nl catalog keys for the new upstream functionality pulled in by the
rebase (Kobo activity log, KOReader dismiss/remove-device and hash-link
modal, default library access, upload-size settings) and update a few
existing keys whose wording changed upstream (dynamic upload-size hints,
write-and-rename phrasing).

* feat(i18n): switch language picker to a dropdown

Replace the segmented language buttons with a native select, which scales
better as more locales are added.

* feat(i18n): add German and Slovenian translations

Register de and sl in the supported locales and ship full de.json and
sl.json catalogs (4616 keys each, structural parity with en.json).

* fix(client): harden locale switching and translation coverage

Keep locale catalogs structurally aligned, localize rebased UI changes, and use locale-aware formatting. Move language selection into the header, persist authenticated preferences, and prevent stale locale loads from winning.

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
2026-07-12 15:07:29 -06:00