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
* the cache for the session
*/
export async function cleanUpAppCache(): Promise<void> {
export const cleanUpAppCache = async (): Promise<void> => {
if (fs.existsSync(cacheCheckFilePath)) {
await fs.unlinkSync(cacheCheckFilePath);
return;
}
await new Promise((resolve) => session.defaultSession ? session.defaultSession.clearCache(resolve) : null);
}
};
/**
* Creates a new file cache file on app exit
*/
export function createAppCacheFile(): void {
export const createAppCacheFile = (): void => {
fs.writeFileSync(cacheCheckFilePath, '');
}
};

View File

@@ -1,7 +1,7 @@
import { app, Menu, session, shell } from 'electron';
import { isMac, isWindowsOS } from '../common/env';
import { i18n } from '../common/i18n';
import { i18n, LocaleType } from '../common/i18n';
import { logger } from '../common/logger';
import { autoLaunchInstance as autoLaunch } from './auto-launch-controller';
import { config, IConfig } from './config-handler';
@@ -55,9 +55,11 @@ const menuItemsArray = Object.keys(menuSections)
export class AppMenu {
private menu: Electron.Menu | undefined;
private menuList: Electron.MenuItemConstructorOptions[];
private readonly locale: LocaleType;
constructor() {
this.menuList = [];
this.locale = i18n.getLocale();
this.buildMenu();
}
@@ -77,6 +79,15 @@ export class AppMenu {
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
*

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,20 +3,37 @@ import * as path from 'path';
import * as url from 'url';
import { isMac, isWindowsOS } from '../common/env';
import { i18n, LocaleType } from '../common/i18n';
import { logger } from '../common/logger';
import { ICustomBrowserWindow, windowHandler } from './window-handler';
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
*
* @param componentName
* @param opts
*/
export function createComponentWindow(
export const createComponentWindow = (
componentName: string,
opts?: Electron.BrowserWindowConstructorOptions): BrowserWindow {
opts?: Electron.BrowserWindowConstructorOptions): BrowserWindow => {
const parent = windowHandler.getMainWindow() || undefined;
const options = {
@@ -48,30 +65,14 @@ export function createComponentWindow(
browserWindow.loadURL(targetUrl);
preventWindowNavigation(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
*
* @param count {number}
*/
export function showBadgeCount(count: number): void {
export const showBadgeCount = (count: number): void => {
if (typeof count !== 'number') {
logger.warn(`badgeCount: invalid func arg, must be a number: ${count}`);
return;
@@ -95,7 +96,7 @@ export function showBadgeCount(count: number): void {
mainWindow.setOverlayIcon(null, '');
}
}
}
};
/**
* Sets the data url
@@ -103,7 +104,7 @@ export function showBadgeCount(count: number): void {
* @param dataUrl
* @param count
*/
export function setDataUrl(dataUrl: string, count: number): void {
export const setDataUrl = (dataUrl: string, count: number): void => {
const mainWindow = windowHandler.getMainWindow();
if (mainWindow && dataUrl && count) {
const img = nativeImage.createFromDataURL(dataUrl);
@@ -111,30 +112,7 @@ export function setDataUrl(dataUrl: string, count: number): void {
const desc = 'Symphony has ' + count + ' unread messages';
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
@@ -145,10 +123,10 @@ export function updateAlwaysOnTop(isAlwaysOnTop: boolean, shouldActivateMainWind
* @param {Boolean} shouldFocus whether to get window to focus or just show
* 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
if (windowHandler.getIsAutoReload()) return;
if (windowHandler.isAutoReload) return;
const windows = windowHandler.getAllWindows();
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.
*
* @param {BrowserWindow} browserWin node emitter event to be tested
* @return {Boolean} returns true if exists otherwise false
*/
export function isValidWindow(browserWin: Electron.BrowserWindow): boolean {
export const isValidWindow = (browserWin: Electron.BrowserWindow): boolean => {
if (!checkValidWindow) {
return true;
}
@@ -190,4 +192,16 @@ export function isValidWindow(browserWin: Electron.BrowserWindow): boolean {
}
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 path from 'path';
// import { logger } from './logger';
import { formatString } from './utils';
const localeCodeRegex = /^([a-z]{2})-([A-Z]{2})$/;
export type localeType = 'en-US' | 'ja-JP';
export type LocaleType = 'en-US' | 'ja-JP';
class Translation {
private static translate = (value: string, resource) => resource[value];
private locale: localeType = 'en-US';
private locale: LocaleType = 'en-US';
private loadedResource: object = {};
/**
* Apply the locale for translation
*
* @param locale
*/
public setLocale(locale: localeType): void {
public setLocale(locale: LocaleType): void {
const localeMatch: string[] | null = locale.match(localeCodeRegex);
if (!locale && (!localeMatch || localeMatch.length < 1)) {
// logger.error(`Translation: invalid locale ${locale} found`);
return;
}
this.locale = locale;
// logger.info(`Translation: locale updated with ${locale}`);
}
/**
* Gets the current locale
*/
public getLocale(): localeType {
public getLocale(): LocaleType {
return this.locale;
}
/**
* fetches and returns the translated value
*
* @param value {string}
* @param data {object}
*/
@@ -50,9 +49,10 @@ class Translation {
/**
* Reads the resources dir and returns the data
*
* @param locale
*/
public loadResource(locale: localeType): object | null {
public loadResource(locale: LocaleType): object | null {
const resourcePath = path.resolve(__dirname, '..', 'locale', `${locale}.json`);
if (!fs.existsSync(resourcePath)) {

View File

@@ -5,12 +5,10 @@ import AboutBox from './about-app';
import LoadingScreen from './loading-screen';
import MoreInfo from './more-info';
document.addEventListener('DOMContentLoaded', load);
/**
* Loads the appropriate component
*/
function load() {
const load = () => {
const query = new URL(window.location.href).searchParams;
const componentName = query.get('componentName');
@@ -28,4 +26,6 @@ function load() {
}
const element = React.createElement(component);
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 { SSFApi } from './ssf-api';
createAPI();
/**
* creates API exposed from electron.
*/
function createAPI() {
const createAPI = () => {
// 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
if (window.self !== window.top) {
@@ -26,7 +24,9 @@ function createAPI() {
//
// @ts-ignore
window.ssf = new SSFApi();
}
};
createAPI();
// When the window is completely loaded
ipcRenderer.on('page-load', () => {

View File

@@ -8,15 +8,20 @@ const local = {
};
// Throttle func
const throttleSetBadgeCount = throttle((count) => {
console.log(count);
console.log(apiCmds.setBadgeCount);
const throttledSetBadgeCount = throttle((count) => {
local.ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.setBadgeCount,
count,
});
}, 1000);
const throttledSetLocale = throttle((locale) => {
local.ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.setLocale,
locale,
});
}, 1000);
export class SSFApi {
/**
* 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+
*/
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-use-before-declare": true,
"no-var-requires": true,
"only-arrow-functions": true,
"no-console": [
false,
"log",