[PM-3753] Update electron desktop language handling (#6482)

* [PM-3753] Update desktop language handling

* Remove i18n service import aliases

* Validate the provided locale before loading it

* Support underscores in locales
This commit is contained in:
Daniel García 2023-10-24 12:32:54 +02:00 committed by GitHub
parent 17897cfe35
commit 222345f0c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 102 additions and 18 deletions

View File

@ -17,7 +17,7 @@ import { EventUploadService } from "@bitwarden/common/services/event/event-uploa
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { I18nService } from "../../platform/services/i18n.service";
import { I18nRendererService } from "../../platform/services/i18n.renderer.service";
import { NativeMessagingService } from "../../services/native-messaging.service";
@Injectable()
@ -51,7 +51,7 @@ export class InitService {
this.syncService.fullSync(true);
await this.vaultTimeoutService.init(true);
const locale = await this.stateService.getLocale();
await (this.i18nService as I18nService).init(locale);
await (this.i18nService as I18nRendererService).init(locale);
(this.eventUploadService as EventUploadService).init(true);
this.twoFactorService.init();
setTimeout(() => this.notificationsService.init(), 3000);

View File

@ -48,7 +48,7 @@ import { ElectronRendererSecureStorageService } from "../../platform/services/el
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
import { ElectronStateService } from "../../platform/services/electron-state.service";
import { ElectronStateService as ElectronStateServiceAbstraction } from "../../platform/services/electron-state.service.abstraction";
import { I18nService } from "../../platform/services/i18n.service";
import { I18nRendererService } from "../../platform/services/i18n.renderer.service";
import { EncryptedMessageHandlerService } from "../../services/encrypted-message-handler.service";
import { NativeMessageHandlerService } from "../../services/native-message-handler.service";
import { NativeMessagingService } from "../../services/native-messaging.service";
@ -91,7 +91,7 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
},
{
provide: I18nServiceAbstraction,
useClass: I18nService,
useClass: I18nRendererService,
deps: [SYSTEM_LANGUAGE, LOCALES_DIRECTORY],
},
{

View File

@ -21,12 +21,12 @@ import { DesktopCredentialStorageListener } from "./platform/main/desktop-creden
import { ElectronLogService } from "./platform/services/electron-log.service";
import { ElectronStateService } from "./platform/services/electron-state.service";
import { ElectronStorageService } from "./platform/services/electron-storage.service";
import { I18nService } from "./platform/services/i18n.service";
import { I18nMainService } from "./platform/services/i18n.main.service";
import { ElectronMainMessagingService } from "./services/electron-main-messaging.service";
export class Main {
logService: ElectronLogService;
i18nService: I18nService;
i18nService: I18nMainService;
storageService: ElectronStorageService;
memoryStorageService: MemoryStorageService;
messagingService: ElectronMainMessagingService;
@ -76,7 +76,7 @@ export class Main {
}
this.logService = new ElectronLogService(null, app.getPath("userData"));
this.i18nService = new I18nService("en", "./locales/");
this.i18nService = new I18nMainService("en", "./locales/");
const storageDefaults: any = {};
// Default vault timeout to "on restart", and action to "lock"

View File

@ -39,6 +39,9 @@ export default {
ipcRenderer.on("systemThemeUpdated", (_event, theme: ThemeType) => callback(theme));
},
getLanguageFile: (formattedLocale: string): Promise<object> =>
ipcRenderer.invoke("getLanguageFile", formattedLocale),
sendMessage: (message: { command: string } & any) =>
ipcRenderer.send("messagingService", message),
onMessage: (callback: (message: { command: string } & any) => void) => {

View File

@ -0,0 +1,90 @@
import { promises as fs } from "fs";
import * as path from "path";
import { ipcMain } from "electron";
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
export class I18nMainService extends BaseI18nService {
constructor(systemLanguage: string, localesDirectory: string) {
super(systemLanguage, localesDirectory, (formattedLocale: string) =>
this.readLanguageFile(formattedLocale)
);
ipcMain.handle("getLanguageFile", async (event, formattedLocale: string) =>
this.readLanguageFile(formattedLocale)
);
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
this.supportedTranslationLocales = [
"en",
"af",
"ar",
"az",
"be",
"bg",
"bn",
"bs",
"ca",
"cs",
"da",
"de",
"el",
"en-GB",
"en-IN",
"eo",
"es",
"et",
"eu",
"fa",
"fi",
"fil",
"fr",
"he",
"hi",
"hr",
"hu",
"id",
"it",
"ja",
"ka",
"km",
"kn",
"ko",
"lv",
"me",
"ml",
"nb",
"nl",
"nn",
"pl",
"pt-BR",
"pt-PT",
"ro",
"ru",
"si",
"sk",
"sl",
"sr",
"sv",
"th",
"tr",
"uk",
"vi",
"zh-CN",
"zh-TW",
];
}
private async readLanguageFile(formattedLocale: string): Promise<any> {
// Check that the provided locale only contains letters and dashes and underscores to avoid possible path traversal
if (!/^[a-zA-Z_-]+$/.test(formattedLocale)) {
return Promise.resolve({});
}
const filePath = path.join(__dirname, this.localesDirectory, formattedLocale, "messages.json");
const localesJson = await fs.readFile(filePath, "utf8");
const locales = JSON.parse(localesJson.replace(/^\uFEFF/, "")); // strip the BOM
return Promise.resolve(locales);
}
}

View File

@ -1,18 +1,9 @@
import * as fs from "fs";
import * as path from "path";
import { I18nService as BaseI18nService } from "@bitwarden/common/platform/services/i18n.service";
export class I18nService extends BaseI18nService {
export class I18nRendererService extends BaseI18nService {
constructor(systemLanguage: string, localesDirectory: string) {
super(systemLanguage, localesDirectory, (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);
return ipc.platform.getLanguageFile(formattedLocale);
});
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found