mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
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
@ -222,8 +222,7 @@ public class ServerController : BaseApiController
|
||||
Id = dto.Id,
|
||||
Title = dto.Id.Replace('-', ' '),
|
||||
Cron = dto.Cron,
|
||||
CreatedAt = dto.CreatedAt,
|
||||
LastExecution = dto.LastExecution,
|
||||
LastExecutionUtc = dto.LastExecution.HasValue ? new DateTime(dto.LastExecution.Value.Ticks, DateTimeKind.Utc) : null
|
||||
});
|
||||
|
||||
return Ok(recurringJobs);
|
||||
|
@ -9,7 +9,7 @@ namespace API.DTOs;
|
||||
/// A Chapter is the lowest grouping of a reading medium. A Chapter contains a set of MangaFiles which represents the underlying
|
||||
/// file (abstracted from type).
|
||||
/// </summary>
|
||||
public class ChapterDto : IHasReadTimeEstimate, IEntityDate
|
||||
public class ChapterDto : IHasReadTimeEstimate
|
||||
{
|
||||
public int Id { get; init; }
|
||||
/// <summary>
|
||||
@ -59,8 +59,6 @@ public class ChapterDto : IHasReadTimeEstimate, IEntityDate
|
||||
/// <summary>
|
||||
/// When chapter was created
|
||||
/// </summary>
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
/// <summary>
|
||||
|
@ -26,12 +26,4 @@ public class DeviceDto
|
||||
/// Platform (ie) Windows 10
|
||||
/// </summary>
|
||||
public DevicePlatform Platform { get; set; }
|
||||
/// <summary>
|
||||
/// Last time this device was used to send a file
|
||||
/// </summary>
|
||||
public DateTime LastUsed { get; set; }
|
||||
/// <summary>
|
||||
/// Last time this device was used to send a file
|
||||
/// </summary>
|
||||
public DateTime LastUsedUtc { get; set; }
|
||||
}
|
||||
|
@ -15,14 +15,6 @@ public class JobDto
|
||||
/// <summary>
|
||||
/// When the job was created
|
||||
/// </summary>
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
/// <summary>
|
||||
/// Last time the job was run
|
||||
/// </summary>
|
||||
public DateTime? LastExecution { get; set; }
|
||||
/// <summary>
|
||||
/// When the job was created
|
||||
/// </summary>
|
||||
public DateTime? CreatedAtUtc { get; set; }
|
||||
/// <summary>
|
||||
/// Last time the job was run
|
||||
|
@ -20,6 +20,4 @@ public class MediaErrorDto
|
||||
/// Exception message
|
||||
/// </summary>
|
||||
public string Details { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ public class ScrobbleEventDto
|
||||
public bool IsProcessed { get; set; }
|
||||
public int? VolumeNumber { get; set; }
|
||||
public int? ChapterNumber { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public float? Rating { get; set; }
|
||||
public ScrobbleEventType ScrobbleEventType { get; set; }
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using API.Entities.Enums.Theme;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Services;
|
||||
|
||||
namespace API.DTOs.Theme;
|
||||
@ -8,7 +6,7 @@ namespace API.DTOs.Theme;
|
||||
/// <summary>
|
||||
/// Represents a set of css overrides the user can upload to Kavita and will load into webui
|
||||
/// </summary>
|
||||
public class SiteThemeDto : IEntityDate
|
||||
public class SiteThemeDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
@ -32,9 +30,5 @@ public class SiteThemeDto : IEntityDate
|
||||
/// Where did the theme come from
|
||||
/// </summary>
|
||||
public ThemeProvider Provider { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
public string Selector => "bg-" + Name.ToLower();
|
||||
}
|
||||
|
@ -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';
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,7 @@
|
||||
|
||||
"user-scrobble-history": {
|
||||
"title": "Scrobble History",
|
||||
"description": "Here you will find any scrobble events linked with your account. In order for events to exist, you must have an active\n scrobble provider configured. All events that have been processed will clear after a month. If there are non-processed events, it\n is likely these cannot form matches upstream. Please reach out to your admin to get them corrected.",
|
||||
"description": "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.",
|
||||
"filter-label": "Filter",
|
||||
"created-header": "Created",
|
||||
"last-modified-header": "Last Modified",
|
||||
@ -164,7 +164,7 @@
|
||||
|
||||
"user-holds": {
|
||||
"title": "Scrobble Holds",
|
||||
"description": "This is a user-managed list of Series that will not be scrobbled to upstream providers. You can remove a series at\n any time and the next Scrobble-able event (reading progress, rating, want to read status) will trigger events."
|
||||
"description": "This is a user-managed list of Series that will not be scrobbled to upstream providers. You can remove a series at any time and the next Scrobble-able event (reading progress, rating, want to read status) will trigger events."
|
||||
},
|
||||
|
||||
"theme-manager": {
|
||||
@ -243,7 +243,7 @@
|
||||
"current-password-label": "Current Password",
|
||||
"email-not-confirmed": "This email is not confirmed",
|
||||
"email-updated-title": "Email Updated",
|
||||
"email-updated-description": "You can use the following link below to confirm the email for your account.\n If your server is externally accessible, an email will have been sent to the email and the link can be used to confirm the email.",
|
||||
"email-updated-description": "You can use the following link below to confirm the email for your account. If your server is externally accessible, an email will have been sent to the email and the link can be used to confirm the email.",
|
||||
"setup-user-account": "Setup user's account",
|
||||
"invite-url-label": "Invite Url",
|
||||
"invite-url-tooltip": "Copy this and paste in a new tab",
|
||||
@ -280,7 +280,7 @@
|
||||
"no-token-set": "No Token Set",
|
||||
"token-set": "Token Set",
|
||||
"generate": "Generate",
|
||||
"instructions": "First time users should click on \"{{scrobbling-providers.generate}}\" below to allow Kavita+ to talk with {{service}}.\n Once you authorize the program, copy and paste the token in the input below. You can regenerate your token at any time.",
|
||||
"instructions": "First time users should click on \"{{scrobbling-providers.generate}}\" below to allow Kavita+ to talk with {{service}}. Once you authorize the program, copy and paste the token in the input below. You can regenerate your token at any time.",
|
||||
"token-input-label": "{{service}} Token Goes Here",
|
||||
"edit": "{{common.edit}}",
|
||||
"cancel": "{{common.cancel}}",
|
||||
@ -540,11 +540,11 @@
|
||||
"invite-user": {
|
||||
"title": "Invite User",
|
||||
"close": "{{common.close}}",
|
||||
"description": "Invite a user to your server. Enter their email in and we will send them an email to create an account. If you do not want to use our email service, you can <a href=\"https://wiki.kavitareader.com/en/guides/misc/email\" rel=\"noopener noreferrer\" target=\"_blank\">host your own</a>\n email service or use a fake email (Forgot User will not work). A link will be presented regardless and can be used to setup the account manually.",
|
||||
"description": "Invite a user to your server. Enter their email in and we will send them an email to create an account. If you do not want to use our email service, you can <a href=\"https://wiki.kavitareader.com/en/guides/misc/email\" rel=\"noopener noreferrer\" target=\"_blank\">host your own</a> email service or use a fake email (Forgot User will not work). A link will be presented regardless and can be used to setup the account manually.",
|
||||
"email": "{{common.email}}",
|
||||
"required-field": "{{common.required-field}}",
|
||||
"setup-user-title": "User invited",
|
||||
"setup-user-description": "You can use the following link below to setup the account for your user or use the copy button. You may need to log out before using the link to register a new user.\n If your server is externally accessible, an email will have been sent to the user and the links can be used by them to finish setting up their account.",
|
||||
"setup-user-description": "You can use the following link below to setup the account for your user or use the copy button. You may need to log out before using the link to register a new user. If your server is externally accessible, an email will have been sent to the user and the links can be used by them to finish setting up their account.",
|
||||
"setup-user-account": "Setup user's account",
|
||||
"setup-user-account-tooltip": "Copy this and paste in a new tab. You may need to log out.",
|
||||
"invite-url-label": "Invite Url",
|
||||
@ -659,7 +659,7 @@
|
||||
"description": "Complete the form to register an admin account",
|
||||
"username-label": "{{common.username}}",
|
||||
"email-label": "{{common.email}}",
|
||||
"email-tooltip": "Email does not need to be a real address, but provides access to forgot password.\n It is not sent outside the server unless forgot password is used without a custom email service host.",
|
||||
"email-tooltip": "Email does not need to be a real address, but provides access to forgot password. It is not sent outside the server unless forgot password is used without a custom email service host.",
|
||||
"password-label": "{{common.password}}",
|
||||
"required-field": "{{validation.required-field}}",
|
||||
"valid-email": "{{validation.valid-email}}",
|
||||
@ -941,6 +941,7 @@
|
||||
"length-title": "Length",
|
||||
"pages-count": "{{num}} Pages",
|
||||
"words-count": "{{num}} Words",
|
||||
|
||||
"reading-time-title": "Read Time",
|
||||
"date-added-title": "Date Added",
|
||||
"size-title": "Size",
|
||||
@ -952,7 +953,7 @@
|
||||
"range-hours": "{{value}} {{hourWord}}",
|
||||
"hour": "Hour",
|
||||
"hours": "Hours",
|
||||
"read-time-title": "{{series-info-cards.read-time-title}}"
|
||||
"read-time-title": "{{series-info-cards.read-time-title}}",
|
||||
},
|
||||
|
||||
"series-info-cards": {
|
||||
@ -1069,7 +1070,7 @@
|
||||
},
|
||||
|
||||
"manage-scrobble-errors": {
|
||||
"description": "This table contains issues found during scrobbling. This list is non-managed.\n You can clear it at any time and wait for the next scrobble upload to see. If there is an unknown series, you are best correcting the\nseries name or localized series name or adding a weblink for the providers.",
|
||||
"description": "This table contains issues found during scrobbling. This list is non-managed. You can clear it at any time and wait for the next scrobble upload to see. If there is an unknown series, you are best correcting the series name or localized series name or adding a weblink for the providers.",
|
||||
"filter-label": "Filter",
|
||||
"clear-errors": "Clear Errors",
|
||||
"series-header": "Series",
|
||||
@ -1614,7 +1615,13 @@
|
||||
"x-axis-label": "Time",
|
||||
"y-axis-label": "Hours Read",
|
||||
"no-data": "No Reading Progress",
|
||||
"time-frame-label": "Time Frame"
|
||||
"time-frame-label": "Time Frame",
|
||||
"this-week": "{{time-periods.this-week}}",
|
||||
"last-7-days": "{{time-periods.last-7-days}}",
|
||||
"last-30-days": "{{time-periods.last-30-days}}",
|
||||
"last-90-days": "{{time-periods.last-90-days}}",
|
||||
"last-year": "{{time-periods.last-year}}",
|
||||
"all-time": "{{time-periods.all-time}}"
|
||||
},
|
||||
|
||||
"manga-format-stats": {
|
||||
@ -1753,7 +1760,7 @@
|
||||
"confirm-delete-series": "Are you sure you want to delete this series? It will not modify files on disk.",
|
||||
"alert-bad-theme": "There is invalid or unsafe css in the theme. Please reach out to your admin to have this corrected. Defaulting to dark theme.",
|
||||
"confirm-library-delete": "Are you sure you want to delete the {{name}} library? You cannot undo this action.",
|
||||
"confirm-library-type-change": "Changing library type will trigger a new scan with different parsing rules and may lead to\n series being re-created and hence you may loose progress and bookmarks. You should backup before you do this. Are you sure you want to continue?",
|
||||
"confirm-library-type-change": "Changing library type will trigger a new scan with different parsing rules and may lead to series being re-created and hence you may loose progress and bookmarks. You should backup before you do this. Are you sure you want to continue?",
|
||||
"confirm-download-size": "The {{entityType}} is {{size}}. Are you sure you want to continue?"
|
||||
},
|
||||
|
||||
|
@ -33,7 +33,7 @@ export function preloadUser(userService: AccountService, transloco: TranslocoSer
|
||||
}
|
||||
|
||||
// If no user or locale is available, fallback to the default language ('en')
|
||||
const localStorageLocale = localStorage.getItem(userService.localeKey) || 'en';
|
||||
const localStorageLocale = localStorage.getItem(AccountService.localeKey) || 'en';
|
||||
transloco.setActiveLang(localStorageLocale);
|
||||
return transloco.load(localStorageLocale)
|
||||
})).subscribe();
|
||||
@ -71,7 +71,7 @@ const languageCodes = [
|
||||
'syr', 'syr-SY', 'ta', 'ta-IN', 'te', 'te-IN', 'th', 'th-TH', 'tl', 'tl-PH', 'tn',
|
||||
'tn-ZA', 'tr', 'tr-TR', 'tt', 'tt-RU', 'ts', 'uk', 'uk-UA', 'ur', 'ur-PK', 'uz',
|
||||
'uz-UZ', 'uz-UZ', 'vi', 'vi-VN', 'xh', 'xh-ZA', 'zh', 'zh-CN', 'zh-HK', 'zh-MO',
|
||||
'zh-SG', 'zh-TW', 'zu', 'zu-ZA'
|
||||
'zh-SG', 'zh-TW', 'zu', 'zu-ZA', 'zh_Hans'
|
||||
];
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
@ -109,13 +109,13 @@ bootstrapApplication(AppComponent, {
|
||||
provide: TRANSLOCO_CONFIG,
|
||||
useValue: {
|
||||
reRenderOnLangChange: true,
|
||||
availableLangs: languageCodes, // TODO: Derive this from the directory
|
||||
availableLangs: languageCodes,
|
||||
prodMode: environment.production,
|
||||
defaultLang: 'en',
|
||||
fallbackLang: 'en',
|
||||
missingHandler: {
|
||||
useFallbackTranslation: true,
|
||||
allowEmpty: true,
|
||||
allowEmpty: false,
|
||||
},
|
||||
flatten: {
|
||||
aot: !isDevMode()
|
||||
|
52
openapi.json
52
openapi.json
@ -7,7 +7,7 @@
|
||||
"name": "GPL-3.0",
|
||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||
},
|
||||
"version": "0.7.6.5"
|
||||
"version": "0.7.6.6"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
@ -12822,16 +12822,6 @@
|
||||
"type": "integer",
|
||||
"description": "Platform (ie) Windows 10",
|
||||
"format": "int32"
|
||||
},
|
||||
"lastUsed": {
|
||||
"type": "string",
|
||||
"description": "Last time this device was used to send a file",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastUsedUtc": {
|
||||
"type": "string",
|
||||
"description": "Last time this device was used to send a file",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@ -13391,18 +13381,6 @@
|
||||
"description": "Human Readable title for the Job",
|
||||
"nullable": true
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"description": "When the job was created",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"lastExecution": {
|
||||
"type": "string",
|
||||
"description": "Last time the job was run",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"createdAtUtc": {
|
||||
"type": "string",
|
||||
"description": "When the job was created",
|
||||
@ -13879,14 +13857,6 @@
|
||||
"type": "string",
|
||||
"description": "Exception message",
|
||||
"nullable": true
|
||||
},
|
||||
"created": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -14850,11 +14820,11 @@
|
||||
"format": "int32",
|
||||
"nullable": true
|
||||
},
|
||||
"lastModified": {
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"created": {
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
@ -16439,22 +16409,6 @@
|
||||
"description": "Where did the theme come from",
|
||||
"format": "int32"
|
||||
},
|
||||
"created": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModified": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"selector": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user