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..4a3556c37e67 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').setup(extensionId) } diff --git a/lib/renderer/extensions/i18n.js b/lib/renderer/extensions/i18n.js new file mode 100644 index 000000000000..d0279601f43d --- /dev/null +++ b/lib/renderer/extensions/i18n.js @@ -0,0 +1,84 @@ +// 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') + +let metadata + +const getExtensionMetadata = (extensionId) => { + if (!metadata) { + metadata = ipcRenderer.sendSync('CHROME_I18N_MANIFEST', extensionId) + } + return metadata +} + +const getMessagesPath = (extensionId, language) => { + const metadata = getExtensionMetadata(extensionId) + const defaultLocale = metadata.default_locale || 'en' + const localesDirectory = path.join(metadata.srcDirectory, '_locales') + let messagesPath = path.join(localesDirectory, language, 'messages.json') + if (!fs.statSyncNoException(messagesPath)) { + messagesPath = path.join(localesDirectory, defaultLocale, 'messages.json') + } + return messagesPath +} + +const getMessages = (extensionId, language) => { + try { + const messagesPath = getMessagesPath(extensionId, language) + return JSON.parse(fs.readFileSync(messagesPath)) || {} + } catch (error) { + return {} + } +} + +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) +} + +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) + } + } +} 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 @@