diff --git a/README.md b/README.md index d035bd7..bbd4b96 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,15 @@ $PROFILE` in which case it becomes `Element-$PROFILE`, or it is using one of the above created by a pre-1.7 install, in which case it will be `Riot` or `Riot-$PROFILE`. +Translations +========================== + +To add a new translation, head to the [translating doc](https://github.com/vector-im/element-web/blob/develop/docs/translating.md). + +For a developer guide, see the [translating dev doc](https://github.com/vector-im/element-web/blob/develop/docs/translating-dev.md). + +[translationsstatus](https://translate.element.io/engage/element-desktop/?utm_source=widget) + Report bugs & give feedback ========================== diff --git a/package.json b/package.json index e1f070e..7b0c839 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "license": "Apache-2.0", "files": [], "scripts": { + "i18n": "matrix-gen-i18n", + "prunei18n": "matrix-prune-i18n", + "diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && matrix-gen-i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json", "mkdirs": "mkdirp packages deploys", "fetch": "yarn run mkdirs && node scripts/fetch-package.js", "asar-webapp": "asar p webapp webapp.asar", @@ -31,6 +34,7 @@ }, "dependencies": { "auto-launch": "^5.0.5", + "counterpart": "^0.18.6", "electron-store": "^6.0.1", "electron-window-state": "^5.0.3", "minimist": "^1.2.3", @@ -48,6 +52,7 @@ "find-npm-prefix": "^1.0.2", "fs-extra": "^8.1.0", "glob": "^7.1.6", + "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", "mkdirp": "^1.0.3", "needle": "^2.5.0", "node-pre-gyp": "^0.15.0", diff --git a/src/electron-main.js b/src/electron-main.js index a53d559..e98da5c 100644 --- a/src/electron-main.js +++ b/src/electron-main.js @@ -34,7 +34,7 @@ const AutoLaunch = require('auto-launch'); const path = require('path'); const tray = require('./tray'); -const vectorMenu = require('./vectormenu'); +const buildMenuTemplate = require('./vectormenu'); const webContentsHandler = require('./webcontents-handler'); const updater = require('./updater'); const {getProfileFromDeeplink, protocolInit, recordSSOSession} = require('./protocol'); @@ -57,6 +57,8 @@ try { } } +const { _t, AppLocalization } = require('./language-helper'); + let seshatSupported = false; let Seshat; let SeshatRecovery; @@ -84,6 +86,7 @@ let vectorConfig; let iconPath; let trayConfig; let launcher; +let appLocalization; if (argv["help"]) { console.log("Options:"); @@ -266,8 +269,8 @@ const warnBeforeExit = (event, input) => { if (shouldWarnBeforeExit && exitShortcutPressed) { const shouldCancelCloseRequest = dialog.showMessageBoxSync(mainWindow, { type: "question", - buttons: ["Cancel", "Close Element"], - message: "Are you sure you want to quit?", + buttons: [_t("Cancel"), _t("Close Element")], + message: _t("Are you sure you want to quit?"), defaultId: 1, cancelId: 0, }) === 0; @@ -364,6 +367,9 @@ ipcMain.on('ipcCall', async function(ev, payload) { launcher.disable(); } break; + case 'setLanguage': + appLocalization.setAppLocale(args[0]); + break; case 'shouldWarnBeforeExit': ret = store.get('warnBeforeExit', true); break; @@ -915,7 +921,6 @@ app.on('ready', async () => { }, }); mainWindow.loadURL('vector://vector/webapp/'); - Menu.setApplicationMenu(vectorMenu); // Handle spellchecker // For some reason spellCheckerEnabled isn't persisted so we have to use the store here @@ -964,6 +969,14 @@ app.on('ready', async () => { } webContentsHandler(mainWindow.webContents); + + appLocalization = new AppLocalization({ + store, + components: [ + () => tray.initApplicationMenu(), + () => Menu.setApplicationMenu(buildMenuTemplate()), + ], + }); }); app.on('window-all-closed', () => { diff --git a/src/i18n/strings/basefile.json b/src/i18n/strings/basefile.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/src/i18n/strings/basefile.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json new file mode 100644 index 0000000..b5a97fc --- /dev/null +++ b/src/i18n/strings/en_EN.json @@ -0,0 +1,46 @@ +{ + "Cancel": "Cancel", + "Close Element": "Close Element", + "Are you sure you want to quit?": "Are you sure you want to quit?", + "Show/Hide": "Show/Hide", + "Quit": "Quit", + "Edit": "Edit", + "Undo": "Undo", + "Redo": "Redo", + "Cut": "Cut", + "Copy": "Copy", + "Paste": "Paste", + "Paste and Match Style": "Paste and Match Style", + "Delete": "Delete", + "Select All": "Select All", + "View": "View", + "Actual Size": "Actual Size", + "Zoom In": "Zoom In", + "Zoom Out": "Zoom Out", + "Preferences": "Preferences", + "Toggle Full Screen": "Toggle Full Screen", + "Toggle Developer Tools": "Toggle Developer Tools", + "Window": "Window", + "Minimize": "Minimize", + "Close": "Close", + "Help": "Help", + "Element Help": "Element Help", + "About": "About", + "Services": "Services", + "Hide": "Hide", + "Hide Others": "Hide Others", + "Unhide": "Unhide", + "Speech": "Speech", + "Start Speaking": "Start Speaking", + "Stop Speaking": "Stop Speaking", + "Zoom": "Zoom", + "Bring All to Front": "Bring All to Front", + "File": "File", + "Copy image": "Copy image", + "Copy email address": "Copy email address", + "Copy link address": "Copy link address", + "Save image as...": "Save image as...", + "Failed to save image": "Failed to save image", + "The image failed to save": "The image failed to save", + "Add to dictionary": "Add to dictionary" +} diff --git a/src/language-helper.js b/src/language-helper.js new file mode 100644 index 0000000..baf6c46 --- /dev/null +++ b/src/language-helper.js @@ -0,0 +1,129 @@ +/* +Copyright 2021 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +const counterpart = require('counterpart'); + +const DEFAULT_LOCALE = "en"; + +function _td(text) { + return text; +} + +function _t(text, variables = {}) { + const args = Object.assign({ interpolate: false }, variables); + + const { count } = args; + + // Horrible hack to avoid https://github.com/vector-im/element-web/issues/4191 + // The interpolation library that counterpart uses does not support undefined/null + // values and instead will throw an error. This is a problem since everywhere else + // in JS land passing undefined/null will simply stringify instead, and when converting + // valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null + // if there are no existing null guards. To avoid this making the app completely inoperable, + // we'll check all the values for undefined/null and stringify them here. + Object.keys(args).forEach((key) => { + if (args[key] === undefined) { + console.warn("safeCounterpartTranslate called with undefined interpolation name: " + key); + args[key] = 'undefined'; + } + if (args[key] === null) { + console.warn("safeCounterpartTranslate called with null interpolation name: " + key); + args[key] = 'null'; + } + }); + let translated = counterpart.translate(text, args); + if (translated === undefined && count !== undefined) { + // counterpart does not do fallback if no pluralisation exists + // in the preferred language, so do it here + translated = counterpart.translate(text, Object.assign({}, args, {locale: DEFAULT_LOCALE})); + } + + // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution) + return translated; +} + +class AppLocalization { + static STORE_KEY = "locale" + store = null + + constructor({ store, components = [] }) { + counterpart.registerTranslations("en", this.fetchTranslationJson("en_EN")); + counterpart.setFallbackLocale('en'); + counterpart.setSeparator('|'); + + if (Array.isArray(components)) { + this.localizedComponents = new Set(components); + } + + this.store = store; + if (this.store.has(AppLocalization.STORE_KEY)) { + const locales = this.store.get(AppLocalization.STORE_KEY); + this.setAppLocale(locales); + } + + this.resetLocalizedUI(); + } + + fetchTranslationJson(locale) { + try { + console.log("Fetching translation json for locale: " + locale); + return require(`./i18n/strings/${locale}.json`); + } catch (e) { + console.log(`Could not fetch translation json for locale: '${locale}'`, e); + return null; + } + } + + get languageTranslationJson() { + return this.translationJsonMap.get(this.language); + } + + setAppLocale(locales) { + console.log(`Changing application language to ${locales}`); + + if (!Array.isArray(locales)) { + locales = [locales]; + } + + locales.forEach(locale => { + const translations = this.fetchTranslationJson(locale); + if (translations !== null) { + counterpart.registerTranslations(locale, translations); + } + }); + + counterpart.setLocale(locales); + this.store.set(AppLocalization.STORE_KEY, locales); + + this.resetLocalizedUI(); + } + + resetLocalizedUI() { + console.log("Resetting the UI components after locale change"); + this.localizedComponents.forEach(componentSetup => { + if (typeof componentSetup === "function") { + componentSetup(); + } + }); + } +} + + +module.exports = { + AppLocalization, + _t, + _td, +}; diff --git a/src/tray.js b/src/tray.js index e235e77..7e43025 100644 --- a/src/tray.js +++ b/src/tray.js @@ -19,6 +19,7 @@ const {app, Tray, Menu, nativeImage} = require('electron'); const pngToIco = require('png-to-ico'); const path = require('path'); const fs = require('fs'); +const { _t } = require('./language-helper'); let trayIcon = null; @@ -33,39 +34,24 @@ exports.destroy = function() { } }; +const toggleWin = function() { + if (global.mainWindow.isVisible() && !global.mainWindow.isMinimized()) { + global.mainWindow.hide(); + } else { + if (global.mainWindow.isMinimized()) global.mainWindow.restore(); + if (!global.mainWindow.isVisible()) global.mainWindow.show(); + global.mainWindow.focus(); + } +}; + exports.create = function(config) { // no trays on darwin if (process.platform === 'darwin' || trayIcon) return; - - const toggleWin = function() { - if (global.mainWindow.isVisible() && !global.mainWindow.isMinimized()) { - global.mainWindow.hide(); - } else { - if (global.mainWindow.isMinimized()) global.mainWindow.restore(); - if (!global.mainWindow.isVisible()) global.mainWindow.show(); - global.mainWindow.focus(); - } - }; - - const contextMenu = Menu.buildFromTemplate([ - { - label: `Show/Hide ${config.brand}`, - click: toggleWin, - }, - { type: 'separator' }, - { - label: 'Quit', - click: function() { - app.quit(); - }, - }, - ]); - const defaultIcon = nativeImage.createFromPath(config.icon_path); trayIcon = new Tray(defaultIcon); trayIcon.setToolTip(config.brand); - trayIcon.setContextMenu(contextMenu); + initApplicationMenu(); trayIcon.on('click', toggleWin); let lastFavicon = null; @@ -104,3 +90,27 @@ exports.create = function(config) { trayIcon.setToolTip(title); }); }; + +function initApplicationMenu() { + if (!trayIcon) { + return; + } + + const contextMenu = Menu.buildFromTemplate([ + { + label: _t('Show/Hide'), + click: toggleWin, + }, + { type: 'separator' }, + { + label: _t('Quit'), + click: function() { + app.quit(); + }, + }, + ]); + + trayIcon.setContextMenu(contextMenu); +} + +exports.initApplicationMenu = initApplicationMenu; diff --git a/src/vectormenu.js b/src/vectormenu.js index 66fa573..8f1fb63 100644 --- a/src/vectormenu.js +++ b/src/vectormenu.js @@ -15,129 +15,212 @@ limitations under the License. */ const {app, shell, Menu} = require('electron'); +const { _t } = require('./language-helper'); -// Menu template from http://electron.atom.io/docs/api/menu/, edited -const template = [ - { - label: '&Edit', - submenu: [ - { role: 'undo' }, - { role: 'redo' }, - { type: 'separator' }, - { role: 'cut' }, - { role: 'copy' }, - { role: 'paste' }, - { role: 'pasteandmatchstyle' }, - { role: 'delete' }, - { role: 'selectall' }, - ], - }, - { - label: '&View', - submenu: [ - { type: 'separator' }, - { role: 'resetzoom' }, - { role: 'zoomin', accelerator: 'CommandOrControl+=' }, - { role: 'zoomout' }, - { type: 'separator' }, - { - label: 'Preferences', - accelerator: 'Command+,', // Mac-only accelerator - click() { global.mainWindow.webContents.send('preferences'); }, - }, - { role: 'togglefullscreen' }, - { role: 'toggledevtools' }, - ], - }, - { - label: '&Window', - role: 'window', - submenu: [ - { role: 'minimize' }, - { role: 'close' }, - ], - }, - { - label: '&Help', - role: 'help', - submenu: [ - { - label: 'Element Help', - click() { shell.openExternal('https://element.io/help'); }, - }, - ], - }, -]; - -// macOS has specific menu conventions... -if (process.platform === 'darwin') { - template.unshift({ - // first macOS menu is the name of the app - label: app.name, - submenu: [ - { role: 'about' }, - { type: 'separator' }, - { - role: 'services', - submenu: [], - }, - { type: 'separator' }, - { role: 'hide' }, - { role: 'hideothers' }, - { role: 'unhide' }, - { type: 'separator' }, - { role: 'quit' }, - ], - }); - // Edit menu. - // This has a 'speech' section on macOS - template[1].submenu.push( - { type: 'separator' }, +function buildMenuTemplate() { + // Menu template from http://electron.atom.io/docs/api/menu/, edited + const template = [ { - label: 'Speech', + label: _t('Edit'), + accelerator: 'e', submenu: [ - { role: 'startspeaking' }, - { role: 'stopspeaking' }, + { + role: 'undo', + label: _t('Undo'), + }, + { + role: 'redo', + label: _t('Redo'), + }, + { type: 'separator' }, + { + role: 'cut', + label: _t('Cut'), + }, + { + role: 'copy', + label: _t('Copy'), + }, + { + role: 'paste', + label: _t('Paste'), + }, + { + role: 'pasteandmatchstyle', + label: _t('Paste and Match Style'), + }, + { + role: 'delete', + label: _t('Delete'), + }, + { + role: 'selectall', + label: _t('Select All'), + }, ], - }); - - // Window menu. - // This also has specific functionality on macOS - template[3].submenu = [ - { - label: 'Close', - accelerator: 'CmdOrCtrl+W', - role: 'close', }, { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize', + label: _t('View'), + accelerator: 'V', + submenu: [ + { type: 'separator' }, + { + role: 'resetzoom', + label: _t('Actual Size'), + }, + { + role: 'zoomin', + accelerator: 'CommandOrControl+=', + label: _t('Zoom In'), + }, + { + role: 'zoomout', + label: _t('Zoom Out'), + }, + { type: 'separator' }, + { + label: _t('Preferences'), + accelerator: 'Command+,', // Mac-only accelerator + click() { global.mainWindow.webContents.send('preferences'); }, + }, + { + role: 'togglefullscreen', + label: _t('Toggle Full Screen'), + }, + { + role: 'toggledevtools', + label: _t('Toggle Developer Tools'), + }, + ], }, { - label: 'Zoom', - role: 'zoom', + label: _t('Window'), + accelerator: 'w', + role: 'window', + submenu: [ + { + role: 'minimize', + label: _t('Minimize'), + }, + { + role: 'close', + label: _t('Close'), + }, + ], }, { - type: 'separator', - }, - { - label: 'Bring All to Front', - role: 'front', + label: _t('Help'), + accelerator: 'h', + role: 'help', + submenu: [ + { + label: _t('Element Help'), + click() { shell.openExternal('https://element.io/help'); }, + }, + ], }, ]; -} else { - template.unshift({ - label: '&File', - submenu: [ - // For some reason, 'about' does not seem to work on windows. - /*{ - role: 'about' - },*/ - { role: 'quit' }, - ], - }); + + // macOS has specific menu conventions... + if (process.platform === 'darwin') { + template.unshift({ + // first macOS menu is the name of the app + role: 'appMenu', + label: app.name, + submenu: [ + { + role: 'about', + label: _t('About'), + }, + { type: 'separator' }, + { + role: 'services', + label: _t('Services'), + submenu: [], + }, + { type: 'separator' }, + { + role: 'hide', + label: _t('Hide'), + }, + { + role: 'hideothers', + label: _t('Hide Others'), + }, + { + role: 'unhide', + label: _t('Unhide'), + }, + { type: 'separator' }, + { + role: 'quit', + label: _t('Quit'), + }, + ], + }); + // Edit menu. + // This has a 'speech' section on macOS + template[1].submenu.push( + { type: 'separator' }, + { + label: _t('Speech'), + submenu: [ + { + role: 'startspeaking', + label: _t('Start Speaking'), + }, + { + role: 'stopspeaking', + label: _t('Stop Speaking'), + }, + ], + }); + + // Window menu. + // This also has specific functionality on macOS + template[3].submenu = [ + { + label: _t('Close'), + accelerator: 'CmdOrCtrl+W', + role: 'close', + }, + { + label: _t('Minimize'), + accelerator: 'CmdOrCtrl+M', + role: 'minimize', + }, + { + label: _t('Zoom'), + role: 'zoom', + }, + { + type: 'separator', + }, + { + label: _t('Bring All to Front'), + role: 'front', + }, + ]; + } else { + template.unshift({ + label: _t('File'), + accelerator: 'f', + submenu: [ + // For some reason, 'about' does not seem to work on windows. + /*{ + role: 'about', + label: _t('About'), + },*/ + { + role: 'quit', + label: _t('Quit'), + }, + ], + }); + } + + return Menu.buildFromTemplate(template); } -module.exports = Menu.buildFromTemplate(template); +module.exports = buildMenuTemplate; diff --git a/src/webcontents-handler.js b/src/webcontents-handler.js index 3201772..24523e7 100644 --- a/src/webcontents-handler.js +++ b/src/webcontents-handler.js @@ -3,6 +3,7 @@ const url = require('url'); const fs = require('fs'); const request = require('request'); const path = require('path'); +const { _t } = require('./language-helper'); const MAILTO_PREFIX = "mailto:"; @@ -73,7 +74,8 @@ function onLinkContextMenu(ev, params) { if (params.hasImageContents) { popupMenu.append(new MenuItem({ - label: '&Copy image', + label: _t('Copy image'), + accelerator: 'c', click() { ev.sender.copyImageAt(params.x, params.y); }, @@ -85,14 +87,16 @@ function onLinkContextMenu(ev, params) { // Special-case e-mail URLs to strip the `mailto:` like modern browsers do if (url.startsWith(MAILTO_PREFIX)) { popupMenu.append(new MenuItem({ - label: 'Copy email &address', + label: _t('Copy email address'), + accelerator: 'a', click() { clipboard.writeText(url.substr(MAILTO_PREFIX.length)); }, })); } else { popupMenu.append(new MenuItem({ - label: 'Copy link &address', + label: _t('Copy link address'), + accelerator: 'a', click() { clipboard.writeText(url); }, @@ -104,7 +108,8 @@ function onLinkContextMenu(ev, params) { // only the renderer can resolve them so don't give the user an option to. if (params.hasImageContents && !url.startsWith('blob:')) { popupMenu.append(new MenuItem({ - label: 'Sa&ve image as...', + label: _t('Save image as...'), + accelerator: 'a', async click() { const targetFileName = params.titleText || "image.png"; const {filePath} = await dialog.showSaveDialog({ @@ -123,8 +128,8 @@ function onLinkContextMenu(ev, params) { console.error(err); dialog.showMessageBox({ type: "error", - title: "Failed to save image", - message: "The image failed to save", + title: _t("Failed to save image"), + message: _t("The image failed to save"), }); } }, @@ -151,7 +156,7 @@ function _CutCopyPasteSelectContextMenus(params) { options.push({ type: 'separator', }, { - label: 'Add to dictionary', + label: _t('Add to dictionary'), click: (menuItem, browserWindow) => { browserWindow.webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord); }, @@ -162,22 +167,26 @@ function _CutCopyPasteSelectContextMenus(params) { options.push({ role: 'cut', - label: 'Cu&t', + label: _t('Cut'), + accelerator: 't', enabled: params.editFlags.canCut, }, { role: 'copy', - label: '&Copy', + label: _t('Copy'), + accelerator: 'c', enabled: params.editFlags.canCopy, }, { role: 'paste', - label: '&Paste', + label: _t('Paste'), + accelerator: 'p', enabled: params.editFlags.canPaste, }, { role: 'pasteandmatchstyle', enabled: params.editFlags.canPaste, }, { role: 'selectall', - label: "Select &All", + label: _t("Select All"), + accelerator: 'a', enabled: params.editFlags.canSelectAll, }); return options; diff --git a/yarn.lock b/yarn.lock index 3ad5c5e..1b9aedb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,6 +26,13 @@ dependencies: "@babel/highlight" "^7.10.4" +"@babel/code-frame@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" + integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== + dependencies: + "@babel/highlight" "^7.12.13" + "@babel/generator@^7.10.5": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69" @@ -35,6 +42,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.13.16": + version "7.13.16" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.16.tgz#0befc287031a201d84cdfc173b46b320ae472d14" + integrity sha512-grBBR75UnKOcUWMp8WoDxNsWCFl//XCK6HWTrBQKTr5SV9f5g0pNOjdyzi/DTBv12S9GnYPInIXQBTky7OXEMg== + dependencies: + "@babel/types" "^7.13.16" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-function-name@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" @@ -44,6 +60,15 @@ "@babel/template" "^7.10.4" "@babel/types" "^7.10.4" +"@babel/helper-function-name@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a" + integrity sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA== + dependencies: + "@babel/helper-get-function-arity" "^7.12.13" + "@babel/template" "^7.12.13" + "@babel/types" "^7.12.13" + "@babel/helper-get-function-arity@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" @@ -51,6 +76,13 @@ dependencies: "@babel/types" "^7.10.4" +"@babel/helper-get-function-arity@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" + integrity sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg== + dependencies: + "@babel/types" "^7.12.13" + "@babel/helper-split-export-declaration@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1" @@ -58,11 +90,23 @@ dependencies: "@babel/types" "^7.10.4" +"@babel/helper-split-export-declaration@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" + integrity sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg== + dependencies: + "@babel/types" "^7.12.13" + "@babel/helper-validator-identifier@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== +"@babel/helper-validator-identifier@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== + "@babel/highlight@^7.0.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" @@ -81,11 +125,25 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.12.13": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1" + integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.7.0": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== +"@babel/parser@^7.12.13", "@babel/parser@^7.13.16": + version "7.13.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.16.tgz#0f18179b0448e6939b1f3f5c4c355a3a9bcdfd37" + integrity sha512-6bAg36mCwuqLO0hbR+z7PHuqWiCeP7Dzg73OpQwsAB1Eb8HnGEz5xYBzCfbu+YjoaJsJs+qheDxVAuqbt3ILEw== + "@babel/runtime@^7.7.2": version "7.11.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" @@ -102,6 +160,29 @@ "@babel/parser" "^7.10.4" "@babel/types" "^7.10.4" +"@babel/template@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" + integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/parser" "^7.12.13" + "@babel/types" "^7.12.13" + +"@babel/traverse@^7.13.17": + version "7.13.17" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.17.tgz#c85415e0c7d50ac053d758baec98b28b2ecfeea3" + integrity sha512-BMnZn0R+X6ayqm3C3To7o1j7Q020gWdqdyP50KEoVqaCO2c/Im7sYZSmVgvefp8TTMQ+9CtwuBp0Z1CZ8V3Pvg== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.16" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.13.16" + "@babel/types" "^7.13.17" + debug "^4.1.0" + globals "^11.1.0" + "@babel/traverse@^7.7.0": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" @@ -126,6 +207,14 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.12.13", "@babel/types@^7.13.16", "@babel/types@^7.13.17": + version "7.13.17" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.17.tgz#48010a115c9fba7588b4437dd68c9469012b38b4" + integrity sha512-RawydLgxbOPDlTLJNtoIypwdmAy//uQIzlKt2+iBiJaRlVuI6QLUxVAyWGNfOzp8Yu4L4lLIacoCyTNtpb4wiA== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + to-fast-properties "^2.0.0" + "@develar/schema-utils@~2.6.5": version "2.6.5" resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6" @@ -1597,6 +1686,17 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +counterpart@^0.18.6: + version "0.18.6" + resolved "https://registry.yarnpkg.com/counterpart/-/counterpart-0.18.6.tgz#cf6b60d8ef99a4b44b8bf6445fa99b4bd1b2f9dd" + integrity sha512-cAIDAYbC3x8S2DDbvFEJ4TzPtPYXma25/kfAkfmprNLlkPWeX4SdUp1c2xklfphqCU3HnDaivR4R3BrAYf5OMA== + dependencies: + date-names "^0.1.11" + except "^0.1.3" + extend "^3.0.0" + pluralizers "^0.1.7" + sprintf-js "^1.0.3" + crc-32@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" @@ -1683,6 +1783,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +date-names@^0.1.11: + version "0.1.13" + resolved "https://registry.yarnpkg.com/date-names/-/date-names-0.1.13.tgz#c4358f6f77c8056e2f5ea68fdbb05f0bf1e53bd0" + integrity sha512-IxxoeD9tdx8pXVcmqaRlPvrXIsSrSrIZzfzlOkm9u+hyzKp5Wk/odt9O/gd7Ockzy8n/WHeEpTVJ2bF3mMV4LA== + debounce-fn@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7" @@ -1711,14 +1816,14 @@ debug@^3.1.0, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: +debug@^4.0.1, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: ms "^2.1.1" -debug@^4.3.1: +debug@^4.1.0, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -2472,6 +2577,13 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +except@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/except/-/except-0.1.3.tgz#98261c91958551536b44482238e9783fb73d292a" + integrity sha1-mCYckZWFUVNrREgiOOl4P7c9KSo= + dependencies: + indexof "0.0.1" + execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" @@ -2495,7 +2607,7 @@ exit-on-epipe@~1.0.1: resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -2611,6 +2723,11 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +foreachasync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/foreachasync/-/foreachasync-3.0.0.tgz#5502987dc8714be3392097f32e0071c9dee07cf6" + integrity sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY= + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -3123,6 +3240,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= + infer-owner@^1.0.3, infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" @@ -3975,6 +4097,14 @@ make-fetch-happen@^5.0.0: socks-proxy-agent "^4.0.0" ssri "^6.0.0" +"matrix-web-i18n@github:matrix-org/matrix-web-i18n": + version "1.1.2" + resolved "https://codeload.github.com/matrix-org/matrix-web-i18n/tar.gz/63f9119bc0bc304e83d4e8e22364caa7850e7671" + dependencies: + "@babel/parser" "^7.13.16" + "@babel/traverse" "^7.13.17" + walk "^2.3.14" + meant@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/meant/-/meant-1.0.3.tgz#67769af9de1d158773e928ae82c456114903554c" @@ -4140,11 +4270,16 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.2, ms@^2.0.0, ms@^2.1.1: +ms@2.1.2, ms@^2.0.0: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -4948,6 +5083,11 @@ plist@^3.0.1: xmlbuilder "^9.0.7" xmldom "^0.5.0" +pluralizers@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/pluralizers/-/pluralizers-0.1.7.tgz#8d38dd0a1b660e739b10ab2eab10b684c9d50142" + integrity sha512-mw6AejUiCaMQ6uPN9ObjJDTnR5AnBSmnHHy3uVTbxrSFSxO5scfwpTs8Dxyb6T2v7GSulhvOq+pm9y+hXUvtOA== + png-to-ico@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/png-to-ico/-/png-to-ico-2.1.1.tgz#35be46f93c1ac8d77025f6f4b60c1fa567c1d47c" @@ -5708,6 +5848,11 @@ split-on-first@^1.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== +sprintf-js@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -6402,6 +6547,13 @@ version-range@^1.0.0: dependencies: version-compare "^1.0.0" +walk@^2.3.14: + version "2.3.14" + resolved "https://registry.yarnpkg.com/walk/-/walk-2.3.14.tgz#60ec8631cfd23276ae1e7363ce11d626452e1ef3" + integrity sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg== + dependencies: + foreachasync "^3.0.0" + wcwidth@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"