summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/main/index.ts26
-rw-r--r--gui/src/main/load-translations.ts72
-rw-r--r--gui/src/renderer/app.tsx52
-rw-r--r--gui/src/renderer/containers/SelectLanguagePage.tsx4
-rw-r--r--gui/src/renderer/lib/load-translations.ts22
-rw-r--r--gui/src/renderer/preload.ts2
-rw-r--r--gui/src/shared/gettext.ts59
-rw-r--r--gui/src/shared/ipc-event-channel.ts13
8 files changed, 147 insertions, 103 deletions
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index ec67280e12..872c043f89 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -34,7 +34,7 @@ import {
RelaySettingsUpdate,
TunnelState,
} from '../shared/daemon-rpc-types';
-import { loadTranslations, messages } from '../shared/gettext';
+import { messages, relayLocations } from '../shared/gettext';
import { SYSTEM_PREFERRED_LOCALE_KEY } from '../shared/gui-settings-state';
import { IpcMainEventChannel } from '../shared/ipc-event-channel';
import log, { ConsoleOutput, Logger } from '../shared/logging';
@@ -65,11 +65,13 @@ import {
IpcInput,
OLD_LOG_FILES,
} from './logging';
+import { loadTranslations } from './load-translations';
import NotificationController from './notification-controller';
import { resolveBin } from './proc';
import ReconnectionBackoff from './reconnection-backoff';
import TrayIconController, { TrayIconType } from './tray-icon-controller';
import WindowController from './window-controller';
+import { ITranslations } from '../shared/ipc-schema';
// Only import when running app on Linux.
const linuxSplitTunneling = process.platform === 'linux' && require('./linux-split-tunneling');
@@ -204,6 +206,7 @@ class ApplicationMain {
private autoConnectFallbackScheduler = new Scheduler();
private rendererLog?: Logger;
+ private translations: ITranslations = { locale: this.locale };
public run() {
// Remove window animations to combat window flickering when opening window. Can be removed when
@@ -373,7 +376,7 @@ class ApplicationMain {
this.blockRequests();
- this.updateCurrentLocale();
+ this.translations = this.updateCurrentLocale();
this.daemonRpc.addConnectionObserver(
new ConnectionObserver(this.onDaemonConnected, this.onDaemonDisconnected),
@@ -1007,6 +1010,7 @@ class ApplicationMain {
upgradeVersion: this.upgradeVersion,
guiSettings: this.guiSettings.state,
wireguardPublicKey: this.wireguardPublicKey,
+ translations: this.translations,
}));
IpcMainEventChannel.settings.handleSetAllowLan((allowLan: boolean) =>
@@ -1074,7 +1078,7 @@ class ApplicationMain {
IpcMainEventChannel.guiSettings.handleSetPreferredLocale((locale: string) => {
this.guiSettings.preferredLocale = locale;
- this.didChangeLocale();
+ return Promise.resolve(this.updateCurrentLocale());
});
IpcMainEventChannel.account.handleCreate(() => this.createNewAccount());
@@ -1368,15 +1372,13 @@ class ApplicationMain {
log.info(`Detected locale: ${this.locale}`);
- loadTranslations(this.locale, messages);
- }
-
- private didChangeLocale() {
- this.updateCurrentLocale();
-
- if (this.windowController) {
- IpcMainEventChannel.locale.notify(this.windowController.webContents, this.locale);
- }
+ const messagesTranslations = loadTranslations(this.locale, messages);
+ const relayLocationsTranslations = loadTranslations(this.locale, relayLocations);
+ return {
+ locale: this.locale,
+ messages: messagesTranslations,
+ relayLocations: relayLocationsTranslations,
+ };
}
// Since the app frontend never performs any network requests, all requests originating from the
diff --git a/gui/src/main/load-translations.ts b/gui/src/main/load-translations.ts
new file mode 100644
index 0000000000..63d76f0ac6
--- /dev/null
+++ b/gui/src/main/load-translations.ts
@@ -0,0 +1,72 @@
+import fs from 'fs';
+import { GetTextTranslations, po } from 'gettext-parser';
+import Gettext from 'node-gettext';
+import path from 'path';
+import log from '../shared/logging';
+
+const SOURCE_LANGUAGE = 'en';
+const LOCALES_DIR = path.resolve(__dirname, '../../locales');
+
+export function loadTranslations(
+ currentLocale: string,
+ catalogue: Gettext,
+): GetTextTranslations | undefined {
+ // 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) {
+ const parsedTranslations = parseTranslation(locale, domain, catalogue);
+ if (parsedTranslations) {
+ log.info(`Loaded translations ${locale}/${domain}`);
+ catalogue.setLocale(locale);
+ return parsedTranslations;
+ }
+ }
+
+ // 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);
+ return;
+}
+
+function parseTranslation(
+ locale: string,
+ domain: string,
+ catalogue: Gettext,
+): GetTextTranslations | undefined {
+ 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 undefined;
+ }
+
+ let translations: GetTextTranslations;
+ try {
+ translations = po.parse(contents);
+ } catch (error) {
+ log.error(`Cannot parse the gettext file "${filename}": ${error.message}`);
+ return undefined;
+ }
+
+ catalogue.addTranslations(locale, domain, translations);
+
+ return translations;
+}
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index a877c7aacb..7f243b6101 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -23,6 +23,7 @@ import { ILinuxSplitTunnelingApplication } from '../shared/application-types';
import log, { ConsoleOutput } from '../shared/logging';
import consumePromise from '../shared/promise';
import History from './lib/history';
+import { loadTranslations } from './lib/load-translations';
import {
AccountToken,
@@ -100,20 +101,6 @@ export default class AppRenderer {
log.addOutput(new ConsoleOutput(LogLevel.debug));
log.addOutput(new IpcOutput(LogLevel.debug));
- IpcRendererEventChannel.locale.listen((locale) => {
- // load translations for the new locale
- this.loadTranslations(locale);
-
- // set current locale
- this.setLocale(locale);
-
- // refresh the relay list pair with the new translations
- this.propagateRelayListPairToRedux();
-
- // refresh the location with the new translations
- this.propagateLocationToRedux();
- });
-
IpcRendererEventChannel.windowShape.listen((windowShapeParams) => {
if (typeof windowShapeParams.arrowPosition === 'number') {
this.reduxActions.userInterface.updateWindowArrowPosition(windowShapeParams.arrowPosition);
@@ -188,9 +175,17 @@ export default class AppRenderer {
// Request the initial state from the main process
const initialState = IpcRendererEventChannel.state.get();
- // Load translations
- this.loadTranslations(initialState.locale);
this.setLocale(initialState.locale);
+ loadTranslations(
+ messages,
+ initialState.translations.locale,
+ initialState.translations.messages,
+ );
+ loadTranslations(
+ relayLocations,
+ initialState.translations.locale,
+ initialState.translations.relayLocations,
+ );
this.setAccountExpiry(initialState.accountData && initialState.accountData.expiry);
this.handleAccountChange(undefined, initialState.settings.accountToken);
@@ -466,8 +461,23 @@ export default class AppRenderer {
];
}
- public setPreferredLocale(preferredLocale: string) {
- IpcRendererEventChannel.guiSettings.setPreferredLocale(preferredLocale);
+ public async setPreferredLocale(preferredLocale: string): Promise<void> {
+ const translations = await IpcRendererEventChannel.guiSettings.setPreferredLocale(
+ preferredLocale,
+ );
+
+ // set current locale
+ this.setLocale(translations.locale);
+
+ // load translations for new locale
+ loadTranslations(messages, translations.locale, translations.messages);
+ loadTranslations(relayLocations, translations.locale, translations.relayLocations);
+
+ // refresh the relay list pair with the new translations
+ this.propagateRelayListPairToRedux();
+
+ // refresh the location with the new translations
+ this.propagateLocationToRedux();
}
public getPreferredLocaleDisplayName(localeCode: string): string {
@@ -481,12 +491,6 @@ export default class AppRenderer {
this.loginTimer = global.setTimeout(() => this.history.resetWith('/connect'), 1000);
}
- private loadTranslations(locale: string) {
- for (const catalogue of [messages, relayLocations]) {
- window.loadTranslations(locale, catalogue);
- }
- }
-
private setLocale(locale: string) {
this.locale = locale;
this.reduxActions.userInterface.updateLocale(locale);
diff --git a/gui/src/renderer/containers/SelectLanguagePage.tsx b/gui/src/renderer/containers/SelectLanguagePage.tsx
index eb052fb8a6..5c073c22e1 100644
--- a/gui/src/renderer/containers/SelectLanguagePage.tsx
+++ b/gui/src/renderer/containers/SelectLanguagePage.tsx
@@ -11,8 +11,8 @@ const mapStateToProps = (state: IReduxState) => ({
const mapDispatchToProps = (_dispatch: ReduxDispatch, props: RouteComponentProps & IAppContext) => {
return {
preferredLocalesList: props.app.getPreferredLocaleList(),
- setPreferredLocale(locale: string) {
- props.app.setPreferredLocale(locale);
+ async setPreferredLocale(locale: string) {
+ await props.app.setPreferredLocale(locale);
props.history.goBack();
},
onClose() {
diff --git a/gui/src/renderer/lib/load-translations.ts b/gui/src/renderer/lib/load-translations.ts
new file mode 100644
index 0000000000..43a483de25
--- /dev/null
+++ b/gui/src/renderer/lib/load-translations.ts
@@ -0,0 +1,22 @@
+import { GetTextTranslations } from 'gettext-parser';
+import Gettext from 'node-gettext';
+import log from '../../shared/logging';
+
+const SOURCE_LANGUAGE = 'en';
+
+export function loadTranslations(
+ catalogue: Gettext,
+ locale: string,
+ translations?: GetTextTranslations,
+) {
+ if (translations) {
+ catalogue.addTranslations(locale, catalogue.domain, translations);
+ catalogue.setLocale(locale);
+ log.info(`Loaded translations ${locale}/${catalogue.domain}`);
+ } else {
+ // 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, catalogue.domain, {});
+ catalogue.setLocale(SOURCE_LANGUAGE);
+ }
+}
diff --git a/gui/src/renderer/preload.ts b/gui/src/renderer/preload.ts
index 2eacea5b8c..c5d1ca6e6a 100644
--- a/gui/src/renderer/preload.ts
+++ b/gui/src/renderer/preload.ts
@@ -1,5 +1,3 @@
-import { loadTranslations } from '../shared/gettext';
import { IpcRendererEventChannel } from '../shared/ipc-event-channel';
-window.loadTranslations = (locale, catalogue) => loadTranslations(locale, catalogue);
window.ipc = IpcRendererEventChannel;
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-event-channel.ts b/gui/src/shared/ipc-event-channel.ts
index 19c11f85ab..59304e3678 100644
--- a/gui/src/shared/ipc-event-channel.ts
+++ b/gui/src/shared/ipc-event-channel.ts
@@ -1,3 +1,4 @@
+import { GetTextTranslations } from 'gettext-parser';
import { ICurrentAppVersionInfo } from '../main/index';
import { IWindowShapeParameters } from '../main/window-controller';
import { ILinuxSplitTunnelingApplication } from '../shared/application-types';
@@ -33,6 +34,12 @@ interface ILogEntry {
message: string;
}
+export interface ITranslations {
+ locale: string;
+ messages?: GetTextTranslations;
+ relayLocations?: GetTextTranslations;
+}
+
export interface IRelayListPair {
relays: IRelayList;
bridges: IRelayList;
@@ -52,6 +59,7 @@ export interface IAppStateSnapshot {
upgradeVersion: IAppVersionInfo;
guiSettings: IGuiSettingsState;
wireguardPublicKey?: IWireguardPublicKey;
+ translations: ITranslations;
}
// The different types of requests are:
@@ -97,9 +105,6 @@ const ipc = {
state: {
get: invokeSync<void, IAppStateSnapshot>(),
},
- locale: {
- '': notifyRenderer<string>(),
- },
windowShape: {
'': notifyRenderer<IWindowShapeParameters>(),
},
@@ -155,7 +160,7 @@ const ipc = {
setAutoConnect: send<boolean>(),
setStartMinimized: send<boolean>(),
setMonochromaticIcon: send<boolean>(),
- setPreferredLocale: send<string>(),
+ setPreferredLocale: invoke<string, ITranslations>(),
setUnpinnedWindow: send<boolean>(),
},
account: {