Localized Dates (#2182)
* Removed 4 properties from SiteThemeDto which weren't supposed to be there. * Removed another set of date fields not used on DTOs * Hangfire jobs will now grab a utc date and render that date in user's local timezone. * Scrobble errors are now localized dates. Added simplified chinese language code * Fixed a bunch of newlines in the translation files * Localized compact number and fixed some missing localizations * Fixed remove from on deck key issue * Scrobble events is now localized * Scrobble events is now localized * Removed some duplicate fields from chapter
This commit is contained in:
parent
c3b3f9a640
commit
a65963c817
29 changed files with 99 additions and 150 deletions
|
|
@ -21,7 +21,7 @@ export interface Chapter {
|
|||
pagesRead: number; // Attached for the given user when requesting from API
|
||||
isSpecial: boolean;
|
||||
title: string;
|
||||
created: string;
|
||||
createdUtc: string;
|
||||
/**
|
||||
* Actual name of the Chapter if populated in underlying metadata
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,4 +6,5 @@ export interface Device {
|
|||
platform: DevicePlatform;
|
||||
emailAddress: string;
|
||||
lastUsed: string;
|
||||
}
|
||||
lastUsedUtc: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,5 @@ export interface Job {
|
|||
id: string;
|
||||
title: string;
|
||||
cron: string;
|
||||
createdAt: string;
|
||||
lastExecution: string;
|
||||
}
|
||||
lastExecutionUtc: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ export interface ScrobbleEvent {
|
|||
scrobbleEventType: ScrobbleEventType;
|
||||
rating: number | null;
|
||||
processedDateUtc: string;
|
||||
lastModified: string;
|
||||
created: string;
|
||||
lastModifiedUtc: string;
|
||||
createdUtc: string;
|
||||
volumeNumber: number | null;
|
||||
chapterNumber: number | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ export class AccountService {
|
|||
private readonly destroyRef = inject(DestroyRef);
|
||||
baseUrl = environment.apiUrl;
|
||||
userKey = 'kavita-user';
|
||||
public lastLoginKey = 'kavita-lastlogin';
|
||||
public localeKey = 'kavita-locale';
|
||||
public static lastLoginKey = 'kavita-lastlogin';
|
||||
public static localeKey = 'kavita-locale';
|
||||
private currentUser: User | undefined;
|
||||
|
||||
// Stores values, when someone subscribes gives (1) of last values seen.
|
||||
|
|
@ -135,7 +135,7 @@ export class AccountService {
|
|||
Array.isArray(roles) ? user.roles = roles : user.roles.push(roles);
|
||||
|
||||
localStorage.setItem(this.userKey, JSON.stringify(user));
|
||||
localStorage.setItem(this.lastLoginKey, user.username);
|
||||
localStorage.setItem(AccountService.lastLoginKey, user.username);
|
||||
if (user.preferences && user.preferences.theme) {
|
||||
this.themeService.setTheme(user.preferences.theme.name);
|
||||
} else {
|
||||
|
|
@ -268,8 +268,8 @@ export class AccountService {
|
|||
this.currentUser.preferences = settings;
|
||||
this.setCurrentUser(this.currentUser);
|
||||
|
||||
// Update the locale on disk (for logout only)
|
||||
localStorage.setItem(this.localeKey, this.currentUser.preferences.locale);
|
||||
// Update the locale on disk (for logout and compact-number pipe)
|
||||
localStorage.setItem(AccountService.localeKey, this.currentUser.preferences.locale);
|
||||
}
|
||||
return settings;
|
||||
}), takeUntilDestroyed(this.destroyRef));
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@
|
|||
<table class="table table-striped table-hover table-sm scrollable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" sortable="created" (sort)="updateSort($event)">
|
||||
<th scope="col" sortable="createdUtc" (sort)="updateSort($event)">
|
||||
{{t('created-header')}}
|
||||
</th>
|
||||
<th scope="col" sortable="lastModified" (sort)="updateSort($event)" direction="desc">
|
||||
<th scope="col" sortable="lastModifiedUtc" (sort)="updateSort($event)" direction="desc">
|
||||
{{t('last-modified-header')}}
|
||||
</th>
|
||||
<th scope="col">
|
||||
|
|
@ -48,10 +48,10 @@
|
|||
</tr>
|
||||
<tr *ngFor="let item of events; let idx = index;">
|
||||
<td>
|
||||
{{item.created | date:'MM/dd/yy h:mm a' }}
|
||||
{{item.createdUtc | translocoDate: {dateStyle: 'short', timeStyle: 'medium', } | defaultValue }}
|
||||
</td>
|
||||
<td>
|
||||
{{item.lastModified | date:'MM/dd/yy h:mm a' }}
|
||||
{{item.lastModifiedUtc | translocoDate: {dateStyle: 'short', timeStyle: 'medium' } | defaultValue }}
|
||||
</td>
|
||||
<td>
|
||||
{{item.scrobbleEventType | scrobbleEventType}}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,13 @@ 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";
|
||||
import {DefaultValuePipe} from "../../pipe/default-value.pipe";
|
||||
import {TranslocoLocaleModule} from "@ngneat/transloco-locale";
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-scrobble-history',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader, TranslocoModule],
|
||||
imports: [CommonModule, ScrobbleEventTypePipe, NgbPagination, ReactiveFormsModule, SortableHeader, TranslocoModule, DefaultValuePipe, TranslocoLocaleModule],
|
||||
templateUrl: './user-scrobble-history.component.html',
|
||||
styleUrls: ['./user-scrobble-history.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
@ -36,7 +38,7 @@ export class UserScrobbleHistoryComponent implements OnInit {
|
|||
get ScrobbleEventType() { return ScrobbleEventType; }
|
||||
|
||||
ngOnInit() {
|
||||
this.loadPage({column: 'created', direction: 'desc'});
|
||||
this.loadPage({column: 'createdUtc', direction: 'desc'});
|
||||
|
||||
this.formGroup.get('filter')?.valueChanges.pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef)).subscribe(query => {
|
||||
this.loadPage();
|
||||
|
|
@ -81,9 +83,9 @@ export class UserScrobbleHistoryComponent implements OnInit {
|
|||
|
||||
private mapSortColumnField(column: string | undefined) {
|
||||
switch (column) {
|
||||
case 'created': return ScrobbleEventSortField.Created;
|
||||
case 'createdUtc': return ScrobbleEventSortField.Created;
|
||||
case 'isProcessed': return ScrobbleEventSortField.IsProcessed;
|
||||
case 'lastModified': return ScrobbleEventSortField.LastModified;
|
||||
case 'lastModifiedUtc': return ScrobbleEventSortField.LastModified;
|
||||
case 'seriesName': return ScrobbleEventSortField.Series;
|
||||
}
|
||||
return ScrobbleEventSortField.None;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,4 @@ export interface KavitaMediaError {
|
|||
filePath: string;
|
||||
comment: string;
|
||||
details: string;
|
||||
created: string;
|
||||
createdUtc: string;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
<a href="library/{{item.libraryId}}/series/{{item.seriesId}}" target="_blank">{{item.details}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{item.created | date:'shortDate'}}
|
||||
{{item.createdUtc | translocoDate: {dateStyle: 'short', timeStyle: 'medium' } | defaultValue }}
|
||||
</td>
|
||||
<td>
|
||||
{{item.comment}}
|
||||
|
|
|
|||
|
|
@ -26,11 +26,14 @@ import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
|
|||
import {FilterPipe} from "../../pipe/filter.pipe";
|
||||
import {LoadingComponent} from "../../shared/loading/loading.component";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
import {DefaultDatePipe} from "../../pipe/default-date.pipe";
|
||||
import {DefaultValuePipe} from "../../pipe/default-value.pipe";
|
||||
import {TranslocoLocaleModule} from "@ngneat/transloco-locale";
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-scrobble-errors',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule, FilterPipe, LoadingComponent, SortableHeader, TranslocoModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, FilterPipe, LoadingComponent, SortableHeader, TranslocoModule, DefaultDatePipe, DefaultValuePipe, TranslocoLocaleModule],
|
||||
templateUrl: './manage-scrobble-errors.component.html',
|
||||
styleUrls: ['./manage-scrobble-errors.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
<td>
|
||||
{{task.title | titlecase}}
|
||||
</td>
|
||||
<td>{{task.lastExecution | date:'short' | defaultValue }}</td>
|
||||
<td>{{task.lastExecutionUtc | translocoDate: {dateStyle: 'short', timeStyle: 'medium' } | defaultValue }}</td>
|
||||
<td>{{task.cron}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {DownloadService} from 'src/app/shared/_services/download.service';
|
|||
import {DefaultValuePipe} from '../../pipe/default-value.pipe';
|
||||
import {AsyncPipe, DatePipe, NgFor, NgIf, NgTemplateOutlet, TitleCasePipe} from '@angular/common';
|
||||
import {TranslocoModule, TranslocoService} from "@ngneat/transloco";
|
||||
import {TranslocoLocaleModule} from "@ngneat/transloco-locale";
|
||||
|
||||
interface AdhocTask {
|
||||
name: string;
|
||||
|
|
@ -28,7 +29,7 @@ interface AdhocTask {
|
|||
styleUrls: ['./manage-tasks-settings.component.scss'],
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [NgIf, ReactiveFormsModule, NgbTooltip, NgFor, AsyncPipe, TitleCasePipe, DatePipe, DefaultValuePipe, TranslocoModule, NgTemplateOutlet]
|
||||
imports: [NgIf, ReactiveFormsModule, NgbTooltip, NgFor, AsyncPipe, TitleCasePipe, DatePipe, DefaultValuePipe, TranslocoModule, NgTemplateOutlet, TranslocoLocaleModule]
|
||||
})
|
||||
export class ManageTasksSettingsComponent implements OnInit {
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
<ng-container *ngIf="totalPages > 0">
|
||||
<div class="col-auto mb-2">
|
||||
<app-icon-and-title [label]="t('length-title')" [clickable]="false" fontClasses="fa-regular fa-file-lines" [title]="t('pages-title')">
|
||||
<app-icon-and-title [label]="t('length-title')" [clickable]="false" fontClasses="fa-regular fa-file-lines">
|
||||
{{t('pages-count', {num: totalPages | compactNumber})}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
|
|
@ -60,11 +60,11 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="showExtendedProperties && chapter.created && chapter.created !== '' && (chapter.created | date: 'shortDate') !== '1/1/01'">
|
||||
<ng-container *ngIf="showExtendedProperties && chapter.createdUtc && chapter.createdUtc !== '' && (chapter.createdUtc | date: 'shortDate') !== '1/1/01'">
|
||||
<div class="vr d-none d-lg-block m-2"></div>
|
||||
<div class="col-auto">
|
||||
<app-icon-and-title [label]="t('date-added-title')" [clickable]="false" fontClasses="fa-solid fa-file-import" [title]="t('date-added-title')">
|
||||
{{chapter.created | date:'short' | defaultDate}}
|
||||
{{chapter.createdUtc | translocoDate: {dateStyle: 'short', timeStyle: 'short' } | defaultDate}}
|
||||
</app-icon-and-title>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
|||
import {MetadataDetailComponent} from "../../series-detail/_components/metadata-detail/metadata-detail.component";
|
||||
import {FilterQueryParam} from "../../shared/_services/filter-utilities.service";
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
import {TranslocoLocaleModule} from "@ngneat/transloco-locale";
|
||||
|
||||
@Component({
|
||||
selector: 'app-entity-info-cards',
|
||||
standalone: true,
|
||||
imports: [CommonModule, IconAndTitleComponent, SafeHtmlPipe, DefaultDatePipe, BytesPipe, CompactNumberPipe, AgeRatingPipe, NgbTooltip, MetadataDetailComponent, TranslocoModule],
|
||||
imports: [CommonModule, IconAndTitleComponent, SafeHtmlPipe, DefaultDatePipe, BytesPipe, CompactNumberPipe, AgeRatingPipe, NgbTooltip, MetadataDetailComponent, TranslocoModule, CompactNumberPipe, TranslocoLocaleModule],
|
||||
templateUrl: './entity-info-cards.component.html',
|
||||
styleUrls: ['./entity-info-cards.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
|||
if (this.actions[othersIndex].children.findIndex(o => o.action === Action.RemoveFromOnDeck) < 0) {
|
||||
this.actions[othersIndex].children.push({
|
||||
action: Action.RemoveFromOnDeck,
|
||||
title: this.translocoService.translate('actionable.remove-from-on-deck'),
|
||||
title: 'remove-from-on-deck',
|
||||
callback: (action: ActionItem<Series>, series: Series) => this.handleSeriesActionCallback(action, series),
|
||||
class: 'danger',
|
||||
requiresAdmin: false,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,5 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
// TODO: Figure out how to handle culture based on localization setting
|
||||
const formatter = new Intl.NumberFormat('en-GB', {
|
||||
//@ts-ignore
|
||||
notation: 'compact', // https://github.com/microsoft/TypeScript/issues/36533
|
||||
maximumSignificantDigits: 3
|
||||
});
|
||||
|
||||
const formatterForDoublePercision = new Intl.NumberFormat('en-GB', {
|
||||
//@ts-ignore
|
||||
notation: 'compact', // https://github.com/microsoft/TypeScript/issues/36533
|
||||
maximumSignificantDigits: 2
|
||||
});
|
||||
import {AccountService} from "../_services/account.service";
|
||||
|
||||
const specialCases = [4, 7, 10, 13];
|
||||
|
||||
|
|
@ -21,14 +9,35 @@ const specialCases = [4, 7, 10, 13];
|
|||
})
|
||||
export class CompactNumberPipe implements PipeTransform {
|
||||
|
||||
constructor() {}
|
||||
|
||||
transform(value: number): string {
|
||||
// Weblate allows some non-standard languages, like 'zh_Hans', which should be just 'zh'. So we handle that here
|
||||
const locale = localStorage.getItem(AccountService.localeKey)?.split('_')[0];
|
||||
return this.transformValue(locale || 'en', value);
|
||||
}
|
||||
|
||||
private transformValue(locale: string, value: number) {
|
||||
const formatter = new Intl.NumberFormat(locale, {
|
||||
//@ts-ignore
|
||||
notation: 'compact', // https://github.com/microsoft/TypeScript/issues/36533
|
||||
maximumSignificantDigits: 3
|
||||
});
|
||||
|
||||
const formatterForDoublePrecision = new Intl.NumberFormat(locale, {
|
||||
//@ts-ignore
|
||||
notation: 'compact', // https://github.com/microsoft/TypeScript/issues/36533
|
||||
maximumSignificantDigits: 2
|
||||
});
|
||||
|
||||
if (value < 1000) return value + '';
|
||||
if (specialCases.includes((value + '').length)) { // from 4, every 3 will have a case where we need to override
|
||||
return formatterForDoublePercision.format(value);
|
||||
return formatterForDoublePrecision.format(value);
|
||||
}
|
||||
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="dashboard-card-content">
|
||||
<div class="row g-0 mb-2 align-items-center">
|
||||
<div class="col-4">
|
||||
<h4>{{t('reading-activity')}}</h4>
|
||||
<h4>{{t('title')}}</h4>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<form [formGroup]="formGroup" class="d-inline-flex float-end">
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ import {TranslocoModule, TranslocoService} from "@ngneat/transloco";
|
|||
styleUrls: ['./server-stats.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [NgIf, IconAndTitleComponent, StatListComponent, TopReadersComponent, FileBreakdownStatsComponent, PublicationStatusStatsComponent, ReadingActivityComponent, DayBreakdownComponent, AsyncPipe, DecimalPipe, CompactNumberPipe, TimeDurationPipe, BytesPipe, TranslocoModule]
|
||||
imports: [NgIf, IconAndTitleComponent, StatListComponent, TopReadersComponent, FileBreakdownStatsComponent,
|
||||
PublicationStatusStatsComponent, ReadingActivityComponent, DayBreakdownComponent, AsyncPipe, DecimalPipe,
|
||||
CompactNumberPipe, TimeDurationPipe, BytesPipe, TranslocoModule]
|
||||
})
|
||||
export class ServerStatsComponent {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { CompactNumberPipe } from 'src/app/pipe/compact-number.pipe';
|
||||
import { StatisticsService } from 'src/app/_services/statistics.service';
|
||||
import { GenericListModalComponent } from '../_modals/generic-list-modal/generic-list-modal.component';
|
||||
import { TimeAgoPipe } from '../../../pipe/time-ago.pipe';
|
||||
import { TimeDurationPipe } from '../../../pipe/time-duration.pipe';
|
||||
import { CompactNumberPipe as CompactNumberPipe_1 } from '../../../pipe/compact-number.pipe';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { IconAndTitleComponent } from '../../../shared/icon-and-title/icon-and-title.component';
|
||||
import {TranslocoModule} from "@ngneat/transloco";
|
||||
import {AccountService} from "../../../_services/account.service";
|
||||
import {CompactNumberPipe} from "../../../pipe/compact-number.pipe";
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-stats-info-cards',
|
||||
|
|
@ -16,7 +16,7 @@ import {TranslocoModule} from "@ngneat/transloco";
|
|||
styleUrls: ['./user-stats-info-cards.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [IconAndTitleComponent, DecimalPipe, CompactNumberPipe_1, TimeDurationPipe, TimeAgoPipe, TranslocoModule]
|
||||
imports: [IconAndTitleComponent, DecimalPipe, CompactNumberPipe, TimeDurationPipe, TimeAgoPipe, TranslocoModule]
|
||||
})
|
||||
export class UserStatsInfoCardsComponent {
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ export class UserStatsInfoCardsComponent {
|
|||
@Input() lastActive: string = '';
|
||||
@Input() avgHoursPerWeekSpentReading: number = 0;
|
||||
|
||||
constructor(private statsService: StatisticsService, private modalService: NgbModal) { }
|
||||
constructor(private statsService: StatisticsService, private modalService: NgbModal, private accountService: AccountService) { }
|
||||
|
||||
openPageByYearList() {
|
||||
const numberPipe = new CompactNumberPipe();
|
||||
|
|
@ -46,5 +46,4 @@ export class UserStatsInfoCardsComponent {
|
|||
ref.componentInstance.title = 'Words Read By Year';
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue