mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00: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:
@@ -4,4 +4,5 @@ import { TranslationService } from "./translation.service";
|
||||
|
||||
export abstract class I18nService extends TranslationService {
|
||||
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 { GlobalState, GlobalStateProvider, KeyDefinition, TRANSLATION_DISK } from "../state";
|
||||
|
||||
import { TranslationService } from "./translation.service";
|
||||
|
||||
const LOCALE_KEY = new KeyDefinition<string>(TRANSLATION_DISK, "locale", {
|
||||
deserializer: (value) => value,
|
||||
});
|
||||
|
||||
export class I18nService extends TranslationService implements I18nServiceAbstraction {
|
||||
protected _locale = new ReplaySubject<string>(1);
|
||||
private _translationLocale: string;
|
||||
locale$: Observable<string> = this._locale.asObservable();
|
||||
translationLocale: string;
|
||||
protected translationLocaleState: GlobalState<string>;
|
||||
locale$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
protected systemLanguage: string,
|
||||
protected localesDirectory: string,
|
||||
protected getLocalesJson: (formattedLocale: string) => Promise<any>,
|
||||
globalStateProvider: GlobalStateProvider,
|
||||
) {
|
||||
super(systemLanguage, localesDirectory, getLocalesJson);
|
||||
this.translationLocaleState = globalStateProvider.get(LOCALE_KEY);
|
||||
this.locale$ = this.translationLocaleState.state$.pipe(map((locale) => locale ?? null));
|
||||
}
|
||||
|
||||
get translationLocale(): string {
|
||||
return this._translationLocale;
|
||||
async setLocale(locale: string): Promise<void> {
|
||||
await this.translationLocaleState.update(() => locale);
|
||||
}
|
||||
|
||||
set translationLocale(locale: string) {
|
||||
this._translationLocale = locale;
|
||||
this._locale.next(locale);
|
||||
override async init() {
|
||||
const storedLocale = await firstValueFrom(this.translationLocaleState.state$);
|
||||
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_MEMORY = new StateDefinition("crypto", "memory");
|
||||
export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk");
|
||||
export const TRANSLATION_DISK = new StateDefinition("translation", "disk");
|
||||
|
||||
// Secrets Manager
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import { UserNotificationSettingsKeyMigrator } from "./migrations/29-move-user-n
|
||||
import { FixPremiumMigrator } from "./migrations/3-fix-premium";
|
||||
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 { PreferredLanguageMigrator } from "./migrations/32-move-preferred-language";
|
||||
import { RemoveEverBeenUnlockedMigrator } from "./migrations/4-remove-ever-been-unlocked";
|
||||
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
|
||||
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";
|
||||
|
||||
export const MIN_VERSION = 2;
|
||||
export const CURRENT_VERSION = 31;
|
||||
export const CURRENT_VERSION = 32;
|
||||
export type MinVersion = typeof MIN_VERSION;
|
||||
|
||||
export function createMigrationBuilder() {
|
||||
@@ -70,7 +71,8 @@ export function createMigrationBuilder() {
|
||||
.with(MoveBiometricUnlockToStateProviders, 27, 28)
|
||||
.with(UserNotificationSettingsKeyMigrator, 28, 29)
|
||||
.with(PolicyMigrator, 29, 30)
|
||||
.with(EnableContextMenuMigrator, 30, CURRENT_VERSION);
|
||||
.with(EnableContextMenuMigrator, 30, 31)
|
||||
.with(PreferredLanguageMigrator, 31, CURRENT_VERSION);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user