From 84960af793d0a43166400f5024c469ba762cfc1e Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Jun 2016 15:34:17 -0700 Subject: [PATCH 1/9] Add initial chrome.i18n.getMessage API --- filenames.gypi | 1 + lib/browser/chrome-extension.js | 4 ++++ lib/renderer/chrome-api.js | 2 ++ lib/renderer/extensions/i18n.js | 34 +++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+) create mode 100644 lib/renderer/extensions/i18n.js diff --git a/filenames.gypi b/filenames.gypi index 5f1fdc3b141f..af1942fc3697 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -63,6 +63,7 @@ 'lib/renderer/api/remote.js', 'lib/renderer/api/screen.js', 'lib/renderer/api/web-frame.js', + 'lib/renderer/extensions/i18n.js', ], 'js2c_sources': [ 'lib/common/asar.js', diff --git a/lib/browser/chrome-extension.js b/lib/browser/chrome-extension.js index fc49f5e06be5..97bf292f9e23 100644 --- a/lib/browser/chrome-extension.js +++ b/lib/browser/chrome-extension.js @@ -115,6 +115,10 @@ ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) page.webContents.sendToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo) }) +ipcMain.on('CHROME_I18N_MANIFEST', function (event, extensionId) { + event.returnValue = manifestMap[extensionId] +}) + ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message) { const page = backgroundPages[extensionId] if (!page) { diff --git a/lib/renderer/chrome-api.js b/lib/renderer/chrome-api.js index 17c3f5bfbd1a..b22fe743fd2d 100644 --- a/lib/renderer/chrome-api.js +++ b/lib/renderer/chrome-api.js @@ -199,4 +199,6 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) { setPopup () {}, getPopup () {} } + + chrome.i18n = require('./extensions/i18n.js') } diff --git a/lib/renderer/extensions/i18n.js b/lib/renderer/extensions/i18n.js new file mode 100644 index 000000000000..34a559bedb39 --- /dev/null +++ b/lib/renderer/extensions/i18n.js @@ -0,0 +1,34 @@ +const {ipcRenderer} = require('electron') +const fs = require('fs') +const path = require('path') + +const getMessagesPath = (language) => { + const manifest = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', chrome.runtime.id) + let messagesPath = path.join(manifest.srcDirectory, '_locales', language, 'messages.json') + if (!fs.statSyncNoException(messagesPath)) { + messagesPath = path.join(manifest.srcDirectory, '_locales', manifest.default_locale, 'messages.json') + } + return messagesPath +} + +const getMessages = (language) => { + try { + return JSON.parse(fs.readFileSync(getMessagesPath(language))) || {} + } catch (error) { + return {} + } +} + +const getLanguage = () => { + return navigator.language.replace(/-.*$/, '').toLowerCase() +} + +module.exports = { + getMessage (messageName, substitutions) { + const language = getLanguage() + const messages = getMessages(language) + if (messages.hasOwnProperty(messageName)) { + return messages[messageName].message + } + } +} From d54de73e0322d8148d5c303ace4899caded8baf6 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Jun 2016 15:36:08 -0700 Subject: [PATCH 2/9] :art: --- lib/renderer/extensions/i18n.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/renderer/extensions/i18n.js b/lib/renderer/extensions/i18n.js index 34a559bedb39..168a29ef577f 100644 --- a/lib/renderer/extensions/i18n.js +++ b/lib/renderer/extensions/i18n.js @@ -2,11 +2,16 @@ const {ipcRenderer} = require('electron') const fs = require('fs') const path = require('path') +const getExtensionMetadata = () => { + return ipcRenderer.sendSync('CHROME_I18N_MANIFEST', chrome.runtime.id) +} + const getMessagesPath = (language) => { - const manifest = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', chrome.runtime.id) - let messagesPath = path.join(manifest.srcDirectory, '_locales', language, 'messages.json') + const {srcDirectory, default_locale} = getExtensionMetadata() + const localesDirectory = path.join(srcDirectory, '_locales') + let messagesPath = path.join(localesDirectory, language, 'messages.json') if (!fs.statSyncNoException(messagesPath)) { - messagesPath = path.join(manifest.srcDirectory, '_locales', manifest.default_locale, 'messages.json') + messagesPath = path.join(localesDirectory, default_locale, 'messages.json') } return messagesPath } From d4925e6226771f727f48da1c5bf46a75e08128e3 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Jun 2016 16:03:43 -0700 Subject: [PATCH 3/9] Add initial support for placeholders and substitutions --- lib/renderer/extensions/i18n.js | 35 ++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/renderer/extensions/i18n.js b/lib/renderer/extensions/i18n.js index 168a29ef577f..b14499e2b208 100644 --- a/lib/renderer/extensions/i18n.js +++ b/lib/renderer/extensions/i18n.js @@ -1,3 +1,9 @@ +// Implementation of chrome.i18n.getMessage +// https://developer.chrome.com/extensions/i18n#method-getMessage +// +// Does not implement predefined messages: +// https://developer.chrome.com/extensions/i18n#overview-predefined + const {ipcRenderer} = require('electron') const fs = require('fs') const path = require('path') @@ -28,12 +34,39 @@ const getLanguage = () => { return navigator.language.replace(/-.*$/, '').toLowerCase() } +const replaceNumberedSubstitutions = (message, substitutions) => { + return message.replace(/\$(\d+)/, (_, number) => { + const index = parseInt(number, 10) - 1 + return substitutions[index] || '' + }) +} + +const replacePlaceholders = (message, placeholders, substitutions) => { + if (typeof substitutions === 'string') { + substitutions = [substitutions] + } + if (!Array.isArray(substitutions)) { + substitutions = [] + } + + if (placeholders) { + Object.keys(placeholders).forEach((name) => { + let {content} = placeholders[name] + content = replaceNumberedSubstitutions(content, substitutions) + message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content) + }) + } + + return replaceNumberedSubstitutions(message, substitutions) +} + module.exports = { getMessage (messageName, substitutions) { const language = getLanguage() const messages = getMessages(language) if (messages.hasOwnProperty(messageName)) { - return messages[messageName].message + const {message, placeholders} = messages[messageName] + return replacePlaceholders(message, placeholders, substitutions) } } } From ea9d2dadf8a9a223e26839855eda536e3da237b5 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Jun 2016 16:04:17 -0700 Subject: [PATCH 4/9] Add fixme for sync ipc --- lib/renderer/extensions/i18n.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/renderer/extensions/i18n.js b/lib/renderer/extensions/i18n.js index b14499e2b208..5085365c7beb 100644 --- a/lib/renderer/extensions/i18n.js +++ b/lib/renderer/extensions/i18n.js @@ -9,6 +9,7 @@ const fs = require('fs') const path = require('path') const getExtensionMetadata = () => { + // FIXME(kevinsawicki) Either cache this or don't use sync IPC to obtain it return ipcRenderer.sendSync('CHROME_I18N_MANIFEST', chrome.runtime.id) } From 2367cd574e11dbedf4bbc16f589d16f358881343 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Jun 2016 16:10:27 -0700 Subject: [PATCH 5/9] Cache extension metadata --- lib/renderer/extensions/i18n.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/renderer/extensions/i18n.js b/lib/renderer/extensions/i18n.js index 5085365c7beb..d6362e3954f2 100644 --- a/lib/renderer/extensions/i18n.js +++ b/lib/renderer/extensions/i18n.js @@ -8,9 +8,13 @@ const {ipcRenderer} = require('electron') const fs = require('fs') const path = require('path') +let metadata + const getExtensionMetadata = () => { - // FIXME(kevinsawicki) Either cache this or don't use sync IPC to obtain it - return ipcRenderer.sendSync('CHROME_I18N_MANIFEST', chrome.runtime.id) + if (!metadata) { + metadata = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', chrome.runtime.id) + } + return metadata } const getMessagesPath = (language) => { From 9f64c39f8c6a30f1bea4cae331ea14cfc9612ba8 Mon Sep 17 00:00:00 2001 From: Kevin Sawicki Date: Tue, 7 Jun 2016 17:00:53 -0700 Subject: [PATCH 6/9] Remove lint warnings --- lib/renderer/chrome-api.js | 2 +- lib/renderer/extensions/i18n.js | 32 +++++++++++++++++++------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/renderer/chrome-api.js b/lib/renderer/chrome-api.js index b22fe743fd2d..4a3556c37e67 100644 --- a/lib/renderer/chrome-api.js +++ b/lib/renderer/chrome-api.js @@ -200,5 +200,5 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) { getPopup () {} } - chrome.i18n = require('./extensions/i18n.js') + chrome.i18n = require('./extensions/i18n.js').setup(extensionId) } diff --git a/lib/renderer/extensions/i18n.js b/lib/renderer/extensions/i18n.js index d6362e3954f2..cee0fb17aa80 100644 --- a/lib/renderer/extensions/i18n.js +++ b/lib/renderer/extensions/i18n.js @@ -10,15 +10,15 @@ const path = require('path') let metadata -const getExtensionMetadata = () => { +const getExtensionMetadata = (extensionId) => { if (!metadata) { - metadata = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', chrome.runtime.id) + metadata = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', extensionId) } return metadata } -const getMessagesPath = (language) => { - const {srcDirectory, default_locale} = getExtensionMetadata() +const getMessagesPath = (extensionId, language) => { + const {srcDirectory, default_locale} = getExtensionMetadata(extensionId) const localesDirectory = path.join(srcDirectory, '_locales') let messagesPath = path.join(localesDirectory, language, 'messages.json') if (!fs.statSyncNoException(messagesPath)) { @@ -27,9 +27,10 @@ const getMessagesPath = (language) => { return messagesPath } -const getMessages = (language) => { +const getMessages = (extensionId, language) => { try { - return JSON.parse(fs.readFileSync(getMessagesPath(language))) || {} + const messagesPath = getMessagesPath(extensionId, language) + return JSON.parse(fs.readFileSync(messagesPath)) || {} } catch (error) { return {} } @@ -65,13 +66,18 @@ const replacePlaceholders = (message, placeholders, substitutions) => { return replaceNumberedSubstitutions(message, substitutions) } -module.exports = { - getMessage (messageName, substitutions) { - const language = getLanguage() - const messages = getMessages(language) - if (messages.hasOwnProperty(messageName)) { - const {message, placeholders} = messages[messageName] - return replacePlaceholders(message, placeholders, substitutions) +const getMessage = (extensionId, messageName, substitutions) => { + const messages = getMessages(extensionId, getLanguage()) + if (messages.hasOwnProperty(messageName)) { + const {message, placeholders} = messages[messageName] + return replacePlaceholders(message, placeholders, substitutions) + } +} + +exports.setup = (extensionId) => { + return { + getMessage (messageName, substitutions) { + return getMessage(extensionId, messageName, substitutions) } } } From 0ed10658a3971170e6bd6b65cea63b67c4f21e4e Mon Sep 17 00:00:00 2001 From: Jessica Lord Date: Wed, 8 Jun 2016 14:57:20 -0700 Subject: [PATCH 7/9] Add spec --- spec/api-browser-window-spec.js | 1 + .../devtools-extensions/foo/_locales/en/messages.json | 10 ++++++++++ spec/fixtures/devtools-extensions/foo/index.html | 3 ++- spec/fixtures/devtools-extensions/foo/manifest.json | 3 ++- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/devtools-extensions/foo/_locales/en/messages.json diff --git a/spec/api-browser-window-spec.js b/spec/api-browser-window-spec.js index bad3b80a9b5c..cf3442d59ce4 100644 --- a/spec/api-browser-window-spec.js +++ b/spec/api-browser-window-spec.js @@ -865,6 +865,7 @@ describe('browser-window module', function () { ipcMain.once('answer', function (event, message) { assert.equal(message.runtimeId, 'foo') assert.equal(message.tabId, w.webContents.id) + assert.equal(message.i18nString, 'foo - bar (baz)') done() }) }) diff --git a/spec/fixtures/devtools-extensions/foo/_locales/en/messages.json b/spec/fixtures/devtools-extensions/foo/_locales/en/messages.json new file mode 100644 index 000000000000..fe5653c2b411 --- /dev/null +++ b/spec/fixtures/devtools-extensions/foo/_locales/en/messages.json @@ -0,0 +1,10 @@ +{ + "foo": { + "message": "foo - $BAZ$ ($2)", + "placeholders": { + "baz": { + "content": "$1" + } + } + } +} diff --git a/spec/fixtures/devtools-extensions/foo/index.html b/spec/fixtures/devtools-extensions/foo/index.html index 2e0a29aff3d0..414370ae2c06 100644 --- a/spec/fixtures/devtools-extensions/foo/index.html +++ b/spec/fixtures/devtools-extensions/foo/index.html @@ -6,7 +6,8 @@