diff options
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/main/index.ts | 26 | ||||
| -rw-r--r-- | gui/src/main/load-translations.ts | 72 | ||||
| -rw-r--r-- | gui/src/renderer/app.tsx | 52 | ||||
| -rw-r--r-- | gui/src/renderer/containers/SelectLanguagePage.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/lib/load-translations.ts | 22 | ||||
| -rw-r--r-- | gui/src/renderer/preload.ts | 2 | ||||
| -rw-r--r-- | gui/src/shared/gettext.ts | 59 | ||||
| -rw-r--r-- | gui/src/shared/ipc-event-channel.ts | 13 |
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: { |
