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,6 +1,7 @@
|
|||
<nav class="navbar navbar-expand-md navbar-dark fixed-top" *ngIf="navService?.navbarVisible$ | async">
|
||||
<ng-container *transloco="let t; read: 'nav-header'">
|
||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top" *ngIf="navService?.navbarVisible$ | async">
|
||||
<div class="container-fluid">
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
|
||||
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">{{t('skip-alt')}}</a>
|
||||
<a class="side-nav-toggle" *ngIf="navService?.sideNavVisibility$ | async" (click)="hideSideNav()"><i class="fas fa-bars"></i></a>
|
||||
<a class="navbar-brand dark-exempt" routerLink="/libraries" routerLinkActive="active">
|
||||
<img width="28" height="28" class="logo" ngSrc="assets/images/logo-32.png" alt="kavita icon" aria-hidden="true"/>
|
||||
|
|
@ -9,127 +10,127 @@
|
|||
<ul class="navbar-nav col me-auto">
|
||||
|
||||
<div class="nav-item" *ngIf="(accountService.currentUser$ | async) as user">
|
||||
<label for="nav-search" class="form-label visually-hidden">Search series</label>
|
||||
<label for="nav-search" class="form-label visually-hidden">{{t('search-series-alt')}}</label>
|
||||
<div class="ng-autocomplete">
|
||||
<app-grouped-typeahead
|
||||
#search
|
||||
id="nav-search"
|
||||
[minQueryLength]="2"
|
||||
initialValue=""
|
||||
placeholder="Search…"
|
||||
[placeholder]="t('search-alt')"
|
||||
[groupedData]="searchResults"
|
||||
(inputChanged)="onChangeSearch($event)"
|
||||
(clearField)="clearSearch()"
|
||||
(focusChanged)="focusUpdate($event)"
|
||||
>
|
||||
|
||||
<ng-template #libraryTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickLibraryResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.name}}</span>
|
||||
<ng-template #libraryTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickLibraryResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #seriesTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickSeriesSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<ng-container *ngIf="searchTerm.toLowerCase().trim() as st">
|
||||
<span *ngIf="item.name.toLowerCase().trim().indexOf(st) >= 0; else localizedName">{{item.name}}</span>
|
||||
<ng-template #localizedName>
|
||||
<span [innerHTML]="item.localizedName"></span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<ng-template #seriesTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickSeriesSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getSeriesCoverImage(item.seriesId)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<ng-container *ngIf="searchTerm.toLowerCase().trim() as st">
|
||||
<span *ngIf="item.name.toLowerCase().trim().indexOf(st) >= 0; else localizedName">{{item.name}}</span>
|
||||
<ng-template #localizedName>
|
||||
<span [innerHTML]="item.localizedName"></span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<div class="text-light fst-italic" style="font-size: 0.8rem;">in {{item.libraryName}}</div>
|
||||
<div class="text-light fst-italic" style="font-size: 0.8rem;">in {{item.libraryName}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #collectionTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickCollectionSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getCollectionCoverImage(item.id)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
<span *ngIf="item.promoted">
|
||||
<ng-template #collectionTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickCollectionSearchResult(item)">
|
||||
<div style="width: 24px" class="me-1">
|
||||
<app-image class="me-3 search-result" width="24px" [imageUrl]="imageService.getCollectionCoverImage(item.id)"></app-image>
|
||||
</div>
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
<span *ngIf="item.promoted">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true" title="Promoted"></i>
|
||||
<span class="visually-hidden">(promoted)</span>
|
||||
<span class="visually-hidden">{{t('promoted')}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #readingListTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickReadingListSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
<span *ngIf="item.promoted">
|
||||
<ng-template #readingListTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickReadingListSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
<span *ngIf="item.promoted">
|
||||
<i class="fa fa-angle-double-up" aria-hidden="true" title="Promoted"></i>
|
||||
<span class="visually-hidden">(promoted)</span>
|
||||
<span class="visually-hidden">{{t('promoted')}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #tagTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="goTo('tags', item.id)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
<ng-template #tagTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="goTo('tags', item.id)">
|
||||
<div class="ms-1">
|
||||
<span>{{item.title}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #personTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goToPerson(item.role, item.id)">
|
||||
<div class="ms-1">
|
||||
<ng-template #personTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goToPerson(item.role, item.id)">
|
||||
<div class="ms-1">
|
||||
|
||||
<div [innerHTML]="item.name"></div>
|
||||
<div class="text-light fst-italic">{{item.role | personRole}}</div>
|
||||
<div [innerHTML]="item.name"></div>
|
||||
<div class="text-light fst-italic">{{item.role | personRole}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #genreTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goTo('genres', item.id)">
|
||||
<div class="ms-1">
|
||||
<div [innerHTML]="item.title"></div>
|
||||
<ng-template #genreTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="goTo('genres', item.id)">
|
||||
<div class="ms-1">
|
||||
<div [innerHTML]="item.title"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
|
||||
|
||||
<ng-template #chapterTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="clickChapterSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<ng-container *ngIf="item.files.length > 0">
|
||||
<app-series-format [format]="item.files?.[0].format"></app-series-format>
|
||||
</ng-container>
|
||||
<span>{{item.titleName}}</span>
|
||||
<ng-template #chapterTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" class="clickable" (click)="clickChapterSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<ng-container *ngIf="item.files.length > 0">
|
||||
<app-series-format [format]="item.files?.[0].format"></app-series-format>
|
||||
</ng-container>
|
||||
<span>{{item.titleName}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #fileTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickFileSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<span>{{item.filePath}}</span>
|
||||
<ng-template #fileTemplate let-item>
|
||||
<div style="display: flex;padding: 5px;" (click)="clickFileSearchResult(item)">
|
||||
<div class="ms-1">
|
||||
<app-series-format [format]="item.format"></app-series-format>
|
||||
<span>{{item.filePath}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #noResultsTemplate let-notFound>
|
||||
No results found
|
||||
</ng-template>
|
||||
<ng-template #noResultsTemplate let-notFound>
|
||||
{{t('no-data')}}
|
||||
</ng-template>
|
||||
|
||||
</app-grouped-typeahead>
|
||||
</app-grouped-typeahead>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
|
|
@ -138,7 +139,7 @@
|
|||
<div class="back-to-top" *ngIf="backToTopNeeded">
|
||||
<button class="btn btn-icon scroll-to-top" (click)="scrollToTop()">
|
||||
<i class="fa fa-angle-double-up nav" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">Scroll to Top</span>
|
||||
<span class="visually-hidden">{{t('scroll-to-top-alt')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -149,7 +150,7 @@
|
|||
<div class="nav-item not-xs-only">
|
||||
<a routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')" class="dark-exempt btn btn-icon" title="Server Settings">
|
||||
<i class="fa fa-cogs nav" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">Server Settings</span>
|
||||
<span class="visually-hidden">{{t('server-settings')}}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
@ -160,14 +161,16 @@
|
|||
<span class="d-none d-xs-none d-sm-none d-md-inline-block">{{user.username | sentenceCase}}</span>
|
||||
</button>
|
||||
<div ngbDropdownMenu>
|
||||
<a class="xs-only" ngbDropdownItem routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')">Server Settings</a>
|
||||
<a ngbDropdownItem routerLink="/preferences/">Settings</a>
|
||||
<a ngbDropdownItem href="https://wiki.kavitareader.com" rel="noopener noreferrer" target="_blank">Help</a>
|
||||
<a ngbDropdownItem routerLink="/announcements/" *ngIf="accountService.hasAdminRole(user)">Announcements</a>
|
||||
<a ngbDropdownItem (click)="logout()">Logout</a>
|
||||
<a class="xs-only" ngbDropdownItem routerLink="/admin/dashboard" *ngIf="user.roles.includes('Admin')">{{t('server-settings')}}</a>
|
||||
<a ngbDropdownItem routerLink="/preferences/">{{t('settings')}}</a>
|
||||
<a ngbDropdownItem href="https://wiki.kavitareader.com" rel="noopener noreferrer" target="_blank">{{t('help')}}</a>
|
||||
<a ngbDropdownItem routerLink="/announcements/" *ngIf="accountService.hasAdminRole(user)">{{t('announcements')}}</a>
|
||||
<a ngbDropdownItem (click)="logout()">{{t('logout')}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
||||
</ng-container>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue