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:
Joe Milazzo 2023-08-03 10:33:51 -05:00 committed by GitHub
parent 670bf82c38
commit 3b23d63234
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
389 changed files with 13652 additions and 7925 deletions

View file

@ -1,85 +1,88 @@
<div class="card mt-2">
<div class="card-body">
<div class="card-title">
<div class="container-fluid row mb-2">
<div class="col-10 col-sm-10">
<h4 id="license-key-header">Kavita+ License</h4>
</div>
<div class="col-2 text-end">
<ng-container *ngIf="hasLicense; else noLicense">
<ng-container *ngIf="hasValidLicense; else invalidLicenseBuy">
<a class="btn btn-primary btn-sm me-1" [href]="manageLink" target="_blank" rel="noreferrer nofollow">Manage</a>
<ng-container *transloco="let t; read: 'license'">
<div class="card mt-2">
<div class="card-body">
<div class="card-title">
<div class="container-fluid row mb-2">
<div class="col-10 col-sm-10">
<h4 id="license-key-header">{{t('title')}}</h4>
</div>
<div class="col-2 text-end">
<ng-container *ngIf="hasLicense; else noLicense">
<ng-container *ngIf="hasValidLicense; else invalidLicenseBuy">
<a class="btn btn-primary btn-sm me-1" [href]="manageLink" target="_blank" rel="noreferrer nofollow">{{t('manage')}}</a>
</ng-container>
<ng-template #invalidLicenseBuy>
<a class="btn btn-primary btn-sm me-1"
[ngbTooltip]="t('invalid-license-tooltip')"
href="mailto:kavitareader@gmail.com?subject=Kavita+Subscription+Renewal&body=Description%3A%0D%0A%0D%0ALicense%20Key%3A%0D%0A%0D%0AYour%20Email%3A"
>{{t('renew')}}</a>
</ng-template>
<button class="btn btn-secondary btn-sm me-1" style="width: 58px" (click)="validateLicense()">
<span *ngIf="!isChecking">{{t('check')}}</span>
<app-loading [loading]="isChecking" size="spinner-border-sm"></app-loading>
</button>
<button class="btn btn-secondary btn-sm" style="width: 62px" (click)="toggleViewMode()">
<span *ngIf="!isViewMode">{{t('cancel')}}</span>
<span *ngIf="isViewMode">{{t('edit')}}</span>
</button>
</ng-container>
<ng-template #invalidLicenseBuy>
<a class="btn btn-primary btn-sm me-1"
ngbTooltip="If your subscription has ended, you must email support to get a new subscription created"
href="mailto:kavitareader@gmail.com?subject=Kavita+Subscription+Renewal&body=Description%3A%0D%0A%0D%0ALicense%20Key%3A%0D%0A%0D%0AYour%20Email%3A"
>Renew</a>
<ng-template #noLicense>
<a class="btn btn-secondary btn-sm me-1" [href]="buyLink" target="_blank" rel="noreferrer nofollow">{{t('buy')}}</a>
<button class="btn btn-primary btn-sm" (click)="toggleViewMode()">{{isViewMode ? t('activate') : t('cancel')}}</button>
</ng-template>
<button class="btn btn-secondary btn-sm me-1" style="width: 58px" (click)="validateLicense()">
<span *ngIf="!isChecking">Check</span>
<app-loading [loading]="isChecking" size="spinner-border-sm"></app-loading>
</button>
<button class="btn btn-secondary btn-sm" style="width: 62px" (click)="toggleViewMode()">
<span *ngIf="!isViewMode">Cancel</span>
<span *ngIf="isViewMode">Edit</span>
</button>
</ng-container>
<ng-template #noLicense>
<a class="btn btn-secondary btn-sm me-1" [href]="buyLink" target="_blank" rel="noreferrer nofollow">Buy</a>
<button class="btn btn-primary btn-sm" (click)="toggleViewMode()">{{isViewMode ? 'Activate' : 'Cancel'}}</button>
</ng-template>
</div>
</div>
</div>
</div>
<ng-container *ngIf="isViewMode">
<div class="container-fluid row">
<ng-container *ngIf="isViewMode">
<div class="container-fluid row">
<span class="col-12">
<ng-container *ngIf="hasLicense; else noToken">
<span class="me-1">*********</span>
<ng-container *ngIf="!isChecking; else checking">
<i *ngIf="hasValidLicense" ngbTooltip="License is valid" class="fa-solid fa-check-circle successful-validation ms-1">
<span class="visually-hidden">License is Valid</span>
<i *ngIf="hasValidLicense" [ngbTooltip]="t('license-valid')" class="fa-solid fa-check-circle successful-validation ms-1">
<span class="visually-hidden">{{t('license-valid')}}</span>
</i>
<i class="error fa-solid fa-exclamation-circle ms-1" ngbTooltip="License Invalid" *ngIf="!hasValidLicense">
<span class="visually-hidden">License Not Valid</span>
<i class="error fa-solid fa-exclamation-circle ms-1" [ngbTooltip]="t('license-not-valid')" *ngIf="!hasValidLicense">
<span class="visually-hidden">{{t('license-not-valid')}}</span>
</i>
</ng-container>
<ng-template #checking>
<div class="spinner-border spinner-border-sm text-primary" role="status">
<span class="visually-hidden">Loading...</span>
<span class="visually-hidden">{{t('loading')}}</span>
</div>
</ng-template>
</ng-container>
<ng-template #noToken>No license key</ng-template>
<ng-template #noToken>{{t('no-license-key')}}</ng-template>
</span>
</div>
</ng-container>
</div>
</ng-container>
<div #collapse="ngbCollapse" [(ngbCollapse)]="isViewMode">
<form [formGroup]="formGroup">
<p>Enter the License Key and Email used to register with Stripe</p>
<div class="form-group mb-3">
<label for="license-key">License Key</label>
<input id="license-key" type="text" class="form-control" formControlName="licenseKey" autocomplete="off"/>
<div #collapse="ngbCollapse" [(ngbCollapse)]="isViewMode">
<form [formGroup]="formGroup">
<p>{{t('activate-description')}}</p>
<div class="form-group mb-3">
<label for="license-key">{{t('activate-license-label')}}</label>
<input id="license-key" type="text" class="form-control" formControlName="licenseKey" autocomplete="off"/>
</div>
<div class="form-group mb-3">
<label for="email">{{t('activate-email-label')}}</label>
<input id="email" type="email" class="form-control" formControlName="email" autocomplete="off"/>
</div>
</form>
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
<button type="button" class="flex-fill btn btn-danger me-1" aria-describedby="license-key-header" (click)="deleteLicense()">
{{t('activate-delete')}}
</button>
<button type="submit" class="flex-fill btn btn-primary" aria-describedby="license-key-header" [disabled]="!formGroup.get('email')?.value || !formGroup.get('licenseKey')?.value" (click)="saveForm()">
<span *ngIf="!isSaving">{{t('activate-save')}}</span>
<app-loading [loading]="isSaving" size="spinner-border-sm"></app-loading>
</button>
</div>
<div class="form-group mb-3">
<label for="email">Email</label>
<input id="email" type="email" class="form-control" formControlName="email" autocomplete="off"/>
</div>
</form>
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end mb-3">
<button type="button" class="flex-fill btn btn-danger me-1" aria-describedby="license-key-header" (click)="deleteLicense()">
Delete
</button>
<button type="submit" class="flex-fill btn btn-primary" aria-describedby="license-key-header" [disabled]="!formGroup.get('email')?.value || !formGroup.get('licenseKey')?.value" (click)="saveForm()">
<span *ngIf="!isSaving">Save</span>
<app-loading [loading]="isSaving" size="spinner-border-sm"></app-loading>
</button>
</div>
</div>
</div>
</div>
</ng-container>