Localization - First Pass (#2174)
* Started designing the backend localization service * Worked in Transloco for initial PoC * Worked in Transloco for initial PoC * Translated the login screen * translated dashboard screen * Started work on the backend * Fixed a logic bug * translated edit-user screen * Hooked up the backend for having a locale property. * Hooked up the ability to view the available locales and switch to them. * Made the localization service languages be derived from what's in langs/ directory. * Fixed up localization switching * Switched when we check for a license on UI bootstrap * Tweaked some code * Fixed the bug where dashboard wasn't loading and made it so language switching is working. * Fixed a bug on dashboard with languagePath * Converted user-scrobble-history.component.html * Converted spoiler.component.html * Converted review-series-modal.component.html * Converted review-card-modal.component.html * Updated the readme * Translated using Weblate (English) Currently translated at 100.0% (54 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/en/ * Converted review-card.component.html * Deleted dead component * Converted want-to-read.component.html * Added translation using Weblate (Korean) * Translated using Weblate (Spanish) Currently translated at 40.7% (22 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/es/ * Translated using Weblate (Korean) Currently translated at 62.9% (34 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/ko/ * Converted user-preferences.component.html * Translated using Weblate (Korean) Currently translated at 92.5% (50 of 54 strings) Translation: Kavita/ui Translate-URL: https://hosted.weblate.org/projects/kavita/ui/ko/ * Converted user-holds.component.html * Converted theme-manager.component.html * Converted restriction-selector.component.html * Converted manage-devices.component.html * Converted edit-device.component.html * Converted change-password.component.html * Converted change-email.component.html * Converted change-age-restriction.component.html * Converted api-key.component.html * Converted anilist-key.component.html * Converted typeahead.component.html * Converted user-stats-info-cards.component.html * Converted user-stats.component.html * Converted top-readers.component.html * Converted some pipes and ensure translation is loaded before the app. * Finished all but one pipe for localization * Converted directory-picker.component.html * Converted library-access-modal.component.html * Converted a few components * Converted a few components * Converted a few components * Converted a few components * Converted a few components * Merged weblate in * ... -> … update * Updated the readme * Updateded all fonts to be woff2 * Cleaned up some strings to increase re-use * Removed an old flow (that doesn't exist in backend any longer) from when we introduced emails on Kavita. * Converted Series detail * Lots more converted * Lots more converted & hooked up the ability to flatten during prod build the language files. * Lots more converted * Lots more converted & fixed a bunch of broken pipes due to inject() * Lots more converted * Lots more converted * Lots more converted & fixed some bad keys * Lots more converted * Fixed some bugs with admin dasbhoard nested tabs not rendering on first load due to not using onpush change detection * Fixed up some localization errors and fixed forgot password error when the user doesn't have change password permission * Fixed a stupid build issue again * Started adding errors for interceptor and backend. * Finished off manga-reader * More translations * Few fixes * Fixed a bug where character tag badges weren't showing the name on chapter info * All components are translated * All toasts are translated * All confirm/alerts are translated * Trying something new for the backend * Migrated the localization strings for the backend into a new file. * Updated the localization service to be able to do backend localization with fallback to english. * Cleaned up some external reviews code to reduce looping * Localized AccountController.cs * 60% done with controllers * All controllers are done * All KavitaExceptions are covered * Some shakeout fixes * Prep for initial merge * Everything is done except options and basic shakeout proves response times are good. Unit tests are broken. * Fixed up the unit tests * All unit tests are now working * Removed some quantifier * I'm not sure I can support localization for some Volume/Chapter/Book strings within the codebase. --------- Co-authored-by: Robbie Davis <robbie@therobbiedavis.com> Co-authored-by: majora2007 <kavitareader@gmail.com> Co-authored-by: expertjun <jtrobin@naver.com> Co-authored-by: ThePromidius <thepromidiusyt@gmail.com>
This commit is contained in:
parent
670bf82c38
commit
3b23d63234
389 changed files with 13652 additions and 7925 deletions
|
|
@ -1,32 +0,0 @@
|
|||
<div>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">KavitaPlus Features</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h5>Current Features</h5>
|
||||
<ul class="list-group mb-2">
|
||||
<li class="list-group-item">Scrobble Support</li>
|
||||
<li class="list-group-item">Series Recommendations</li>
|
||||
<li class="list-group-item">Series Reviews</li>
|
||||
<li class="list-group-item">Remove Donation on Side nav</li>
|
||||
</ul>
|
||||
|
||||
<h5>Planned Features</h5>
|
||||
<ul class="list-group mb-2">
|
||||
<li class="list-group-item">More external data providers</li>
|
||||
<li class="list-group-item">Webhooks</li>
|
||||
<li class="list-group-item">Kobo Progress Syncing</li>
|
||||
<li class="list-group-item">Trending/External rating integration</li>
|
||||
<li class="list-group-item">Your ideas upvoted via FeatHub</li>
|
||||
</ul>
|
||||
|
||||
<div class="text-muted">These feature unlock for the whole server while subscription active</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" (click)="close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import {ChangeDetectionStrategy, Component} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap";
|
||||
|
||||
@Component({
|
||||
selector: 'app-feature-list-modal',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './feature-list-modal.component.html',
|
||||
styleUrls: ['./feature-list-modal.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FeatureListModalComponent {
|
||||
|
||||
constructor(private modal: NgbActiveModal) {}
|
||||
|
||||
close() {
|
||||
this.modal.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
<div>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{review.username + "'s"}} Review {{review.isExternal ? '(external)' : ''}}</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
|
||||
<ng-container *transloco="let t; read:'review-card-modal'">
|
||||
<div>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{t('user-review', {username: review.username})}} {{review.isExternal ? t('external-mod') : ''}}</h4>
|
||||
<button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="close()"></button>
|
||||
</div>
|
||||
<div class="modal-body scrollable-modal">
|
||||
<p *ngIf="review.tagline" [innerHTML]="review.tagline | safeHtml"></p>
|
||||
<p #container class="img-max-width" [innerHTML]="review.body | safeHtml"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a *ngIf="review.externalUrl" class="btn btn-icon" [href]="review.externalUrl | safeHtml" target="_blank" rel="noopener noreferrer" [title]="review.externalUrl">
|
||||
{{t('go-to-review')}}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary" (click)="close()">{{t('close')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body scrollable-modal">
|
||||
<p *ngIf="review.tagline" [innerHTML]="review.tagline | safeHtml"></p>
|
||||
<p #container class="img-max-width" [innerHTML]="review.body | safeHtml"></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a *ngIf="review.externalUrl" class="btn btn-icon" [href]="review.externalUrl | safeHtml" target="_blank" rel="noopener noreferrer" [title]="review.externalUrl">
|
||||
Go To Review
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary" (click)="close()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-container>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ import {ReactiveFormsModule} from "@angular/forms";
|
|||
import {UserReview} from "../review-card/user-review";
|
||||
import {SpoilerComponent} from "../spoiler/spoiler.component";
|
||||
import {SafeHtmlPipe} from "../../pipe/safe-html.pipe";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
selector: 'app-review-card-modal',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule, SpoilerComponent, SafeHtmlPipe],
|
||||
imports: [CommonModule, ReactiveFormsModule, SpoilerComponent, SafeHtmlPipe, TranslocoModule],
|
||||
templateUrl: './review-card-modal.component.html',
|
||||
styleUrls: ['./review-card-modal.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
|
|
|||
|
|
@ -1,35 +1,37 @@
|
|||
<div class="card mb-3" style="max-width: 320px; max-height: 160px; height: 160px" (click)="showModal()">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 d-none d-md-block">
|
||||
<i class="img-fluid rounded-start fa-solid fa-circle-user profile-image" aria-hidden="true"></i>
|
||||
<div *ngIf="isMyReview" class="my-review">
|
||||
<i class="fa-solid fa-star" aria-hidden="true" title="This is your review"></i>
|
||||
<span class="visually-hidden">This is your review</span>
|
||||
<ng-container *transloco="let t; read:'review-card'">
|
||||
<div class="card mb-3" style="max-width: 320px; max-height: 160px; height: 160px" (click)="showModal()">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2 d-none d-md-block">
|
||||
<i class="img-fluid rounded-start fa-solid fa-circle-user profile-image" aria-hidden="true"></i>
|
||||
<div *ngIf="isMyReview" class="my-review">
|
||||
<i class="fa-solid fa-star" aria-hidden="true" [title]="t('your-review')"></i>
|
||||
<span class="visually-hidden">{{t('your-review')}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title" [title]="review.tagline">
|
||||
<ng-container *ngIf="review.tagline && review.tagline.length > 0; else noTagline">{{review.tagline.substring(0, 29)}}{{review.tagline.length > 29 ? '…' : ''}}</ng-container>
|
||||
<ng-template #noTagline>
|
||||
{{review.isExternal ? 'External Review' : 'Review'}}
|
||||
</ng-template>
|
||||
</h6>
|
||||
<p class="card-text no-images">
|
||||
<app-read-more [text]="(review.isExternal ? review.bodyJustText : review.body) || ''" [maxLength]="100" [showToggle]="false"></app-read-more>
|
||||
</p>
|
||||
<div class="col-md-10">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title" [title]="review.tagline">
|
||||
<ng-container *ngIf="review.tagline && review.tagline.length > 0; else noTagline">{{review.tagline.substring(0, 29)}}{{review.tagline.length > 29 ? '…' : ''}}</ng-container>
|
||||
<ng-template #noTagline>
|
||||
{{review.isExternal ? t('external-review') : t('local-review')}}
|
||||
</ng-template>
|
||||
</h6>
|
||||
<p class="card-text no-images">
|
||||
<app-read-more [text]="(review.isExternal ? review.bodyJustText : review.body) || ''" [maxLength]="100" [showToggle]="false"></app-read-more>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer bg-transparent text-muted">
|
||||
<ng-container *ngIf="isMyReview; else normalReview">
|
||||
<i class="d-md-none fa-solid fa-star me-1" aria-hidden="true" title="This is your review"></i>
|
||||
</ng-container>
|
||||
<ng-template #normalReview>
|
||||
<img class="me-1" [ngSrc]="review.provider | providerImage" width="20" height="20" alt="">
|
||||
</ng-template>
|
||||
{{(isMyReview ? '' : review.username | defaultValue:'')}}
|
||||
<span style="float: right" *ngIf="review.isExternal">Rating {{review.score}}%</span>
|
||||
<div class="card-footer bg-transparent text-muted">
|
||||
<ng-container *ngIf="isMyReview; else normalReview">
|
||||
<i class="d-md-none fa-solid fa-star me-1" aria-hidden="true" [title]="t('your-review')"></i>
|
||||
</ng-container>
|
||||
<ng-template #normalReview>
|
||||
<img class="me-1" [ngSrc]="review.provider | providerImage" width="20" height="20" alt="">
|
||||
</ng-template>
|
||||
{{(isMyReview ? '' : review.username | defaultValue:'')}}
|
||||
<span style="float: right" *ngIf="review.isExternal">{{t('rating-percentage', {r: review.score})}}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@ import {ReadMoreComponent} from "../../shared/read-more/read-more.component";
|
|||
import {DefaultValuePipe} from "../../pipe/default-value.pipe";
|
||||
import {ImageComponent} from "../../shared/image/image.component";
|
||||
import {ProviderImagePipe} from "../../pipe/provider-image.pipe";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
selector: 'app-review-card',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReadMoreComponent, DefaultValuePipe, ImageComponent, NgOptimizedImage, ProviderImagePipe],
|
||||
imports: [CommonModule, ReadMoreComponent, DefaultValuePipe, ImageComponent, NgOptimizedImage, ProviderImagePipe, TranslocoModule],
|
||||
templateUrl: './review-card.component.html',
|
||||
styleUrls: ['./review-card.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
|
|
@ -1,28 +1,30 @@
|
|||
<div>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">Edit Review</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()">
|
||||
<ng-container *transloco="let t; read:'review-series-modal'">
|
||||
<div>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{t('title')}}</h4>
|
||||
<button type="button" class="btn-close" [attr.aria-label]="t('close')" (click)="close()">
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form [formGroup]="reviewGroup">
|
||||
<div class="row g-0">
|
||||
<label for="tagline" class="form-label">Tagline</label>
|
||||
<input id="tagline" class="form-control" formControlName="tagline" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form [formGroup]="reviewGroup">
|
||||
<div class="row g-0">
|
||||
<label for="tagline" class="form-label">{{t('tagline-label')}}</label>
|
||||
<input id="tagline" class="form-control" formControlName="tagline" />
|
||||
</div>
|
||||
|
||||
<div class="row g-0 mt-2">
|
||||
<label for="review" class="form-label">Review</label>
|
||||
<textarea id="review" class="form-control" formControlName="reviewBody" rows="3" ></textarea>
|
||||
</div>
|
||||
</form>
|
||||
<div class="row g-0 mt-2">
|
||||
<label for="review" class="form-label">{{t('review-label')}}</label>
|
||||
<textarea id="review" class="form-control" formControlName="reviewBody" rows="3" ></textarea>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" (click)="close()">Close</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="save()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" (click)="close()">{{t('close')}}</button>
|
||||
<button type="submit" class="btn btn-primary" (click)="save()">{{t('save')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ import {NgbActiveModal, NgbRating} from '@ng-bootstrap/ng-bootstrap';
|
|||
import { SeriesService } from 'src/app/_services/series.service';
|
||||
import {UserReview} from "../review-card/user-review";
|
||||
import {CommonModule} from "@angular/common";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
selector: 'app-review-series-modal',
|
||||
standalone: true,
|
||||
imports: [CommonModule, NgbRating, ReactiveFormsModule],
|
||||
imports: [CommonModule, NgbRating, ReactiveFormsModule, TranslocoModule],
|
||||
templateUrl: './review-series-modal.component.html',
|
||||
styleUrls: ['./review-series-modal.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import {inject, Pipe, PipeTransform} from '@angular/core';
|
||||
import {ScrobbleEventType} from "../_models/scrobbling/scrobble-event";
|
||||
import {TranslocoPipe, TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
@Pipe({
|
||||
name: 'scrobbleEventType',
|
||||
|
|
@ -7,13 +8,20 @@ import {ScrobbleEventType} from "../_models/scrobbling/scrobble-event";
|
|||
})
|
||||
export class ScrobbleEventTypePipe implements PipeTransform {
|
||||
|
||||
translocoService = inject(TranslocoService);
|
||||
|
||||
transform(value: ScrobbleEventType): string {
|
||||
switch (value) {
|
||||
case ScrobbleEventType.ChapterRead: return 'Reading Progress';
|
||||
case ScrobbleEventType.ScoreUpdated: return 'Rating Update';
|
||||
case ScrobbleEventType.AddWantToRead: return 'Want To Read: Add';
|
||||
case ScrobbleEventType.RemoveWantToRead: return 'Want To Read: Remove';
|
||||
case ScrobbleEventType.Review: return 'Review update';
|
||||
case ScrobbleEventType.ChapterRead:
|
||||
return this.translocoService.translate('scrobble-event-type-pipe.chapter-read');
|
||||
case ScrobbleEventType.ScoreUpdated:
|
||||
return this.translocoService.translate('scrobble-event-type-pipe.score-updated');
|
||||
case ScrobbleEventType.AddWantToRead:
|
||||
return this.translocoService.translate('scrobble-event-type-pipe.want-to-read-add');
|
||||
case ScrobbleEventType.RemoveWantToRead:
|
||||
return this.translocoService.translate('scrobble-event-type-pipe.want-to-read-remove');
|
||||
case ScrobbleEventType.Review:
|
||||
return this.translocoService.translate('scrobble-event-type-pipe.review');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
<div (click)="toggle()" [attr.aria-expanded]="!isCollapsed" class="btn spoiler" tabindex="0">
|
||||
<span *ngIf="isCollapsed; else show">Spoiler, click to show</span>
|
||||
<ng-template #show>
|
||||
<div [innerHTML]="html | safeHtml"></div>
|
||||
</ng-template>
|
||||
</div>
|
||||
<ng-container *transloco="let t; read:'spoiler'">
|
||||
<div (click)="toggle()" [attr.aria-expanded]="!isCollapsed" class="btn spoiler" tabindex="0">
|
||||
<span *ngIf="isCollapsed; else show">{{t('click-to-show')}}</span>
|
||||
<ng-template #show>
|
||||
<div [innerHTML]="html | safeHtml"></div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@ import {
|
|||
} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {SafeHtmlPipe} from "../../pipe/safe-html.pipe";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
selector: 'app-spoiler',
|
||||
standalone: true,
|
||||
imports: [CommonModule, SafeHtmlPipe],
|
||||
imports: [CommonModule, SafeHtmlPipe, TranslocoModule],
|
||||
templateUrl: './spoiler.component.html',
|
||||
styleUrls: ['./spoiler.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
|
|
|||
|
|
@ -1,51 +1,50 @@
|
|||
<h5>Scrobble History</h5>
|
||||
<p>Here you will find any scrobble events linked with your account. In order for events to exist, you must have an active
|
||||
scrobble provider configured. All events that have been processed will clear after a month. If there are non-processed events, it
|
||||
is likely these cannot form matches upstream. Please reach out to your admin to get them corrected.</p>
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-md-10">
|
||||
<form [formGroup]="formGroup">
|
||||
<div class="form-group pe-1">
|
||||
<label for="filter">Filter</label>
|
||||
<input id="filter" type="text" class="form-control" formControlName="filter" autocomplete="off"/>
|
||||
</div>
|
||||
</form>
|
||||
<ng-container *transloco="let t; read:'user-scrobble-history'">
|
||||
<h5>{{t('title')}}</h5>
|
||||
<p>{{t('description')}}</p>
|
||||
<div class="row g-0 mb-2">
|
||||
<div class="col-md-10">
|
||||
<form [formGroup]="formGroup">
|
||||
<div class="form-group pe-1">
|
||||
<label for="filter">{{t('filter-label')}}</label>
|
||||
<input id="filter" type="text" class="form-control" formControlName="filter" autocomplete="off"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-2 mt-4">
|
||||
<ngb-pagination *ngIf="pagination"
|
||||
[(page)]="pagination.currentPage"
|
||||
[pageSize]="pagination.itemsPerPage"
|
||||
[collectionSize]="pagination.totalItems"
|
||||
(pageChange)="onPageChange($event)"
|
||||
></ngb-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2 mt-4">
|
||||
<ngb-pagination *ngIf="pagination"
|
||||
[(page)]="pagination.currentPage"
|
||||
[pageSize]="pagination.itemsPerPage"
|
||||
[collectionSize]="pagination.totalItems"
|
||||
(pageChange)="onPageChange($event)"
|
||||
></ngb-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-striped table-hover table-sm scrollable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" sortable="created" (sort)="updateSort($event)">
|
||||
Created
|
||||
{{t('created-header')}}
|
||||
</th>
|
||||
<th scope="col" sortable="lastModified" (sort)="updateSort($event)" direction="desc">
|
||||
Last Modified
|
||||
{{t('last-modified-header')}}
|
||||
</th>
|
||||
<th scope="col">
|
||||
Type
|
||||
{{t('type-header')}}
|
||||
</th>
|
||||
<th scope="col" sortable="seriesName" (sort)="updateSort($event)">
|
||||
Series
|
||||
{{t('series-header')}}
|
||||
</th>
|
||||
<th scope="col">
|
||||
Data
|
||||
{{t('data-header')}}
|
||||
</th>
|
||||
<th scope="col">
|
||||
Is Processed
|
||||
{{t('is-processed-header')}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngIf="events.length === 0">
|
||||
<td colspan="6">No Data</td>
|
||||
<td colspan="6">{{t('no-data')}}/td>
|
||||
</tr>
|
||||
<tr *ngFor="let item of events; let idx = index;">
|
||||
<td>
|
||||
|
|
@ -63,22 +62,24 @@
|
|||
<td>
|
||||
<ng-container [ngSwitch]="item.scrobbleEventType">
|
||||
<ng-container *ngSwitchCase="ScrobbleEventType.ChapterRead">
|
||||
Volume {{item.volumeNumber}} Chapter {{item.chapterNumber}}
|
||||
{{t('volume-and-chapter-num', {v: item.volumeNumber, c: item.chapterNumber})}}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="ScrobbleEventType.ScoreUpdated">
|
||||
Rating {{item.rating}}
|
||||
{{t('rating', {r: item.rating})}}
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
Not Applicable
|
||||
{{t('not-applicable')}}
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<i class="fa-regular fa-circle icon" aria-hidden="true" *ngIf="!item.isProcessed"></i>
|
||||
<i class="fa-solid fa-check-circle icon" aria-hidden="true" *ngIf="item.isProcessed"></i>
|
||||
<span class="visually-hidden" attr.aria-labelledby="scrobble-history--{{idx}}">{{item.isProcessed ? 'Processed' : 'Not Processed'}}</span>
|
||||
<span class="visually-hidden" attr.aria-labelledby="scrobble-history--{{idx}}">
|
||||
{{item.isProcessed ? t('processed') : t('not-processed')}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -2,21 +2,21 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, injec
|
|||
import {CommonModule} from '@angular/common';
|
||||
|
||||
import {ScrobblingService} from "../../_services/scrobbling.service";
|
||||
import {shareReplay} from "rxjs";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {ScrobbleEvent, ScrobbleEventType} from "../../_models/scrobbling/scrobble-event";
|
||||
import {ScrobbleEventTypePipe} from "../scrobble-event-type.pipe";
|
||||
import {NgbPagination} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {ScrobbleEventSortField} from "../../_models/scrobbling/scrobble-event-filter";
|
||||
import {debounceTime, map, take, tap} from "rxjs/operators";
|
||||
import {debounceTime, take} from "rxjs/operators";
|
||||
import {PaginatedResult, Pagination} from "../../_models/pagination";
|
||||
import {SortableHeader, SortEvent} from "../table/_directives/sortable-header.directive";
|
||||
import {FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-scrobble-history',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader],
|
||||
imports: [CommonModule, ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader, TranslocoModule],
|
||||
templateUrl: './user-scrobble-history.component.html',
|
||||
styleUrls: ['./user-scrobble-history.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue