diff options
Diffstat (limited to 'gui/src/shared')
| -rw-r--r-- | gui/src/shared/gettext.ts | 59 | ||||
| -rw-r--r-- | gui/src/shared/ipc-helpers.ts | 60 | ||||
| -rw-r--r-- | gui/src/shared/ipc-schema.ts (renamed from gui/src/shared/ipc-event-channel.ts) | 34 | ||||
| -rw-r--r-- | gui/src/shared/ipc-types.ts | 10 | ||||
| -rw-r--r-- | gui/src/shared/notifications/error.ts | 4 | ||||
| -rw-r--r-- | gui/src/shared/notifications/no-valid-key.ts | 2 |
6 files changed, 60 insertions, 109 deletions
diff --git a/gui/src/shared/gettext.ts b/gui/src/shared/gettext.ts index 3283269c70..0416e8b1ce 100644 --- a/gui/src/shared/gettext.ts +++ b/gui/src/shared/gettext.ts @@ -1,67 +1,8 @@ -import fs from 'fs'; -import { po } from 'gettext-parser'; import Gettext from 'node-gettext'; -import path from 'path'; import { LocalizationContexts } from './localization-contexts'; import log from './logging'; const SOURCE_LANGUAGE = 'en'; -const LOCALES_DIR = path.resolve(__dirname, '../../locales'); - -export function loadTranslations(currentLocale: string, catalogue: Gettext) { - // First look for exact match of the current locale - const preferredLocales = []; - - if (currentLocale !== SOURCE_LANGUAGE) { - preferredLocales.push(currentLocale); - } - - // In case of region bound locale like en-US, fallback to en. - const language = Gettext.getLanguageCode(currentLocale); - if (currentLocale !== language) { - preferredLocales.push(language); - } - - const domain = catalogue.domain; - for (const locale of preferredLocales) { - if (parseTranslation(locale, domain, catalogue)) { - log.info(`Loaded translations ${locale}/${domain}`); - catalogue.setLocale(locale); - return; - } - } - - // Reset the locale to source language if we couldn't load the catalogue for the requested locale - // Add empty translations to suppress some of the warnings produces by node-gettext - catalogue.addTranslations(SOURCE_LANGUAGE, domain, {}); - catalogue.setLocale(SOURCE_LANGUAGE); -} - -function parseTranslation(locale: string, domain: string, catalogue: Gettext): boolean { - const filename = path.join(LOCALES_DIR, locale, `${domain}.po`); - let contents: string; - - try { - contents = fs.readFileSync(filename, { encoding: 'utf8' }); - } catch (error) { - if (error.code !== 'ENOENT') { - log.error(`Cannot read the gettext file "${filename}": ${error.message}`); - } - return false; - } - - let translations: ReturnType<typeof po.parse>; - try { - translations = po.parse(contents); - } catch (error) { - log.error(`Cannot parse the gettext file "${filename}": ${error.message}`); - return false; - } - - catalogue.addTranslations(locale, domain, translations); - - return true; -} function setErrorHandler(catalogue: Gettext) { catalogue.on('error', (error) => { diff --git a/gui/src/shared/ipc-helpers.ts b/gui/src/shared/ipc-helpers.ts index d1a72a2df6..59eee46459 100644 --- a/gui/src/shared/ipc-helpers.ts +++ b/gui/src/shared/ipc-helpers.ts @@ -1,4 +1,4 @@ -import { ipcMain, ipcRenderer, WebContents } from 'electron'; +import { IpcMain as EIpcMain, IpcRenderer as EIpcRenderer, WebContents } from 'electron'; import { capitalize } from './string-helpers'; import log from './logging'; @@ -7,26 +7,21 @@ type Sender<T, R> = (arg: T) => R; type Notifier<T> = (webContents: WebContents, arg: T) => void; type Listener<T> = (callback: (arg: T) => void) => void; -interface IpcCall<T, R> { - direction: 'renderer-to-main' | 'main-to-renderer'; - send: (event: string) => Notifier<T> | Sender<T, R>; - receive: (event: string) => Listener<T> | Handler<T, R>; -} - -interface MainToRenderer<T> extends IpcCall<T, never> { +interface MainToRenderer<T> { direction: 'main-to-renderer'; - send: (event: string) => Notifier<T>; - receive: (event: string) => Listener<T>; + send: (event: string, ipcMain: EIpcMain) => Notifier<T>; + receive: (event: string, ipcRenderer: EIpcRenderer) => Listener<T>; } -interface RendererToMain<T, R> extends IpcCall<T, R> { +interface RendererToMain<T, R> { direction: 'renderer-to-main'; - send: (event: string) => Sender<T, R>; - receive: (event: string) => Handler<T, R>; + send: (event: string, ipcRenderer: EIpcRenderer) => Sender<T, R>; + receive: (event: string, ipcMain: EIpcMain) => Handler<T, R>; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -type AnyIpcCall = IpcCall<any, any>; +type AnyIpcCall = MainToRenderer<any> | RendererToMain<any, any>; + type Schema = Record<string, Record<string, AnyIpcCall>>; // Renames all IPC calls, e.g. `callName` to either `notifyCallName` or `handleCallName` depending @@ -66,22 +61,31 @@ type IpcRenderer<S extends Schema> = { }; // Preforms the transformation of the main event channel in accordance with the above types. -export function createIpcMain<S extends Schema>(ipc: S): IpcMain<S> { - return createIpc(ipc, (event, key, spec) => { +export function createIpcMain<S extends Schema>(schema: S, ipcMain: EIpcMain): IpcMain<S> { + return createIpc(schema, (event, key, spec) => { const capitalizedKey = capitalize(key); const newKey = spec.direction === 'main-to-renderer' ? `notify${capitalizedKey}` : `handle${capitalizedKey}`; - const newValue = spec.direction === 'main-to-renderer' ? spec.send(event) : spec.receive(event); + const newValue = + spec.direction === 'main-to-renderer' + ? spec.send(event, ipcMain) + : spec.receive(event, ipcMain); return [newKey, newValue]; }); } // Preforms the transformation of the renderer event channel in accordance with the above types. -export function createIpcRenderer<S extends Schema>(ipc: S): IpcRenderer<S> { - return createIpc(ipc, (event, key, spec) => { +export function createIpcRenderer<S extends Schema>( + schema: S, + ipcRenderer: EIpcRenderer, +): IpcRenderer<S> { + return createIpc(schema, (event, key, spec) => { const newKey = spec.direction === 'main-to-renderer' ? `listen${capitalize(key)}` : key; - const newValue = spec.direction === 'main-to-renderer' ? spec.receive(event) : spec.send(event); + const newValue = + spec.direction === 'main-to-renderer' + ? spec.receive(event, ipcRenderer) + : spec.send(event, ipcRenderer); return [newKey, newValue]; }); @@ -105,8 +109,8 @@ function createIpc<S extends Schema, T, R extends IpcMain<S> | IpcRenderer<S>>( export function send<T>(): RendererToMain<T, void> { return { direction: 'renderer-to-main', - send: (event: string) => (newValue: T) => ipcRenderer.send(event, newValue), - receive: (event: string) => (handlerFn: (value: T) => void) => { + send: (event, ipcRenderer) => (newValue: T) => ipcRenderer.send(event, newValue), + receive: (event, ipcMain) => (handlerFn: (value: T) => void) => { ipcMain.on(event, (_event, newValue: T) => { handlerFn(newValue); }); @@ -118,8 +122,8 @@ export function send<T>(): RendererToMain<T, void> { export function invokeSync<T, R>(): RendererToMain<T, R> { return { direction: 'renderer-to-main', - send: (event: string) => (newValue: T) => ipcRenderer.sendSync(event, newValue), - receive: (event: string) => (handlerFn: (value: T) => R) => { + send: (event, ipcRenderer) => (newValue: T) => ipcRenderer.sendSync(event, newValue), + receive: (event, ipcMain) => (handlerFn: (value: T) => R) => { ipcMain.on(event, (ipcEvent, newValue: T) => { ipcEvent.returnValue = handlerFn(newValue); }); @@ -141,13 +145,13 @@ export function notifyRenderer<T>(): MainToRenderer<T> { return { direction: 'main-to-renderer', send: notifyRendererImpl, - receive: (event: string) => (fn: (value: T) => void) => { + receive: (event, ipcRenderer) => (fn: (value: T) => void) => { ipcRenderer.on(event, (_event, newState: T) => fn(newState)); }, }; } -function notifyRendererImpl<T>(event: string): Notifier<T> { +function notifyRendererImpl<T>(event: string, _ipcMain: EIpcMain): Notifier<T> { return (webContents: WebContents, value: T) => { if (webContents.isDestroyed()) { log.error(`sender(${event}): webContents is already destroyed!`); @@ -159,7 +163,7 @@ function notifyRendererImpl<T>(event: string): Notifier<T> { type RequestResult<T> = { type: 'success'; value: T } | { type: 'error'; message: string }; -function invokeImpl<T, R>(event: string): Sender<T, Promise<R>> { +function invokeImpl<T, R>(event: string, ipcRenderer: EIpcRenderer): Sender<T, Promise<R>> { return async (arg: T): Promise<R> => { const result: RequestResult<R> = await ipcRenderer.invoke(event, arg); switch (result.type) { @@ -171,7 +175,7 @@ function invokeImpl<T, R>(event: string): Sender<T, Promise<R>> { }; } -function handle<T, R>(event: string): Handler<T, Promise<R>> { +function handle<T, R>(event: string, ipcMain: EIpcMain): Handler<T, Promise<R>> { return (fn: (arg: T) => Promise<R>) => { ipcMain.handle(event, async (_ipcEvent, arg: T) => { try { diff --git a/gui/src/shared/ipc-event-channel.ts b/gui/src/shared/ipc-schema.ts index 19c11f85ab..a31745c01f 100644 --- a/gui/src/shared/ipc-event-channel.ts +++ b/gui/src/shared/ipc-schema.ts @@ -1,6 +1,5 @@ -import { ICurrentAppVersionInfo } from '../main/index'; -import { IWindowShapeParameters } from '../main/window-controller'; -import { ILinuxSplitTunnelingApplication } from '../shared/application-types'; +import { GetTextTranslations } from 'gettext-parser'; +import { ILinuxSplitTunnelingApplication } from './application-types'; import { AccountToken, BridgeSettings, @@ -18,20 +17,20 @@ import { VoucherResponse, } from './daemon-rpc-types'; import { IGuiSettingsState } from './gui-settings-state'; -import { - createIpcMain, - createIpcRenderer, - invoke, - invokeSync, - notifyRenderer, - send, -} from './ipc-helpers'; import { LogLevel } from './logging-types'; interface ILogEntry { level: LogLevel; message: string; } +import { invoke, invokeSync, notifyRenderer, send } from './ipc-helpers'; +import { ICurrentAppVersionInfo, IWindowShapeParameters } from './ipc-types'; + +export interface ITranslations { + locale: string; + messages?: GetTextTranslations; + relayLocations?: GetTextTranslations; +} export interface IRelayListPair { relays: IRelayList; @@ -52,6 +51,9 @@ export interface IAppStateSnapshot { upgradeVersion: IAppVersionInfo; guiSettings: IGuiSettingsState; wireguardPublicKey?: IWireguardPublicKey; + translations: ITranslations; + platform: NodeJS.Platform; + runningInDevelopment: boolean; } // The different types of requests are: @@ -93,13 +95,10 @@ export interface IAppStateSnapshot { // listenFourth: (fn: (arg: boolean) => void) => void, // }, // } -const ipc = { +export const ipcSchema = { state: { get: invokeSync<void, IAppStateSnapshot>(), }, - locale: { - '': notifyRenderer<string>(), - }, windowShape: { '': notifyRenderer<IWindowShapeParameters>(), }, @@ -155,7 +154,7 @@ const ipc = { setAutoConnect: send<boolean>(), setStartMinimized: send<boolean>(), setMonochromaticIcon: send<boolean>(), - setPreferredLocale: send<string>(), + setPreferredLocale: invoke<string, ITranslations>(), setUnpinnedWindow: send<boolean>(), }, account: { @@ -192,6 +191,3 @@ const ipc = { log: send<ILogEntry>(), }, }; - -export const IpcMainEventChannel = createIpcMain(ipc); -export const IpcRendererEventChannel = createIpcRenderer(ipc); diff --git a/gui/src/shared/ipc-types.ts b/gui/src/shared/ipc-types.ts new file mode 100644 index 0000000000..7781551ed5 --- /dev/null +++ b/gui/src/shared/ipc-types.ts @@ -0,0 +1,10 @@ +export interface ICurrentAppVersionInfo { + gui: string; + daemon: string; + isConsistent: boolean; + isBeta: boolean; +} + +export interface IWindowShapeParameters { + arrowPosition?: number; +} diff --git a/gui/src/shared/notifications/error.ts b/gui/src/shared/notifications/error.ts index d580bf1f1e..c5f15482dc 100644 --- a/gui/src/shared/notifications/error.ts +++ b/gui/src/shared/notifications/error.ts @@ -45,7 +45,7 @@ export class ErrorNotificationProvider function getMessage(errorDetails: IErrorState, accountExpiry?: string): string { if (errorDetails.blockFailure) { if (errorDetails.cause.reason === 'set_firewall_policy_error') { - switch (process.platform) { + switch (process.platform ?? window.platform) { case 'win32': return messages.pgettext( 'notifications', @@ -86,7 +86,7 @@ function getMessage(errorDetails: IErrorState, accountExpiry?: string): string { 'Could not configure IPv6. Disable it in the app or enable it on your device.', ); case 'set_firewall_policy_error': - switch (process.platform) { + switch (process.platform ?? window.platform) { case 'win32': return messages.pgettext( 'notifications', diff --git a/gui/src/shared/notifications/no-valid-key.ts b/gui/src/shared/notifications/no-valid-key.ts index d1b90095e2..8da145a1f0 100644 --- a/gui/src/shared/notifications/no-valid-key.ts +++ b/gui/src/shared/notifications/no-valid-key.ts @@ -14,7 +14,7 @@ export class NoValidKeyNotificationProvider implements InAppNotificationProvider public mayDisplay() { const usingWireGuard = this.context.tunnelProtocol === 'wireguard' || - (this.context.tunnelProtocol === 'any' && process.platform !== 'win32'); + (this.context.tunnelProtocol === 'any' && (process.platform ?? window.platform) !== 'win32'); const keyInvalid = this.context.wireGuardKey.type === 'key-not-set' || this.context.wireGuardKey.type === 'too-many-keys' || |
