fix(i18n): resolve translation gaps + language reactivity#439
fix(i18n): resolve translation gaps + language reactivity#439
Conversation
## [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>
…or improved configuration (#139)
…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
* 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
📝 WalkthroughWalkthroughComponents 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
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
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
There was a problem hiding this comment.
🧹 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 extractingsortOptionsinitialization to reduce duplication.The same
sortOptionsarray is built twice: once at initialization and again in thelangChanges$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
filterOptionsandsortOptionsarrays are duplicated between initial assignment and thelangChanges$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 throughtranslateError('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
📒 Files selected for processing (11)
frontend/src/app/features/author-browser/components/author-browser/author-browser.component.tsfrontend/src/app/features/book/components/book-browser/book-browser.component.htmlfrontend/src/app/features/book/components/book-browser/book-browser.component.tsfrontend/src/app/features/series-browser/components/series-browser/series-browser.component.tsfrontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.tsfrontend/src/app/shared/components/icon-picker/icon-picker-component.htmlfrontend/src/app/shared/components/icon-picker/icon-picker-component.tsfrontend/src/i18n/en/book.jsonfrontend/src/i18n/en/shared.jsonfrontend/src/i18n/es/book.jsonfrontend/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.jsonfrontend/src/i18n/es/book.jsonfrontend/src/i18n/en/shared.jsonfrontend/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.htmlfrontend/src/app/features/author-browser/components/author-browser/author-browser.component.tsfrontend/src/app/features/series-browser/components/series-browser/series-browser.component.tsfrontend/src/app/shared/components/icon-picker/icon-picker-component.htmlfrontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.tsfrontend/src/app/shared/components/icon-picker/icon-picker-component.tsfrontend/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.tsfrontend/src/app/features/series-browser/components/series-browser/series-browser.component.tsfrontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.tsfrontend/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}: Preferinject()over constructor injection
Followfrontend/eslint.config.js: component selectors useapp-*, directive selectors useapp*, andanyis disallowed
Files:
frontend/src/app/features/author-browser/components/author-browser/author-browser.component.tsfrontend/src/app/features/series-browser/components/series-browser/series-browser.component.tsfrontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.tsfrontend/src/app/shared/components/icon-picker/icon-picker-component.tsfrontend/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.jsonfrontend/src/app/shared/components/icon-picker/icon-picker-component.htmlfrontend/src/i18n/en/shared.jsonfrontend/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.htmlfrontend/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.tsfrontend/src/app/features/series-browser/components/series-browser/series-browser.component.tsfrontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.tsfrontend/src/app/shared/components/icon-picker/icon-picker-component.tsfrontend/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.tsfrontend/src/app/features/series-browser/components/series-browser/series-browser.component.tsfrontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.tsfrontend/src/app/shared/components/icon-picker/icon-picker-component.tsfrontend/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.tsfrontend/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.htmlfrontend/src/i18n/en/shared.jsonfrontend/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
entityTypestranslation 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
entityTypesare 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 ofIntl.DisplayNamesfor localized language names.This aligns with the suggestion in Issue
#103to useIntl.DisplayNamesinstead 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 theactiveLangsignal. Since this method is called from withinlanguageStats(which already tracks the signal), it works correctly, but usingthis.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
currentFilterLabelwhen the language changes. Note that this file uses the oldertakeUntil(this.destroy$)pattern rather than the moderntakeUntilDestroyed(destroyRef), but this is consistent with existing subscriptions in this component (e.g., lines 520-528, 540, 564-569).
694-708: LGTM!The
getEntityTypeLabelKeymethod provides a clean, type-safe mapping fromEntityTypeenum values to i18n keys. The return type literal union ensures only valid keys are returned.
458-469: No action needed. AllFilterTypevalues have correspondingi18nkeys inbook.filter.labelsfor 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 theshared.iconPickerprefix, 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 EnglishiconPickertranslation 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: SpanishiconPickertranslations 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
…#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
…bookReaderComponent (#435)
* 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
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
frontend/src/app/features/author-browser/components/author-browser/author-browser.component.tsfrontend/src/app/features/book/components/book-browser/book-browser.component.htmlfrontend/src/app/features/book/components/book-browser/book-browser.component.tsfrontend/src/app/features/series-browser/components/series-browser/series-browser.component.tsfrontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.tsfrontend/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.htmlfrontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.tsfrontend/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.tsfrontend/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}: Preferinject()over constructor injection
Followfrontend/eslint.config.js: component selectors useapp-*, directive selectors useapp*, andanyis disallowed
Files:
frontend/src/app/features/stats/component/library-stats/charts/language-chart/language-chart.component.tsfrontend/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.tsfrontend/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.tsfrontend/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
DestroyRefwithtakeUntilDestroyed(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(...)+computeddependency onactiveLang()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
| 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'} | ||
| ]; | ||
| }); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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:
- Create a single
activeLangsignal by bridginglangChanges$viatoSignal()(yourlanguage-chart.component.tsin this same PR already demonstrates this pattern). - Express the option arrays as
computed()signals that readactiveLang()— 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.
| 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); | ||
| } |
There was a problem hiding this comment.
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.
0bf513c to
62434c5
Compare
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
Summary by CodeRabbit
New Features
Improvements