未验证 提交 72c275c9 编写于 作者: A Amir Ajorloo 提交者: GitHub

Add change language option. (#5693)

* Add change language option. Move available languages to index.config. Make lang value in cookie the most important, then the env variable and the least important is the default browser header.

* Update zh translations based on helight suggestions.

* Refactor code and fix caching issue

* Small optimizations
Co-authored-by: NSebastian Florek <sebastian.florek@kubermatic.com>
上级 cb66bd27
......@@ -36,6 +36,17 @@ function build::frontend {
--prod \
--localize \
--outputPath=${FRONTEND_DIR}
# Avoid locale caching due to the same output file naming
# We'll add language code prefix to the generated main javascript file.
languages=($(ls ${FRONTEND_DIR}))
for language in "${languages[@]}"; do
localeDir=${FRONTEND_DIR}/${language}
filename=("$(find "${localeDir}" -name 'main.*.js' -exec basename {} \;)")
mv "${localeDir}/${filename}" "${localeDir}/${language}.${filename}"
sed -i "s/${filename}/${language}.${filename}/" "${localeDir}/index.html"
done
}
function build::backend {
......
......@@ -100,6 +100,7 @@
"extractCss": true,
"namedChunks": false,
"aot": true,
"localize": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
......
......@@ -5442,6 +5442,22 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="fe46ccaae902ce974e2441abe752399288298619" datatype="html">
<source>Language</source>
<target state="new">Sprache</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="170c19b70d40071916e39cad610ac12f269dd473" datatype="html">
<source>Change the language of the dashboard</source>
<target state="new">Ändern Sie die Sprache des Dashboards</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>
......@@ -5456,6 +5456,22 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="fe46ccaae902ce974e2441abe752399288298619" datatype="html">
<source>Language</source>
<target state="new">Langue</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="170c19b70d40071916e39cad610ac12f269dd473" datatype="html">
<source>Change the language of the dashboard</source>
<target state="new">Changer la langue du tableau de bord</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>
......@@ -5402,6 +5402,22 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="fe46ccaae902ce974e2441abe752399288298619" datatype="html">
<source>Language</source>
<target state="new">言語</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="170c19b70d40071916e39cad610ac12f269dd473" datatype="html">
<source>Change the language of the dashboard</source>
<target state="new">ダッシュボードの言語を変更する</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="dc935c1eb87651baf1b1b0e53119e6dec2db8862" datatype="html">
<source>
Shell in
......
......@@ -5514,6 +5514,22 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="fe46ccaae902ce974e2441abe752399288298619" datatype="html">
<source>Language</source>
<target state="new">언어</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="170c19b70d40071916e39cad610ac12f269dd473" datatype="html">
<source>Change the language of the dashboard</source>
<target state="new">대시 보드의 언어 변경</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="dc935c1eb87651baf1b1b0e53119e6dec2db8862" datatype="html">
<source>
Shell in
......
......@@ -3748,6 +3748,20 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="fe46ccaae902ce974e2441abe752399288298619" datatype="html">
<source>Language</source>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="170c19b70d40071916e39cad610ac12f269dd473" datatype="html">
<source>Change the language of the dashboard</source>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a" datatype="html">
<source>About</source>
<context-group purpose="location">
......
......@@ -5517,6 +5517,22 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="fe46ccaae902ce974e2441abe752399288298619" datatype="html">
<source>Language</source>
<target state="new">语言</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="170c19b70d40071916e39cad610ac12f269dd473" datatype="html">
<source>Change the language of the dashboard</source>
<target state="new">更改 dashboard 的语言</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="dc935c1eb87651baf1b1b0e53119e6dec2db8862" datatype="html">
<source>
Shell in
......
......@@ -5517,6 +5517,22 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="fe46ccaae902ce974e2441abe752399288298619" datatype="html">
<source>Language</source>
<target state="new">语言</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="170c19b70d40071916e39cad610ac12f269dd473" datatype="html">
<source>Change the language of the dashboard</source>
<target state="new">更改 dashboard 的语言</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="dc935c1eb87651baf1b1b0e53119e6dec2db8862" datatype="html">
<source>
Shell in
......
......@@ -5517,6 +5517,22 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="fe46ccaae902ce974e2441abe752399288298619" datatype="html">
<source>Language</source>
<target state="new">語言</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="170c19b70d40071916e39cad610ac12f269dd473" datatype="html">
<source>Change the language of the dashboard</source>
<target state="new">更改 dashboard 的語言</target>
<context-group purpose="location">
<context context-type="sourcefile">../src/app/frontend/settings/local/template.html</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="dc935c1eb87651baf1b1b0e53119e6dec2db8862" datatype="html">
<source>
Shell in
......
......@@ -89,13 +89,17 @@ func getAssetsDir() string {
// LocaleHandler serves different html versions based on the Accept-Language header.
func (handler *LocaleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.EscapedPath() == "/" || r.URL.EscapedPath() == "/index.html" {
// Do not store the html page in the cache. If the user is to click on 'switch language',
// we want a different index.html (for the right locale) to be served when the page refreshes.
w.Header().Add("Cache-Control", "no-store")
acceptLanguage := ""
cookie, err := r.Cookie("lang")
if err == nil {
acceptLanguage = cookie.Value
}
acceptLanguage := os.Getenv("ACCEPT_LANGUAGE")
if acceptLanguage == "" {
if len(acceptLanguage) == 0 {
acceptLanguage = os.Getenv("ACCEPT_LANGUAGE")
}
if len(acceptLanguage) == 0 {
acceptLanguage = r.Header.Get("Accept-Language")
}
......
......@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {MatSelect} from '@angular/material/select';
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
......@@ -20,15 +20,14 @@ import {NamespaceList} from '@api/backendapi';
import {Subject} from 'rxjs';
import {distinctUntilChanged, filter, startWith, switchMap, takeUntil} from 'rxjs/operators';
import {CONFIG} from '../../../index.config';
import {Config, CONFIG_DI_TOKEN} from '../../../index.config';
import {NAMESPACE_STATE_PARAM} from '../../params/params';
import {HistoryService} from '../../services/global/history';
import {NamespaceService} from '../../services/global/namespace';
import {NotificationSeverity, NotificationsService} from '../../services/global/notifications';
import {NotificationsService} from '../../services/global/notifications';
import {KdStateService} from '../../services/global/state';
import {EndpointManager, Resource} from '../../services/resource/endpoint';
import {ResourceService} from '../../services/resource/resource';
import {NamespaceChangeDialog} from './changedialog/dialog';
@Component({
......@@ -57,12 +56,13 @@ export class NamespaceSelectorComponent implements OnInit, OnDestroy {
private readonly dialog_: MatDialog,
private readonly kdState_: KdStateService,
private readonly notifications_: NotificationsService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _historyService: HistoryService
private readonly activatedRoute_: ActivatedRoute,
private readonly historyService_: HistoryService,
@Inject(CONFIG_DI_TOKEN) private readonly appConfig_: Config
) {}
ngOnInit(): void {
this._activatedRoute.queryParams.pipe(takeUntil(this.unsubscribe_)).subscribe(params => {
this.activatedRoute_.queryParams.pipe(takeUntil(this.unsubscribe_)).subscribe(params => {
const namespace = params.namespace;
if (!namespace) {
this.setDefaultQueryParams_();
......@@ -183,12 +183,12 @@ export class NamespaceSelectorComponent implements OnInit, OnDestroy {
if (confirmed) {
this.selectedNamespace = this._getCurrentResourceNamespaceParam();
this.router_.navigate([], {
relativeTo: this._activatedRoute,
relativeTo: this.activatedRoute_,
queryParams: {[NAMESPACE_STATE_PARAM]: this.selectedNamespace},
queryParamsHandling: 'merge',
});
} else {
this._historyService.goToPreviousState('overview');
this.historyService_.goToPreviousState('overview');
}
});
}
......@@ -205,7 +205,7 @@ export class NamespaceSelectorComponent implements OnInit, OnDestroy {
} else {
// Change only the namespace as currently not on details view.
this.router_.navigate([], {
relativeTo: this._activatedRoute,
relativeTo: this.activatedRoute_,
queryParams: {[NAMESPACE_STATE_PARAM]: namespace},
queryParamsHandling: 'merge',
});
......@@ -229,7 +229,7 @@ export class NamespaceSelectorComponent implements OnInit, OnDestroy {
}
private _getCurrentRoute(): ActivatedRoute {
let route = this._activatedRoute.root;
let route = this.activatedRoute_.root;
while (route && route.firstChild) {
route = route.firstChild;
}
......@@ -247,8 +247,8 @@ export class NamespaceSelectorComponent implements OnInit, OnDestroy {
}
setDefaultQueryParams_() {
this.router_.navigate([this._activatedRoute.snapshot.url], {
queryParams: {[NAMESPACE_STATE_PARAM]: CONFIG.defaultNamespace},
this.router_.navigate([this.activatedRoute_.snapshot.url], {
queryParams: {[NAMESPACE_STATE_PARAM]: this.appConfig_.defaultNamespace},
queryParamsHandling: 'merge',
});
}
......
......@@ -13,7 +13,7 @@
// limitations under the License.
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Inject, Injectable} from '@angular/core';
import {Router} from '@angular/router';
import {CookieService} from 'ngx-cookie-service';
import {of} from 'rxjs';
......@@ -21,7 +21,7 @@ import {Observable} from 'rxjs';
import {first, switchMap} from 'rxjs/operators';
import {AuthResponse, CsrfToken, LoginSpec, LoginStatus} from 'typings/backendapi';
import {CONFIG} from '../../../index.config';
import {Config, CONFIG_DI_TOKEN} from '../../../index.config';
import {K8SError} from '../../errors/errors';
import {CsrfTokenService} from './csrftoken';
......@@ -29,14 +29,13 @@ import {KdStateService} from './state';
@Injectable()
export class AuthService {
private readonly _config = CONFIG;
constructor(
private readonly cookies_: CookieService,
private readonly router_: Router,
private readonly http_: HttpClient,
private readonly csrfTokenService_: CsrfTokenService,
private readonly stateService_: KdStateService
private readonly stateService_: KdStateService,
@Inject(CONFIG_DI_TOKEN) private readonly config_: Config
) {
this.init_();
}
......@@ -55,17 +54,17 @@ export class AuthService {
}
if (this.isCurrentProtocolSecure_()) {
this.cookies_.set(this._config.authTokenCookieName, token, null, null, null, true, 'Strict');
this.cookies_.set(this.config_.authTokenCookieName, token, null, null, null, true, 'Strict');
return;
}
if (this.isCurrentDomainSecure_()) {
this.cookies_.set(this._config.authTokenCookieName, token, null, null, location.hostname, false, 'Strict');
this.cookies_.set(this.config_.authTokenCookieName, token, null, null, location.hostname, false, 'Strict');
}
}
private getTokenCookie_(): string {
return this.cookies_.get(this._config.authTokenCookieName) || '';
return this.cookies_.get(this.config_.authTokenCookieName) || '';
}
private isCurrentDomainSecure_(): boolean {
......@@ -77,8 +76,8 @@ export class AuthService {
}
removeAuthCookies(): void {
this.cookies_.delete(this._config.authTokenCookieName);
this.cookies_.delete(this._config.skipLoginPageCookieName);
this.cookies_.delete(this.config_.authTokenCookieName);
this.cookies_.delete(this.config_.skipLoginPageCookieName);
}
/**
......@@ -90,7 +89,7 @@ export class AuthService {
.pipe(
switchMap((csrfToken: CsrfToken) =>
this.http_.post<AuthResponse>('api/v1/login', loginSpec, {
headers: new HttpHeaders().set(this._config.csrfHeaderName, csrfToken.token),
headers: new HttpHeaders().set(this.config_.csrfHeaderName, csrfToken.token),
})
)
)
......@@ -126,7 +125,7 @@ export class AuthService {
'api/v1/token/refresh',
{jweToken: token},
{
headers: new HttpHeaders().set(this._config.csrfHeaderName, csrfToken.token),
headers: new HttpHeaders().set(this.config_.csrfHeaderName, csrfToken.token),
}
);
})
......@@ -161,7 +160,7 @@ export class AuthService {
skipLoginPage(skip: boolean): void {
this.removeAuthCookies();
this.cookies_.set(this._config.skipLoginPageCookieName, skip.toString(), null, null, null, false, 'Strict');
this.cookies_.set(this.config_.skipLoginPageCookieName, skip.toString(), null, null, null, false, 'Strict');
}
/**
......@@ -170,7 +169,7 @@ export class AuthService {
* In case cookie is not set login page will also be visible.
*/
isLoginPageEnabled(): boolean {
return !(this.cookies_.get(this._config.skipLoginPageCookieName) === 'true');
return !(this.cookies_.get(this.config_.skipLoginPageCookieName) === 'true');
}
/**
......
......@@ -13,22 +13,22 @@
// limitations under the License.
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Inject, Injectable} from '@angular/core';
import {CookieService} from 'ngx-cookie-service';
import {Observable} from 'rxjs';
import {CONFIG} from '../../../index.config';
import {Config, CONFIG_DI_TOKEN} from '../../../index.config';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private readonly cookies_: CookieService) {}
constructor(private readonly cookies_: CookieService, @Inject(CONFIG_DI_TOKEN) private readonly appConfig_: Config) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authCookie = this.cookies_.get(CONFIG.authTokenCookieName);
const authCookie = this.cookies_.get(this.appConfig_.authTokenCookieName);
// Filter requests made to our backend starting with 'api/v1' and append request header
// with token stored in a cookie.
if (req.url.startsWith('api/v1') && authCookie.length) {
const authReq = req.clone({
headers: req.headers.set(CONFIG.authTokenHeaderName, authCookie),
headers: req.headers.set(this.appConfig_.authTokenHeaderName, authCookie),
});
return next.handle(authReq);
......
......@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {EventEmitter, Injectable} from '@angular/core';
import {CONFIG} from '../../../index.config';
import {EventEmitter, Inject, Injectable} from '@angular/core';
import {Config, CONFIG_DI_TOKEN} from '../../../index.config';
@Injectable()
export class NamespaceService {
onNamespaceChangeEvent = new EventEmitter<string>();
constructor(@Inject(CONFIG_DI_TOKEN) private readonly appConfig_: Config) {}
/**
* Internal key for empty selection. To differentiate empty string from nulls.
*/
......@@ -37,7 +39,7 @@ export class NamespaceService {
}
current(): string {
return this.currentNamespace_ || CONFIG.defaultNamespace;
return this.currentNamespace_ || this.appConfig_.defaultNamespace;
}
getAllNamespacesKey(): string {
......@@ -45,7 +47,7 @@ export class NamespaceService {
}
getDefaultNamespace(): string {
return CONFIG.defaultNamespace;
return this.appConfig_.defaultNamespace;
}
isNamespaceValid(namespace: string): boolean {
......
......@@ -19,7 +19,7 @@ import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog
import {switchMap} from 'rxjs/operators';
import {AlertDialog, AlertDialogConfig} from '../../../../common/dialogs/alert/dialog';
import {CsrfTokenService} from '../../../../common/services/global/csrftoken';
import {CONFIG} from '../../../../index.config';
import {Config, CONFIG_DI_TOKEN} from '../../../../index.config';
export interface CreateNamespaceDialogMeta {
namespaces: string[];
......@@ -35,8 +35,6 @@ export interface CreateNamespaceDialogMeta {
export class CreateNamespaceDialog implements OnInit {
form: FormGroup;
private readonly config_ = CONFIG;
/**
* Max-length validation rule for namespace
*/
......@@ -52,7 +50,8 @@ export class CreateNamespaceDialog implements OnInit {
private readonly http_: HttpClient,
private readonly csrfToken_: CsrfTokenService,
private readonly matDialog_: MatDialog,
private readonly fb_: FormBuilder
private readonly fb_: FormBuilder,
@Inject(CONFIG_DI_TOKEN) private readonly appConfig_: Config
) {}
ngOnInit(): void {
......@@ -83,7 +82,7 @@ export class CreateNamespaceDialog implements OnInit {
this.http_.post<{valid: boolean}>(
'api/v1/namespace',
{...namespaceSpec},
{headers: new HttpHeaders().set(this.config_.csrfHeaderName, csrfToken.token)}
{headers: new HttpHeaders().set(this.appConfig_.csrfHeaderName, csrfToken.token)}
)
)
)
......
......@@ -19,7 +19,7 @@ import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog
import {switchMap} from 'rxjs/operators';
import {AlertDialog, AlertDialogConfig} from '../../../../common/dialogs/alert/dialog';
import {CsrfTokenService} from '../../../../common/services/global/csrftoken';
import {CONFIG} from '../../../../index.config';
import {Config, CONFIG_DI_TOKEN} from '../../../../index.config';
export interface CreateSecretDialogMeta {
namespace: string;
......@@ -32,8 +32,6 @@ export interface CreateSecretDialogMeta {
export class CreateSecretDialog implements OnInit {
form: FormGroup;
private readonly config_ = CONFIG;
/**
* Max-length validation rule for secretName.
*/
......@@ -55,7 +53,8 @@ export class CreateSecretDialog implements OnInit {
private readonly http_: HttpClient,
private readonly csrfToken_: CsrfTokenService,
private readonly matDialog_: MatDialog,
private readonly fb_: FormBuilder
private readonly fb_: FormBuilder,
@Inject(CONFIG_DI_TOKEN) private readonly appConfig_: Config
) {}
ngOnInit(): void {
......@@ -98,7 +97,7 @@ export class CreateSecretDialog implements OnInit {
this.http_.post<{valid: boolean}>(
'api/v1/secret/',
{...secretSpec},
{headers: new HttpHeaders().set(this.config_.csrfHeaderName, csrfToken.token)}
{headers: new HttpHeaders().set(this.appConfig_.csrfHeaderName, csrfToken.token)}
)
)
)
......
......@@ -14,9 +14,41 @@
import {InjectionToken} from '@angular/core';
import {MatTooltipDefaultOptions} from '@angular/material/tooltip';
import {LanguageConfig} from 'typings/frontendapi';
export const CONFIG_DI_TOKEN = new InjectionToken<Config>('kd.config');
const SUPPORTED_LANGUAGES: LanguageConfig[] = [
{
label: 'German',
value: 'de',
},
{
label: 'English',
value: 'en',
},
{
label: 'Japanese',
value: 'ja',
},
{
label: 'Korean',
value: 'ko',
},
{
label: 'Chinese Simplified',
value: 'zh-Hans',
},
{
label: 'Chinese Traditional',
value: 'zh-Hant',
},
{
label: 'Chinese Traditional Hong Kong',
value: 'zh-Hant-HK',
},
];
export interface Config {
authTokenCookieName: string;
skipLoginPageCookieName: string;
......@@ -24,6 +56,9 @@ export interface Config {
authTokenHeaderName: string;
defaultNamespace: string;
authModeCookieName: string;
supportedLanguages: LanguageConfig[];
defaultLanguage: string;
languageCookieName: string;
}
export const CONFIG: Config = {
......@@ -33,6 +68,9 @@ export const CONFIG: Config = {
skipLoginPageCookieName: 'skipLoginPage',
defaultNamespace: 'default',
authModeCookieName: 'authMode',
supportedLanguages: SUPPORTED_LANGUAGES,
defaultLanguage: 'en',
languageCookieName: 'lang',
};
// Override default material tooltip values.
......
......@@ -12,21 +12,47 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Component, OnInit} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {Component, Inject, OnInit, ViewChild} from '@angular/core';
import {MatSelect} from '@angular/material/select';
import {LocalSettings} from '@api/backendapi';
import {LanguageConfig} from '@api/frontendapi';
import {CookieService} from 'ngx-cookie-service';
import {LocalSettingsService} from '../../common/services/global/localsettings';
import {environment} from '../../environments/environment';
import {Config, CONFIG_DI_TOKEN} from '../../index.config';
@Component({selector: 'kd-local-settings', templateUrl: './template.html'})
export class LocalSettingsComponent implements OnInit {
settings: LocalSettings = {} as LocalSettings;
languages: LanguageConfig[] = [];
selectedLanguage: string;
constructor(private readonly settings_: LocalSettingsService) {}
@ViewChild(MatSelect, {static: true}) private readonly select_: MatSelect;
constructor(
private readonly settings_: LocalSettingsService,
private readonly cookies_: CookieService,
@Inject(DOCUMENT) private readonly document_: Document,
@Inject(CONFIG_DI_TOKEN) private readonly appConfig_: Config
) {}
ngOnInit(): void {
this.languages = this.appConfig_.supportedLanguages;
this.settings = this.settings_.get();
this.selectedLanguage = this.cookies_.get(this.appConfig_.languageCookieName) || this.appConfig_.defaultLanguage;
}
onThemeChange(): void {
this.settings_.handleThemeChange(this.settings.isThemeDark);
}
onLanguageSelected(selectedLanguage: string) {
this.cookies_.set(this.appConfig_.languageCookieName, selectedLanguage);
this.document_.location.reload();
}
isProdMode(): boolean {
return environment.production;
}
}
......@@ -33,5 +33,20 @@ limitations under the License.
(change)="onThemeChange()"></mat-slide-toggle>
</div>
</kd-settings-entry>
<kd-settings-entry *ngIf="isProdMode()"
key="Language"
i18n-key
desc="Change the language of the dashboard"
i18n-desc>
<mat-form-field fxFlex>
<mat-select (ngModelChange)="onLanguageSelected($event)"
[(ngModel)]="selectedLanguage">
<mat-option *ngFor="let language of languages"
[value]="language.value">
{{language.label}}
</mat-option>
</mat-select>
</mat-form-field>
</kd-settings-entry>
</div>
</kd-card>
......@@ -140,3 +140,8 @@ export interface ViewportMetadata {
target: HTMLElement;
visible: boolean;
}
export interface LanguageConfig {
value: string;
label: string;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册