Typescript - Add logic to translate menu items

This commit is contained in:
Kiran Niranjan
2018-12-04 12:16:38 +05:30
parent 5e31833b91
commit 7fd7cbfcad
12 changed files with 141 additions and 113 deletions

View File

@@ -9,17 +9,17 @@ const cacheCheckFilePath: string = path.join(app.getPath('userData'), 'CacheChec
* Deletes app cache file if exists or clears * Deletes app cache file if exists or clears
* the cache for the session * the cache for the session
*/ */
export async function cleanUpAppCache(): Promise<void> { export const cleanUpAppCache = async (): Promise<void> => {
if (fs.existsSync(cacheCheckFilePath)) { if (fs.existsSync(cacheCheckFilePath)) {
await fs.unlinkSync(cacheCheckFilePath); await fs.unlinkSync(cacheCheckFilePath);
return; return;
} }
await new Promise((resolve) => session.defaultSession ? session.defaultSession.clearCache(resolve) : null); await new Promise((resolve) => session.defaultSession ? session.defaultSession.clearCache(resolve) : null);
} };
/** /**
* Creates a new file cache file on app exit * Creates a new file cache file on app exit
*/ */
export function createAppCacheFile(): void { export const createAppCacheFile = (): void => {
fs.writeFileSync(cacheCheckFilePath, ''); fs.writeFileSync(cacheCheckFilePath, '');
} };

View File

@@ -1,7 +1,7 @@
import { app, Menu, session, shell } from 'electron'; import { app, Menu, session, shell } from 'electron';
import { isMac, isWindowsOS } from '../common/env'; import { isMac, isWindowsOS } from '../common/env';
import { i18n } from '../common/i18n'; import { i18n, LocaleType } from '../common/i18n';
import { logger } from '../common/logger'; import { logger } from '../common/logger';
import { autoLaunchInstance as autoLaunch } from './auto-launch-controller'; import { autoLaunchInstance as autoLaunch } from './auto-launch-controller';
import { config, IConfig } from './config-handler'; import { config, IConfig } from './config-handler';
@@ -55,9 +55,11 @@ const menuItemsArray = Object.keys(menuSections)
export class AppMenu { export class AppMenu {
private menu: Electron.Menu | undefined; private menu: Electron.Menu | undefined;
private menuList: Electron.MenuItemConstructorOptions[]; private menuList: Electron.MenuItemConstructorOptions[];
private readonly locale: LocaleType;
constructor() { constructor() {
this.menuList = []; this.menuList = [];
this.locale = i18n.getLocale();
this.buildMenu(); this.buildMenu();
} }
@@ -77,6 +79,15 @@ export class AppMenu {
Menu.setApplicationMenu(this.menu); Menu.setApplicationMenu(this.menu);
} }
/**
* Rebuilds the application menu on locale changes
*
* @param locale {LocaleType}
*/
public update(locale: LocaleType): void {
if (this.locale !== locale) this.buildMenu();
}
/** /**
* Displays popup application at the x,y coordinates * Displays popup application at the x,y coordinates
* *

View File

@@ -7,7 +7,7 @@ import { config, IConfig } from './config-handler';
/** /**
* Sets chrome flags * Sets chrome flags
*/ */
export default function setChromeFlags() { export const setChromeFlags = () => {
const { customFlags } = config.getGlobalConfigFields([ 'customFlags' ]) as IConfig; const { customFlags } = config.getGlobalConfigFields([ 'customFlags' ]) as IConfig;
const configFlags: object = { const configFlags: object = {
@@ -63,4 +63,4 @@ export default function setChromeFlags() {
} }
} }
} }
} };

View File

@@ -1,9 +1,10 @@
import { BrowserWindow, ipcMain } from 'electron'; import { BrowserWindow, ipcMain } from 'electron';
import { apiCmds, apiName, IApiArgs } from '../common/api-interface'; import { apiCmds, apiName, IApiArgs } from '../common/api-interface';
import { LocaleType } from '../common/i18n';
import { logger } from '../common/logger'; import { logger } from '../common/logger';
import { windowHandler } from './window-handler'; import { windowHandler } from './window-handler';
import { isValidWindow, setDataUrl, showBadgeCount } from './window-utils'; import { isValidWindow, setDataUrl, showBadgeCount, updateLocale } from './window-utils';
/** /**
* Handle API related ipc messages from renderers. Only messages from windows * Handle API related ipc messages from renderers. Only messages from windows
@@ -81,7 +82,7 @@ ipcMain.on(apiName.symphonyApi, (event: Electron.Event, arg: IApiArgs) => {
case apiCmds.popupMenu: { case apiCmds.popupMenu: {
const browserWin = BrowserWindow.fromWebContents(event.sender); const browserWin = BrowserWindow.fromWebContents(event.sender);
if (browserWin && !browserWin.isDestroyed()) { if (browserWin && !browserWin.isDestroyed()) {
const appMenu = windowHandler.getApplicationMenu(); const appMenu = windowHandler.appMenu;
if (appMenu) appMenu.popupMenu(browserWin); if (appMenu) appMenu.popupMenu(browserWin);
} }
break; break;
@@ -100,14 +101,13 @@ ipcMain.on(apiName.symphonyApi, (event: Electron.Event, arg: IApiArgs) => {
if (typeof arg.isInMeeting === 'boolean') { if (typeof arg.isInMeeting === 'boolean') {
setIsInMeeting(arg.isInMeeting); setIsInMeeting(arg.isInMeeting);
} }
break; break;*/
case ApiCmds.setLocale: case apiCmds.setLocale:
if (typeof arg.locale === 'string') { if (typeof arg.locale === 'string') {
let browserWin = electron.BrowserWindow.fromWebContents(event.sender); updateLocale(arg.locale as LocaleType);
windowMgr.setLocale(browserWin, { language: arg.locale });
} }
break; break;
case ApiCmds.keyPress: /*case ApiCmds.keyPress:
if (typeof arg.keyCode === 'number') { if (typeof arg.keyCode === 'number') {
windowMgr.handleKeyPress(arg.keyCode); windowMgr.handleKeyPress(arg.keyCode);
} }

View File

@@ -5,7 +5,7 @@ import { logger } from '../common/logger';
import { getCommandLineArgs } from '../common/utils'; import { getCommandLineArgs } from '../common/utils';
import { cleanUpAppCache, createAppCacheFile } from './app-cache-handler'; import { cleanUpAppCache, createAppCacheFile } from './app-cache-handler';
import { autoLaunchInstance } from './auto-launch-controller'; import { autoLaunchInstance } from './auto-launch-controller';
import setChromeFlags from './chrome-flags'; import { setChromeFlags } from './chrome-flags';
import { config } from './config-handler'; import { config } from './config-handler';
import './main-api-handler'; import './main-api-handler';
import { windowHandler } from './window-handler'; import { windowHandler } from './window-handler';
@@ -13,16 +13,10 @@ import { windowHandler } from './window-handler';
const allowMultiInstance: string | boolean = getCommandLineArgs(process.argv, '--multiInstance', true) || isDevEnv; const allowMultiInstance: string | boolean = getCommandLineArgs(process.argv, '--multiInstance', true) || isDevEnv;
const singleInstanceLock: boolean = allowMultiInstance ? true : app.requestSingleInstanceLock(); const singleInstanceLock: boolean = allowMultiInstance ? true : app.requestSingleInstanceLock();
if (!singleInstanceLock) {
app.quit();
} else {
main();
}
/** /**
* Main function that init the application * Main function that init the application
*/ */
async function main() { const main = async () => {
await app.whenReady(); await app.whenReady();
createAppCacheFile(); createAppCacheFile();
windowHandler.showLoadingScreen(); windowHandler.showLoadingScreen();
@@ -42,6 +36,12 @@ async function main() {
* Sets chrome flags from global config * Sets chrome flags from global config
*/ */
setChromeFlags(); setChromeFlags();
};
if (!singleInstanceLock) {
app.quit();
} else {
main();
} }
/** /**

View File

@@ -25,7 +25,7 @@ export class WindowHandler {
/** /**
* Main window opts * Main window opts
*/ */
private static getMainWindowOpts() { private static getMainWindowOpts(): ICustomBrowserWindowConstructorOpts {
return { return {
alwaysOnTop: false, alwaysOnTop: false,
frame: true, frame: true,
@@ -46,7 +46,7 @@ export class WindowHandler {
/** /**
* Loading window opts * Loading window opts
*/ */
private static getLoadingWindowOpts() { private static getLoadingWindowOpts(): Electron.BrowserWindowConstructorOptions {
return { return {
alwaysOnTop: false, alwaysOnTop: false,
center: true, center: true,
@@ -77,7 +77,9 @@ export class WindowHandler {
return url.format(parsedUrl); return url.format(parsedUrl);
} }
private appMenu: AppMenu | null; public appMenu: AppMenu | null;
public isAutoReload: boolean;
private readonly windowOpts: ICustomBrowserWindowConstructorOpts; private readonly windowOpts: ICustomBrowserWindowConstructorOpts;
private readonly globalConfig: IConfig; private readonly globalConfig: IConfig;
// Window reference // Window reference
@@ -86,7 +88,6 @@ export class WindowHandler {
private loadingWindow: Electron.BrowserWindow | null; private loadingWindow: Electron.BrowserWindow | null;
private aboutAppWindow: Electron.BrowserWindow | null; private aboutAppWindow: Electron.BrowserWindow | null;
private moreInfoWindow: Electron.BrowserWindow | null; private moreInfoWindow: Electron.BrowserWindow | null;
private isAutoReload: boolean;
constructor(opts?: Electron.BrowserViewConstructorOptions) { constructor(opts?: Electron.BrowserViewConstructorOptions) {
this.windows = {}; this.windows = {};
@@ -129,6 +130,7 @@ export class WindowHandler {
); );
this.mainWindow.webContents.send('initiate-custom-title-bar'); this.mainWindow.webContents.send('initiate-custom-title-bar');
} }
this.mainWindow.webContents.toggleDevTools();
this.mainWindow.show(); this.mainWindow.show();
this.appMenu = new AppMenu(); this.appMenu = new AppMenu();
this.createAboutAppWindow(); this.createAboutAppWindow();
@@ -154,13 +156,6 @@ export class WindowHandler {
return this.windows; return this.windows;
} }
/**
* Gets the application menu
*/
public getApplicationMenu(): AppMenu | null {
return this.appMenu;
}
/** /**
* Sets is auto reload when the application * Sets is auto reload when the application
* is auto reloaded for optimizing memory * is auto reloaded for optimizing memory
@@ -171,15 +166,6 @@ export class WindowHandler {
this.isAutoReload = shouldAutoReload; this.isAutoReload = shouldAutoReload;
} }
/**
* Gets is auto reload
*
* @return isAutoReload {boolean}
*/
public getIsAutoReload(): boolean {
return this.isAutoReload;
}
/** /**
* Checks if the window and a key has a window * Checks if the window and a key has a window
* @param key {string} * @param key {string}

View File

@@ -3,20 +3,37 @@ import * as path from 'path';
import * as url from 'url'; import * as url from 'url';
import { isMac, isWindowsOS } from '../common/env'; import { isMac, isWindowsOS } from '../common/env';
import { i18n, LocaleType } from '../common/i18n';
import { logger } from '../common/logger'; import { logger } from '../common/logger';
import { ICustomBrowserWindow, windowHandler } from './window-handler'; import { ICustomBrowserWindow, windowHandler } from './window-handler';
const checkValidWindow = true; const checkValidWindow = true;
/**
* Prevents window from navigating
*
* @param browserWindow
*/
export const preventWindowNavigation = (browserWindow: Electron.BrowserWindow): void => {
const listener = (e: Electron.Event, winUrl: string) => {
if (browserWindow.isDestroyed()
|| browserWindow.webContents.isDestroyed()
|| winUrl === browserWindow.webContents.getURL()) return;
e.preventDefault();
};
browserWindow.webContents.on('will-navigate', listener);
};
/** /**
* Creates components windows * Creates components windows
* *
* @param componentName * @param componentName
* @param opts * @param opts
*/ */
export function createComponentWindow( export const createComponentWindow = (
componentName: string, componentName: string,
opts?: Electron.BrowserWindowConstructorOptions): BrowserWindow { opts?: Electron.BrowserWindowConstructorOptions): BrowserWindow => {
const parent = windowHandler.getMainWindow() || undefined; const parent = windowHandler.getMainWindow() || undefined;
const options = { const options = {
@@ -48,30 +65,14 @@ export function createComponentWindow(
browserWindow.loadURL(targetUrl); browserWindow.loadURL(targetUrl);
preventWindowNavigation(browserWindow); preventWindowNavigation(browserWindow);
return browserWindow; return browserWindow;
} };
/**
* Prevents window from navigating
*
* @param browserWindow
*/
export function preventWindowNavigation(browserWindow: Electron.BrowserWindow) {
const listener = (e: Electron.Event, winUrl: string) => {
if (browserWindow.isDestroyed()
|| browserWindow.webContents.isDestroyed()
|| winUrl === browserWindow.webContents.getURL()) return;
e.preventDefault();
};
browserWindow.webContents.on('will-navigate', listener);
}
/** /**
* Shows the badge count * Shows the badge count
* *
* @param count {number} * @param count {number}
*/ */
export function showBadgeCount(count: number): void { export const showBadgeCount = (count: number): void => {
if (typeof count !== 'number') { if (typeof count !== 'number') {
logger.warn(`badgeCount: invalid func arg, must be a number: ${count}`); logger.warn(`badgeCount: invalid func arg, must be a number: ${count}`);
return; return;
@@ -95,7 +96,7 @@ export function showBadgeCount(count: number): void {
mainWindow.setOverlayIcon(null, ''); mainWindow.setOverlayIcon(null, '');
} }
} }
} };
/** /**
* Sets the data url * Sets the data url
@@ -103,7 +104,7 @@ export function showBadgeCount(count: number): void {
* @param dataUrl * @param dataUrl
* @param count * @param count
*/ */
export function setDataUrl(dataUrl: string, count: number): void { export const setDataUrl = (dataUrl: string, count: number): void => {
const mainWindow = windowHandler.getMainWindow(); const mainWindow = windowHandler.getMainWindow();
if (mainWindow && dataUrl && count) { if (mainWindow && dataUrl && count) {
const img = nativeImage.createFromDataURL(dataUrl); const img = nativeImage.createFromDataURL(dataUrl);
@@ -111,30 +112,7 @@ export function setDataUrl(dataUrl: string, count: number): void {
const desc = 'Symphony has ' + count + ' unread messages'; const desc = 'Symphony has ' + count + ' unread messages';
mainWindow.setOverlayIcon(img, desc); mainWindow.setOverlayIcon(img, desc);
} }
} };
/**
* Sets always on top property based on isAlwaysOnTop
*
* @param isAlwaysOnTop
* @param shouldActivateMainWindow
*/
export function updateAlwaysOnTop(isAlwaysOnTop: boolean, shouldActivateMainWindow: boolean = true) {
const browserWins: ICustomBrowserWindow[] = BrowserWindow.getAllWindows() as ICustomBrowserWindow[];
if (browserWins.length > 0) {
browserWins
.filter((browser) => typeof browser.notificationObj !== 'object')
.forEach((browser) => browser.setAlwaysOnTop(isAlwaysOnTop));
// An issue where changing the alwaysOnTop property
// focus the pop-out window
// Issue - Electron-209/470
const mainWindow = windowHandler.getMainWindow();
if (mainWindow && mainWindow.winName && shouldActivateMainWindow) {
activate(mainWindow.winName);
}
}
}
/** /**
* Tries finding a window we have created with given name. If found, then * Tries finding a window we have created with given name. If found, then
@@ -145,10 +123,10 @@ export function updateAlwaysOnTop(isAlwaysOnTop: boolean, shouldActivateMainWind
* @param {Boolean} shouldFocus whether to get window to focus or just show * @param {Boolean} shouldFocus whether to get window to focus or just show
* without giving focus * without giving focus
*/ */
function activate(windowName: string, shouldFocus: boolean = true): void { export const activate = (windowName: string, shouldFocus: boolean = true): void => {
// Electron-136: don't activate when the app is reloaded programmatically // Electron-136: don't activate when the app is reloaded programmatically
if (windowHandler.getIsAutoReload()) return; if (windowHandler.isAutoReload) return;
const windows = windowHandler.getAllWindows(); const windows = windowHandler.getAllWindows();
for (const key in windows) { for (const key in windows) {
@@ -167,14 +145,38 @@ function activate(windowName: string, shouldFocus: boolean = true): void {
} }
} }
} }
} };
/**
* Sets always on top property based on isAlwaysOnTop
*
* @param isAlwaysOnTop
* @param shouldActivateMainWindow
*/
export const updateAlwaysOnTop = (isAlwaysOnTop: boolean, shouldActivateMainWindow: boolean = true): void => {
const browserWins: ICustomBrowserWindow[] = BrowserWindow.getAllWindows() as ICustomBrowserWindow[];
if (browserWins.length > 0) {
browserWins
.filter((browser) => typeof browser.notificationObj !== 'object')
.forEach((browser) => browser.setAlwaysOnTop(isAlwaysOnTop));
// An issue where changing the alwaysOnTop property
// focus the pop-out window
// Issue - Electron-209/470
const mainWindow = windowHandler.getMainWindow();
if (mainWindow && mainWindow.winName && shouldActivateMainWindow) {
activate(mainWindow.winName);
}
}
};
/** /**
* Ensure events comes from a window that we have created. * Ensure events comes from a window that we have created.
*
* @param {BrowserWindow} browserWin node emitter event to be tested * @param {BrowserWindow} browserWin node emitter event to be tested
* @return {Boolean} returns true if exists otherwise false * @return {Boolean} returns true if exists otherwise false
*/ */
export function isValidWindow(browserWin: Electron.BrowserWindow): boolean { export const isValidWindow = (browserWin: Electron.BrowserWindow): boolean => {
if (!checkValidWindow) { if (!checkValidWindow) {
return true; return true;
} }
@@ -190,4 +192,16 @@ export function isValidWindow(browserWin: Electron.BrowserWindow): boolean {
} }
return result; return result;
} };
/**
* Updates the locale and rebuilds the entire application menu
*
* @param locale {LocaleType}
*/
export const updateLocale = (locale: LocaleType): void => {
// sets the new locale
i18n.setLocale(locale);
const appMenu = windowHandler.appMenu;
if (appMenu) appMenu.update(locale);
};

View File

@@ -1,42 +1,41 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
// import { logger } from './logger';
import { formatString } from './utils'; import { formatString } from './utils';
const localeCodeRegex = /^([a-z]{2})-([A-Z]{2})$/; const localeCodeRegex = /^([a-z]{2})-([A-Z]{2})$/;
export type localeType = 'en-US' | 'ja-JP'; export type LocaleType = 'en-US' | 'ja-JP';
class Translation { class Translation {
private static translate = (value: string, resource) => resource[value]; private static translate = (value: string, resource) => resource[value];
private locale: localeType = 'en-US'; private locale: LocaleType = 'en-US';
private loadedResource: object = {}; private loadedResource: object = {};
/** /**
* Apply the locale for translation * Apply the locale for translation
*
* @param locale * @param locale
*/ */
public setLocale(locale: localeType): void { public setLocale(locale: LocaleType): void {
const localeMatch: string[] | null = locale.match(localeCodeRegex); const localeMatch: string[] | null = locale.match(localeCodeRegex);
if (!locale && (!localeMatch || localeMatch.length < 1)) { if (!locale && (!localeMatch || localeMatch.length < 1)) {
// logger.error(`Translation: invalid locale ${locale} found`);
return; return;
} }
this.locale = locale; this.locale = locale;
// logger.info(`Translation: locale updated with ${locale}`);
} }
/** /**
* Gets the current locale * Gets the current locale
*/ */
public getLocale(): localeType { public getLocale(): LocaleType {
return this.locale; return this.locale;
} }
/** /**
* fetches and returns the translated value * fetches and returns the translated value
*
* @param value {string} * @param value {string}
* @param data {object} * @param data {object}
*/ */
@@ -50,9 +49,10 @@ class Translation {
/** /**
* Reads the resources dir and returns the data * Reads the resources dir and returns the data
*
* @param locale * @param locale
*/ */
public loadResource(locale: localeType): object | null { public loadResource(locale: LocaleType): object | null {
const resourcePath = path.resolve(__dirname, '..', 'locale', `${locale}.json`); const resourcePath = path.resolve(__dirname, '..', 'locale', `${locale}.json`);
if (!fs.existsSync(resourcePath)) { if (!fs.existsSync(resourcePath)) {

View File

@@ -5,12 +5,10 @@ import AboutBox from './about-app';
import LoadingScreen from './loading-screen'; import LoadingScreen from './loading-screen';
import MoreInfo from './more-info'; import MoreInfo from './more-info';
document.addEventListener('DOMContentLoaded', load);
/** /**
* Loads the appropriate component * Loads the appropriate component
*/ */
function load() { const load = () => {
const query = new URL(window.location.href).searchParams; const query = new URL(window.location.href).searchParams;
const componentName = query.get('componentName'); const componentName = query.get('componentName');
@@ -28,4 +26,6 @@ function load() {
} }
const element = React.createElement(component); const element = React.createElement(component);
ReactDOM.render(element, document.getElementById('Root')); ReactDOM.render(element, document.getElementById('Root'));
} };
document.addEventListener('DOMContentLoaded', load);

View File

@@ -5,12 +5,10 @@ import * as ReactDOM from 'react-dom';
import WindowsTitleBar from '../renderer/windows-title-bar'; import WindowsTitleBar from '../renderer/windows-title-bar';
import { SSFApi } from './ssf-api'; import { SSFApi } from './ssf-api';
createAPI();
/** /**
* creates API exposed from electron. * creates API exposed from electron.
*/ */
function createAPI() { const createAPI = () => {
// iframes (and any other non-top level frames) get no api access // iframes (and any other non-top level frames) get no api access
// http://stackoverflow.com/questions/326069/how-to-identify-if-a-webpage-is-being-loaded-inside-an-iframe-or-directly-into-t/326076 // http://stackoverflow.com/questions/326069/how-to-identify-if-a-webpage-is-being-loaded-inside-an-iframe-or-directly-into-t/326076
if (window.self !== window.top) { if (window.self !== window.top) {
@@ -26,7 +24,9 @@ function createAPI() {
// //
// @ts-ignore // @ts-ignore
window.ssf = new SSFApi(); window.ssf = new SSFApi();
} };
createAPI();
// When the window is completely loaded // When the window is completely loaded
ipcRenderer.on('page-load', () => { ipcRenderer.on('page-load', () => {

View File

@@ -8,15 +8,20 @@ const local = {
}; };
// Throttle func // Throttle func
const throttleSetBadgeCount = throttle((count) => { const throttledSetBadgeCount = throttle((count) => {
console.log(count);
console.log(apiCmds.setBadgeCount);
local.ipcRenderer.send(apiName.symphonyApi, { local.ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.setBadgeCount, cmd: apiCmds.setBadgeCount,
count, count,
}); });
}, 1000); }, 1000);
const throttledSetLocale = throttle((locale) => {
local.ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.setLocale,
locale,
});
}, 1000);
export class SSFApi { export class SSFApi {
/** /**
* sets the count on the tray icon to the given number. * sets the count on the tray icon to the given number.
@@ -26,7 +31,18 @@ export class SSFApi {
* note: for windws the number displayed will be 1 to 99 and 99+ * note: for windws the number displayed will be 1 to 99 and 99+
*/ */
public setBadgeCount(count: number): void { public setBadgeCount(count: number): void {
throttleSetBadgeCount(count); throttledSetBadgeCount(count);
}
/**
* Sets the language which updates the application locale
* @param {string} locale - language identifier and a region identifier
* Ex: en-US, ja-JP
*/
public setLocale(locale): void {
if (typeof locale === 'string') {
throttledSetLocale(locale);
}
} }
} }

View File

@@ -26,6 +26,7 @@
"no-unused-expression": true, "no-unused-expression": true,
"no-use-before-declare": true, "no-use-before-declare": true,
"no-var-requires": true, "no-var-requires": true,
"only-arrow-functions": true,
"no-console": [ "no-console": [
false, false,
"log", "log",