diff --git a/src/portal/src/app/app.component.ts b/src/portal/src/app/app.component.ts
index c75d4553c..29e91306d 100644
--- a/src/portal/src/app/app.component.ts
+++ b/src/portal/src/app/app.component.ts
@@ -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
diff --git a/src/portal/src/app/shared/components/navigator/navigator.component.html b/src/portal/src/app/shared/components/navigator/navigator.component.html
index e9a813193..7dc5da3fa 100644
--- a/src/portal/src/app/shared/components/navigator/navigator.component.html
+++ b/src/portal/src/app/shared/components/navigator/navigator.component.html
@@ -20,14 +20,7 @@
- English
- 中文简体
- 中文繁體
- Español
- Français
- Português do Brasil
- Türkçe
- Deutsch
+ {{lang[1]}}
diff --git a/src/portal/src/app/shared/components/navigator/navigator.component.ts b/src/portal/src/app/shared/components/navigator/navigator.component.ts
index 200462937..101e9085a 100644
--- a/src/portal/src/app/shared/components/navigator/navigator.component.ts
+++ b/src/portal/src/app/shared/components/navigator/navigator.component.ts
@@ -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();
@Output() showDialogModalAction = new EventEmitter();
- 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
diff --git a/src/portal/src/app/shared/entities/shared.const.ts b/src/portal/src/app/shared/entities/shared.const.ts
index 86825fa22..b741ec7bb 100644
--- a/src/portal/src/app/shared/entities/shared.const.ts
+++ b/src/portal/src/app/shared/entities/shared.const.ts
@@ -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.
*/
diff --git a/src/portal/src/app/shared/pipes/harbor-datetime.pipe.ts b/src/portal/src/app/shared/pipes/harbor-datetime.pipe.ts
index a962f23bf..07ff551b0 100644
--- a/src/portal/src/app/shared/pipes/harbor-datetime.pipe.ts
+++ b/src/portal/src/app/shared/pipes/harbor-datetime.pipe.ts
@@ -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');
}
diff --git a/src/portal/src/app/shared/services/session.service.ts b/src/portal/src/app/shared/services/session.service.ts
index f5db71220..d4105845e 100644
--- a/src/portal/src/app/shared/services/session.service.ts
+++ b/src/portal/src/app/shared/services/session.service.ts
@@ -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';
diff --git a/src/portal/src/app/shared/shared.module.ts b/src/portal/src/app/shared/shared.module.ts
index c9c6b8120..d515195dd 100644
--- a/src/portal/src/app/shared/shared.module.ts
+++ b/src/portal/src/app/shared/shared.module.ts
@@ -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 = {
+ "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.
diff --git a/src/portal/src/app/shared/units/shared.utils.ts b/src/portal/src/app/shared/units/shared.utils.ts
index eea30898f..4545a6bca 100644
--- a/src/portal/src/app/shared/units/shared.utils.ts
+++ b/src/portal/src/app/shared/units/shared.utils.ts
@@ -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
*