From 7d32a9f094b4f31216ecbb7b43e11371070e2477 Mon Sep 17 00:00:00 2001 From: reyraa Date: Fri, 13 Oct 2017 11:19:03 +0200 Subject: [PATCH 01/23] Add json storage to store selected language --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index fe6b51f56..3e6cd0b44 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "del-cli": "1.1.0", "electron": "1.7.8", "electron-builder": "19.32.2", + "electron-json-storage": "^3.1.1", "enzyme": "2.9.1", "eslint": "4.8.0", "eslint-config-airbnb": "15.1.0", From 7515df456776eca77d8084e57d48c844fb2f20de Mon Sep 17 00:00:00 2001 From: reyraa Date: Fri, 13 Oct 2017 11:19:54 +0200 Subject: [PATCH 02/23] Create a utility to receive the default/stored language from electron --- src/utils/ipcLocale.js | 19 +++++++++++++++++++ src/utils/ipcLocale.test.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/utils/ipcLocale.js create mode 100644 src/utils/ipcLocale.test.js diff --git a/src/utils/ipcLocale.js b/src/utils/ipcLocale.js new file mode 100644 index 000000000..8e3cfa9da --- /dev/null +++ b/src/utils/ipcLocale.js @@ -0,0 +1,19 @@ +export default { + init: (i18n) => { + const { ipc } = window; + let localeInit = false; + + if (ipc) { + ipc.on('detectedLocale', (action, locale) => { + i18n.changeLanguage(locale); + localeInit = true; + }); + + i18n.on('languageChanged', (locale) => { + if (localeInit) { + ipc.send('set-locale', locale); + } + }); + } + }, +}; diff --git a/src/utils/ipcLocale.test.js b/src/utils/ipcLocale.test.js new file mode 100644 index 000000000..2dce661fa --- /dev/null +++ b/src/utils/ipcLocale.test.js @@ -0,0 +1,34 @@ +import { expect } from 'chai'; +import { spy } from 'sinon'; +import ipcLocale from './ipcLocale'; + +describe('ipcLocale', () => { + const ipc = { + on: spy(), + send: spy(), + }; + + const i18n = { + changeLanguage: spy(), + on: spy(), + }; + + describe('init', () => { + it('should be a function', () => { + expect(typeof ipcLocale.init).to.be.equal('function'); + }); + + it('calling init when ipc is not on window should do nothing', () => { + ipcLocale.init(); + expect(ipc.on).to.not.have.been.calledWith(); + expect(ipc.send).to.not.have.been.calledWith(); + }); + + it('calling init when ipc is available on window should bind listeners', () => { + window.ipc = ipc; + ipcLocale.init(i18n); + expect(ipc.on).to.have.been.calledWith(); + expect(i18n.on).to.have.been.calledWith(); + }); + }); +}); From 35ea64302ee4f38ce12fa1be8b9fa5db65ceb594 Mon Sep 17 00:00:00 2001 From: reyraa Date: Fri, 13 Oct 2017 11:20:28 +0200 Subject: [PATCH 03/23] Initiate the apiLocale listener --- src/main.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.js b/src/main.js index 460440bd7..95215cf0b 100644 --- a/src/main.js +++ b/src/main.js @@ -10,9 +10,11 @@ import i18n from './i18n'; // initialized i18next instance import proxyLogin from './utils/proxyLogin'; import externalLinks from './utils/externalLinks'; import env from './constants/env'; +import ipcLocale from './utils/ipcLocale'; if (env.production) { proxyLogin.init(); + ipcLocale.init(i18n); externalLinks.init(); } From da90a94a8abe861cb00dc0dd9d50d7ce76c8f01b Mon Sep 17 00:00:00 2001 From: reyraa Date: Fri, 13 Oct 2017 11:21:19 +0200 Subject: [PATCH 04/23] Use a generic t function to translate menu labels --- app/src/menu.js | 193 +++++++++++++++++++++++++----------------------- 1 file changed, 102 insertions(+), 91 deletions(-) diff --git a/app/src/menu.js b/app/src/menu.js index 853922823..06bdace8f 100644 --- a/app/src/menu.js +++ b/app/src/menu.js @@ -1,100 +1,111 @@ -import electron from 'electron'; // eslint-disable-line import/no-extraneous-dependencies +const electron = require('electron'); // eslint-disable-line import/no-extraneous-dependencies const { Menu } = electron; -const template = [ - { - label: 'Edit', - submenu: [ - { - role: 'undo', - }, - { - role: 'redo', - }, - { - type: 'separator', - }, - { - role: 'cut', - }, - { - role: 'copy', - }, - { - role: 'paste', - }, - { - role: 'selectall', - }, - ], - }, - { - label: 'View', - submenu: [ - { - role: 'reload', - }, - { - role: 'togglefullscreen', - }, - ], - }, - { - label: 'Window', - submenu: [ - { - role: 'minimize', - }, - ], - }, - { - label: 'Help', - submenu: [ - { - label: 'Lisk Website', - click() { - electron.shell.openExternal('https://lisk.io'); +const buildTemplate = t => + [ + { + label: t('Edit'), + submenu: [ + { + role: 'undo', + label: t('Undo'), }, - }, - { - label: 'Lisk Chat', - click() { - electron.shell.openExternal('https://lisk.chat'); + { + role: 'redo', + label: t('Redo'), }, - }, - { - label: 'Lisk Explorer', - click() { - electron.shell.openExternal('https://explorer.lisk.io'); + { + type: t('separator'), }, - }, - { - label: 'Lisk Forum', - click() { - electron.shell.openExternal('https://forum.lisk.io'); + { + role: 'cut', + label: t('Cut'), }, - }, - { - type: 'separator', - }, - { - label: 'Report Issue...', - click() { - electron.shell.openExternal('https://lisk.zendesk.com/hc/en-us/requests/new'); + { + role: 'copy', + label: t('Copy'), }, - }, - { - label: 'What\'s New...', - click() { - electron.shell.openExternal('https://github.com/LiskHQ/lisk-nano/releases'); + { + role: 'paste', + label: t('Paste'), }, - }, - ], - }, -]; + { + role: 'selectall', + label: t('Select all'), + }, + ], + }, + { + label: t('View'), + submenu: [ + { + role: 'reload', + label: t('Reload'), + }, + { + role: 'togglefullscreen', + label: t('Toggle full screen'), + }, + ], + }, + { + label: t('Window'), + submenu: [ + { + role: 'minimize', + label: t('Minimize'), + }, + ], + }, + { + label: t('Help'), + submenu: [ + { + label: t('Lisk Website'), + click() { + electron.shell.openExternal('https://lisk.io'); + }, + }, + { + label: t('Lisk Chat'), + click() { + electron.shell.openExternal('https://lisk.chat'); + }, + }, + { + label: t('Lisk Explorer'), + click() { + electron.shell.openExternal('https://explorer.lisk.io'); + }, + }, + { + label: t('Lisk Forum'), + click() { + electron.shell.openExternal('https://forum.lisk.io'); + }, + }, + { + type: 'separator', + }, + { + label: t('Report Issue...'), + click() { + electron.shell.openExternal('https://lisk.zendesk.com/hc/en-us/requests/new'); + }, + }, + { + label: t('What\'s New...'), + click() { + electron.shell.openExternal('https://github.com/LiskHQ/lisk-nano/releases'); + }, + }, + ], + }, + ]; -export default (app, copyright) => { +module.exports = (app, copyright, t) => { + const template = buildTemplate(t); if (process.platform === 'darwin') { const name = app.getName(); template.unshift({ @@ -102,23 +113,23 @@ export default (app, copyright) => { submenu: [ { role: 'about', - label: 'About', + label: t('About'), }, { role: 'quit', - label: 'Quit', + label: t('Quit'), }, ], }); } else { template[template.length - 1].submenu.push({ - label: 'About', + label: t('About'), click(item, focusedWindow) { if (focusedWindow) { const options = { buttons: ['OK'], icon: `${__dirname}/assets/lisk.png`, - message: `Lisk Nano\nVersion ${app.getVersion()}\n${copyright}`, + message: `${t('Lisk Nano\nVersion')} ${app.getVersion()}\n${copyright}`, }; electron.dialog.showMessageBox(focusedWindow, options, () => {}); } From ef091e2f845a82c87ccf365ddaef8af57f1231f6 Mon Sep 17 00:00:00 2001 From: reyraa Date: Fri, 13 Oct 2017 11:22:03 +0200 Subject: [PATCH 05/23] Add listeners amd event emitters to comminicate the locale with the client app --- app/src/main.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/app/src/main.js b/app/src/main.js index 16aa78bdd..f5346577a 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -7,6 +7,12 @@ const { app, BrowserWindow, Menu, ipcMain } = electron; let win; let isUILoaded = false; let eventStack = []; +// @todo change en with the detected lang +let defaultLng = 'en'; +// replace this with i18next and detect the default/stored language +const i18n = { + t: str => str, +}; const copyright = `Copyright © 2016 - ${new Date().getFullYear()} Lisk Foundation`; const protocolName = 'lisk'; @@ -19,7 +25,22 @@ const sendUrlToRouter = (url) => { } }; +/** + * Sends an event to client application + * @param {String} locale - the 2 letter name of the local + */ +const sendDetectedLang = (locale) => { + if (isUILoaded && win && win.webContents) { + win.webContents.send('detectedLocale', locale); + } else { + eventStack.push({ event: 'detectedLocale', value: locale }); + } +}; + function createWindow() { + // set language of the react app + sendDetectedLang(defaultLng); + const { width, height } = electron.screen.getPrimaryDisplay().workAreaSize; win = new BrowserWindow({ width: width > 2000 ? Math.floor(width * 0.5) : width - 250, @@ -138,3 +159,11 @@ ipcMain.on('proxyCredentialsEntered', (event, username, password) => { global.myTempFunction(username, password); }); +ipcMain.on('set-locale', (event, locale) => { + if (locale.substr(0, 2) !== defaultLng) { + // @todo store the locale here for next time app launches + defaultLng = locale; + Menu.setApplicationMenu(buildMenu(app, copyright, i18n.t)); + event.returnValue = 'Rebuilt electron menu.'; + } +}); From bbee2930b9d22ed3d30e189a1aaeec57d8a84fa3 Mon Sep 17 00:00:00 2001 From: yashar Date: Mon, 16 Oct 2017 00:46:07 +0330 Subject: [PATCH 06/23] Add electron app translation scanning to i18n-scanner.js --- src/i18n-scanner.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n-scanner.js b/src/i18n-scanner.js index 702fb5a06..b4e226240 100644 --- a/src/i18n-scanner.js +++ b/src/i18n-scanner.js @@ -26,7 +26,8 @@ const customHandler = function (key, options) { }; const files = glob.sync('./src/**/*.js', {}); -files.forEach((file) => { +const electronFiles = glob.sync('./app/src/**/*.js', {}); +[...files, ...electronFiles].forEach((file) => { const content = fs.readFileSync(file, 'utf-8'); parser.parseFuncFromString(content, { list: translationFunctionNames }, customHandler); }); From 9331bda822c2a71431fa51296e10eac84c6096bf Mon Sep 17 00:00:00 2001 From: yashar Date: Mon, 16 Oct 2017 00:46:42 +0330 Subject: [PATCH 07/23] Fix a bug in menu.js --- app/src/menu.js | 54 ++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/app/src/menu.js b/app/src/menu.js index 06bdace8f..a1e2909bb 100644 --- a/app/src/menu.js +++ b/app/src/menu.js @@ -2,85 +2,85 @@ const electron = require('electron'); // eslint-disable-line import/no-extraneou const { Menu } = electron; -const buildTemplate = t => +const buildTemplate = i18n => [ { - label: t('Edit'), + label: i18n.t('Edit'), submenu: [ { role: 'undo', - label: t('Undo'), + label: i18n.t('Undo'), }, { role: 'redo', - label: t('Redo'), + label: i18n.t('Redo'), }, { - type: t('separator'), + type: i18n.t('separator'), }, { role: 'cut', - label: t('Cut'), + label: i18n.t('Cut'), }, { role: 'copy', - label: t('Copy'), + label: i18n.t('Copy'), }, { role: 'paste', - label: t('Paste'), + label: i18n.t('Paste'), }, { role: 'selectall', - label: t('Select all'), + label: i18n.t('Select all'), }, ], }, { - label: t('View'), + label: i18n.t('View'), submenu: [ { role: 'reload', - label: t('Reload'), + label: i18n.t('Reload'), }, { role: 'togglefullscreen', - label: t('Toggle full screen'), + label: i18n.t('Toggle full screen'), }, ], }, { - label: t('Window'), + label: i18n.t('Window'), submenu: [ { role: 'minimize', - label: t('Minimize'), + label: i18n.t('Minimize'), }, ], }, { - label: t('Help'), + label: i18n.t('Help'), submenu: [ { - label: t('Lisk Website'), + label: i18n.t('Lisk Website'), click() { electron.shell.openExternal('https://lisk.io'); }, }, { - label: t('Lisk Chat'), + label: i18n.t('Lisk Chat'), click() { electron.shell.openExternal('https://lisk.chat'); }, }, { - label: t('Lisk Explorer'), + label: i18n.t('Lisk Explorer'), click() { electron.shell.openExternal('https://explorer.lisk.io'); }, }, { - label: t('Lisk Forum'), + label: i18n.t('Lisk Forum'), click() { electron.shell.openExternal('https://forum.lisk.io'); }, @@ -89,13 +89,13 @@ const buildTemplate = t => type: 'separator', }, { - label: t('Report Issue...'), + label: i18n.t('Report Issue...'), click() { electron.shell.openExternal('https://lisk.zendesk.com/hc/en-us/requests/new'); }, }, { - label: t('What\'s New...'), + label: i18n.t('What\'s New...'), click() { electron.shell.openExternal('https://github.com/LiskHQ/lisk-nano/releases'); }, @@ -104,8 +104,8 @@ const buildTemplate = t => }, ]; -module.exports = (app, copyright, t) => { - const template = buildTemplate(t); +module.exports = (app, copyright, i18n) => { + const template = buildTemplate(i18n); if (process.platform === 'darwin') { const name = app.getName(); template.unshift({ @@ -113,23 +113,23 @@ module.exports = (app, copyright, t) => { submenu: [ { role: 'about', - label: t('About'), + label: i18n.t('About'), }, { role: 'quit', - label: t('Quit'), + label: i18n.t('Quit'), }, ], }); } else { template[template.length - 1].submenu.push({ - label: t('About'), + label: i18n.t('About'), click(item, focusedWindow) { if (focusedWindow) { const options = { buttons: ['OK'], icon: `${__dirname}/assets/lisk.png`, - message: `${t('Lisk Nano\nVersion')} ${app.getVersion()}\n${copyright}`, + message: `${i18n.t('Lisk Nano\nVersion')} ${app.getVersion()}\n${copyright}`, }; electron.dialog.showMessageBox(focusedWindow, options, () => {}); } From 13b8a3e1657e23482e1e860e89ee75fb2d676356 Mon Sep 17 00:00:00 2001 From: yashar Date: Mon, 16 Oct 2017 00:47:26 +0330 Subject: [PATCH 08/23] Add i18n to main.js and use it in main.js --- app/src/main.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/main.js b/app/src/main.js index f5346577a..75b4b9b8c 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -1,5 +1,6 @@ import electron from 'electron'; // eslint-disable-line import/no-extraneous-dependencies import path from 'path'; +import i18n from './i18n'; import buildMenu from './menu'; const { app, BrowserWindow, Menu, ipcMain } = electron; @@ -10,9 +11,9 @@ let eventStack = []; // @todo change en with the detected lang let defaultLng = 'en'; // replace this with i18next and detect the default/stored language -const i18n = { - t: str => str, -}; +// const i18n = { +// t: str => str, +// }; const copyright = `Copyright © 2016 - ${new Date().getFullYear()} Lisk Foundation`; const protocolName = 'lisk'; @@ -60,7 +61,7 @@ function createWindow() { sendUrlToRouter(process.argv.slice(1)); } - Menu.setApplicationMenu(buildMenu(app, copyright)); + Menu.setApplicationMenu(buildMenu(app, copyright, i18n)); win.loadURL(`file://${__dirname}/index.html`); win.on('closed', () => { win = null; }); @@ -160,10 +161,12 @@ ipcMain.on('proxyCredentialsEntered', (event, username, password) => { }); ipcMain.on('set-locale', (event, locale) => { + console.log(locale); if (locale.substr(0, 2) !== defaultLng) { // @todo store the locale here for next time app launches defaultLng = locale; - Menu.setApplicationMenu(buildMenu(app, copyright, i18n.t)); + i18n.changeLanguage(locale.substr(0, 2)); + Menu.setApplicationMenu(buildMenu(app, copyright, i18n)); event.returnValue = 'Rebuilt electron menu.'; } }); From 06b4d59f94bc6ff45e4a9826b4d237aad3aeeb80 Mon Sep 17 00:00:00 2001 From: yashar Date: Mon, 16 Oct 2017 00:49:58 +0330 Subject: [PATCH 09/23] Add electron menu translation strings to commmon.json --- src/locales/en/common.json | 41 +++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/locales/en/common.json b/src/locales/en/common.json index db69d473d..eb10a6618 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -1,5 +1,6 @@ { " Make sure that you are using the latest version of Lisk Nano.": " Make sure that you are using the latest version of Lisk Nano.", + "About": "About", "Account saved": "Account saved", "Account was successfully forgotten.": "Account was successfully forgotten.", "Add vote to": "Add vote to", @@ -20,13 +21,16 @@ "Click to send all funds": "Click to send all funds", "Confirm": "Confirm", "Connection re-established": "Connection re-established", + "Copy": "Copy", "Copy address to clipboard": "Copy address to clipboard", "Custom Node": "Custom Node", + "Cut": "Cut", "Delegate": "Delegate", "Delegate Registration": "Delegate Registration", "Delegate name": "Delegate name", "Delegate registration was successfully submitted with username: \"{{username}}\". It can take several seconds before it is processed.": "Delegate registration was successfully submitted with username: \"{{username}}\". It can take several seconds before it is processed.", "Downvotes:": "Downvotes:", + "Edit": "Edit", "Enter the missing word": "Enter the missing word", "Enter your passphrase": "Enter your passphrase", "Entered passphrase does not belong to the active account": "Entered passphrase does not belong to the active account", @@ -40,6 +44,7 @@ "Forget this account": "Forget this account", "Forging": "Forging", "From / To": "From / To", + "Help": "Help", "Hide passphrase": "Hide passphrase", "Insufficient funds": "Insufficient funds", "Insufficient funds for {{amount}} LSK fee": "Insufficient funds for {{amount}} LSK fee", @@ -49,12 +54,18 @@ "Language": "Language", "Last 24 hours": "Last 24 hours", "Lisk Address": "Lisk Address", + "Lisk Chat": "Lisk Chat", + "Lisk Explorer": "Lisk Explorer", + "Lisk Forum": "Lisk Forum", + "Lisk Nano\nVersion": "Lisk Nano\nVersion", + "Lisk Website": "Lisk Website", "Login": "Login", "Losing access to this passphrase will mean no funds can be sent from this account.": "Losing access to this passphrase will mean no funds can be sent from this account.", "Mainnet": "Mainnet", "Maximum of {{n}} votes exceeded.": "Maximum of {{n}} votes exceeded.", "Maximum of {{n}} votes in one transaction exceeded.": "Maximum of {{n}} votes in one transaction exceeded.", "Message": "Message", + "Minimize": "Minimize", "Move your mouse to generate random bytes": "Move your mouse to generate random bytes", "Multisignature Creation": "Multisignature Creation", "Name": "Name", @@ -76,6 +87,7 @@ "Passphrase of the account will be required to perform any transaction.": "Passphrase of the account will be required to perform any transaction.", "Passphrase should have 12 words, entered passphrase has {{length}}": "Passphrase should have 12 words, entered passphrase has {{length}}", "Password": "Password", + "Paste": "Paste", "Peer": "Peer", "Please click Next, then move around your mouse randomly to generate a random passphrase.": "Please click Next, then move around your mouse randomly to generate a random passphrase.", "Please keep it safe!": "Please keep it safe!", @@ -83,16 +95,20 @@ "Processing delegate names: ": "Processing delegate names: ", "Proxy Authentication": "Proxy Authentication", "Public Key": "Public Key", + "Quit": "Quit", "Rank": "Rank", "Receive LSK": "Receive LSK", "Recipient Address": "Recipient Address", + "Redo": "Redo", "Register": "Register", "Register Second Passphrase": "Register Second Passphrase", "Register as delegate": "Register as delegate", "Register second passphrase": "Register second passphrase", + "Reload": "Reload", "Remember this account": "Remember this account", "Remove vote from": "Remove vote from", "Repeat the transaction": "Repeat the transaction", + "Report Issue...": "Report Issue...", "Required": "Required", "Result": "Result", "Result copied to clipboard": "Result copied to clipboard", @@ -105,6 +121,7 @@ "Second Signature Creation": "Second Signature Creation", "Second passphrase registration was successfully submitted. It can take several seconds before it is processed.": "Second passphrase registration was successfully submitted. It can take several seconds before it is processed.", "Select a network": "Select a network", + "Select all": "Select all", "Send": "Send", "Send Lisk from Blockchain Application": "Send Lisk from Blockchain Application", "Send Lisk to Blockchain Application": "Send Lisk to Blockchain Application", @@ -126,6 +143,7 @@ "This will save public key of your account on this device, so next time it will launch without the need to log in. However, you will be prompted to enter the passphrase once you want to do any transaction.": "This will save public key of your account on this device, so next time it will launch without the need to log in. However, you will be prompted to enter the passphrase once you want to do any transaction.", "Time": "Time", "Timestamp": "Timestamp", + "Toggle full screen": "Toggle full screen", "Total fee": "Total fee", "Total new votes:": "Total new votes:", "Total votes:": "Total votes:", @@ -134,21 +152,25 @@ "Transactions": "Transactions", "URL is invalid": "URL is invalid", "Unable to connect to the node": "Unable to connect to the node", + "Undo": "Undo", "Uptime": "Uptime", "Upvotes:": "Upvotes:", "Username": "Username", "Verify message": "Verify message", + "View": "View", "Vote": "Vote", "Vote for delegates": "Vote for delegates", "Voting": "Voting", + "What's New...": "What's New...", "When you have the signature, you only need the publicKey of the signer in order to verify that the message came from the right private/publicKey pair. Be aware, everybody knowing the signature and the publicKey can verify the message. If ever there is a dispute, everybody can take the publicKey and signature to a judge and prove that the message is coming from the specific private/publicKey pair.": "When you have the signature, you only need the publicKey of the signer in order to verify that the message came from the right private/publicKey pair. Be aware, everybody knowing the signature and the publicKey can verify the message. If ever there is a dispute, everybody can take the publicKey and signature to a judge and prove that the message is coming from the specific private/publicKey pair.", + "Window": "Window", "Word \"{{invalidWord}}\" is not on the passphrase Word List.": "Word \"{{invalidWord}}\" is not on the passphrase Word List.", "Word \"{{invalidWord}}\" is not on the passphrase Word List. Most similar word on the list is \"{{similarWord}}\"": "Word \"{{invalidWord}}\" is not on the passphrase Word List. Most similar word on the list is \"{{similarWord}}\"", "Yes! It's safe": "Yes! It's safe", "You can select up to {{count}} delegates in one voting turn.": "You can select up to {{count}} delegates in one voting turn.", - "You can select up to {{count}} delegates in one voting turn._plural": "You can select up to {{count}} delegates in one voting turn.", + "You can select up to {{count}} delegates in one voting turn._plural": "", "You can vote for up to {{count}} delegates in total.": "You can vote for up to {{count}} delegates in total.", - "You can vote for up to {{count}} delegates in total._plural": "You can vote for up to {{count}} delegates in total.", + "You can vote for up to {{count}} delegates in total._plural": "", "You have not forged any blocks yet": "You have not forged any blocks yet", "You need to become a delegate to start forging. If you already registered to become a delegate, your registration hasn't been processed, yet.": "You need to become a delegate to start forging. If you already registered to become a delegate, your registration hasn't been processed, yet.", "You've received {{value}} LSK.": "You've received {{value}} LSK.", @@ -160,16 +182,17 @@ "logout": "logout", "my votes": "my votes", "send": "send", + "separator": "separator", "your passphrase will be required for logging in to your account.": "your passphrase will be required for logging in to your account.", "your second passphrase will be required for all transactions sent from this account": "your second passphrase will be required for all transactions sent from this account", - "{{count}} delegate names successfully resolved to add vote to.": "{{count}} delegate name successfully resolved to add vote to.", - "{{count}} delegate names successfully resolved to add vote to._plural": "{{count}} delegate names successfully resolved to add vote to.", - "{{count}} delegate names successfully resolved to remove vote from.": "{{count}} delegate name successfully resolved to remove vote from.", - "{{count}} delegate names successfully resolved to remove vote from._plural": "{{count}} delegate names successfully resolved to remove vote from.", + "{{count}} delegate names successfully resolved to add vote to.": "{{count}} delegate names successfully resolved to add vote to.", + "{{count}} delegate names successfully resolved to add vote to._plural": "", + "{{count}} delegate names successfully resolved to remove vote from.": "{{count}} delegate names successfully resolved to remove vote from.", + "{{count}} delegate names successfully resolved to remove vote from._plural": "", "{{count}} of delegate names selected for unvote were not voted for:": "{{count}} of delegate names selected for unvote were not voted for:", - "{{count}} of delegate names selected for unvote were not voted for:_plural": "{{count}} of delegate names selected for unvote were not voted for:", + "{{count}} of delegate names selected for unvote were not voted for:_plural": "", "{{count}} of delegate names selected for vote were already voted for:": "{{count}} of delegate names selected for vote were already voted for:", - "{{count}} of delegate names selected for vote were already voted for:_plural": "{{count}} of delegate names selected for vote were already voted for:", + "{{count}} of delegate names selected for vote were already voted for:_plural": "", "{{count}} of entered delegate names could not be resolved:": "{{count}} of entered delegate names could not be resolved:", - "{{count}} of entered delegate names could not be resolved:_plural": "{{count}} of entered delegate names could not be resolved:" + "{{count}} of entered delegate names could not be resolved:_plural": "" } From c248fe8dffe8a6b078c425c170b819a43ca5ec6c Mon Sep 17 00:00:00 2001 From: yashar Date: Mon, 16 Oct 2017 00:50:59 +0330 Subject: [PATCH 10/23] Create i18next confing for electron --- app/src/i18n.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 app/src/i18n.js diff --git a/app/src/i18n.js b/app/src/i18n.js new file mode 100755 index 000000000..ef9a400ef --- /dev/null +++ b/app/src/i18n.js @@ -0,0 +1,31 @@ +import i18n from 'i18next'; // eslint-disable-line import/no-extraneous-dependencies +import languages from './languages'; + +const resources = Object.keys(languages).reduce((accumulator, key) => { + accumulator[key] = { + common: languages[key].common, + }; + return accumulator; +}, {}); + +i18n + .init({ + fallbackLng: 'en', + resources, + lang: 'en', + // have a common namespace used around the full app + ns: ['common'], + defaultNS: 'common', + saveMissing: true, + debug: true, + }, (err, t) => { + t(); + // initialized and ready to go! + console.log(i18n.t('Undo')); + console.log(`Current language used: ${i18n.language}`); + }); + +// i18n.setLocale('de'); +// const currentLocale = i18n.getLocale(); +// console.log(currentLocale); +export default i18n; From 482ee3282620f19c78e5e1990c5d52fcf51b3609 Mon Sep 17 00:00:00 2001 From: yashar Date: Mon, 16 Oct 2017 00:52:36 +0330 Subject: [PATCH 11/23] Create language constant in app folder --- app/src/languages.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 app/src/languages.js diff --git a/app/src/languages.js b/app/src/languages.js new file mode 100644 index 000000000..65f087a50 --- /dev/null +++ b/app/src/languages.js @@ -0,0 +1,12 @@ +const languages = { + en: { + name: 'English', + common: require('../dist/locales/en/common.json'), + }, + de: { + name: 'Deutsch', + common: require('../dist/locales/de/common.json'), + }, +}; + +export default languages; From d117421b7ec8eccdfcf2184926e0799a1fa987de Mon Sep 17 00:00:00 2001 From: yashar Date: Mon, 16 Oct 2017 01:00:21 +0330 Subject: [PATCH 12/23] Add locales folder to copy-files script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3e6cd0b44..0680043d4 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dist:win": "build --win --ia32 --x64", "dist:mac": "build --mac", "dist:linux": "build --linux --ia32 --x64 --armv7l", - "copy-files": "mkdir app/dist && cp -r ./src/index.html ./src/assets ./app/dist", + "copy-files": "mkdir app/dist && cp -r ./src/index.html ./src/assets ./src/locales ./app/dist ", "clean": "del app/dist -f", "eslint": "eslint ./src/ ./app/src/ ./app/config/ ./features/", "storybook": "start-storybook -p 6006 -s ./src/", From a1bcac12ea179379d163340e74da4eac11eae653 Mon Sep 17 00:00:00 2001 From: yashar Date: Mon, 16 Oct 2017 01:08:20 +0330 Subject: [PATCH 13/23] Fix some bugs in main.js and i18n.js in app folder --- app/src/i18n.js | 11 +++-------- app/src/main.js | 1 - 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/src/i18n.js b/app/src/i18n.js index ef9a400ef..42a5b5a00 100755 --- a/app/src/i18n.js +++ b/app/src/i18n.js @@ -13,19 +13,14 @@ i18n fallbackLng: 'en', resources, lang: 'en', - // have a common namespace used around the full app ns: ['common'], defaultNS: 'common', saveMissing: true, - debug: true, + debug: false, }, (err, t) => { - t(); + t('key'); // initialized and ready to go! - console.log(i18n.t('Undo')); - console.log(`Current language used: ${i18n.language}`); + console.log(`Current language used: ${i18n.language}`); //eslint-disable-line }); -// i18n.setLocale('de'); -// const currentLocale = i18n.getLocale(); -// console.log(currentLocale); export default i18n; diff --git a/app/src/main.js b/app/src/main.js index 75b4b9b8c..f909d043a 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -161,7 +161,6 @@ ipcMain.on('proxyCredentialsEntered', (event, username, password) => { }); ipcMain.on('set-locale', (event, locale) => { - console.log(locale); if (locale.substr(0, 2) !== defaultLng) { // @todo store the locale here for next time app launches defaultLng = locale; From 95275d261cd109d015d198a7461de5f707529aaa Mon Sep 17 00:00:00 2001 From: yashar Date: Mon, 16 Oct 2017 11:59:59 +0330 Subject: [PATCH 14/23] Fix a bug in languages.js --- app/src/languages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/languages.js b/app/src/languages.js index 65f087a50..1d052a3fd 100644 --- a/app/src/languages.js +++ b/app/src/languages.js @@ -1,11 +1,11 @@ const languages = { en: { name: 'English', - common: require('../dist/locales/en/common.json'), + common: require('../build/locales/en/common.json'), }, de: { name: 'Deutsch', - common: require('../dist/locales/de/common.json'), + common: require('../build/locales/de/common.json'), }, }; From 0f907ec6ecf1615b5362750f74b21793f9a479b3 Mon Sep 17 00:00:00 2001 From: yashar Date: Tue, 17 Oct 2017 13:28:55 +0330 Subject: [PATCH 15/23] Store user selected language in a json file --- app/src/main.js | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/app/src/main.js b/app/src/main.js index f909d043a..c21769f5e 100644 --- a/app/src/main.js +++ b/app/src/main.js @@ -1,5 +1,6 @@ import electron from 'electron'; // eslint-disable-line import/no-extraneous-dependencies import path from 'path'; +import storage from 'electron-json-storage'; // eslint-disable-line import/no-extraneous-dependencies import i18n from './i18n'; import buildMenu from './menu'; @@ -9,11 +10,8 @@ let win; let isUILoaded = false; let eventStack = []; // @todo change en with the detected lang -let defaultLng = 'en'; -// replace this with i18next and detect the default/stored language -// const i18n = { -// t: str => str, -// }; +const defaultLng = 'en'; +let lang; const copyright = `Copyright © 2016 - ${new Date().getFullYear()} Lisk Foundation`; const protocolName = 'lisk'; @@ -38,9 +36,20 @@ const sendDetectedLang = (locale) => { } }; +// read config data from JSON file +storage.get('config', (error, data) => { + if (error) throw error; + lang = data.lang; + sendDetectedLang(lang); +}); + function createWindow() { // set language of the react app - sendDetectedLang(defaultLng); + if (lang) { + sendDetectedLang(lang); + } else { + sendDetectedLang(defaultLng); + } const { width, height } = electron.screen.getPrimaryDisplay().workAreaSize; win = new BrowserWindow({ @@ -161,10 +170,14 @@ ipcMain.on('proxyCredentialsEntered', (event, username, password) => { }); ipcMain.on('set-locale', (event, locale) => { - if (locale.substr(0, 2) !== defaultLng) { - // @todo store the locale here for next time app launches - defaultLng = locale; - i18n.changeLanguage(locale.substr(0, 2)); + const langCode = locale.substr(0, 2); + if (langCode) { + lang = langCode; + i18n.changeLanguage(langCode); + // write selected lang on JSON file + storage.set('config', { lang: langCode }, (error) => { + if (error) throw error; + }); Menu.setApplicationMenu(buildMenu(app, copyright, i18n)); event.returnValue = 'Rebuilt electron menu.'; } From e94a2989a870e898b23411ad039b645b3cfe71cb Mon Sep 17 00:00:00 2001 From: yashar Date: Tue, 17 Oct 2017 13:30:58 +0330 Subject: [PATCH 16/23] Update electron-json-file in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ddf578345..05415279d 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "del-cli": "1.1.0", "electron": "1.7.8", "electron-builder": "19.32.2", - "electron-json-storage": "^3.1.1", + "electron-json-storage": "^3.2.0", "enzyme": "2.9.1", "eslint": "4.8.0", "eslint-config-airbnb": "15.1.0", From 42e6e12b8f0ca3f533ccf20fecbc1f7c0e31fb26 Mon Sep 17 00:00:00 2001 From: yashar Date: Tue, 17 Oct 2017 14:42:37 +0330 Subject: [PATCH 17/23] Fix a bug in common.json --- src/locales/en/common.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/locales/en/common.json b/src/locales/en/common.json index a20fc6d33..6cb9faf21 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -181,6 +181,7 @@ "Zero not allowed": "Zero not allowed", "confirmation": "confirmation", "confirmations": "confirmations", + "key": "key", "logout": "logout", "my votes": "my votes", "send": "send", From c3175f8c1572ee208897203aa15244bcd0eb406a Mon Sep 17 00:00:00 2001 From: yashar Date: Tue, 17 Oct 2017 14:50:54 +0330 Subject: [PATCH 18/23] Fix a bug in menu.js --- app/src/menu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/menu.js b/app/src/menu.js index a1e2909bb..76326ba8a 100644 --- a/app/src/menu.js +++ b/app/src/menu.js @@ -129,7 +129,7 @@ module.exports = (app, copyright, i18n) => { const options = { buttons: ['OK'], icon: `${__dirname}/assets/lisk.png`, - message: `${i18n.t('Lisk Nano\nVersion')} ${app.getVersion()}\n${copyright}`, + message: `${i18n.t('Lisk Nano')} \n ${i18n.t('Version')} ${app.getVersion()}\n${copyright}`, }; electron.dialog.showMessageBox(focusedWindow, options, () => {}); } From e690f71bf73923a4e7cdffc234cbcf4d197c3997 Mon Sep 17 00:00:00 2001 From: yashar Date: Tue, 17 Oct 2017 16:24:33 +0330 Subject: [PATCH 19/23] Fix a bug in common.json and electron menu strings to it --- src/locales/en/common.json | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/locales/en/common.json b/src/locales/en/common.json index 6cb9faf21..4863ae1f8 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -53,13 +53,13 @@ "LSK received": "LSK received", "Language": "Language", "Last 24 hours": "Last 24 hours", - "Last {{count}} days": "Last {{count}} day", - "Last {{count}} days_plural": "Last {{count}} days", + "Last {{count}} days": "Last {{count}} days", + "Last {{count}} days_plural": "", "Lisk Address": "Lisk Address", "Lisk Chat": "Lisk Chat", "Lisk Explorer": "Lisk Explorer", "Lisk Forum": "Lisk Forum", - "Lisk Nano\nVersion": "Lisk Nano\nVersion", + "Lisk Nano": "Lisk Nano", "Lisk Website": "Lisk Website", "Login": "Login", "Losing access to this passphrase will mean no funds can be sent from this account.": "Losing access to this passphrase will mean no funds can be sent from this account.", @@ -159,6 +159,7 @@ "Upvotes:": "Upvotes:", "Username": "Username", "Verify message": "Verify message", + "Version": "Version", "View": "View", "Vote": "Vote", "Vote for delegates": "Vote for delegates", @@ -170,9 +171,9 @@ "Word \"{{invalidWord}}\" is not on the passphrase Word List. Most similar word on the list is \"{{similarWord}}\"": "Word \"{{invalidWord}}\" is not on the passphrase Word List. Most similar word on the list is \"{{similarWord}}\"", "Yes! It's safe": "Yes! It's safe", "You can select up to {{count}} delegates in one voting turn.": "You can select up to {{count}} delegates in one voting turn.", - "You can select up to {{count}} delegates in one voting turn._plural": "", + "You can select up to {{count}} delegates in one voting turn._plural": "You can select up to {{count}} delegates in one voting turn.", "You can vote for up to {{count}} delegates in total.": "You can vote for up to {{count}} delegates in total.", - "You can vote for up to {{count}} delegates in total._plural": "", + "You can vote for up to {{count}} delegates in total._plural": "You can vote for up to {{count}} delegates in total.", "You have not forged any blocks yet": "You have not forged any blocks yet", "You need to become a delegate to start forging. If you already registered to become a delegate, your registration hasn't been processed, yet.": "You need to become a delegate to start forging. If you already registered to become a delegate, your registration hasn't been processed, yet.", "You've received {{value}} LSK.": "You've received {{value}} LSK.", @@ -188,14 +189,14 @@ "separator": "separator", "your passphrase will be required for logging in to your account.": "your passphrase will be required for logging in to your account.", "your second passphrase will be required for all transactions sent from this account": "your second passphrase will be required for all transactions sent from this account", - "{{count}} delegate names successfully resolved to add vote to.": "{{count}} delegate names successfully resolved to add vote to.", - "{{count}} delegate names successfully resolved to add vote to._plural": "", - "{{count}} delegate names successfully resolved to remove vote from.": "{{count}} delegate names successfully resolved to remove vote from.", - "{{count}} delegate names successfully resolved to remove vote from._plural": "", + "{{count}} delegate names successfully resolved to add vote to.": "{{count}} delegate name successfully resolved to add vote to.", + "{{count}} delegate names successfully resolved to add vote to._plural": "{{count}} delegate names successfully resolved to add vote to.", + "{{count}} delegate names successfully resolved to remove vote from.": "{{count}} delegate name successfully resolved to remove vote from.", + "{{count}} delegate names successfully resolved to remove vote from._plural": "{{count}} delegate names successfully resolved to remove vote from.", "{{count}} of delegate names selected for unvote were not voted for:": "{{count}} of delegate names selected for unvote were not voted for:", - "{{count}} of delegate names selected for unvote were not voted for:_plural": "", + "{{count}} of delegate names selected for unvote were not voted for:_plural": "{{count}} of delegate names selected for unvote were not voted for:", "{{count}} of delegate names selected for vote were already voted for:": "{{count}} of delegate names selected for vote were already voted for:", - "{{count}} of delegate names selected for vote were already voted for:_plural": "", + "{{count}} of delegate names selected for vote were already voted for:_plural": "{{count}} of delegate names selected for vote were already voted for:", "{{count}} of entered delegate names could not be resolved:": "{{count}} of entered delegate names could not be resolved:", - "{{count}} of entered delegate names could not be resolved:_plural": "" + "{{count}} of entered delegate names could not be resolved:_plural": "{{count}} of entered delegate names could not be resolved:" } From 743539a97684279c22b4691c43145ed19e12914f Mon Sep 17 00:00:00 2001 From: yashar Date: Tue, 17 Oct 2017 16:34:33 +0330 Subject: [PATCH 20/23] Run npm clean and copy-files before npm run eslint in genkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index ed05cc161..459e91c34 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -62,7 +62,7 @@ node('lisk-nano') { stage ('Run Eslint') { try { ansiColor('xterm') { - sh 'npm run --silent eslint' + sh 'npm run --silent clean && npm run --silent copy-files && npm run --silent eslint' } } catch (err) { echo "Error: ${err}" From 925b1bda19806a962bc5e42744c8c3e9716e51e2 Mon Sep 17 00:00:00 2001 From: yashar Date: Tue, 17 Oct 2017 17:15:58 +0330 Subject: [PATCH 21/23] Fix a bug in menu.json --- app/src/menu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/menu.js b/app/src/menu.js index 76326ba8a..5aa16335d 100644 --- a/app/src/menu.js +++ b/app/src/menu.js @@ -16,7 +16,7 @@ const buildTemplate = i18n => label: i18n.t('Redo'), }, { - type: i18n.t('separator'), + type: 'separator', }, { role: 'cut', From e8d0f05317300f56355f52161b39783d4cc3abbd Mon Sep 17 00:00:00 2001 From: yashar Date: Tue, 17 Oct 2017 17:16:43 +0330 Subject: [PATCH 22/23] Change some translation strings in common.json --- src/locales/en/common.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/locales/en/common.json b/src/locales/en/common.json index 4863ae1f8..73067cd27 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -53,8 +53,8 @@ "LSK received": "LSK received", "Language": "Language", "Last 24 hours": "Last 24 hours", - "Last {{count}} days": "Last {{count}} days", - "Last {{count}} days_plural": "", + "Last {{count}} days": "Last {{count}} day", + "Last {{count}} days_plural": "Last {{count}} days", "Lisk Address": "Lisk Address", "Lisk Chat": "Lisk Chat", "Lisk Explorer": "Lisk Explorer", From c44b4762c098d4f9c4d7ef9d93d99abbef87636b Mon Sep 17 00:00:00 2001 From: yashar Date: Wed, 18 Oct 2017 01:00:20 +0330 Subject: [PATCH 23/23] Fix a bug in ipcLocale test file --- src/utils/ipcLocale.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/utils/ipcLocale.test.js b/src/utils/ipcLocale.test.js index 2dce661fa..85c6d5894 100644 --- a/src/utils/ipcLocale.test.js +++ b/src/utils/ipcLocale.test.js @@ -14,16 +14,19 @@ describe('ipcLocale', () => { }; describe('init', () => { - it('should be a function', () => { - expect(typeof ipcLocale.init).to.be.equal('function'); + beforeEach(() => { + delete window.ipc; }); - it('calling init when ipc is not on window should do nothing', () => { ipcLocale.init(); expect(ipc.on).to.not.have.been.calledWith(); expect(ipc.send).to.not.have.been.calledWith(); }); + it('should be a function', () => { + expect(typeof ipcLocale.init).to.be.equal('function'); + }); + it('calling init when ipc is available on window should bind listeners', () => { window.ipc = ipc; ipcLocale.init(i18n);