mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-25 12:15:18 +01:00
[PM-6511] New i18n for angular (#8122)
* Use state provider to store preferred language * migrate preferred language * Use new i18n provider to get LOCAL_ID * Fix preloaded english i18n This is a mock service that forces english translations, it doesn't need the i18n interface that allows changing of locales. * PR improvements * Fixup merge
This commit is contained in:
parent
c10a59b019
commit
f4150ffda6
@ -195,12 +195,12 @@ import { BrowserStateService as StateServiceAbstraction } from "../platform/serv
|
|||||||
import { BrowserConfigService } from "../platform/services/browser-config.service";
|
import { BrowserConfigService } from "../platform/services/browser-config.service";
|
||||||
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
|
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
|
||||||
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
|
||||||
import { BrowserI18nService } from "../platform/services/browser-i18n.service";
|
|
||||||
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
|
||||||
import BrowserMessagingPrivateModeBackgroundService from "../platform/services/browser-messaging-private-mode-background.service";
|
import BrowserMessagingPrivateModeBackgroundService from "../platform/services/browser-messaging-private-mode-background.service";
|
||||||
import BrowserMessagingService from "../platform/services/browser-messaging.service";
|
import BrowserMessagingService from "../platform/services/browser-messaging.service";
|
||||||
import BrowserPlatformUtilsService from "../platform/services/browser-platform-utils.service";
|
import BrowserPlatformUtilsService from "../platform/services/browser-platform-utils.service";
|
||||||
import { BrowserStateService } from "../platform/services/browser-state.service";
|
import { BrowserStateService } from "../platform/services/browser-state.service";
|
||||||
|
import I18nService from "../platform/services/i18n.service";
|
||||||
import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service";
|
import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service";
|
||||||
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
||||||
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
||||||
@ -462,7 +462,7 @@ export default class MainBackground {
|
|||||||
},
|
},
|
||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
this.i18nService = new BrowserI18nService(BrowserApi.getUILanguage(), this.stateService);
|
this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider);
|
||||||
this.cryptoService = new BrowserCryptoService(
|
this.cryptoService = new BrowserCryptoService(
|
||||||
this.keyGenerationService,
|
this.keyGenerationService,
|
||||||
this.cryptoFunctionService,
|
this.cryptoFunctionService,
|
||||||
@ -969,7 +969,7 @@ export default class MainBackground {
|
|||||||
await this.stateService.init();
|
await this.stateService.init();
|
||||||
|
|
||||||
await this.vaultTimeoutService.init(true);
|
await this.vaultTimeoutService.init(true);
|
||||||
await (this.i18nService as BrowserI18nService).init();
|
await (this.i18nService as I18nService).init();
|
||||||
await (this.eventUploadService as EventUploadService).init(true);
|
await (this.eventUploadService as EventUploadService).init(true);
|
||||||
await this.runtimeBackground.init();
|
await this.runtimeBackground.init();
|
||||||
await this.notificationBackground.init();
|
await this.notificationBackground.init();
|
||||||
|
@ -4,6 +4,10 @@ import { I18nService as BaseI18nService } from "@bitwarden/common/platform/servi
|
|||||||
import I18nService from "../../services/i18n.service";
|
import I18nService from "../../services/i18n.service";
|
||||||
|
|
||||||
import { FactoryOptions, CachedServices, factory } from "./factory-options";
|
import { FactoryOptions, CachedServices, factory } from "./factory-options";
|
||||||
|
import {
|
||||||
|
GlobalStateProviderInitOptions,
|
||||||
|
globalStateProviderFactory,
|
||||||
|
} from "./global-state-provider.factory";
|
||||||
|
|
||||||
type I18nServiceFactoryOptions = FactoryOptions & {
|
type I18nServiceFactoryOptions = FactoryOptions & {
|
||||||
i18nServiceOptions: {
|
i18nServiceOptions: {
|
||||||
@ -11,7 +15,7 @@ type I18nServiceFactoryOptions = FactoryOptions & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type I18nServiceInitOptions = I18nServiceFactoryOptions;
|
export type I18nServiceInitOptions = I18nServiceFactoryOptions & GlobalStateProviderInitOptions;
|
||||||
|
|
||||||
export async function i18nServiceFactory(
|
export async function i18nServiceFactory(
|
||||||
cache: { i18nService?: AbstractI18nService } & CachedServices,
|
cache: { i18nService?: AbstractI18nService } & CachedServices,
|
||||||
@ -21,7 +25,11 @@ export async function i18nServiceFactory(
|
|||||||
cache,
|
cache,
|
||||||
"i18nService",
|
"i18nService",
|
||||||
opts,
|
opts,
|
||||||
() => new I18nService(opts.i18nServiceOptions.systemLanguage),
|
async () =>
|
||||||
|
new I18nService(
|
||||||
|
opts.i18nServiceOptions.systemLanguage,
|
||||||
|
await globalStateProviderFactory(cache, opts),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (!(service as BaseI18nService as any).inited) {
|
if (!(service as BaseI18nService as any).inited) {
|
||||||
await (service as BaseI18nService).init();
|
await (service as BaseI18nService).init();
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { ReplaySubject } from "rxjs";
|
|
||||||
|
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
|
|
||||||
import { browserSession, sessionSync } from "../decorators/session-sync-observable";
|
|
||||||
|
|
||||||
import I18nService from "./i18n.service";
|
|
||||||
|
|
||||||
@browserSession
|
|
||||||
export class BrowserI18nService extends I18nService {
|
|
||||||
@sessionSync({ initializer: (s: string) => s })
|
|
||||||
protected _locale: ReplaySubject<string>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
systemLanguage: string,
|
|
||||||
private stateService: StateService,
|
|
||||||
) {
|
|
||||||
super(systemLanguage);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,18 @@
|
|||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
export default class I18nService extends BaseI18nService {
|
export default class I18nService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string) {
|
constructor(systemLanguage: string, globalStateProvider: GlobalStateProvider) {
|
||||||
super(systemLanguage, null, async (formattedLocale: string) => {
|
super(
|
||||||
// Deprecated
|
systemLanguage,
|
||||||
const file = await fetch(this.localesDirectory + formattedLocale + "/messages.json");
|
null,
|
||||||
return await file.json();
|
async (formattedLocale: string) => {
|
||||||
});
|
// Deprecated
|
||||||
|
const file = await fetch(this.localesDirectory + formattedLocale + "/messages.json");
|
||||||
|
return await file.json();
|
||||||
|
},
|
||||||
|
globalStateProvider,
|
||||||
|
);
|
||||||
|
|
||||||
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
||||||
this.supportedTranslationLocales = [
|
this.supportedTranslationLocales = [
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { APP_INITIALIZER, LOCALE_ID, NgModule, NgZone } from "@angular/core";
|
import { APP_INITIALIZER, NgModule, NgZone } from "@angular/core";
|
||||||
|
|
||||||
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
|
import { UnauthGuard as BaseUnauthGuardService } from "@bitwarden/angular/auth/guards";
|
||||||
import { ThemingService } from "@bitwarden/angular/platform/services/theming/theming.service";
|
import { ThemingService } from "@bitwarden/angular/platform/services/theming/theming.service";
|
||||||
@ -60,10 +60,7 @@ import {
|
|||||||
} from "@bitwarden/common/platform/abstractions/log.service";
|
} from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import {
|
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
StateService as BaseStateServiceAbstraction,
|
|
||||||
StateService,
|
|
||||||
} from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
AbstractMemoryStorageService,
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
@ -75,7 +72,11 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l
|
|||||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||||
import { DerivedStateProvider, StateProvider } from "@bitwarden/common/platform/state";
|
import {
|
||||||
|
DerivedStateProvider,
|
||||||
|
GlobalStateProvider,
|
||||||
|
StateProvider,
|
||||||
|
} from "@bitwarden/common/platform/state";
|
||||||
import { SearchService } from "@bitwarden/common/services/search.service";
|
import { SearchService } from "@bitwarden/common/services/search.service";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username";
|
import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username";
|
||||||
@ -112,11 +113,11 @@ import { BrowserStateService as StateServiceAbstraction } from "../../platform/s
|
|||||||
import { BrowserConfigService } from "../../platform/services/browser-config.service";
|
import { BrowserConfigService } from "../../platform/services/browser-config.service";
|
||||||
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
|
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
|
||||||
import { BrowserFileDownloadService } from "../../platform/services/browser-file-download.service";
|
import { BrowserFileDownloadService } from "../../platform/services/browser-file-download.service";
|
||||||
import { BrowserI18nService } from "../../platform/services/browser-i18n.service";
|
|
||||||
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service";
|
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service";
|
||||||
import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service";
|
import BrowserMessagingPrivateModePopupService from "../../platform/services/browser-messaging-private-mode-popup.service";
|
||||||
import BrowserMessagingService from "../../platform/services/browser-messaging.service";
|
import BrowserMessagingService from "../../platform/services/browser-messaging.service";
|
||||||
import { BrowserStateService } from "../../platform/services/browser-state.service";
|
import { BrowserStateService } from "../../platform/services/browser-state.service";
|
||||||
|
import I18nService from "../../platform/services/i18n.service";
|
||||||
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
||||||
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
||||||
import { BrowserSendService } from "../../services/browser-send.service";
|
import { BrowserSendService } from "../../services/browser-send.service";
|
||||||
@ -158,11 +159,6 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
DebounceNavigationService,
|
DebounceNavigationService,
|
||||||
DialogService,
|
DialogService,
|
||||||
PopupCloseWarningService,
|
PopupCloseWarningService,
|
||||||
{
|
|
||||||
provide: LOCALE_ID,
|
|
||||||
useFactory: () => getBgService<I18nServiceAbstraction>("i18nService")().translationLocale,
|
|
||||||
deps: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: (initService: InitService) => initService.init(),
|
useFactory: (initService: InitService) => initService.init(),
|
||||||
@ -254,10 +250,10 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
{ provide: TokenService, useFactory: getBgService<TokenService>("tokenService"), deps: [] },
|
{ provide: TokenService, useFactory: getBgService<TokenService>("tokenService"), deps: [] },
|
||||||
{
|
{
|
||||||
provide: I18nServiceAbstraction,
|
provide: I18nServiceAbstraction,
|
||||||
useFactory: (stateService: BrowserStateService) => {
|
useFactory: (globalStateProvider: GlobalStateProvider) => {
|
||||||
return new BrowserI18nService(BrowserApi.getUILanguage(), stateService);
|
return new I18nService(BrowserApi.getUILanguage(), globalStateProvider);
|
||||||
},
|
},
|
||||||
deps: [StateService],
|
deps: [GlobalStateProvider],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CryptoService,
|
provide: CryptoService,
|
||||||
|
@ -231,7 +231,6 @@ export class Main {
|
|||||||
p = path.join(process.env.HOME, ".config/Bitwarden CLI");
|
p = path.join(process.env.HOME, ".config/Bitwarden CLI");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.i18nService = new I18nService("en", "./locales");
|
|
||||||
this.platformUtilsService = new CliPlatformUtilsService(ClientType.Cli, packageJson);
|
this.platformUtilsService = new CliPlatformUtilsService(ClientType.Cli, packageJson);
|
||||||
this.logService = new ConsoleLogService(
|
this.logService = new ConsoleLogService(
|
||||||
this.platformUtilsService.isDev(),
|
this.platformUtilsService.isDev(),
|
||||||
@ -270,6 +269,8 @@ export class Main {
|
|||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.i18nService = new I18nService("en", "./locales", this.globalStateProvider);
|
||||||
|
|
||||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||||
storageServiceProvider,
|
storageServiceProvider,
|
||||||
stateEventRegistrarService,
|
stateEventRegistrarService,
|
||||||
@ -665,8 +666,7 @@ export class Main {
|
|||||||
await this.stateService.init();
|
await this.stateService.init();
|
||||||
this.containerService.attachToGlobal(global);
|
this.containerService.attachToGlobal(global);
|
||||||
await this.environmentService.setUrlsFromStorage();
|
await this.environmentService.setUrlsFromStorage();
|
||||||
const locale = await this.stateService.getLocale();
|
await this.i18nService.init();
|
||||||
await this.i18nService.init(locale);
|
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
this.configService.init();
|
this.configService.init();
|
||||||
|
|
||||||
|
@ -2,18 +2,28 @@ import * as fs from "fs";
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
export class I18nService extends BaseI18nService {
|
export class I18nService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(
|
||||||
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
|
systemLanguage: string,
|
||||||
const filePath = path.join(
|
localesDirectory: string,
|
||||||
__dirname,
|
globalStateProvider: GlobalStateProvider,
|
||||||
this.localesDirectory + "/" + formattedLocale + "/messages.json",
|
) {
|
||||||
);
|
super(
|
||||||
const localesJson = fs.readFileSync(filePath, "utf8");
|
systemLanguage,
|
||||||
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
|
localesDirectory,
|
||||||
return Promise.resolve(locales);
|
(formattedLocale: string) => {
|
||||||
});
|
const filePath = path.join(
|
||||||
|
__dirname,
|
||||||
|
this.localesDirectory + "/" + formattedLocale + "/messages.json",
|
||||||
|
);
|
||||||
|
const localesJson = fs.readFileSync(filePath, "utf8");
|
||||||
|
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
|
||||||
|
return Promise.resolve(locales);
|
||||||
|
},
|
||||||
|
globalStateProvider,
|
||||||
|
);
|
||||||
|
|
||||||
this.supportedTranslationLocales = ["en"];
|
this.supportedTranslationLocales = ["en"];
|
||||||
}
|
}
|
||||||
|
@ -264,7 +264,7 @@ export class SettingsComponent implements OnInit {
|
|||||||
enableDuckDuckGoBrowserIntegration:
|
enableDuckDuckGoBrowserIntegration:
|
||||||
await this.stateService.getEnableDuckDuckGoBrowserIntegration(),
|
await this.stateService.getEnableDuckDuckGoBrowserIntegration(),
|
||||||
theme: await this.stateService.getTheme(),
|
theme: await this.stateService.getTheme(),
|
||||||
locale: (await this.stateService.getLocale()) ?? null,
|
locale: await firstValueFrom(this.i18nService.locale$),
|
||||||
};
|
};
|
||||||
this.form.setValue(initialValues, { emitEvent: false });
|
this.form.setValue(initialValues, { emitEvent: false });
|
||||||
|
|
||||||
@ -553,7 +553,7 @@ export class SettingsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveLocale() {
|
async saveLocale() {
|
||||||
await this.stateService.setLocale(this.form.value.locale);
|
await this.i18nService.setLocale(this.form.value.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveTheme() {
|
async saveTheme() {
|
||||||
|
@ -52,8 +52,7 @@ export class InitService {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.syncService.fullSync(true);
|
this.syncService.fullSync(true);
|
||||||
await this.vaultTimeoutService.init(true);
|
await this.vaultTimeoutService.init(true);
|
||||||
const locale = await this.stateService.getLocale();
|
await (this.i18nService as I18nRendererService).init();
|
||||||
await (this.i18nService as I18nRendererService).init(locale);
|
|
||||||
(this.eventUploadService as EventUploadService).init(true);
|
(this.eventUploadService as EventUploadService).init(true);
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
setTimeout(() => this.notificationsService.init(), 3000);
|
setTimeout(() => this.notificationsService.init(), 3000);
|
||||||
|
@ -43,7 +43,7 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta
|
|||||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
||||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state";
|
||||||
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
|
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
|
||||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
@ -104,7 +104,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
|||||||
{
|
{
|
||||||
provide: I18nServiceAbstraction,
|
provide: I18nServiceAbstraction,
|
||||||
useClass: I18nRendererService,
|
useClass: I18nRendererService,
|
||||||
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY],
|
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY, GlobalStateProvider],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: MessagingServiceAbstraction,
|
provide: MessagingServiceAbstraction,
|
||||||
|
@ -97,7 +97,6 @@ export class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logService = new ElectronLogMainService(null, app.getPath("userData"));
|
this.logService = new ElectronLogMainService(null, app.getPath("userData"));
|
||||||
this.i18nService = new I18nMainService("en", "./locales/");
|
|
||||||
|
|
||||||
const storageDefaults: any = {};
|
const storageDefaults: any = {};
|
||||||
// Default vault timeout to "on restart", and action to "lock"
|
// Default vault timeout to "on restart", and action to "lock"
|
||||||
@ -112,6 +111,8 @@ export class Main {
|
|||||||
);
|
);
|
||||||
const globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
const globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||||
|
|
||||||
|
this.i18nService = new I18nMainService("en", "./locales/", globalStateProvider);
|
||||||
|
|
||||||
const accountService = new AccountServiceImplementation(
|
const accountService = new AccountServiceImplementation(
|
||||||
new NoopMessagingService(),
|
new NoopMessagingService(),
|
||||||
this.logService,
|
this.logService,
|
||||||
@ -218,8 +219,7 @@ export class Main {
|
|||||||
this.migrationRunner.run().then(
|
this.migrationRunner.run().then(
|
||||||
async () => {
|
async () => {
|
||||||
await this.windowMain.init();
|
await this.windowMain.init();
|
||||||
const locale = await this.stateService.getLocale();
|
await this.i18nService.init();
|
||||||
await this.i18nService.init(locale != null ? locale : app.getLocale());
|
|
||||||
this.messagingMain.init();
|
this.messagingMain.init();
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
import { ipcMain } from "electron";
|
import { app, ipcMain } from "electron";
|
||||||
|
|
||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
export class I18nMainService extends BaseI18nService {
|
export class I18nMainService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(
|
||||||
super(systemLanguage, localesDirectory, (formattedLocale: string) =>
|
systemLanguage: string,
|
||||||
this.readLanguageFile(formattedLocale),
|
localesDirectory: string,
|
||||||
|
globalStateProvider: GlobalStateProvider,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
systemLanguage,
|
||||||
|
localesDirectory,
|
||||||
|
(formattedLocale: string) => this.readLanguageFile(formattedLocale),
|
||||||
|
globalStateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle("getLanguageFile", async (event, formattedLocale: string) =>
|
ipcMain.handle("getLanguageFile", async (event, formattedLocale: string) =>
|
||||||
@ -76,6 +84,12 @@ export class I18nMainService extends BaseI18nService {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async init(): Promise<void> {
|
||||||
|
// Set system language to electron language
|
||||||
|
this.systemLanguage = app.getLocale();
|
||||||
|
await super.init();
|
||||||
|
}
|
||||||
|
|
||||||
private readLanguageFile(formattedLocale: string): Promise<any> {
|
private readLanguageFile(formattedLocale: string): Promise<any> {
|
||||||
// Check that the provided locale only contains letters and dashes and underscores to avoid possible path traversal
|
// Check that the provided locale only contains letters and dashes and underscores to avoid possible path traversal
|
||||||
if (!/^[a-zA-Z_-]+$/.test(formattedLocale)) {
|
if (!/^[a-zA-Z_-]+$/.test(formattedLocale)) {
|
||||||
|
@ -1,10 +1,20 @@
|
|||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
export class I18nRendererService extends BaseI18nService {
|
export class I18nRendererService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(
|
||||||
super(systemLanguage, localesDirectory, (formattedLocale: string) => {
|
systemLanguage: string,
|
||||||
return ipc.platform.getLanguageFile(formattedLocale);
|
localesDirectory: string,
|
||||||
});
|
globalStateProvider: GlobalStateProvider,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
systemLanguage,
|
||||||
|
localesDirectory,
|
||||||
|
(formattedLocale: string) => {
|
||||||
|
return ipc.platform.getLanguageFile(formattedLocale);
|
||||||
|
},
|
||||||
|
globalStateProvider,
|
||||||
|
);
|
||||||
|
|
||||||
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
||||||
this.supportedTranslationLocales = [
|
this.supportedTranslationLocales = [
|
||||||
|
@ -29,6 +29,7 @@ import { MigrationBuilderService } from "@bitwarden/common/platform/services/mig
|
|||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
/* eslint-disable import/no-restricted-paths -- Implementation for memory storage */
|
/* eslint-disable import/no-restricted-paths -- Implementation for memory storage */
|
||||||
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||||
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
|
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
|
||||||
|
|
||||||
@ -74,7 +75,7 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
|
|||||||
{
|
{
|
||||||
provide: I18nServiceAbstraction,
|
provide: I18nServiceAbstraction,
|
||||||
useClass: I18nService,
|
useClass: I18nService,
|
||||||
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY],
|
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY, GlobalStateProvider],
|
||||||
},
|
},
|
||||||
{ provide: AbstractStorageService, useClass: HtmlStorageService },
|
{ provide: AbstractStorageService, useClass: HtmlStorageService },
|
||||||
{
|
{
|
||||||
|
@ -1,20 +1,30 @@
|
|||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
||||||
|
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
import { SupportedTranslationLocales } from "../../translation-constants";
|
import { SupportedTranslationLocales } from "../../translation-constants";
|
||||||
|
|
||||||
export class I18nService extends BaseI18nService {
|
export class I18nService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(
|
||||||
super(systemLanguage || "en-US", localesDirectory, async (formattedLocale: string) => {
|
systemLanguage: string,
|
||||||
const filePath =
|
localesDirectory: string,
|
||||||
this.localesDirectory +
|
globalStateProvider: GlobalStateProvider,
|
||||||
"/" +
|
) {
|
||||||
formattedLocale +
|
super(
|
||||||
"/messages.json?cache=" +
|
systemLanguage || "en-US",
|
||||||
process.env.CACHE_TAG;
|
localesDirectory,
|
||||||
const localesResult = await fetch(filePath);
|
async (formattedLocale: string) => {
|
||||||
const locales = await localesResult.json();
|
const filePath =
|
||||||
return locales;
|
this.localesDirectory +
|
||||||
});
|
"/" +
|
||||||
|
formattedLocale +
|
||||||
|
"/messages.json?cache=" +
|
||||||
|
process.env.CACHE_TAG;
|
||||||
|
const localesResult = await fetch(filePath);
|
||||||
|
const locales = await localesResult.json();
|
||||||
|
return locales;
|
||||||
|
},
|
||||||
|
globalStateProvider,
|
||||||
|
);
|
||||||
|
|
||||||
this.supportedTranslationLocales = SupportedTranslationLocales;
|
this.supportedTranslationLocales = SupportedTranslationLocales;
|
||||||
}
|
}
|
||||||
|
@ -50,8 +50,7 @@ export class InitService {
|
|||||||
|
|
||||||
setTimeout(() => this.notificationsService.init(), 3000);
|
setTimeout(() => this.notificationsService.init(), 3000);
|
||||||
await this.vaultTimeoutService.init(true);
|
await this.vaultTimeoutService.init(true);
|
||||||
const locale = await this.stateService.getLocale();
|
await (this.i18nService as I18nService).init();
|
||||||
await (this.i18nService as I18nService).init(locale);
|
|
||||||
(this.eventUploadService as EventUploadService).init(true);
|
(this.eventUploadService as EventUploadService).init(true);
|
||||||
this.twoFactorService.init();
|
this.twoFactorService.init();
|
||||||
const htmlEl = this.win.document.documentElement;
|
const htmlEl = this.win.document.documentElement;
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import { APP_INITIALIZER, NgModule } from "@angular/core";
|
import { APP_INITIALIZER, NgModule } from "@angular/core";
|
||||||
|
import { Observable, of } from "rxjs";
|
||||||
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
|
import { TranslationService } from "@bitwarden/common/platform/services/translation.service";
|
||||||
|
|
||||||
import eng from "../../../locales/en/messages.json";
|
import eng from "../../../locales/en/messages.json";
|
||||||
|
|
||||||
class PreloadedEnglishI18nService extends BaseI18nService {
|
class PreloadedEnglishI18nService extends TranslationService implements I18nService {
|
||||||
|
translationLocale = "en";
|
||||||
|
locale$: Observable<string> = of("en");
|
||||||
constructor() {
|
constructor() {
|
||||||
super("en", "", () => {
|
super("en", "", () => {
|
||||||
return Promise.resolve(eng);
|
return Promise.resolve(eng);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLocale(): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function i18nInitializer(i18nService: I18nService): () => Promise<void> {
|
function i18nInitializer(i18nService: I18nService): () => Promise<void> {
|
||||||
|
@ -142,7 +142,7 @@ export class PreferencesComponent implements OnInit {
|
|||||||
),
|
),
|
||||||
enableFavicons: !(await this.settingsService.getDisableFavicon()),
|
enableFavicons: !(await this.settingsService.getDisableFavicon()),
|
||||||
theme: await this.stateService.getTheme(),
|
theme: await this.stateService.getTheme(),
|
||||||
locale: (await this.stateService.getLocale()) ?? null,
|
locale: (await firstValueFrom(this.i18nService.locale$)) ?? null,
|
||||||
};
|
};
|
||||||
this.startingLocale = initialFormValues.locale;
|
this.startingLocale = initialFormValues.locale;
|
||||||
this.startingTheme = initialFormValues.theme;
|
this.startingTheme = initialFormValues.theme;
|
||||||
@ -169,7 +169,7 @@ export class PreferencesComponent implements OnInit {
|
|||||||
await this.themingService.updateConfiguredTheme(values.theme);
|
await this.themingService.updateConfiguredTheme(values.theme);
|
||||||
this.startingTheme = values.theme;
|
this.startingTheme = values.theme;
|
||||||
}
|
}
|
||||||
await this.stateService.setLocale(values.locale);
|
await this.i18nService.setLocale(values.locale);
|
||||||
if (values.locale !== this.startingLocale) {
|
if (values.locale !== this.startingLocale) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,4 +4,5 @@ import { TranslationService } from "./translation.service";
|
|||||||
|
|
||||||
export abstract class I18nService extends TranslationService {
|
export abstract class I18nService extends TranslationService {
|
||||||
locale$: Observable<string>;
|
locale$: Observable<string>;
|
||||||
|
abstract setLocale(locale: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,36 @@
|
|||||||
import { Observable, ReplaySubject } from "rxjs";
|
import { Observable, firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
import { I18nService as I18nServiceAbstraction } from "../abstractions/i18n.service";
|
import { I18nService as I18nServiceAbstraction } from "../abstractions/i18n.service";
|
||||||
|
import { GlobalState, GlobalStateProvider, KeyDefinition, TRANSLATION_DISK } from "../state";
|
||||||
|
|
||||||
import { TranslationService } from "./translation.service";
|
import { TranslationService } from "./translation.service";
|
||||||
|
|
||||||
|
const LOCALE_KEY = new KeyDefinition<string>(TRANSLATION_DISK, "locale", {
|
||||||
|
deserializer: (value) => value,
|
||||||
|
});
|
||||||
|
|
||||||
export class I18nService extends TranslationService implements I18nServiceAbstraction {
|
export class I18nService extends TranslationService implements I18nServiceAbstraction {
|
||||||
protected _locale = new ReplaySubject<string>(1);
|
translationLocale: string;
|
||||||
private _translationLocale: string;
|
protected translationLocaleState: GlobalState<string>;
|
||||||
locale$: Observable<string> = this._locale.asObservable();
|
locale$: Observable<string>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected systemLanguage: string,
|
protected systemLanguage: string,
|
||||||
protected localesDirectory: string,
|
protected localesDirectory: string,
|
||||||
protected getLocalesJson: (formattedLocale: string) => Promise<any>,
|
protected getLocalesJson: (formattedLocale: string) => Promise<any>,
|
||||||
|
globalStateProvider: GlobalStateProvider,
|
||||||
) {
|
) {
|
||||||
super(systemLanguage, localesDirectory, getLocalesJson);
|
super(systemLanguage, localesDirectory, getLocalesJson);
|
||||||
|
this.translationLocaleState = globalStateProvider.get(LOCALE_KEY);
|
||||||
|
this.locale$ = this.translationLocaleState.state$.pipe(map((locale) => locale ?? null));
|
||||||
}
|
}
|
||||||
|
|
||||||
get translationLocale(): string {
|
async setLocale(locale: string): Promise<void> {
|
||||||
return this._translationLocale;
|
await this.translationLocaleState.update(() => locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
set translationLocale(locale: string) {
|
override async init() {
|
||||||
this._translationLocale = locale;
|
const storedLocale = await firstValueFrom(this.translationLocaleState.state$);
|
||||||
this._locale.next(locale);
|
await super.init(storedLocale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ export const CLEAR_EVENT_DISK = new StateDefinition("clearEvent", "disk");
|
|||||||
export const CRYPTO_DISK = new StateDefinition("crypto", "disk");
|
export const CRYPTO_DISK = new StateDefinition("crypto", "disk");
|
||||||
export const CRYPTO_MEMORY = new StateDefinition("crypto", "memory");
|
export const CRYPTO_MEMORY = new StateDefinition("crypto", "memory");
|
||||||
export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk");
|
export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk");
|
||||||
|
export const TRANSLATION_DISK = new StateDefinition("translation", "disk");
|
||||||
|
|
||||||
// Secrets Manager
|
// Secrets Manager
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import { UserNotificationSettingsKeyMigrator } from "./migrations/29-move-user-n
|
|||||||
import { FixPremiumMigrator } from "./migrations/3-fix-premium";
|
import { FixPremiumMigrator } from "./migrations/3-fix-premium";
|
||||||
import { PolicyMigrator } from "./migrations/30-move-policy-state-to-state-provider";
|
import { PolicyMigrator } from "./migrations/30-move-policy-state-to-state-provider";
|
||||||
import { EnableContextMenuMigrator } from "./migrations/31-move-enable-context-menu-to-autofill-settings-state-provider";
|
import { EnableContextMenuMigrator } from "./migrations/31-move-enable-context-menu-to-autofill-settings-state-provider";
|
||||||
|
import { PreferredLanguageMigrator } from "./migrations/32-move-preferred-language";
|
||||||
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
|
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
|
||||||
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
|
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
|
||||||
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
|
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
|
||||||
@ -36,7 +37,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
|
|||||||
import { MinVersionMigrator } from "./migrations/min-version";
|
import { MinVersionMigrator } from "./migrations/min-version";
|
||||||
|
|
||||||
export const MIN_VERSION = 2;
|
export const MIN_VERSION = 2;
|
||||||
export const CURRENT_VERSION = 31;
|
export const CURRENT_VERSION = 32;
|
||||||
export type MinVersion = typeof MIN_VERSION;
|
export type MinVersion = typeof MIN_VERSION;
|
||||||
|
|
||||||
export function createMigrationBuilder() {
|
export function createMigrationBuilder() {
|
||||||
@ -70,7 +71,8 @@ export function createMigrationBuilder() {
|
|||||||
.with(MoveBiometricUnlockToStateProviders, 27, 28)
|
.with(MoveBiometricUnlockToStateProviders, 27, 28)
|
||||||
.with(UserNotificationSettingsKeyMigrator, 28, 29)
|
.with(UserNotificationSettingsKeyMigrator, 28, 29)
|
||||||
.with(PolicyMigrator, 29, 30)
|
.with(PolicyMigrator, 29, 30)
|
||||||
.with(EnableContextMenuMigrator, 30, CURRENT_VERSION);
|
.with(EnableContextMenuMigrator, 30, 31)
|
||||||
|
.with(PreferredLanguageMigrator, 31, CURRENT_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function currentVersion(
|
export async function currentVersion(
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
import { MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { MigrationHelper } from "../migration-helper";
|
||||||
|
import { mockMigrationHelper } from "../migration-helper.spec";
|
||||||
|
|
||||||
|
import { LOCALE_KEY, PreferredLanguageMigrator } from "./32-move-preferred-language";
|
||||||
|
|
||||||
|
function exampleJSON() {
|
||||||
|
return {
|
||||||
|
global: {
|
||||||
|
locale: "en",
|
||||||
|
otherStuff: "otherStuff1",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff2",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function rollbackJSON() {
|
||||||
|
return {
|
||||||
|
global_translation_locale: "en",
|
||||||
|
global: {
|
||||||
|
otherStuff: "otherStuff1",
|
||||||
|
},
|
||||||
|
otherStuff: "otherStuff2",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("PreferredLanguageMigrator", () => {
|
||||||
|
let helper: MockProxy<MigrationHelper>;
|
||||||
|
let sut: PreferredLanguageMigrator;
|
||||||
|
|
||||||
|
describe("migrate", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
helper = mockMigrationHelper(exampleJSON(), 31);
|
||||||
|
sut = new PreferredLanguageMigrator(31, 32);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should remove locale setting from global", async () => {
|
||||||
|
await sut.migrate(helper);
|
||||||
|
expect(helper.set).toHaveBeenCalledTimes(1);
|
||||||
|
expect(helper.set).toHaveBeenCalledWith("global", {
|
||||||
|
otherStuff: "otherStuff1",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set locale for global state provider", async () => {
|
||||||
|
await sut.migrate(helper);
|
||||||
|
|
||||||
|
expect(helper.setToGlobal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(helper.setToGlobal).toHaveBeenCalledWith(LOCALE_KEY, "en");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("rollback", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
helper = mockMigrationHelper(rollbackJSON(), 32);
|
||||||
|
sut = new PreferredLanguageMigrator(31, 32);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should null out new values for global", async () => {
|
||||||
|
await sut.rollback(helper);
|
||||||
|
|
||||||
|
expect(helper.setToGlobal).toHaveBeenCalledTimes(1);
|
||||||
|
expect(helper.setToGlobal).toHaveBeenCalledWith(LOCALE_KEY, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add locale back to the old global object", async () => {
|
||||||
|
await sut.rollback(helper);
|
||||||
|
|
||||||
|
expect(helper.set).toHaveBeenCalledTimes(1);
|
||||||
|
expect(helper.set).toHaveBeenCalledWith("global", {
|
||||||
|
locale: "en",
|
||||||
|
otherStuff: "otherStuff1",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,39 @@
|
|||||||
|
import { MigrationHelper } from "../migration-helper";
|
||||||
|
import { Migrator } from "../migrator";
|
||||||
|
|
||||||
|
type ExpectedGlobal = {
|
||||||
|
locale?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LOCALE_KEY = {
|
||||||
|
key: "locale",
|
||||||
|
stateDefinition: {
|
||||||
|
name: "translation",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export class PreferredLanguageMigrator extends Migrator<31, 32> {
|
||||||
|
async migrate(helper: MigrationHelper): Promise<void> {
|
||||||
|
// global state
|
||||||
|
const global = await helper.get<ExpectedGlobal>("global");
|
||||||
|
if (!global?.locale) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await helper.setToGlobal(LOCALE_KEY, global.locale);
|
||||||
|
delete global.locale;
|
||||||
|
await helper.set("global", global);
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollback(helper: MigrationHelper): Promise<void> {
|
||||||
|
const locale = await helper.getFromGlobal<string>(LOCALE_KEY);
|
||||||
|
|
||||||
|
if (!locale) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const global = (await helper.get<ExpectedGlobal>("global")) ?? {};
|
||||||
|
global.locale = locale;
|
||||||
|
await helper.set("global", global);
|
||||||
|
await helper.setToGlobal(LOCALE_KEY, null);
|
||||||
|
}
|
||||||
|
}
|
@ -34,4 +34,8 @@ export class I18nMockService implements I18nService {
|
|||||||
translate(id: string, p1?: string, p2?: string, p3?: string) {
|
translate(id: string, p1?: string, p2?: string, p3?: string) {
|
||||||
return this.t(id, p1, p2, p3);
|
return this.t(id, p1, p2, p3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setLocale(locale: string): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user