mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-26 10:38:00 +01:00
Refactor portal language code (#16795)
* Refactor portal language code This PR makes the following improvements: * The language code is DRYed up by defining `supportedLangs` in terms of `LANGUAGES` (previously `languageNames`). * The language selection dropdown code is DRYed up similarly. * The Angular locale registration code is DRYed up similarly: the omission of a supported language is now a static type error. The above improvements mean that it's now impossible to forget to include a supported language in any of those contexts. Furthermore: * The type of supported languages is replaced by a more accurate one than `string`, namely `SupportedLanguage`. * The value acquired from localStorage will never be used unless it is in fact a supported language. (Today, the GUI breaks pretty badly and errors are spammed in the console if localStorage contains an invalid value.) * Redundant implicit existence checks such as `localStorage &&` and `browserCultureLang &&` are removed. * The implementation of `initLangauge` is generally simplified and clarified. Signed-off-by: Simon Alling <alling.simon@gmail.com> * Restore accidentally deleted date check Signed-off-by: Simon Alling <alling.simon@gmail.com>
This commit is contained in:
parent
ca019f4030
commit
bb007f70bb
@ -18,9 +18,9 @@ import { AppConfigService } from './services/app-config.service';
|
||||
import { ThemeService } from './services/theme.service';
|
||||
import { CustomStyle, HAS_STYLE_MODE, THEME_ARRAY, ThemeInterface } from './services/theme';
|
||||
import { clone } from './shared/units/utils';
|
||||
import { DEFAULT_LANG_LOCALSTORAGE_KEY, DeFaultLang, supportedLangs } from "./shared/entities/shared.const";
|
||||
import { forkJoin, Observable } from "rxjs";
|
||||
import { DEFAULT_LANG_LOCALSTORAGE_KEY, DeFaultLang, supportedLangs, SupportedLanguage } from "./shared/entities/shared.const";
|
||||
import { SkinableConfig } from "./services/skinable-config.service";
|
||||
import { isSupportedLanguage } from './shared/units/shared.utils';
|
||||
|
||||
|
||||
|
||||
@ -75,20 +75,18 @@ export class AppComponent {
|
||||
initLanguage() {
|
||||
this.translate.addLangs(supportedLangs);
|
||||
this.translate.setDefaultLang(DeFaultLang);
|
||||
let selectedLang: string = DeFaultLang;
|
||||
if (localStorage && localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY)) {// If user has selected lang, then directly use it
|
||||
selectedLang = localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY);
|
||||
let selectedLang: SupportedLanguage;
|
||||
const savedLang = localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY);
|
||||
if (isSupportedLanguage(savedLang)) {// If user has selected lang, then directly use it
|
||||
selectedLang = savedLang;
|
||||
} else if (savedLang !== null) { // If there is a saved value, but it is not a supported language, warn and use the default language.
|
||||
console.warn(`Invalid saved language setting ${JSON.stringify(savedLang)}; defaulting to ${JSON.stringify(DeFaultLang)}.`);
|
||||
selectedLang = DeFaultLang;
|
||||
} else {// If user has not selected lang, then use browser language(if contained in supportedLangs)
|
||||
const browserCultureLang: string = this.translate
|
||||
.getBrowserCultureLang()
|
||||
.toLowerCase();
|
||||
if (browserCultureLang && browserCultureLang.trim() !== "") {
|
||||
if (supportedLangs && supportedLangs.length > 0) {
|
||||
if (supportedLangs.find(lang => lang === browserCultureLang)) {
|
||||
selectedLang = browserCultureLang;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectedLang = isSupportedLanguage(browserCultureLang) ? browserCultureLang : DeFaultLang;
|
||||
}
|
||||
localStorage.setItem(DEFAULT_LANG_LOCALSTORAGE_KEY, selectedLang);
|
||||
// use method will load related language json from backend server
|
||||
|
@ -20,14 +20,7 @@
|
||||
<clr-icon size="10" shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("en-us")' [class.lang-selected]='matchLang("en-us")'>English</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("zh-cn")' [class.lang-selected]='matchLang("zh-cn")'>中文简体</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("zh-tw")' [class.lang-selected]='matchLang("zh-tw")'>中文繁體</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("es-es")' [class.lang-selected]='matchLang("es-es")'>Español</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("fr-fr")' [class.lang-selected]='matchLang("fr-fr")'>Français</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("pt-br")' [class.lang-selected]='matchLang("pt-br")'>Português do Brasil</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("tr-tr")' [class.lang-selected]='matchLang("tr-tr")'>Türkçe</a>
|
||||
<a href="javascript:void(0)" clrDropdownItem (click)='switchLanguage("de-de")' [class.lang-selected]='matchLang("de-de")'>Deutsch</a>
|
||||
<a *ngFor="let lang of guiLanguages" href="javascript:void(0)" clrDropdownItem (click)='switchLanguage(lang[0])' [class.lang-selected]='matchLang(lang[0])'>{{lang[1]}}</a>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
<div class="nav-divider"></div>
|
||||
|
@ -26,7 +26,8 @@ import {
|
||||
CommonRoutes,
|
||||
DEFAULT_LANG_LOCALSTORAGE_KEY,
|
||||
DeFaultLang,
|
||||
languageNames,
|
||||
LANGUAGES,
|
||||
SupportedLanguage,
|
||||
} from "../../entities/shared.const";
|
||||
import { CustomStyle, HAS_STYLE_MODE, StyleMode } from "../../../services/theme";
|
||||
|
||||
@ -41,7 +42,8 @@ export class NavigatorComponent implements OnInit {
|
||||
@Output() showAccountSettingsModal = new EventEmitter<ModalEvent>();
|
||||
@Output() showDialogModalAction = new EventEmitter<ModalEvent>();
|
||||
|
||||
selectedLang: string = DeFaultLang;
|
||||
readonly guiLanguages = Object.entries(LANGUAGES);
|
||||
selectedLang: SupportedLanguage = DeFaultLang;
|
||||
appTitle: string = 'APP_TITLE.HARBOR';
|
||||
customStyle: CustomStyle;
|
||||
constructor(
|
||||
@ -58,7 +60,7 @@ export class NavigatorComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
// custom skin
|
||||
this.customStyle = this.skinableConfig.getSkinConfig();
|
||||
this.selectedLang = this.translate.currentLang;
|
||||
this.selectedLang = this.translate.currentLang as SupportedLanguage;
|
||||
if (this.appConfigService.isIntegrationMode()) {
|
||||
this.appTitle = 'APP_TITLE.VIC';
|
||||
}
|
||||
@ -77,7 +79,7 @@ export class NavigatorComponent implements OnInit {
|
||||
}
|
||||
|
||||
public get currentLang(): string {
|
||||
return languageNames[this.selectedLang];
|
||||
return LANGUAGES[this.selectedLang];
|
||||
}
|
||||
|
||||
public get admiralLink(): string {
|
||||
@ -103,8 +105,8 @@ export class NavigatorComponent implements OnInit {
|
||||
|| config.auth_mode === "oidc_auth")) || (user.user_id === 1 && user.username === "admin"));
|
||||
}
|
||||
|
||||
matchLang(lang: string): boolean {
|
||||
return lang.trim() === this.selectedLang;
|
||||
matchLang(lang: SupportedLanguage): boolean {
|
||||
return lang === this.selectedLang;
|
||||
}
|
||||
|
||||
// Open the account setting dialog
|
||||
@ -146,7 +148,7 @@ export class NavigatorComponent implements OnInit {
|
||||
}
|
||||
|
||||
// Switch languages
|
||||
switchLanguage(lang: string): void {
|
||||
switchLanguage(lang: SupportedLanguage): void {
|
||||
this.selectedLang = lang;
|
||||
localStorage.setItem(DEFAULT_LANG_LOCALSTORAGE_KEY, lang);
|
||||
// due to the bug(https://github.com/ngx-translate/core/issues/1258) of translate module
|
||||
|
@ -178,9 +178,9 @@ export const REFRESH_TIME_DIFFERENCE = 10000;
|
||||
//
|
||||
|
||||
|
||||
export const supportedLangs = ['en-us', 'zh-cn', 'zh-tw', 'es-es', 'fr-fr', 'pt-br', 'tr-tr', 'de-de'];
|
||||
export const DeFaultLang = "en-us";
|
||||
export const languageNames = {
|
||||
export type SupportedLanguage = keyof typeof LANGUAGES;
|
||||
export const LANGUAGES = {
|
||||
"en-us": "English",
|
||||
"zh-cn": "中文简体",
|
||||
"zh-tw": "中文繁體",
|
||||
@ -189,7 +189,8 @@ export const languageNames = {
|
||||
"pt-br": "Português do Brasil",
|
||||
"tr-tr": "Türkçe",
|
||||
"de-de": "Deutsch"
|
||||
};
|
||||
} as const;
|
||||
export const supportedLangs = Object.keys(LANGUAGES) as SupportedLanguage[];
|
||||
/**
|
||||
* The default cookie key used to store current used language preference.
|
||||
*/
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { DEFAULT_LANG_LOCALSTORAGE_KEY, DeFaultLang } from "../entities/shared.const";
|
||||
import { isSupportedLanguage } from '../units/shared.utils';
|
||||
|
||||
const baseTimeLine: Date = new Date('1970-1-1');
|
||||
|
||||
@ -11,13 +12,11 @@ const baseTimeLine: Date = new Date('1970-1-1');
|
||||
export class HarborDatetimePipe implements PipeTransform {
|
||||
|
||||
transform(value: any, format?: string): string {
|
||||
let lang: string = DeFaultLang;
|
||||
if (localStorage && localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY)) {
|
||||
lang = localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY);
|
||||
}
|
||||
if (value && value <= baseTimeLine) {// invalid date
|
||||
return '-';
|
||||
}
|
||||
const savedLang = localStorage.getItem(DEFAULT_LANG_LOCALSTORAGE_KEY);
|
||||
const lang = isSupportedLanguage(savedLang) ? savedLang : DeFaultLang;
|
||||
// default format medium
|
||||
return new DatePipe(lang).transform(value, format ? format : 'medium');
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ import {
|
||||
} from "../units/utils";
|
||||
import { FlushAll } from "../units/cache-util";
|
||||
import { SignInCredential } from "../../account/sign-in/sign-in-credential";
|
||||
import { DeFaultLang } from "../entities/shared.const";
|
||||
import { ProjectMemberEntity } from "../../../../ng-swagger-gen/models/project-member-entity";
|
||||
import { DeFaultLang } from "../entities/shared.const";
|
||||
|
||||
|
||||
const signInUrl = '/c/login';
|
||||
|
@ -69,25 +69,32 @@ import { HttpClientTestingModule } from "@angular/common/http/testing";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { HarborDatetimePipe } from './pipes/harbor-datetime.pipe';
|
||||
import { RemainingTimeComponent } from './components/remaining-time/remaining-time.component';
|
||||
|
||||
import { registerLocaleData } from "@angular/common";
|
||||
import zh_cn from '@angular/common/locales/zh-Hans';
|
||||
import zh_tw from '@angular/common/locales/zh-Hans-HK';
|
||||
import es from '@angular/common/locales/es';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import localePt from '@angular/common/locales/pt-PT';
|
||||
import localeTr from '@angular/common/locales/tr';
|
||||
import localeDe from '@angular/common/locales/de';
|
||||
import { RemainingTimeComponent } from './components/remaining-time/remaining-time.component';
|
||||
// add locale data for supported languages ['en-us', 'zh-cn', 'zh-tw', 'es-es', 'fr-fr', 'pt-br', 'tr-tr', 'de-de'];
|
||||
// en-us defaulted supported
|
||||
registerLocaleData(zh_cn, 'zh-cn');
|
||||
registerLocaleData(zh_tw, 'zh-tw');
|
||||
registerLocaleData(es, 'es-es');
|
||||
registerLocaleData(localeFr, 'fr-fr');
|
||||
registerLocaleData(localePt, 'pt-br');
|
||||
registerLocaleData(localeTr, 'tr-tr');
|
||||
registerLocaleData(localeDe, 'de-de');
|
||||
import locale_en from "@angular/common/locales/en";
|
||||
import locale_zh_CN from "@angular/common/locales/zh-Hans";
|
||||
import locale_zh_TW from "@angular/common/locales/zh-Hans-HK";
|
||||
import locale_es from "@angular/common/locales/es";
|
||||
import locale_fr from "@angular/common/locales/fr";
|
||||
import locale_pt from "@angular/common/locales/pt-PT";
|
||||
import locale_tr from "@angular/common/locales/tr";
|
||||
import locale_de from "@angular/common/locales/de";
|
||||
import { SupportedLanguage } from "./entities/shared.const";
|
||||
|
||||
const localesForSupportedLangs: Record<SupportedLanguage, unknown[]> = {
|
||||
"en-us": locale_en,
|
||||
"zh-cn": locale_zh_CN,
|
||||
"zh-tw": locale_zh_TW,
|
||||
"es-es": locale_es,
|
||||
"fr-fr": locale_fr,
|
||||
"pt-br": locale_pt,
|
||||
"tr-tr": locale_tr,
|
||||
"de-de": locale_de,
|
||||
};
|
||||
for (const [ lang, locale ] of Object.entries(localesForSupportedLangs)) {
|
||||
registerLocaleData(locale, lang);
|
||||
}
|
||||
|
||||
|
||||
// ClarityIcons is publicly accessible from the browser's window object.
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { MessageService } from '../components/global-message/message.service';
|
||||
import { AlertType, httpStatusCode } from "../entities/shared.const";
|
||||
import { AlertType, httpStatusCode, SupportedLanguage, LANGUAGES } from "../entities/shared.const";
|
||||
|
||||
/**
|
||||
* To check if form is empty
|
||||
@ -34,6 +34,13 @@ export const isEmptyForm = function (ngForm: NgForm): boolean {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* A type guard that checks if the given value is a supported language.
|
||||
*/
|
||||
export function isSupportedLanguage(x: unknown): x is SupportedLanguage {
|
||||
return Object.keys(LANGUAGES).some(lang => x === lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hanlde the 401 and 403 code
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user