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 *