Skip to content

fix(i18n): resolve translation gaps + language reactivity#439

Open
zachyale wants to merge 194 commits intodevelopfrom
refactor/i18n-translation-reactivity
Open

fix(i18n): resolve translation gaps + language reactivity#439
zachyale wants to merge 194 commits intodevelopfrom
refactor/i18n-translation-reactivity

Conversation

@zachyale
Copy link
Copy Markdown
Member

@zachyale zachyale commented Apr 9, 2026

Description

This PR largely re-implements the translation related changed made by @judelgadoc in #107
That PR was closed as a result of hanging for over 2 weeks with excessive merge conflicts.

Linked Issue: Fixes #103

Changes

  • Adds localization to some areas that were previously hard coded
  • Added some missing translation strings to the json files
  • Adds proper reactive localization to some areas that were not reactive on language change

Summary by CodeRabbit

  • New Features

    • Icon picker UI fully localized with English and Spanish translations.
    • Entity type labels exposed for translation (library/shelf/magicShelf/all/unshelved).
  • Improvements

    • Sort and filter labels now update immediately when the app language changes.
    • Browsing headers and filter displays reflect active locale dynamically.
    • Language chart shows localized language names based on the current language.

github-actions Bot and others added 30 commits March 21, 2026 02:18
## [2.3.0](v2.2.6...v2.3.0) (2026-03-21)

### Features

* **release:** document develop-based stable release previews ([930e526](930e526))

### Bug Fixes

* **api:** fix potential memory leaks in file processing ([031e8ae](031e8ae))
* **ci:** correct artifact download action pin ([37ca101](37ca101))
* **ci:** publish PR test results from workflow_run ([11a76bf](11a76bf))
* **ci:** repair release preview and test result publishing ([afa5b81](afa5b81))
* drop telemetry from app ([#52](#52)) ([4d82cb7](4d82cb7))
* **ui:** repair frontend compile after rebrand ([fea1ec6](fea1ec6))

### Refactors

* **build:** rename frontend dist output to grimmory ([ecf388f](ecf388f))
* **i18n:** rename booklore translation keys to grimmory ([eb94afa](eb94afa))
* **metadata:** move default parser from Amazon to Goodreads ([e252122](e252122))
* pull kepubify & ffprobe during build ([#50](#50)) ([1c15629](1c15629))
* **ui:** rebrand frontend surfaces to grimmory ([d786dd8](d786dd8))

### Chores

* **api:** remove the custom startup banner ([98c9b1a](98c9b1a))
* **deps:** bump flatted from 3.4.1 to 3.4.2 in /booklore-ui ([#73](#73)) ([c4bd0c7](c4bd0c7))
* **funding:** point support links at opencollective ([55c0ac0](55c0ac0))
* **release:** 2.2.7 [skip ci] ([0b5e24c](0b5e24c))
* remove old verbose PR template, replace with temporary more low-key one. ([#84](#84)) ([b868526](b868526))
* **ui:** drop financial support dialog ([#21](#21)) ([62be6b1](62be6b1))

### Documentation

* updated supported file formats in README.md ([#68](#68)) ([f912e80](f912e80))

### Style

* **i18n:** normalize translation json formatting ([#89](#89)) ([857290d](857290d))
* **ui:** simplify the topbar logo branding ([0416d48](0416d48))

(cherry picked from commit c335c7b)
…ooklore PR Port) (#16)

* fix(api): Correct format for fixed-layout epub when using Kobo Sync

* chore: Cleanup duplicate code

* feat: Cache fixed-layout property in db

* fix: Missing nullcheck in after retrieving BookFileEntity

* fix: Remove memory leak in EpubReaderService.java

* chore: Avoid opening the epub twice for fixed-layout extraction

* chore: Fix DB migration from rebase

---------

Co-authored-by: brios <127139797+balazs-szucs@users.noreply.github.com>
#79)

* chore(deps): bump actions/setup-node from 6.2.0 to 6.3.0 (#1)

Dependabot couldn't find the original pull request head commit, ea510f4.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump docker/setup-buildx-action from 3.12.0 to 4.0.0 (#2)

Dependabot couldn't find the original pull request head commit, faed6bf.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump docker/build-push-action from 6.19.2 to 7.0.0 (#3)

Dependabot couldn't find the original pull request head commit, f110823.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump docker/login-action from 3.7.0 to 4.0.0 (#6)

Dependabot couldn't find the original pull request head commit, 9a8d7a1.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(archive): infer archive type via Magic Numbers instead of filename

* fix(archive): improve archive type detection and improve logging for cover image retrieval

---------

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…unt (#87)

* fix: fixed halves missing from series number in Hardcover metadata

* fix: use series primary books count
* refactor(frontend): migrate state to TanStack Query and signals

* fix(frontend): keep book cache in sync after patches
…hints to supported versions (#76)

* chore(deps): bump actions/setup-node from 6.2.0 to 6.3.0 (#1)

Dependabot couldn't find the original pull request head commit, ea510f4.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump docker/setup-buildx-action from 3.12.0 to 4.0.0 (#2)

Dependabot couldn't find the original pull request head commit, faed6bf.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump docker/build-push-action from 6.19.2 to 7.0.0 (#3)

Dependabot couldn't find the original pull request head commit, f110823.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump docker/login-action from 3.7.0 to 4.0.0 (#6)

Dependabot couldn't find the original pull request head commit, 9a8d7a1.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* docs(security): update security policy with reporting guidelines and hints to supported versions

---------

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…oring services (#137)

* refactor(concurrency): improve thread management and locking in monitoring services

* chore: remove unnecessary comments
the kepubify / ffprobe binaries may be on disk but they're
in the "data" directory under `tools` rather than under the
current directory
… in the app (#158)

Co-authored-by: Zack Yancey <yanceyz@proton.me>
…100)

* chore(build): migrate Gradle build scripts from Groovy to Kotlin DSL

* chore(docker): update Gradle build files to Kotlin DSL in Dockerfile
…tead of better fields like ISBN in Goodreads/Bookdrop (#85)

* chore(deps): bump actions/setup-node from 6.2.0 to 6.3.0 (#1)

Dependabot couldn't find the original pull request head commit, ea510f4.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump docker/setup-buildx-action from 3.12.0 to 4.0.0 (#2)

Dependabot couldn't find the original pull request head commit, faed6bf.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump docker/build-push-action from 6.19.2 to 7.0.0 (#3)

Dependabot couldn't find the original pull request head commit, f110823.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump docker/login-action from 3.7.0 to 4.0.0 (#6)

Dependabot couldn't find the original pull request head commit, 9a8d7a1.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(metadata): fix metadata fetching relying for filename "first" instead of better fields like ISBN in Goodreads/Bookdrop

* perf(ci): speed up image builds and centralize cache writes

- Image builds:
  - pin frontend and backend Docker build stages to the build platform so multi-arch packaging can reuse architecture-independent work
  - add an Angular build cache mount and move dynamic version metadata to the end of the runtime stage so static layers stay reusable across tags
  - reduce image workflow checkout depth and keep preview builds on `linux/amd64` only to avoid unnecessary QEMU and history overhead

- Cache policy:
  - make CI packaging smoke builds consume the shared image cache without writing new BuildKit cache state
  - make normal preview builds consume shared GHA and registry caches without mutating the canonical cache
  - keep nightly and stable release builds as the workflows that refresh the shared image cache in both GHA and registry backends

- Preview override:
  - add a `refresh_shared_cache` input to the preview workflow for an explicit no-cache rebuild that repopulates the shared cache when maintainers need to bust and refresh it
  - keep the default preview behavior optimized for fast disposable builds rather than cache churn

- Validation:
  - keep workflow YAML parsing clean after the cache-policy changes
  - keep `git diff --check` clean for the touched Docker and workflow files

* docs: Add a note about how to make a release

* chore: remove unncesary comments

---------

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: James Cox <james@imaj.es>
…nt of frontend lint issues (#163)

* feat(lint): add Angular Lint Threshold workflow for CI integration

* Update workflow name for clarity in CI integration

* fix(lint): update LINT_THRESHOLD to 725 in CI checks

* Also run workflow on main branch

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(lint): simplify lint problem count calculation using jq

* feat(lint): enhance quality threshold checks and reporting in CI workflow

* refactor(lint): rename lint step for clarity in CI workflow

* fix(lint): swap lint warning and error thresholds for correct configuration

* fix(lint): update build warning threshold and correct log processing for accurate warning count

* refactor(lint): remove unnecessary working-directory specification for quality thresholds step

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* refactor(icon): fix SVG icon loading straight to memory

* refactor(icon): loading with improved error handling

* refactor(icon): add flag for SVG icons loading state

* chore(release): 2.3.0 [skip ci]

## [2.3.0](v2.2.6...v2.3.0) (2026-03-21)

### Features

* **release:** document develop-based stable release previews ([930e526](930e526))

### Bug Fixes

* **api:** fix potential memory leaks in file processing ([031e8ae](031e8ae))
* **ci:** correct artifact download action pin ([37ca101](37ca101))
* **ci:** publish PR test results from workflow_run ([11a76bf](11a76bf))
* **ci:** repair release preview and test result publishing ([afa5b81](afa5b81))
* drop telemetry from app ([#52](#52)) ([4d82cb7](4d82cb7))
* **ui:** repair frontend compile after rebrand ([fea1ec6](fea1ec6))

### Refactors

* **build:** rename frontend dist output to grimmory ([ecf388f](ecf388f))
* **i18n:** rename booklore translation keys to grimmory ([eb94afa](eb94afa))
* **metadata:** move default parser from Amazon to Goodreads ([e252122](e252122))
* pull kepubify & ffprobe during build ([#50](#50)) ([1c15629](1c15629))
* **ui:** rebrand frontend surfaces to grimmory ([d786dd8](d786dd8))

### Chores

* **api:** remove the custom startup banner ([98c9b1a](98c9b1a))
* **deps:** bump flatted from 3.4.1 to 3.4.2 in /booklore-ui ([#73](#73)) ([c4bd0c7](c4bd0c7))
* **funding:** point support links at opencollective ([55c0ac0](55c0ac0))
* **release:** 2.2.7 [skip ci] ([0b5e24c](0b5e24c))
* remove old verbose PR template, replace with temporary more low-key one. ([#84](#84)) ([b868526](b868526))
* **ui:** drop financial support dialog ([#21](#21)) ([62be6b1](62be6b1))

### Documentation

* updated supported file formats in README.md ([#68](#68)) ([f912e80](f912e80))

### Style

* **i18n:** normalize translation json formatting ([#89](#89)) ([857290d](857290d))
* **ui:** simplify the topbar logo branding ([0416d48](0416d48))

* fix(book-browser): prevent memory leaks by unsubscribing from observables (#80)

* test(icon): remove redundant initialization in IconServiceTest

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Alex Fair <20632147+afairgiant@users.noreply.github.com>
- Command surface:
  - add a root `Justfile` with namespaced `api`, `ui`, and `release` modules so humans and agents can use the same entrypoints from the repo root
  - add project-local Justfiles for `booklore-api`, `booklore-ui`, and `tools/release` so each area remains self-contained and can still be driven from inside its own directory
  - standardize local developer workflows around `just check`, `just test`, `just api run`, `just ui dev`, `just db-up`, and `just image-build`

- Workflow integration:
  - update the reusable test suite workflow to install `just` and run backend and frontend CI steps through the Justfiles instead of hardcoded shell commands
  - update the semantic-release workflows to install `just` and invoke release tooling via `just release install`, `just release preview`, and `just release run`
  - keep the Docker image publication workflows on the native Docker actions while moving the project-command logic into the canonical command surface

- Documentation:
  - split backend and frontend implementation guidance into `booklore-api/README.md`, `booklore-api/CONTRIBUTING.md`, `booklore-ui/README.md`, and `booklore-ui/CONTRIBUTING.md`
  - simplify the top-level `README.md` and `CONTRIBUTING.md` so they stay focused on overview, policy, and navigation while pointing to component-specific guides for deeper detail
  - align contributor-facing examples on the `just` interface instead of mixing ad hoc Gradle, npm, Docker, and Compose commands

- Developer environment:
  - add `mise.toml` to pin the expected Java and Node toolchain versions for local work
  - switch the development Compose defaults from the old Booklore database values to Grimmory-oriented defaults
  - keep the command layout ready for a future API/UI split without forcing that packaging change yet
- Issue intake:
  - rebrand the bug and feature request forms from Booklore to Grimmory
  - remove the old `[Bug]` and `[Feature]` title prefixes so issue titles can stay natural
  - default issue intake to `needs-triage` while relying on GitHub Issue Types for the primary classification

- New templates:
  - add a dedicated enhancement template for smaller quality-of-life and UX improvements
  - add a documentation template for doc fixes, missing guides, and confusing setup flows
  - add a performance template for runtime, UI, build, and operational performance problems

- Type alignment:
  - map the templates onto the repo's issue-type taxonomy with `Bug`, `Feature`, `Enhancement`, `Documentation`, and `Performance`
  - keep the larger feature template distinct from the smaller enhancement template so incoming requests are easier to triage
- Community links:
  - update the issue-template support link to use the canonical Grimmory Discord invite from `README.md`
  - update the contributing guide to point at the same Discord server

- Consistency:
  - remove the stale invite URL so issue intake, contributor docs, and the repository landing page all reference the same community link
- Community links:
  - replace the old Discord invite code with the new `9YJ7HB4n8T` invite in the repository README
  - update the contributor guide to point at the same server
  - align the issue-template support link with the same canonical invite

- Consistency:
  - keep the public docs and issue intake flow on a single Discord destination so users and contributors land in the right community space
- Agent workflow:
  - add a root `AGENTS.md` focused on agent execution rather than general contributor onboarding
  - document the expected command surface, branch target, and staged verification order
- Repo boundaries:
  - map the backend and frontend layout with the key source, resource, test, i18n, and asset paths
  - call out ownership boundaries for deploy, packaging, tools, docs, and shared assets
- Project rules:
  - capture backend and frontend conventions that agents should follow before editing or validating work
  - include repo-specific PR expectations such as linked issues, test output, and UI evidence
- GitHub Actions caching:
  - replace implicit setup-java cache writes with explicit Gradle cache restore/save steps
  - only persist the shared Gradle cache from long-lived develop and release workflows
  - remove the unnecessary QEMU setup from the single-arch smoke build
  - disable QEMU image caching in multi-arch publish workflows to stop repeated binfmt cache entries
- CodeQL workflow:
  - replace the generated advanced template with a pinned repo-owned CodeQL workflow
  - add per-ref concurrency to cancel superseded CodeQL runs before they create more caches
  - restrict dependency caching to Java, using restore-only on pull requests and full caching on long-lived runs
  - disable trap caching and turn off PR overlay database caching to avoid repetitive low-value CodeQL caches
- Validation:
  - validated the edited workflow YAML with yq
  - did not execute the workflows locally
- Planning:
  - add the staged frontend migration plan under docs/plans
  - record the intended rename, Yarn 4 adoption, lint rollout, and PR replay sequence
- Cleanup ledger:
  - capture the remaining frontend Booklore-era aliases that should survive the cutover temporarily
  - document explicit triggers for removing each compatibility shim after the dust settles
balazs-szucs and others added 6 commits April 8, 2026 18:31
* refactor(file): improve path handling and validation

* fix: update XML escaping to use escapeXml10 for improved compatibility

* feat(mime): integrate Apache Tika for content-based MIME detection across services

* test: enhance MultipartFile tests with input stream handling and content type validation
* chore(deps): bump the npm-dependencies group across 1 directory with 30 updates

Bumps the npm-dependencies group with 30 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
| [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.6` | `21.2.7` |
| [@angular/cdk](https://github.com/angular/components) | `21.2.4` | `21.2.5` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.6` | `21.2.7` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.6` | `21.2.7` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.6` | `21.2.7` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.6` | `21.2.7` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.6` | `21.2.7` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `21.2.6` | `21.2.7` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.6` | `21.2.7` |
| [@angular/service-worker](https://github.com/angular/angular/tree/HEAD/packages/service-worker) | `21.2.6` | `21.2.7` |
| [@embedpdf/pdfium](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/pdfium) | `2.12.1` | `2.14.0` |
| @embedpdf/snippet | `2.12.1` | `2.14.0` |
| [@tanstack/angular-query-experimental](https://github.com/TanStack/query/tree/HEAD/packages/angular-query-experimental) | `5.95.2` | `5.96.2` |
| [ng2-charts](https://github.com/valor-software/ng2-charts) | `8.0.0` | `10.0.0` |
| [ngx-extended-pdf-viewer](https://github.com/stephanrauh/ngx-extended-pdf-viewer) | `25.6.4` | `26.0.0` |
| [ngx-sse-client](https://github.com/marcospds/ngx-sse-client) | `20.0.1` | `21.0.0` |
| [primeng](https://github.com/primefaces/primeng/tree/HEAD/packages/primeng) | `21.1.3` | `21.1.5` |
| [uuid](https://github.com/uuidjs/uuid) | `11.1.0` | `13.0.0` |
| [@analogjs/vite-plugin-angular](https://github.com/analogjs/analog) | `2.3.1` | `2.4.0` |
| [@analogjs/vitest-angular](https://github.com/analogjs/analog) | `2.3.1` | `2.4.0` |
| [@angular-devkit/architect](https://github.com/angular/angular-cli) | `0.2102.4` | `0.2102.6` |
| [@angular-devkit/schematics](https://github.com/angular/angular-cli) | `21.2.4` | `21.2.6` |
| [@angular/build](https://github.com/angular/angular-cli) | `21.2.4` | `21.2.6` |
| [@angular/cli](https://github.com/angular/angular-cli) | `21.2.4` | `21.2.6` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.6` | `21.2.7` |
| [@playwright/test](https://github.com/microsoft/playwright) | `1.58.2` | `1.59.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.5.0` | `25.5.2` |
| [eslint](https://github.com/eslint/eslint) | `10.1.0` | `10.2.0` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.9.3` | `6.0.2` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.57.2` | `8.58.0` |



Updates `@angular/animations` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/animations)

Updates `@angular/cdk` from 21.2.4 to 21.2.5
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](angular/components@v21.2.4...v21.2.5)

Updates `@angular/common` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/common)

Updates `@angular/compiler` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/compiler)

Updates `@angular/core` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/core)

Updates `@angular/forms` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/forms)

Updates `@angular/platform-browser` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/platform-browser)

Updates `@angular/platform-browser-dynamic` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/platform-browser-dynamic)

Updates `@angular/router` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/router)

Updates `@angular/service-worker` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/service-worker)

Updates `@embedpdf/pdfium` from 2.12.1 to 2.14.0
- [Release notes](https://github.com/embedpdf/embed-pdf-viewer/releases)
- [Changelog](https://github.com/embedpdf/embed-pdf-viewer/blob/main/packages/pdfium/CHANGELOG.md)
- [Commits](https://github.com/embedpdf/embed-pdf-viewer/commits/v2.14.0/packages/pdfium)

Updates `@embedpdf/snippet` from 2.12.1 to 2.14.0

Updates `@tanstack/angular-query-experimental` from 5.95.2 to 5.96.2
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/angular-query-experimental/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/angular-query-experimental@5.96.2/packages/angular-query-experimental)

Updates `ng2-charts` from 8.0.0 to 10.0.0
- [Release notes](https://github.com/valor-software/ng2-charts/releases)
- [Commits](valor-software/ng2-charts@v8.0.0...v10.0.0)

Updates `ngx-extended-pdf-viewer` from 25.6.4 to 26.0.0
- [Release notes](https://github.com/stephanrauh/ngx-extended-pdf-viewer/releases)
- [Commits](stephanrauh/ngx-extended-pdf-viewer@25.6.4...26.0.0)

Updates `ngx-sse-client` from 20.0.1 to 21.0.0
- [Release notes](https://github.com/marcospds/ngx-sse-client/releases)
- [Commits](marcospds/ngx-sse-client@v20.0.1...v21.0.0)

Updates `primeng` from 21.1.3 to 21.1.5
- [Release notes](https://github.com/primefaces/primeng/releases)
- [Changelog](https://github.com/primefaces/primeng/blob/master/CHANGELOG.md)
- [Commits](https://github.com/primefaces/primeng/commits/21.1.5/packages/primeng)

Updates `uuid` from 11.1.0 to 13.0.0
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](uuidjs/uuid@v11.1.0...v13.0.0)

Updates `@analogjs/vite-plugin-angular` from 2.3.1 to 2.4.0
- [Release notes](https://github.com/analogjs/analog/releases)
- [Changelog](https://github.com/analogjs/analog/blob/beta/CHANGELOG.md)
- [Commits](analogjs/analog@v2.3.1...v2.4.0)

Updates `@analogjs/vitest-angular` from 2.3.1 to 2.4.0
- [Release notes](https://github.com/analogjs/analog/releases)
- [Changelog](https://github.com/analogjs/analog/blob/beta/CHANGELOG.md)
- [Commits](analogjs/analog@v2.3.1...v2.4.0)

Updates `@angular-devkit/architect` from 0.2102.4 to 0.2102.6
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/commits)

Updates `@angular-devkit/schematics` from 21.2.4 to 21.2.6
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](angular/angular-cli@v21.2.4...v21.2.6)

Updates `@angular/build` from 21.2.4 to 21.2.6
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](angular/angular-cli@v21.2.4...v21.2.6)

Updates `@angular/cli` from 21.2.4 to 21.2.6
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](angular/angular-cli@v21.2.4...v21.2.6)

Updates `@angular/compiler-cli` from 21.2.6 to 21.2.7
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/v21.2.7/packages/compiler-cli)

Updates `@playwright/test` from 1.58.2 to 1.59.1
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](microsoft/playwright@v1.58.2...v1.59.1)

Updates `@types/node` from 25.5.0 to 25.5.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `eslint` from 10.1.0 to 10.2.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](eslint/eslint@v10.1.0...v10.2.0)

Updates `typescript` from 5.9.3 to 6.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](microsoft/TypeScript@v5.9.3...v6.0.2)

Updates `typescript-eslint` from 8.57.2 to 8.58.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@angular/animations"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/cdk"
  dependency-version: 21.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/common"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/compiler"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/core"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/forms"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/router"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/service-worker"
  dependency-version: 21.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@embedpdf/pdfium"
  dependency-version: 2.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@embedpdf/snippet"
  dependency-version: 2.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/angular-query-experimental"
  dependency-version: 5.96.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: ng2-charts
  dependency-version: 10.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: ngx-extended-pdf-viewer
  dependency-version: 26.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: ngx-sse-client
  dependency-version: 21.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: primeng
  dependency-version: 21.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: uuid
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: "@analogjs/vite-plugin-angular"
  dependency-version: 2.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@analogjs/vitest-angular"
  dependency-version: 2.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@angular-devkit/architect"
  dependency-version: 0.2102.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular-devkit/schematics"
  dependency-version: 21.2.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/build"
  dependency-version: 21.2.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 21.2.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 21.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@playwright/test"
  dependency-version: 1.59.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/node"
  dependency-version: 25.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: eslint
  dependency-version: 10.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: typescript
  dependency-version: 6.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.58.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(pdf): improve page change handling and add scroll margin to test setup

* chore(dependencies): downgrade typescript to version 5.9.3

* fix(pdf): update authorization handling and adjust PDF buffer management

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
the book download proxy code path should never be called.  this is
because the book download URL received as part of the kobo proxy points
at the Rakuten Kobo CDN rather than at grimmory.  further, even if this
code could download the file from the CDN (it doesn't), it would be
pointless as we don't return it.  instead of fixing the return this
drops the proxy from that code path
* fix(api): invert kobo book type check for downloads

with the previous organization of code paths this leads to an
exception being raised in the logs even though the response message
is fine

* style: drop extra newline
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Components and templates were updated to use Transloco translations and to subscribe to runtime language changes; translation JSON keys for entity types and an icon picker were added; language-chart now localizes language names via Intl.DisplayNames and reactive lang tracking.

Changes

Cohort / File(s) Summary
Author & Series language subscriptions
frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts, frontend/src/app/features/series-browser/components/.../series-browser.component.ts
Subscribe to t.langChanges$ (with takeUntilDestroyed/destroyRef) and reassign sortOptions/filterOptions labels on language change so dropdown items update at runtime.
Book browser (template, logic, i18n)
frontend/src/app/features/book/components/book-browser/book-browser.component.html, frontend/src/app/features/book/components/book-browser/book-browser.component.ts, frontend/src/i18n/en/book.json, frontend/src/i18n/es/book.json
Template now renders localized entity type labels; added getEntityTypeLabelKey() and switched filter label generation to TranslocoService.translate(); added browser.entityTypes keys in en/es JSON.
Book browser filter label updates
frontend/src/app/features/book/components/book-browser/book-browser.component.ts
Added language-change subscription to refresh currentFilterLabel; replaced FilterLabelHelper name lookup with transloco keys for filter-type labels while preserving display value formatting.
Language chart localization
frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
Added activeLang signal from t.langChanges$, updated computed languageStats to depend on it, and enhanced getDisplayName() to use Intl.DisplayNames with fallbacks; added helpers (isLanguageCode, capitalize, getActiveLang) and RxJS/toSignal interop.
Icon picker template and logic i18n
frontend/src/app/shared/components/icon-picker/icon-picker-component.html, frontend/src/app/shared/components/icon-picker/icon-picker-component.ts, frontend/src/i18n/en/shared.json, frontend/src/i18n/es/shared.json
Wrapped template in transloco namespace and replaced hardcoded strings with t(...); replaced hardcoded error/toast messages with translated keys and parameters; added iconPicker translation namespace in en/es JSON; updated component imports metadata.

Sequence Diagram(s)

(Skipped — changes are per-component i18n updates and do not introduce a new multi-component sequential flow that benefits from a sequence diagram.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • imajes
  • balazs-szucs

Poem

🐰
I hopped through strings both near and far,
Replaced each "hardcoded" with a star,
When language turns, the labels sing,
Icons, charts — now everything,
Translation carrots — hop, hooray! 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows the conventional commit format with 'fix' type and a clear, descriptive scope about i18n translation gaps and language reactivity.
Description check ✅ Passed The PR description includes all required template sections: Description, Linked Issue, and Changes with sufficient detail about the PR's purpose, scope, and implementation approach.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #103: adds localization to hardcoded strings (book-browser, icon-picker, language-chart), implements reactive language updates via langChanges$ subscriptions, and uses Intl.DisplayNames for language labels.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing issue #103: localization additions, translation string imports, and reactive language subscriptions are all aligned with the linked issue requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/i18n-translation-reactivity
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch refactor/i18n-translation-reactivity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts (1)

158-161: Signal dependency invocation could use a clarifying comment.

The this.activeLang() call exists solely to establish a reactive dependency so the computed re-runs on language changes. Consider adding a brief comment to clarify intent for future maintainers.

 public readonly languageStats = computed(() => {
-  this.activeLang();
+  this.activeLang(); // Re-run when active language changes
   return this.calculateLanguageStats(this.filteredBooks());
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts`
around lines 158 - 161, The computed getter languageStats currently calls
this.activeLang() solely to establish a reactive dependency so the computed
re-runs on language changes; add a brief clarifying comment next to the
this.activeLang() invocation (inside the languageStats computed) stating that
the call is intentional and only meant to subscribe to activeLang changes (so
readers won't think it's a no-op bug), keeping the rest of the logic that
returns calculateLanguageStats(this.filteredBooks()) unchanged.
frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts (1)

215-249: Consider extracting sortOptions initialization to reduce duplication.

The same sortOptions array is built twice: once at initialization and again in the langChanges$ callback. Extracting this into a helper method would improve maintainability.

♻️ Suggested refactor
+  private buildSortOptions(): SortOption[] {
+    return [
+      {label: this.t.translate('authorBrowser.sort.name'), value: 'name'},
+      {label: this.t.translate('authorBrowser.sort.bookCount'), value: 'book-count'},
+      {label: this.t.translate('authorBrowser.sort.matched'), value: 'matched'},
+      {label: this.t.translate('authorBrowser.sort.recentlyAdded'), value: 'recently-added'},
+      {label: this.t.translate('authorBrowser.sort.recentlyRead'), value: 'recently-read'},
+      {label: this.t.translate('authorBrowser.sort.readingProgress'), value: 'reading-progress'},
+      {label: this.t.translate('authorBrowser.sort.avgRating'), value: 'avg-rating'},
+      {label: this.t.translate('authorBrowser.sort.photo'), value: 'photo'},
+      {label: this.t.translate('authorBrowser.sort.seriesCount'), value: 'series-count'}
+    ];
+  }
+
   ngOnInit(): void {
     this.pageTitle.setPageTitle(this.t.translate('authorBrowser.pageTitle'));
-    this.sortOptions = [
-      {label: this.t.translate('authorBrowser.sort.name'), value: 'name'},
-      {label: this.t.translate('authorBrowser.sort.bookCount'), value: 'book-count'},
-      {label: this.t.translate('authorBrowser.sort.matched'), value: 'matched'},
-      {label: this.t.translate('authorBrowser.sort.recentlyAdded'), value: 'recently-added'},
-      {label: this.t.translate('authorBrowser.sort.recentlyRead'), value: 'recently-read'},
-      {label: this.t.translate('authorBrowser.sort.readingProgress'), value: 'reading-progress'},
-      {label: this.t.translate('authorBrowser.sort.avgRating'), value: 'avg-rating'},
-      {label: this.t.translate('authorBrowser.sort.photo'), value: 'photo'},
-      {label: this.t.translate('authorBrowser.sort.seriesCount'), value: 'series-count'}
-    ];
+    this.sortOptions = this.buildSortOptions();
     // ... existing code ...
     this.t.langChanges$
       .pipe(takeUntilDestroyed(this.destroyRef))
       .subscribe(() => {
-        this.sortOptions = [
-          {label: this.t.translate('authorBrowser.sort.name'), value: 'name'},
-          // ... all labels ...
-        ];
+        this.sortOptions = this.buildSortOptions();
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts`
around lines 215 - 249, Extract the duplicated array construction into a helper
method (e.g. buildSortOptions()) that returns the translated sortOptions array
using this.t.translate, then replace the two inline arrays with this.sortOptions
= this.buildSortOptions(); call both at initialization and inside the
this.t.langChanges$ subscription; keep the method on the component class so
setupScrollPositionTracking and the existing queryParam handling continue to
work unchanged and ensure the helper returns the same item shape ({label,
value}) as used currently.
frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts (1)

128-162: Same duplication pattern as author-browser—consider extracting builders.

The filterOptions and sortOptions arrays are duplicated between initial assignment and the langChanges$ callback. Consider extracting helper methods for consistency with similar components.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts`
around lines 128 - 162, The component duplicates construction of filterOptions
and sortOptions both on initialization and inside the this.t.langChanges$
subscription; extract two helper methods (e.g., buildFilterOptions() and
buildSortOptions()) that return the translated arrays and call those from the
initial assignments and from the this.t.langChanges$ subscribe callback,
replacing the repeated inline arrays so the translations are centralized and
consistent with other components like author-browser.
frontend/src/app/shared/components/icon-picker/icon-picker-component.ts (1)

236-236: Use the shared error helper here for consistency.

Line 236 still calls this.t.translate(...) directly for an error key; routing this through translateError('noIconsToSave') keeps one consistent error translation path.

Suggested small consistency diff
-      this.batchErrorMessage = this.t.translate('shared.iconPicker.errors.noIconsToSave');
+      this.batchErrorMessage = this.translateError('noIconsToSave');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/shared/components/icon-picker/icon-picker-component.ts` at
line 236, Replace the direct translation call that sets this.batchErrorMessage
with the shared error helper: instead of calling
this.t.translate('shared.iconPicker.errors.noIconsToSave') assign via the common
helper function (translateError('noIconsToSave')) so error messages follow the
centralized translation path; update the assignment that sets
this.batchErrorMessage accordingly and ensure you import or reference the
component's translateError helper used elsewhere in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts`:
- Around line 215-249: Extract the duplicated array construction into a helper
method (e.g. buildSortOptions()) that returns the translated sortOptions array
using this.t.translate, then replace the two inline arrays with this.sortOptions
= this.buildSortOptions(); call both at initialization and inside the
this.t.langChanges$ subscription; keep the method on the component class so
setupScrollPositionTracking and the existing queryParam handling continue to
work unchanged and ensure the helper returns the same item shape ({label,
value}) as used currently.

In
`@frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts`:
- Around line 128-162: The component duplicates construction of filterOptions
and sortOptions both on initialization and inside the this.t.langChanges$
subscription; extract two helper methods (e.g., buildFilterOptions() and
buildSortOptions()) that return the translated arrays and call those from the
initial assignments and from the this.t.langChanges$ subscribe callback,
replacing the repeated inline arrays so the translations are centralized and
consistent with other components like author-browser.

In
`@frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts`:
- Around line 158-161: The computed getter languageStats currently calls
this.activeLang() solely to establish a reactive dependency so the computed
re-runs on language changes; add a brief clarifying comment next to the
this.activeLang() invocation (inside the languageStats computed) stating that
the call is intentional and only meant to subscribe to activeLang changes (so
readers won't think it's a no-op bug), keeping the rest of the logic that
returns calculateLanguageStats(this.filteredBooks()) unchanged.

In `@frontend/src/app/shared/components/icon-picker/icon-picker-component.ts`:
- Line 236: Replace the direct translation call that sets this.batchErrorMessage
with the shared error helper: instead of calling
this.t.translate('shared.iconPicker.errors.noIconsToSave') assign via the common
helper function (translateError('noIconsToSave')) so error messages follow the
centralized translation path; update the assignment that sets
this.batchErrorMessage accordingly and ensure you import or reference the
component's translateError helper used elsewhere in this file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a7bd8b76-104e-4826-9f2b-2e2ef40187bb

📥 Commits

Reviewing files that changed from the base of the PR and between a86d661 and 6a9e5b7.

📒 Files selected for processing (11)
  • frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts
  • frontend/src/app/features/book/components/book-browser/book-browser.component.html
  • frontend/src/app/features/book/components/book-browser/book-browser.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.html
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
  • frontend/src/i18n/en/book.json
  • frontend/src/i18n/en/shared.json
  • frontend/src/i18n/es/book.json
  • frontend/src/i18n/es/shared.json
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (4)
frontend/src/i18n/**

📄 CodeRabbit inference engine (AGENTS.md)

Put user-facing strings in Transloco files under frontend/src/i18n/

Files:

  • frontend/src/i18n/en/book.json
  • frontend/src/i18n/es/book.json
  • frontend/src/i18n/en/shared.json
  • frontend/src/i18n/es/shared.json
frontend/src/**/*.{ts,tsx,html,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation in TypeScript, HTML, and SCSS files

Files:

  • frontend/src/app/features/book/components/book-browser/book-browser.component.html
  • frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.html
  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
  • frontend/src/app/features/book/components/book-browser/book-browser.component.ts
frontend/src/app/**/*.component.ts

📄 CodeRabbit inference engine (AGENTS.md)

Keep Angular code on standalone components. Do not add NgModules

Files:

  • frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/features/book/components/book-browser/book-browser.component.ts
frontend/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/app/**/*.{ts,tsx}: Prefer inject() over constructor injection
Follow frontend/eslint.config.js: component selectors use app-*, directive selectors use app*, and any is disallowed

Files:

  • frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
  • frontend/src/app/features/book/components/book-browser/book-browser.component.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to frontend/src/i18n/** : Put user-facing strings in Transloco files under `frontend/src/i18n/`
Learnt from: alexhb1
Repo: grimmory-tools/grimmory PR: 307
File: frontend/src/i18n/hr/layout.json:3-3
Timestamp: 2026-03-31T14:12:39.521Z
Learning: In `frontend/src/i18n/hr/layout.json` (Croatian locale), newly added i18n keys may intentionally use English placeholder values (e.g., `toggleNavigation`, `adjustSidebarWidth`, `closeNavigation`). Do not flag these as missing Croatian translations — this is a deliberate placeholder approach by the team.
📚 Learning: 2026-03-31T14:12:39.521Z
Learnt from: alexhb1
Repo: grimmory-tools/grimmory PR: 307
File: frontend/src/i18n/hr/layout.json:3-3
Timestamp: 2026-03-31T14:12:39.521Z
Learning: In `frontend/src/i18n/hr/layout.json` (Croatian locale), newly added i18n keys may intentionally use English placeholder values (e.g., `toggleNavigation`, `adjustSidebarWidth`, `closeNavigation`). Do not flag these as missing Croatian translations — this is a deliberate placeholder approach by the team.

Applied to files:

  • frontend/src/i18n/es/book.json
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.html
  • frontend/src/i18n/en/shared.json
  • frontend/src/i18n/es/shared.json
📚 Learning: 2026-04-04T14:03:28.295Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 372
File: frontend/src/app/features/book/service/app-books-api.service.ts:150-178
Timestamp: 2026-04-04T14:03:28.295Z
Learning: In `frontend/src/app/features/book/service/app-books-api.service.ts`, the `summaryToBook` function intentionally uses `as unknown as Book` to cast a partial object to the `Book` type. The returned object omits some required `Book` fields (e.g. `metadata.bookId`, `pdfProgress.page`) and includes an extra `bookFiles: []` property not in the `Book` interface. This is by design — consumers of `AppBooksApiService.books()` (e.g. `BookCardComponent`) only access the subset of properties that are populated from `AppBookSummary`, so no runtime issues occur. Do not flag this as a type safety error.

Applied to files:

  • frontend/src/app/features/book/components/book-browser/book-browser.component.html
  • frontend/src/app/features/book/components/book-browser/book-browser.component.ts
📚 Learning: 2026-04-05T21:16:01.715Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 385
File: frontend/src/app/app.component.ts:55-56
Timestamp: 2026-04-05T21:16:01.715Z
Learning: When reviewing code in the Grimmory frontend (Angular), prefer modern Angular patterns. Specifically: (1) Prefer `DestroyRef` with `takeUntilDestroyed(destroyRef)` for teardown in Angular v16+ instead of manually tracking `Subscription` arrays and calling `unsubscribe()` in `ngOnDestroy()`. (2) Prefer `inject()` for dependency injection over constructor injection where appropriate. (3) Prefer Angular signals (e.g., `signal`, `computed`) over `BehaviorSubject`/`Observable` for state where signals/computed values fit the use case. Flag older patterns when they can be replaced with these modern equivalents without changing behavior.

Applied to files:

  • frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
  • frontend/src/app/features/book/components/book-browser/book-browser.component.ts
📚 Learning: 2026-04-07T09:28:09.587Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 393
File: frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts:255-263
Timestamp: 2026-04-07T09:28:09.587Z
Learning: In this Angular frontend (under frontend/src/app/), flag manual resource management/cleanup patterns when there is an Angular v16+ automatic alternative. Examples to prefer: (1) Instead of manually pairing document/window event listeners with stored cleanup functions (e.g., add/removeEventListener with mouseMoveCleanup/documentClickCleanup/keydownCleanup/touchCleanup fields), register teardown via DestroyRef.onDestroy(cleanupFn) (or equivalent Angular v16+ teardown mechanism). (2) Instead of storing Subscriptions in fields and explicitly unsubscribing in ngOnDestroy (e.g., annotationSaveSubscription/annotationCacheSubscription), use takeUntilDestroyed(destroyRef) (piped into the observable) or other Angular v16+ primitives. (3) If teardown is lifecycle-coupled and can be automated via DestroyRef/takeUntilDestroyed/signals (or other Angular v16+ mechanisms), prefer the automated approach over manual ngOnDestroy cleanup. Raise a review finding for the manual pattern and recommend the aut...

Applied to files:

  • frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
  • frontend/src/app/features/book/components/book-browser/book-browser.component.ts
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to frontend/src/app/**/*.component.ts : Keep Angular code on standalone components. Do not add NgModules

Applied to files:

  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to frontend/src/i18n/** : Put user-facing strings in Transloco files under `frontend/src/i18n/`

Applied to files:

  • frontend/src/app/shared/components/icon-picker/icon-picker-component.html
  • frontend/src/i18n/en/shared.json
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
🪛 HTMLHint (1.9.2)
frontend/src/app/shared/components/icon-picker/icon-picker-component.html

[error] 1-1: Doctype must be declared before any non-comment content.

(doctype-first)

🔇 Additional comments (11)
frontend/src/i18n/en/book.json (1)

53-59: LGTM!

The new entityTypes translation keys are well-structured and align with the PR objective of replacing hardcoded entity type labels.

frontend/src/app/features/book/components/book-browser/book-browser.component.html (1)

21-21: LGTM!

The entity type label now correctly uses Transloco translations instead of directly interpolating the enum value, addressing the i18n gap.

frontend/src/i18n/es/book.json (1)

53-59: LGTM!

Spanish translations for entityTypes are correct and consistent with the English locale structure.

frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts (1)

275-305: Good use of Intl.DisplayNames for localized language names.

This aligns with the suggestion in Issue #103 to use Intl.DisplayNames instead of a hardcoded array. The fallback chain (Intl → LANGUAGE_NAMES map → capitalize) provides good resilience.

One minor observation: Line 279 calls this.getActiveLang() directly rather than reading from the activeLang signal. Since this method is called from within languageStats (which already tracks the signal), it works correctly, but using this.activeLang() would be more consistent with the signal-based approach.

frontend/src/app/features/book/components/book-browser/book-browser.component.ts (3)

642-653: Language change subscription follows existing patterns in this file.

The implementation correctly updates currentFilterLabel when the language changes. Note that this file uses the older takeUntil(this.destroy$) pattern rather than the modern takeUntilDestroyed(destroyRef), but this is consistent with existing subscriptions in this component (e.g., lines 520-528, 540, 564-569).


694-708: LGTM!

The getEntityTypeLabelKey method provides a clean, type-safe mapping from EntityType enum values to i18n keys. The return type literal union ensures only valid keys are returned.


458-469: No action needed. All FilterType values have corresponding i18n keys in book.filter.labels for both English and Spanish translation files. The dynamic key construction is correctly supported by the translation files.

frontend/src/app/shared/components/icon-picker/icon-picker-component.html (1)

1-259: Template localization coverage looks solid and reactive.

All newly changed user-facing labels/placeholders/actions are bound through t(...) under the shared.iconPicker prefix, which aligns with the i18n objective and avoids hardcoded English in this component.

As per coding guidelines, "Put user-facing strings in Transloco files under frontend/src/i18n/".

frontend/src/i18n/en/shared.json (1)

154-200: New English iconPicker translation namespace is well-structured and complete.

The added keys cover UI text, validation errors, and toast messages used by the updated icon picker template and runtime logic.

As per coding guidelines, "Put user-facing strings in Transloco files under frontend/src/i18n/".

frontend/src/app/shared/components/icon-picker/icon-picker-component.ts (1)

16-17: Runtime i18n integration in component logic is implemented cleanly.

Using TranslocoService + translateError(...) for validation/loading/save/delete paths removes hardcoded English from logic and keeps messaging centralized.

Also applies to: 43-45, 64-65, 156-157, 181-194, 270-286, 296-297, 308-322, 330-355

frontend/src/i18n/es/shared.json (1)

154-200: Spanish iconPicker translations are in good parity with the new feature keys.

The added namespace covers the same functional areas (UI, errors, toast) needed by the updated icon picker flows.

As per coding guidelines, "Put user-facing strings in Transloco files under frontend/src/i18n/".

… auth/task endpoints (#437)

* docs(api): add OpenAPI tags and operation metadata to controllers

* docs(api): normalize @operation annotation formatting in OpenAPI controllers

* docs(api): align operationId values with existing controller method names

* chore(api-docs): update api docs to be enabled by default for dev builds
balazs-szucs and others added 13 commits April 9, 2026 22:50
…#372)

* refactor(api): optimize caching mechanisms and improve book filtering logic

* refactor(preferences): remove unused ExternalDocLinkComponent and enhance translate function typing

* refactor(initializer): add Duration import for improved cache management

* refactor(docker): increase MaxMetaspaceSize for improved memory management

* refactor(repository): replace findAllForSummaryByIds with findAllById

* refactor(cache): implement invalidateAppBooksQueries for improved cache management

* refactor(AppBookService): increase default page size and optimize magic shelf book resolution

* refactor(AppBookSpecification, MagicShelfBookService, TaskCancellationManager, magic-shelf-component): streamline join handling and improve memory management

* refactor(AppBookService, MagicShelfBookService): enhance book retrieval logic and improve error handling

* chore: fix formatting

* chore: fix formatting
…and update related code (#334)

* refactor(epub): migrate from documentnode to grimmory epub4j library and update related code

* chore(deps): restore proper imports

* refactor(epub): update to epub4j version 1.0.0 and improve cover image detection

* fix(epub): validate spine indices in convertXPointerRangeToCfi method

* chore(deps): update epub4j dependencies to version 1.0.1

* chore(deps): update epub4j dependency to version 1.0.1

* feat(build): enable preview features and update epub4j dependencies in build configuration

* feat(epub): update epub4j dependencies to version 1.1.0 and refactor resource handling for improved streaming
…g and low priority fetch (#431)

* fix(lazy-loading): improve image loading performance with lazy loading and low priority fetch

* fix(styles): update intrinsic size for virtual scroller items to use CSS variable

* refactor(book-card): simplify redundant cover width binding

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: Zach <zachyale@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…uses and file types (#448)

* fix(filter): update filter options to use CountedOption for read statuses and file types

* fix(specification): validate file types and read statuses, throwing APIException for invalid values
… in infinite scrolling (#433)

* fix(reader): optimize memory management for canvas and image handling in infinite scrolling

* fix(reader): improve image loading management and optimize memory usage in infinite scrolling
* fix(dashboard): improve performance with OnPush change detection

* fix(dashboard): refactor book card component for improved readability and performance

* fix(tests): mock matchMedia for improved test compatibility

* fix(tests): matchMedia mock for better CI compatibility and support

* fix(dashboard): add clearAll method to BookBrowserScrollService and update tests

* fix(dashboard): improve performance with content visibility and refactor styles

* fix(dashboard): improve scroll performance with estimated total height and refactor layout

* fix(tests): add mock for getCurrentUser in userService for improved test coverage

* fix(dashboard): implement infinite scroll for book loading and scroll restoration

* fix(tests): fix lint error

* fix(tests): mock ResizeObserver and IntersectionObserver

* fix(tests): mock ResizeObserver and IntersectionObserver (again)

* fix(dashboard): improve accessibility and performance by updating aria attributes and optimizing animations

* fix(dashboard): improve submenu accessibility and optimize virtual scroller performance

* fix(dashboard): clean up imports and improve component structure

* fix(dashboard): correct mobile check in author browser component

* fix(dashboard): add OnDestroy lifecycle hook to BookBrowserComponent
… for better resource management (#443)

* refactor(settings): replace unsubscribe logic with takeUntilDestroyed for better resource management

* refactor(settings): streamline saveSettings method and improve error handling

* fix(settings): improve error handling in saveSetting method

* refactor(settings): update tab structure

* refactor(settings): implement OnDestroy interface and clean up unused destroyRef instances
…ures (#463)

* fix(reader): resolve epub preview runtime and audiobook download failures

* fix(files): load book library path for additional file downloads

* fix(files): scope additional file operations to book-owned non-primary files
* fix(pdf-reader): localize embedpdf viewer to active app language

* fix(pdf-reader): use runtime locale fallback for embedpdf translations

* fix(pdf-reader): clarify document viewer save info copy
# Conflicts:
#	frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts
#	frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
#	frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
#	frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts (1)

159-196: Extract localized option construction into one helper.

The same translation-array construction is duplicated between initial load and lang-change handling. Centralizing it will reduce drift risk.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts`
around lines 159 - 196, The duplicated construction of filterOptions and
sortOptions in ngOnInit and the t.langChanges$ subscription should be extracted
into a single helper method (e.g. buildFilterAndSortOptions or
updateFilterSortOptions) that sets this.filterOptions and this.sortOptions using
this.t.translate; call that helper from ngOnInit and from the subscription
callback (instead of repeating the arrays), and keep the existing
destroyRef/takeUntilDestroyed subscription behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts`:
- Around line 178-196: The language-change subscription in
series-browser.component (this.t.langChanges$) rebuilds filterOptions and
sortOptions but doesn't refresh the browser title; update the subscription to
also call the same title update used at initialization (the same code that calls
this.titleService.setTitle(...) / sets the page title at line ~160) so the page
title is retranslated on language switch—i.e., invoke the component's
title-setting logic (or call the existing method that computes and calls
this.titleService.setTitle) inside the langChanges$ subscribe block.

In
`@frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts`:
- Around line 276-282: The code is force-capitalizing localized language names
which can break locale-specific casing; in the blocks that use Intl.DisplayNames
(the branch guarded by isLanguageCode(lower)), remove the call to capitalize and
return displayName directly (i.e., replace return this.capitalize(displayName)
with return displayName), and make the same change for the other similar block
around the second Intl.DisplayNames usage (the code referencing getActiveLang()
and calling this.capitalize); keep Intl.DisplayNames and the existing guards
(isLanguageCode/getActiveLang) intact.

---

Nitpick comments:
In
`@frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts`:
- Around line 159-196: The duplicated construction of filterOptions and
sortOptions in ngOnInit and the t.langChanges$ subscription should be extracted
into a single helper method (e.g. buildFilterAndSortOptions or
updateFilterSortOptions) that sets this.filterOptions and this.sortOptions using
this.t.translate; call that helper from ngOnInit and from the subscription
callback (instead of repeating the arrays), and keep the existing
destroyRef/takeUntilDestroyed subscription behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00b2b034-d72f-44b3-b99c-e960636205c5

📥 Commits

Reviewing files that changed from the base of the PR and between 6a9e5b7 and 9a6a933.

📒 Files selected for processing (6)
  • frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts
  • frontend/src/app/features/book/components/book-browser/book-browser.component.html
  • frontend/src/app/features/book/components/book-browser/book-browser.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/app/features/author-browser/components/author-browser/author-browser.component.ts
  • frontend/src/app/features/book/components/book-browser/book-browser.component.ts
  • frontend/src/app/shared/components/icon-picker/icon-picker-component.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (3)
frontend/src/**/*.{ts,tsx,html,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation in TypeScript, HTML, and SCSS files

Files:

  • frontend/src/app/features/book/components/book-browser/book-browser.component.html
  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
frontend/src/app/**/*.component.ts

📄 CodeRabbit inference engine (AGENTS.md)

Keep Angular code on standalone components. Do not add NgModules

Files:

  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
frontend/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/app/**/*.{ts,tsx}: Prefer inject() over constructor injection
Follow frontend/eslint.config.js: component selectors use app-*, directive selectors use app*, and any is disallowed

Files:

  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to frontend/src/i18n/** : Put user-facing strings in Transloco files under `frontend/src/i18n/`
Learnt from: alexhb1
Repo: grimmory-tools/grimmory PR: 307
File: frontend/src/i18n/hr/layout.json:3-3
Timestamp: 2026-03-31T14:12:39.521Z
Learning: In `frontend/src/i18n/hr/layout.json` (Croatian locale), newly added i18n keys may intentionally use English placeholder values (e.g., `toggleNavigation`, `adjustSidebarWidth`, `closeNavigation`). Do not flag these as missing Croatian translations — this is a deliberate placeholder approach by the team.
📚 Learning: 2026-04-04T14:03:28.295Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 372
File: frontend/src/app/features/book/service/app-books-api.service.ts:150-178
Timestamp: 2026-04-04T14:03:28.295Z
Learning: In `frontend/src/app/features/book/service/app-books-api.service.ts`, the `summaryToBook` function intentionally uses `as unknown as Book` to cast a partial object to the `Book` type. The returned object omits some required `Book` fields (e.g. `metadata.bookId`, `pdfProgress.page`) and includes an extra `bookFiles: []` property not in the `Book` interface. This is by design — consumers of `AppBooksApiService.books()` (e.g. `BookCardComponent`) only access the subset of properties that are populated from `AppBookSummary`, so no runtime issues occur. Do not flag this as a type safety error.

Applied to files:

  • frontend/src/app/features/book/components/book-browser/book-browser.component.html
📚 Learning: 2026-04-05T21:16:01.715Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 385
File: frontend/src/app/app.component.ts:55-56
Timestamp: 2026-04-05T21:16:01.715Z
Learning: When reviewing code in the Grimmory frontend (Angular), prefer modern Angular patterns. Specifically: (1) Prefer `DestroyRef` with `takeUntilDestroyed(destroyRef)` for teardown in Angular v16+ instead of manually tracking `Subscription` arrays and calling `unsubscribe()` in `ngOnDestroy()`. (2) Prefer `inject()` for dependency injection over constructor injection where appropriate. (3) Prefer Angular signals (e.g., `signal`, `computed`) over `BehaviorSubject`/`Observable` for state where signals/computed values fit the use case. Flag older patterns when they can be replaced with these modern equivalents without changing behavior.

Applied to files:

  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
📚 Learning: 2026-04-07T09:28:09.587Z
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 393
File: frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts:255-263
Timestamp: 2026-04-07T09:28:09.587Z
Learning: In this Angular frontend (under frontend/src/app/), flag manual resource management/cleanup patterns when there is an Angular v16+ automatic alternative. Examples to prefer: (1) Instead of manually pairing document/window event listeners with stored cleanup functions (e.g., add/removeEventListener with mouseMoveCleanup/documentClickCleanup/keydownCleanup/touchCleanup fields), register teardown via DestroyRef.onDestroy(cleanupFn) (or equivalent Angular v16+ teardown mechanism). (2) Instead of storing Subscriptions in fields and explicitly unsubscribing in ngOnDestroy (e.g., annotationSaveSubscription/annotationCacheSubscription), use takeUntilDestroyed(destroyRef) (piped into the observable) or other Angular v16+ primitives. (3) If teardown is lifecycle-coupled and can be automated via DestroyRef/takeUntilDestroyed/signals (or other Angular v16+ mechanisms), prefer the automated approach over manual ngOnDestroy cleanup. Raise a review finding for the manual pattern and recommend the aut...

Applied to files:

  • frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts
  • frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts
🔇 Additional comments (3)
frontend/src/app/features/book/components/book-browser/book-browser.component.html (1)

21-21: Nice fix: entity type is now properly localized and reactive.

Line 21 correctly routes the entity-type label through Transloco (entityTypes.*) instead of rendering raw enum text, which aligns with the PR objective and should update on language switch.

Based on learnings: Put user-facing strings in Transloco files under frontend/src/i18n/.

frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts (1)

5-5: Good reactive teardown pattern here.

Using takeUntilDestroyed(this.destroyRef) in Line 179 is the right lifecycle-safe approach for this subscription.

Based on learnings: Prefer DestroyRef with takeUntilDestroyed(destroyRef) for teardown in Angular v16+ instead of manual subscription cleanup.

Also applies to: 178-180

frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts (1)

142-147: Nice fix for language-change reactivity in chart labels.

The toSignal(...) + computed dependency on activeLang() ensures chart text updates without reload.

Based on learnings: Prefer Angular signals/computed for state where signals fit the use case.

Also applies to: 157-160

Comment on lines +178 to +196
this.t.langChanges$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.filterOptions = [
{label: this.t.translate('seriesBrowser.filters.all'), value: 'all'},
{label: this.t.translate('seriesBrowser.filters.notStarted'), value: 'not-started'},
{label: this.t.translate('seriesBrowser.filters.inProgress'), value: 'in-progress'},
{label: this.t.translate('seriesBrowser.filters.completed'), value: 'completed'},
{label: this.t.translate('seriesBrowser.filters.abandoned'), value: 'abandoned'}
];
this.sortOptions = [
{label: this.t.translate('seriesBrowser.sort.nameAsc'), value: 'name-asc'},
{label: this.t.translate('seriesBrowser.sort.nameDesc'), value: 'name-desc'},
{label: this.t.translate('seriesBrowser.sort.bookCount'), value: 'book-count'},
{label: this.t.translate('seriesBrowser.sort.progress'), value: 'progress'},
{label: this.t.translate('seriesBrowser.sort.recentlyRead'), value: 'recently-read'},
{label: this.t.translate('seriesBrowser.sort.recentlyAdded'), value: 'recently-added'}
];
});
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update the page title on language switches too.

Line 160 sets the title once, but the lang-change handler (Lines 178-196) does not refresh it. The browser title will stay in the old language until navigation/reload.

Suggested fix
     this.t.langChanges$
       .pipe(takeUntilDestroyed(this.destroyRef))
       .subscribe(() => {
+        this.pageTitle.setPageTitle(this.t.translate('seriesBrowser.pageTitle'));
         this.filterOptions = [
           {label: this.t.translate('seriesBrowser.filters.all'), value: 'all'},
           {label: this.t.translate('seriesBrowser.filters.notStarted'), value: 'not-started'},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts`
around lines 178 - 196, The language-change subscription in
series-browser.component (this.t.langChanges$) rebuilds filterOptions and
sortOptions but doesn't refresh the browser title; update the subscription to
also call the same title update used at initialization (the same code that calls
this.titleService.setTitle(...) / sets the page title at line ~160) so the page
title is retranslated on language switch—i.e., invoke the component's
title-setting logic (or call the existing method that computes and calls
this.titleService.setTitle) inside the langChanges$ subscribe block.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly if there are this many places we need to be dynamic to update localization we likely need a more holistic approach that is transferable to other components

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zachyale — that's a very valid architectural observation. Transloco actually ships a first-class solution for this: translateSignal() from @jsverse/transloco, which is integrated with Angular's dependency injection system and can be called within an injection context. It returns a signal that automatically re-evaluates when the active language changes — no manual subscription or ngOnInit wiring needed.

For option arrays like filterOptions/sortOptions, the idiomatic holistic approach is to:

  1. Create a single activeLang signal by bridging langChanges$ via toSignal() (your language-chart.component.ts in this same PR already demonstrates this pattern).
  2. Express the option arrays as computed() signals that read activeLang() — they will automatically recompute on language change.

Here is how series-browser.component.ts would look with this approach:

// Bridge langChanges$ once — no subscription, no ngOnDestroy wiring needed
private readonly activeLang = toSignal(this.t.langChanges$, {
  initialValue: this.t.getActiveLang()
});

// Reactive option arrays — recompute automatically when activeLang() changes
readonly filterOptions = computed<FilterOption[]>(() => {
  this.activeLang(); // establish reactive dependency
  return [
    { label: this.t.translate('seriesBrowser.filters.all'),        value: 'all' },
    { label: this.t.translate('seriesBrowser.filters.notStarted'), value: 'not-started' },
    { label: this.t.translate('seriesBrowser.filters.inProgress'), value: 'in-progress' },
    { label: this.t.translate('seriesBrowser.filters.completed'),  value: 'completed' },
    { label: this.t.translate('seriesBrowser.filters.abandoned'),  value: 'abandoned' },
  ];
});

readonly sortOptions = computed<SortOption[]>(() => {
  this.activeLang();
  return [
    { label: this.t.translate('seriesBrowser.sort.nameAsc'),      value: 'name-asc' },
    { label: this.t.translate('seriesBrowser.sort.nameDesc'),     value: 'name-desc' },
    { label: this.t.translate('seriesBrowser.sort.bookCount'),    value: 'book-count' },
    { label: this.t.translate('seriesBrowser.sort.progress'),     value: 'progress' },
    { label: this.t.translate('seriesBrowser.sort.recentlyRead'), value: 'recently-read' },
    { label: this.t.translate('seriesBrowser.sort.recentlyAdded'), value: 'recently-added' },
  ];
});

For single translated strings (like the page title), translateSignal is even cleaner:

// Automatically reactive — updates with language changes
private readonly pageTitleLabel = translateSignal('seriesBrowser.pageTitle');

// Then in ngOnInit, or an effect():
effect(() => this.pageTitle.setPageTitle(this.pageTitleLabel()));

The same activeLang + computed() pattern can be extracted into a shared utility or base helper so every component that needs reactive option arrays only calls toSignal(t.langChanges$) once and uses computed() for everything derived from it — no repeated subscribe/ngOnDestroy boilerplate across the codebase. This would apply uniformly to author-browser, series-browser, book-browser, and any future components.

Would you like me to plan these changes as a refactor across all affected components?


✏️ Learnings added
Learnt from: zachyale
Repo: grimmory-tools/grimmory PR: 439
File: frontend/src/app/features/series-browser/components/series-browser/series-browser.component.ts:178-196
Timestamp: 2026-04-11T03:55:57.229Z
Learning: In the Grimmory Angular frontend (frontend/src/app/), the preferred holistic approach for reactive localization (auto-updating on language switch) is: (1) Use `translateSignal()` from `jsverse/transloco` for individual reactive translated strings. (2) For option/label arrays, create a single `activeLang` signal via `toSignal(t.langChanges$, { initialValue: t.getActiveLang() })` and derive option arrays as `computed()` signals that read `activeLang()` — this eliminates all manual `langChanges$` subscriptions and `ngOnDestroy` wiring. The team explicitly prefers this signal/computed pattern over manual subscribe-in-ngOnInit approaches when implementing reactive i18n.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 385
File: frontend/src/app/app.component.ts:55-56
Timestamp: 2026-04-05T21:16:01.715Z
Learning: When reviewing code in the Grimmory frontend (Angular), prefer modern Angular patterns. Specifically: (1) Prefer `DestroyRef` with `takeUntilDestroyed(destroyRef)` for teardown in Angular v16+ instead of manually tracking `Subscription` arrays and calling `unsubscribe()` in `ngOnDestroy()`. (2) Prefer `inject()` for dependency injection over constructor injection where appropriate. (3) Prefer Angular signals (e.g., `signal`, `computed`) over `BehaviorSubject`/`Observable` for state where signals/computed values fit the use case. Flag older patterns when they can be replaced with these modern equivalents without changing behavior.

Learnt from: balazs-szucs
Repo: grimmory-tools/grimmory PR: 393
File: frontend/src/app/features/readers/pdf-reader/pdf-reader.component.ts:255-263
Timestamp: 2026-04-07T09:28:09.587Z
Learning: In this Angular frontend (under frontend/src/app/), flag manual resource management/cleanup patterns when there is an Angular v16+ automatic alternative. Examples to prefer: (1) Instead of manually pairing document/window event listeners with stored cleanup functions (e.g., add/removeEventListener with mouseMoveCleanup/documentClickCleanup/keydownCleanup/touchCleanup fields), register teardown via DestroyRef.onDestroy(cleanupFn) (or equivalent Angular v16+ teardown mechanism). (2) Instead of storing Subscriptions in fields and explicitly unsubscribing in ngOnDestroy (e.g., annotationSaveSubscription/annotationCacheSubscription), use takeUntilDestroyed(destroyRef) (piped into the observable) or other Angular v16+ primitives. (3) If teardown is lifecycle-coupled and can be automated via DestroyRef/takeUntilDestroyed/signals (or other Angular v16+ mechanisms), prefer the automated approach over manual ngOnDestroy cleanup. Raise a review finding for the manual pattern and recommend the automated alternative.

Comment on lines +276 to +282
if (this.isLanguageCode(lower)) {
try {
const displayNames = new Intl.DisplayNames([this.getActiveLang()], {type: 'language'});
const displayName = displayNames.of(lower);
if (displayName) {
return this.capitalize(displayName);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid forcing capitalization on localized language names.

Intl.DisplayNames already returns locale-correct labels. Capitalizing them (Line 281) can produce linguistically incorrect casing for some locales.

Suggested fix
     if (this.isLanguageCode(lower)) {
       try {
         const displayNames = new Intl.DisplayNames([this.getActiveLang()], {type: 'language'});
         const displayName = displayNames.of(lower);
         if (displayName) {
-          return this.capitalize(displayName);
+          return displayName;
         }
       } catch {
         // Fall through to existing mapping behavior.
       }
     }

Also applies to: 291-300

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.ts`
around lines 276 - 282, The code is force-capitalizing localized language names
which can break locale-specific casing; in the blocks that use Intl.DisplayNames
(the branch guarded by isLanguageCode(lower)), remove the call to capitalize and
return displayName directly (i.e., replace return this.capitalize(displayName)
with return displayName), and make the same change for the other similar block
around the second Intl.DisplayNames usage (the code referencing getActiveLang()
and calling this.capitalize); keep Intl.DisplayNames and the existing guards
(isLanguageCode/getActiveLang) intact.

@zachyale zachyale force-pushed the develop branch 2 times, most recently from 0bf513c to 62434c5 Compare April 22, 2026 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Translation problems