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
|
|
@ -19,6 +19,7 @@ import { LibraryService } from './library.service';
|
|||
import { MemberService } from './member.service';
|
||||
import { ReaderService } from './reader.service';
|
||||
import { SeriesService } from './series.service';
|
||||
import {translate, TranslocoService} from "@ngneat/transloco";
|
||||
|
||||
export type LibraryActionCallback = (library: Partial<Library>) => void;
|
||||
export type SeriesActionCallback = (series: Series) => void;
|
||||
|
|
@ -42,7 +43,8 @@ export class ActionService implements OnDestroy {
|
|||
|
||||
constructor(private libraryService: LibraryService, private seriesService: SeriesService,
|
||||
private readerService: ReaderService, private toastr: ToastrService, private modalService: NgbModal,
|
||||
private confirmService: ConfirmService, private memberService: MemberService, private deviceSerivce: DeviceService) { }
|
||||
private confirmService: ConfirmService, private memberService: MemberService, private deviceService: DeviceService,
|
||||
private translocoService: TranslocoService) { }
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy.next();
|
||||
|
|
@ -64,7 +66,7 @@ export class ActionService implements OnDestroy {
|
|||
const force = false; // await this.promptIfForce();
|
||||
|
||||
this.libraryService.scan(library.id, force).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info('Scan queued for ' + library.name);
|
||||
this.toastr.info(this.translocoService.translate('toasts.scan-queued', {name: library.name}));
|
||||
if (callback) {
|
||||
callback(library);
|
||||
}
|
||||
|
|
@ -83,7 +85,7 @@ export class ActionService implements OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!await this.confirmService.confirm('Refresh covers will force all cover images to be recalculated. This is a heavy operation. Are you sure you don\'t want to perform a Scan instead?')) {
|
||||
if (!await this.confirmService.confirm(translate('toasts.confirm-regen-covers'))) {
|
||||
if (callback) {
|
||||
callback(library);
|
||||
}
|
||||
|
|
@ -93,7 +95,7 @@ export class ActionService implements OnDestroy {
|
|||
const forceUpdate = true; //await this.promptIfForce();
|
||||
|
||||
this.libraryService.refreshMetadata(library?.id, forceUpdate).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info('Scan queued for ' + library.name);
|
||||
this.toastr.info(this.translocoService.translate('toasts.scan-queued', {name: library.name}));
|
||||
if (callback) {
|
||||
callback(library);
|
||||
}
|
||||
|
|
@ -119,7 +121,7 @@ export class ActionService implements OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!await this.confirmService.alert('This is a long running process. Please give it the time to complete before invoking again.')) {
|
||||
if (!await this.confirmService.alert(translate('toasts.alert-long-running'))) {
|
||||
if (callback) {
|
||||
callback(library);
|
||||
}
|
||||
|
|
@ -127,7 +129,7 @@ export class ActionService implements OnDestroy {
|
|||
}
|
||||
|
||||
this.libraryService.analyze(library?.id).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info('Library file analysis queued for ' + library.name);
|
||||
this.toastr.info(this.translocoService.translate('toasts.library-file-analysis-queued', {name: library.name}));
|
||||
if (callback) {
|
||||
callback(library);
|
||||
}
|
||||
|
|
@ -142,7 +144,7 @@ export class ActionService implements OnDestroy {
|
|||
markSeriesAsRead(series: Series, callback?: SeriesActionCallback) {
|
||||
this.seriesService.markRead(series.id).pipe(take(1)).subscribe(res => {
|
||||
series.pagesRead = series.pages;
|
||||
this.toastr.success(series.name + ' is now read');
|
||||
this.toastr.success(this.translocoService.translate('toasts.entity-read', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
|
|
@ -157,7 +159,7 @@ export class ActionService implements OnDestroy {
|
|||
markSeriesAsUnread(series: Series, callback?: SeriesActionCallback) {
|
||||
this.seriesService.markUnread(series.id).pipe(take(1)).subscribe(res => {
|
||||
series.pagesRead = 0;
|
||||
this.toastr.success(series.name + ' is now unread');
|
||||
this.toastr.success(this.translocoService.translate('toasts.entity-unread', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
|
|
@ -171,7 +173,7 @@ export class ActionService implements OnDestroy {
|
|||
*/
|
||||
async scanSeries(series: Series, callback?: SeriesActionCallback) {
|
||||
this.seriesService.scan(series.libraryId, series.id).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info('Scan queued for ' + series.name);
|
||||
this.toastr.info(this.translocoService.translate('toasts.scan-queued', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
|
|
@ -185,7 +187,7 @@ export class ActionService implements OnDestroy {
|
|||
*/
|
||||
analyzeFilesForSeries(series: Series, callback?: SeriesActionCallback) {
|
||||
this.seriesService.analyzeFiles(series.libraryId, series.id).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info('Scan queued for ' + series.name);
|
||||
this.toastr.info(this.translocoService.translate('toasts.scan-queued', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
|
|
@ -198,7 +200,7 @@ export class ActionService implements OnDestroy {
|
|||
* @param callback Optional callback to perform actions after API completes
|
||||
*/
|
||||
async refreshMetdata(series: Series, callback?: SeriesActionCallback) {
|
||||
if (!await this.confirmService.confirm('Refresh covers will force all cover images and metadata to be recalculated. This is a heavy operation. Are you sure you don\'t want to perform a Scan instead?')) {
|
||||
if (!await this.confirmService.confirm(translate('toasts.confirm-regen-covers'))) {
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
|
|
@ -206,7 +208,7 @@ export class ActionService implements OnDestroy {
|
|||
}
|
||||
|
||||
this.seriesService.refreshMetadata(series).pipe(take(1)).subscribe((res: any) => {
|
||||
this.toastr.info('Refresh covers queued for ' + series.name);
|
||||
this.toastr.info(this.translocoService.translate('toasts.refresh-covers-queued', {name: series.name}));
|
||||
if (callback) {
|
||||
callback(series);
|
||||
}
|
||||
|
|
@ -223,7 +225,7 @@ export class ActionService implements OnDestroy {
|
|||
this.readerService.markVolumeRead(seriesId, volume.id).pipe(take(1)).subscribe(() => {
|
||||
volume.pagesRead = volume.pages;
|
||||
volume.chapters?.forEach(c => c.pagesRead = c.pages);
|
||||
this.toastr.success('Marked as Read');
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-read'));
|
||||
|
||||
if (callback) {
|
||||
callback(volume);
|
||||
|
|
@ -241,7 +243,7 @@ export class ActionService implements OnDestroy {
|
|||
this.readerService.markVolumeUnread(seriesId, volume.id).subscribe(() => {
|
||||
volume.pagesRead = 0;
|
||||
volume.chapters?.forEach(c => c.pagesRead = 0);
|
||||
this.toastr.success('Marked as Unread');
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-unread'));
|
||||
if (callback) {
|
||||
callback(volume);
|
||||
}
|
||||
|
|
@ -257,7 +259,7 @@ export class ActionService implements OnDestroy {
|
|||
markChapterAsRead(libraryId: number, seriesId: number, chapter: Chapter, callback?: ChapterActionCallback) {
|
||||
this.readerService.saveProgress(libraryId, seriesId, chapter.volumeId, chapter.id, chapter.pages).pipe(take(1)).subscribe(results => {
|
||||
chapter.pagesRead = chapter.pages;
|
||||
this.toastr.success('Marked as Read');
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-read'));
|
||||
if (callback) {
|
||||
callback(chapter);
|
||||
}
|
||||
|
|
@ -273,7 +275,7 @@ export class ActionService implements OnDestroy {
|
|||
markChapterAsUnread(libraryId: number, seriesId: number, chapter: Chapter, callback?: ChapterActionCallback) {
|
||||
this.readerService.saveProgress(libraryId, seriesId, chapter.volumeId, chapter.id, 0).pipe(take(1)).subscribe(results => {
|
||||
chapter.pagesRead = 0;
|
||||
this.toastr.success('Marked as Unread');
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-unread'));
|
||||
if (callback) {
|
||||
callback(chapter);
|
||||
}
|
||||
|
|
@ -294,7 +296,7 @@ export class ActionService implements OnDestroy {
|
|||
volume.chapters?.forEach(c => c.pagesRead = c.pages);
|
||||
});
|
||||
chapters?.forEach(c => c.pagesRead = c.pages);
|
||||
this.toastr.success('Marked as Read');
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-read'));
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
|
|
@ -315,7 +317,7 @@ export class ActionService implements OnDestroy {
|
|||
volume.chapters?.forEach(c => c.pagesRead = 0);
|
||||
});
|
||||
chapters?.forEach(c => c.pagesRead = 0);
|
||||
this.toastr.success('Marked as Unread');
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-unread'));
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
|
|
@ -333,7 +335,7 @@ export class ActionService implements OnDestroy {
|
|||
series.forEach(s => {
|
||||
s.pagesRead = s.pages;
|
||||
});
|
||||
this.toastr.success('Marked as Read');
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-read'));
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
|
|
@ -351,7 +353,7 @@ export class ActionService implements OnDestroy {
|
|||
series.forEach(s => {
|
||||
s.pagesRead = s.pages;
|
||||
});
|
||||
this.toastr.success('Marked as Unread');
|
||||
this.toastr.success(this.translocoService.translate('toasts.mark-unread'));
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
|
|
@ -394,7 +396,7 @@ export class ActionService implements OnDestroy {
|
|||
|
||||
removeMultipleSeriesFromWantToReadList(seriesIds: Array<number>, callback?: VoidActionCallback) {
|
||||
this.memberService.removeSeriesToWantToRead(seriesIds).subscribe(() => {
|
||||
this.toastr.success('Series removed from Want to Read list');
|
||||
this.toastr.success(this.translocoService.translate('toasts.series-removed-want-to-read'));
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
|
@ -538,14 +540,14 @@ export class ActionService implements OnDestroy {
|
|||
* @param callback Optional callback to perform actions after API completes
|
||||
*/
|
||||
async deleteMultipleSeries(seriesIds: Array<Series>, callback?: BooleanActionCallback) {
|
||||
if (!await this.confirmService.confirm('Are you sure you want to delete ' + seriesIds.length + ' series? It will not modify files on disk.')) {
|
||||
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-multiple-series', {count: seriesIds.length}))) {
|
||||
if (callback) {
|
||||
callback(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.seriesService.deleteMultipleSeries(seriesIds.map(s => s.id)).pipe(take(1)).subscribe(() => {
|
||||
this.toastr.success('Series deleted');
|
||||
this.toastr.success(this.translocoService.translate('toasts.series-deleted'));
|
||||
|
||||
if (callback) {
|
||||
callback(true);
|
||||
|
|
@ -554,7 +556,7 @@ export class ActionService implements OnDestroy {
|
|||
}
|
||||
|
||||
async deleteSeries(series: Series, callback?: BooleanActionCallback) {
|
||||
if (!await this.confirmService.confirm('Are you sure you want to delete this series? It will not modify files on disk.')) {
|
||||
if (!await this.confirmService.confirm(translate('toasts.confirm-delete-series'))) {
|
||||
if (callback) {
|
||||
callback(false);
|
||||
}
|
||||
|
|
@ -563,15 +565,15 @@ export class ActionService implements OnDestroy {
|
|||
|
||||
this.seriesService.delete(series.id).subscribe((res: boolean) => {
|
||||
if (callback) {
|
||||
this.toastr.success('Series deleted');
|
||||
this.toastr.success(this.translocoService.translate('toasts.series-deleted'));
|
||||
callback(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendToDevice(chapterIds: Array<number>, device: Device, callback?: VoidActionCallback) {
|
||||
this.deviceSerivce.sendTo(chapterIds, device.id).subscribe(() => {
|
||||
this.toastr.success('File emailed to ' + device.name);
|
||||
this.deviceService.sendTo(chapterIds, device.id).subscribe(() => {
|
||||
this.toastr.success(this.translocoService.translate('toasts.file-send-to', {name: device.name}));
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
|
@ -579,8 +581,8 @@ export class ActionService implements OnDestroy {
|
|||
}
|
||||
|
||||
sendSeriesToDevice(seriesId: number, device: Device, callback?: VoidActionCallback) {
|
||||
this.deviceSerivce.sendSeriesTo(seriesId, device.id).subscribe(() => {
|
||||
this.toastr.success('File(s) emailed to ' + device.name);
|
||||
this.deviceService.sendSeriesTo(seriesId, device.id).subscribe(() => {
|
||||
this.toastr.success(this.translocoService.translate('toasts.file-send-to', {name: device.name}));
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue