Skip to content

feat(shared): add LanguageResolverService for normalizing + localizing language codes#2005

Merged
zachyale merged 1 commit into
developfrom
feat/language-resolver
Jul 16, 2026
Merged

feat(shared): add LanguageResolverService for normalizing + localizing language codes#2005
zachyale merged 1 commit into
developfrom
feat/language-resolver

Conversation

@zachyale

@zachyale zachyale commented Jul 14, 2026

Copy link
Copy Markdown
Member

Description

This PR adds a shared LanguageResolverService, which is the first step towards refactoring the platform to more pragmatically handle displaying/viewing/filtering by languages.

Linked Issue

Resolves #2004

Changes

  • Introduces an unusedLanguageResolverService that outputs languages in a struct

Sample ResolveLanguage structs:

Canadian English w/ French locale active:

{
  raw: 'en-CA',
  tag: 'en-CA',
  base: 'en',
  languageName: 'Anglais',
  regionName: 'Canada',
  displayName: 'Anglais (Canada)',
  recognized: true
}

Klingon (unknown value):

{
  raw: 'klingon',
  tag: 'klingon',
  base: 'klingon',
  languageName: 'Klingon',
  regionName: null,
  displayName: 'Klingon',
  recognized: false
}

Canadian English w/ English locale active:

{
  raw: 'EN_ca',
  tag: 'en-CA',
  base: 'en',
  languageName: 'English',
  regionName: 'Canada',
  displayName: 'English (Canada)',
  recognized: true
}

Manual Testing Steps

  1. Spec coverage

Screenshots (Optional)

N/A

Additional Context (Optional)

This is the first PR to implement this shared service. The following PRs will then refactor existing language spots to use the service

AI Disclosure

Spec coverage

Checklist

  • This PR links and implements an accepted issue.
  • This PR is a single focused change.
  • There are new or updated tests validating this change.
  • I ran just ui check and just api check.
  • I have added screenshots if there were any UI changes.
  • I have disclosed any AI usage as per the organization AI Policy above.
  • I understand all of my submitted changes.

Summary by CodeRabbit

  • New Features

    • Added language and locale resolution with normalized codes, regions, scripts, and language names.
    • Added localized display names that update when the application language changes.
    • Added graceful fallbacks for missing, invalid, or unrecognized language values.
  • Tests

    • Added comprehensive coverage for normalization, localization, fallback behavior, and invalid inputs.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1ccf14c4-8efb-41a4-85a9-bc8230781d33

📥 Commits

Reviewing files that changed from the base of the PR and between 29ebdac and ce56863.

📒 Files selected for processing (2)
  • frontend/src/app/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • grimmory-tools/grimmory-docs (manual)
📜 Recent review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Analyze (java-kotlin)
🧰 Additional context used
📓 Path-based instructions (7)
frontend/src/**/*.{ts,tsx,html,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation in TypeScript, HTML, and SCSS in frontend code

Files:

  • frontend/src/app/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.ts
frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer inject() over constructor injection in frontend code

Files:

  • frontend/src/app/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.ts
frontend/src/**/*.{ts,tsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

frontend/src/**/*.{ts,tsx,html}: Follow frontend/eslint.config.js: component selectors use app-, directive selectors use app, and any is disallowed in frontend code
Put user-facing strings in Transloco files under frontend/src/i18n/

Files:

  • frontend/src/app/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.ts
frontend/src/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for tests in frontend code

Files:

  • frontend/src/app/shared/service/language-resolver.service.spec.ts
**/*

⚙️ CodeRabbit configuration file

**/*: This project is being developed using current and future-facing technologies:

  • Java 25 with --enable-preview (preview features are INTENTIONAL and encouraged)
  • Spring Boot 4 (latest major version, check APIs accordingly)
  • Jackson 3 (new package: tools.jackson.* instead of com.fasterxml.jackson.*)
  • Hibernate 7.3.x (Jakarta Persistence 3.2, new APIs; avoid deprecated Hibernate 5/6 patterns)
  • Angular 21 (signals-based reactivity, no NgModules unless legacy)

Grimmory Internal Tools

Metadata Standards and Compliance

  • For all metadata writing and parsing logic, double-check against Dublin Core and ANSI standards to ensure perfect official compliance.
  • We strictly follow the widespread and official XML-compliant methods for EPUB2, EPUB3, CBX, and PDF formats.

General Java and Spring rules

  • ALWAYS prefer modern, idiomatic Java 25 constructs over legacy patterns.
  • Preview features (--enable-preview) are enabled and intentional; do NOT flag them as risky unless there is a concrete runtime issue.
  • Prefer: records, sealed classes/interfaces, pattern matching (switch expressions, instanceof), structured concurrency (StructuredTaskScope), scoped values, string templates, unnamed patterns/variables.
  • Prefer virtual threads (Thread.ofVirtual(), Executors.newVirtualThreadPerTaskExecutor()) over platform threads for I/O-bound work.
  • Prefer the new Sequenced Collections API (SequencedCollection, SequencedMap) where applicable.
  • Prefer var for local variables when the type is obvious from context.
  • Use stream().toList() instead of stream().collect(Collectors.toList()) for imm...

Files:

  • frontend/src/app/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.ts
**/*.spec.ts

⚙️ CodeRabbit configuration file

**/*.spec.ts: Angular 21 test review:

  • Flag tests with no expect() calls.
  • Flag hardcoded async timeouts; prefer fakeAsync/tick or signal-based testing.
  • Flag missing fixture.detectChanges() after state mutations.

Files:

  • frontend/src/app/shared/service/language-resolver.service.spec.ts
**/*.service.ts

⚙️ CodeRabbit configuration file

**/*.service.ts: Angular 21 service review:

  • Prefer providedIn: 'root' unless scope is intentionally limited.
  • Prefer inject() over constructor DI.
  • Prefer Signals or lightweight RxJS (takeUntilDestroyed) over manual subscription management.
  • Flag unsubscribed Observables (missing takeUntilDestroyed or explicit unsubscribe).
  • Prefer typed HttpClient responses with explicit error handling (catchError).
  • Flag any state mutation outside a defined signal or BehaviorSubject.

Files:

  • frontend/src/app/shared/service/language-resolver.service.ts
🧠 Learnings (5)
📚 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/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.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/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.ts
📚 Learning: 2026-04-11T03:55:57.229Z
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 this Angular frontend (frontend/src/app/), prefer the team’s reactive i18n “signal/computed” pattern: (1) For individual reactive translated strings, use `translateSignal()` from `jsverse/transloco`. (2) For option/label arrays that must update on language switch, create a single `activeLang` signal with `toSignal(t.langChanges$, { initialValue: t.getActiveLang() })`, then derive the arrays as `computed()` signals that read `activeLang()`. This should avoid manual `langChanges$` subscriptions and any `ngOnDestroy` subscription cleanup; prefer this over subscribing in `ngOnInit` when implementing reactive localization.

Applied to files:

  • frontend/src/app/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.ts
📚 Learning: 2026-05-18T14:54:39.422Z
Learnt from: alexhb1
Repo: grimmory-tools/grimmory PR: 1379
File: frontend/src/assets/styles/tailwind.css:3-4
Timestamp: 2026-05-18T14:54:39.422Z
Learning: In the grimmory-tools/grimmory repository, Biome is not used for linting/formatting (no `biome.json` and no Biome dependency in `package.json`). During code reviews, do not raise Biome-related issues or recommend adding/changing `biome.json`/Biome dependencies for formatting or linting in this project.

Applied to files:

  • frontend/src/app/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.ts
📚 Learning: 2026-06-30T01:30:43.644Z
Learnt from: imnotjames
Repo: grimmory-tools/grimmory PR: 1881
File: frontend/src/app/shared/components/icon-picker/icon-picker-component.ts:116-119
Timestamp: 2026-06-30T01:30:43.644Z
Learning: In the Grimmory Angular frontend (Angular 21), do not flag `[(ngModel)]` two-way bindings that are bound directly to a `WritableSignal`. Angular supports two-way binding to writable signals, so usages like `[(ngModel)]="svgSearchText"` should be treated as valid and should not be considered an incorrect overwrite of the signal with a plain value. Only flag if the binding target is not a `WritableSignal`.

Applied to files:

  • frontend/src/app/shared/service/language-resolver.service.spec.ts
  • frontend/src/app/shared/service/language-resolver.service.ts
🔇 Additional comments (2)
frontend/src/app/shared/service/language-resolver.service.ts (1)

49-84: LGTM!

frontend/src/app/shared/service/language-resolver.service.spec.ts (1)

8-136: LGTM!


Walkthrough

Adds LanguageResolverService to normalize raw language values, resolve localized language metadata with fallbacks, react to active locale changes, cache Intl.DisplayNames, and provide comprehensive unit tests.

Changes

Language resolution

Layer / File(s) Summary
Resolver contract and active locale
frontend/src/app/shared/service/language-resolver.service.ts
Defines ResolvedLanguage, language aliases, display-name types, and the reactive activeLocale state.
Normalization and localized resolution
frontend/src/app/shared/service/language-resolver.service.ts
Canonicalizes language tags, extracts region or script metadata, resolves localized names, caches display-name instances, and applies fallbacks.
Resolution behavior tests
frontend/src/app/shared/service/language-resolver.service.spec.ts
Covers invalid and blank inputs, aliases, regions, scripts, unrecognized values, locale changes, and English fallback behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant LanguageResolverService
  participant IntlAPIs
  Caller->>LanguageResolverService: resolve(raw)
  LanguageResolverService->>IntlAPIs: canonicalize and look up names
  IntlAPIs-->>LanguageResolverService: language, region, or script names
  LanguageResolverService-->>Caller: ResolvedLanguage or null
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional commit format and accurately summarizes the new language resolver service.
Description check ✅ Passed The description includes all required sections and clearly explains the change, with only minimal detail in testing steps.
Linked Issues check ✅ Passed The service and tests satisfy issue #2004 by normalizing language values, localizing names, and providing fallbacks.
Out of Scope Changes check ✅ Passed The changes appear scoped to the new service and its tests, with no unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 feat/language-resolver
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/language-resolver

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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.

@zachyale
zachyale merged commit 11f2cd5 into develop Jul 16, 2026
24 checks passed
@zachyale
zachyale deleted the feat/language-resolver branch July 16, 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.

Introduce a shared LanguageResolverService for the frontend

2 participants