diff --git a/.circleci/config.yml b/.circleci/config.yml index e277e6cf..cecd1351 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,19 +12,19 @@ executors: check-image: working_directory: ~/mattermost-desktop docker: - - image: electronuserland/builder:wine-chrome + - image: electronuserland/builder:16-wine-chrome environment: TAR_OPTIONS: --no-same-owner wine-chrome: working_directory: ~/mattermost-desktop docker: - - image: electronuserland/builder:wine-chrome + - image: electronuserland/builder:16-wine-chrome environment: TAR_OPTIONS: --no-same-owner wine-mono: working_directory: ~/mattermost-desktop docker: - - image: electronuserland/builder:wine-mono + - image: electronuserland/builder:16-wine-mono mac: working_directory: ~/mattermost-desktop macos: @@ -140,6 +140,13 @@ jobs: - update_image: apt_opts: "--no-install-recommends" - run: npm run check-types + - run: + name: i18n check + command: | + cp i18n/en.json /tmp/en.json + npm run mmjstool -- i18n extract-desktop --desktop-dir . + diff /tmp/en.json i18n/en.json + rm -rf tmp - run: ELECTRON_DISABLE_SANDBOX=1 npm run test:unit-ci - run: mkdir -p /tmp/test-results - run: cp test-results.xml /tmp/test-results/ diff --git a/e2e/modules/environment.js b/e2e/modules/environment.js index abf23a16..e5b434f8 100644 --- a/e2e/modules/environment.js +++ b/e2e/modules/environment.js @@ -95,6 +95,7 @@ const demoConfig = { darkMode: false, lastActiveTeam: 0, spellCheckerLocales: [], + appLanguage: 'en', }; const demoMattermostConfig = { diff --git a/i18n/bg.json b/i18n/bg.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/bg.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/de.json b/i18n/de.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/de.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/en.json b/i18n/en.json new file mode 100644 index 00000000..49c8b5c2 --- /dev/null +++ b/i18n/en.json @@ -0,0 +1,234 @@ +{ + "common.permissions.canBasicAuth": "Web Authentication", + "common.tabs.TAB_FOCALBOARD": "Boards", + "common.tabs.TAB_MESSAGING": "Channels", + "common.tabs.TAB_PLAYBOOKS": "Playbooks", + "label.accept": "Accept", + "label.add": "Add", + "label.cancel": "Cancel", + "label.change": "Change", + "label.close": "Close", + "label.login": "Login", + "label.no": "No", + "label.ok": "OK", + "label.remove": "Remove", + "label.save": "Save", + "label.yes": "Yes", + "main.allowProtocolDialog.button.saveProtocolAsAllowed": "Yes (Save {protocol} as allowed)", + "main.allowProtocolDialog.detail": "The requested link is {URL}. Do you want to continue?", + "main.allowProtocolDialog.message": "{protocol} link requires an external application.", + "main.allowProtocolDialog.title": "Non http(s) protocol", + "main.app.app.handleAppCertificateError.certError.button.cancelConnection": "Cancel Connection", + "main.app.app.handleAppCertificateError.certError.button.moreDetails": "More Details", + "main.app.app.handleAppCertificateError.certError.dialog.detail": "{extraDetail}origin: {origin}\nError: {error}", + "main.app.app.handleAppCertificateError.certError.dialog.message": "There is a configuration issue with this Mattermost server, or someone is trying to intercept your connection. You also may need to sign into the Wi-Fi you are connected to using your web browser.", + "main.app.app.handleAppCertificateError.certError.dialog.title": "Certificate Error", + "main.app.app.handleAppCertificateError.certNotTrusted.button.cancelConnection": "Cancel Connection", + "main.app.app.handleAppCertificateError.certNotTrusted.button.trustInsecureCertificate": "Trust Insecure Certificate", + "main.app.app.handleAppCertificateError.certNotTrusted.dialog.message": "Certificate from \"{issuerName}\" is not trusted.", + "main.app.app.handleAppCertificateError.certNotTrusted.dialog.title": "Certificate Not Trusted", + "main.app.app.handleAppCertificateError.dialog.extraDetail": "Certificate is different from previous one.\n\n", + "main.app.initialize.downloadBox.allFiles": "All files", + "main.app.utils.migrateMacAppStore.button.dontImport": "Don't Import", + "main.app.utils.migrateMacAppStore.button.selectAndImport": "Select Directory and Import", + "main.app.utils.migrateMacAppStore.dialog.detail": "It appears that an existing {appName} configuration exists, would you like to import it? You will be asked to pick the correct configuration directory.", + "main.app.utils.migrateMacAppStore.dialog.message": "Import Existing Configuration", + "main.autoUpdater.download.dialog.button.download": "Download", + "main.autoUpdater.download.dialog.button.remindMeLater": "Remind me Later", + "main.autoUpdater.download.dialog.detail": "A new version of the {appName} Desktop App is available for you to download and install now.", + "main.autoUpdater.download.dialog.message": "New desktop version available", + "main.autoUpdater.noUpdate.detail": "You are using the latest version of the {appName} Desktop App (version {version}). You'll be notified when a new version is available to install.", + "main.autoUpdater.noUpdate.message": "You're up to date", + "main.autoUpdater.update.dialog.button.remindMeLater": "Remind me Later", + "main.autoUpdater.update.dialog.button.restartAndUpdate": "Restart and Update", + "main.autoUpdater.update.dialog.detail": "A new version of the {appName} Desktop App is ready to install.", + "main.autoUpdater.update.dialog.message": "A new version is ready to install", + "main.badge.noUnreads": "You have no unread messages", + "main.badge.sessionExpired": "Session Expired: Please sign in to continue receiving notifications.", + "main.badge.unreadChannels": "You have unread channels", + "main.badge.unreadMentions": "You have unread mentions ({mentionCount})", + "main.CriticalErrorHandler.uncaughtException.button.reopen": "Reopen", + "main.CriticalErrorHandler.uncaughtException.button.showDetails": "Show Details", + "main.CriticalErrorHandler.uncaughtException.dialog.message": "The {appName} app quit unexpectedly. Click \"{showDetails}\" to learn more or \"{reopen}\" to open the application again.\n\nInternal error: {err}", + "main.CriticalErrorHandler.unresponsive.dialog.message": "The window is no longer responsive.\nDo you wait until the window becomes responsive again?", + "main.menus.app.edit": "&Edit", + "main.menus.app.edit.copy": "Copy", + "main.menus.app.edit.cut": "Cut", + "main.menus.app.edit.paste": "Paste", + "main.menus.app.edit.pasteAndMatchStyle": "Paste and Match Style", + "main.menus.app.edit.redo": "Redo", + "main.menus.app.edit.selectAll": "Select All", + "main.menus.app.edit.undo": "Undo", + "main.menus.app.file": "&File", + "main.menus.app.file.about": "About {appName}", + "main.menus.app.file.exit": "Exit", + "main.menus.app.file.hide": "Hide {appName}", + "main.menus.app.file.hideOthers": "Hide Others", + "main.menus.app.file.preferences": "Preferences...", + "main.menus.app.file.quit": "Quit {appName}", + "main.menus.app.file.settings": "Settings...", + "main.menus.app.file.signInToAnotherServer": "Sign in to Another Server", + "main.menus.app.file.unhide": "Show All", + "main.menus.app.help": "Hel&p", + "main.menus.app.help.checkForUpdates": "Check for Updates", + "main.menus.app.help.commitString": " commit: {hashVersion}", + "main.menus.app.help.downloadUpdate": "Download Update", + "main.menus.app.help.learnMore": "Learn More...", + "main.menus.app.help.restartAndUpdate": "Restart and Update", + "main.menus.app.help.versionString": "Version {version}{commit}", + "main.menus.app.history": "&History", + "main.menus.app.history.back": "Back", + "main.menus.app.history.forward": "Forward", + "main.menus.app.view": "&View", + "main.menus.app.view.actualSize": "Actual Size", + "main.menus.app.view.clearCacheAndReload": "Clear Cache and Reload", + "main.menus.app.view.devToolsAppWrapper": "Developer Tools for Application Wrapper", + "main.menus.app.view.devToolsCurrentServer": "Developer Tools for Current Server", + "main.menus.app.view.find": "Find..", + "main.menus.app.view.fullscreen": "Toggle Full Screen", + "main.menus.app.view.reload": "Reload", + "main.menus.app.view.toggleDarkMode": "Toggle Dark Mode", + "main.menus.app.view.zoomIn": "Zoom In", + "main.menus.app.view.zoomOut": "Zoom Out", + "main.menus.app.window": "&Window", + "main.menus.app.window.bringAllToFront": "Bring All to Front", + "main.menus.app.window.close": "Close", + "main.menus.app.window.closeWindow": "Close Window", + "main.menus.app.window.minimize": "Minimize", + "main.menus.app.window.selectNextTab": "Select Next Tab", + "main.menus.app.window.selectPreviousTab": "Select Previous Tab", + "main.menus.app.window.showServers": "Show Servers", + "main.menus.app.window.zoom": "Zoom", + "main.menus.tray.preferences": "Preferences...", + "main.menus.tray.settings": "Settings...", + "main.notifications.download.complete.body": "Download Complete \n {fileName}", + "main.notifications.download.complete.title": "Download Complete", + "main.notifications.mention.title": "Someone mentioned you", + "main.notifications.upgrade.newVersion.body": "A new version is available for you to download now.", + "main.notifications.upgrade.newVersion.title": "New desktop version available", + "main.notifications.upgrade.readyToInstall.body": "A new desktop version is ready to install now.", + "main.notifications.upgrade.readyToInstall.title": "Click to restart and install update", + "main.tray.tray.expired": "Session Expired: Please sign in to continue receiving notifications.", + "main.tray.tray.mention": "You have been mentioned", + "main.tray.tray.unread": "You have unread channels", + "main.views.viewManager.handleDeepLink.error.body": "There is no configured server in the app that matches the requested url: {url}", + "main.views.viewManager.handleDeepLink.error.title": "No matching server", + "main.windows.mainWindow.closeApp.dialog.checkboxLabel": "Don't ask again", + "main.windows.mainWindow.closeApp.dialog.detail": "You will no longer receive notifications for messages. If you want to leave {appName} running in the system tray, you can enable this in Settings.", + "main.windows.mainWindow.closeApp.dialog.message": "Are you sure you want to quit?", + "main.windows.mainWindow.closeApp.dialog.title": "Close Application", + "main.windows.mainWindow.minimizeToTray.dialog.checkboxLabel": "Don't show again", + "main.windows.mainWindow.minimizeToTray.dialog.message": "{appName} will continue to run in the system tray. This can be disabled in Settings.", + "main.windows.mainWindow.minimizeToTray.dialog.title": "Minimize to Tray", + "renderer.components.autoSaveIndicator.saved": "Saved", + "renderer.components.autoSaveIndicator.saving": "Saving...", + "renderer.components.errorView.cannotConnectToAppName": "Cannot connect to {appName}", + "renderer.components.errorView.havingTroubleConnecting": "We're having trouble connecting to {appName}. We'll continue to try and establish a connection.", + "renderer.components.errorView.refreshThenVerify": "If refreshing this page (Ctrl+R or Command+R) does not work please verify that:", + "renderer.components.errorView.troubleshooting.browserView.canReachFromBrowserWindow": "You can reach {url} from a browser window.", + "renderer.components.errorView.troubleshooting.computerIsConnected": "Your computer is connected to the internet.", + "renderer.components.errorView.troubleshooting.urlIsCorrect.appNameIsCorrect": "The {appName} URL {url} is correct", + "renderer.components.extraBar.back": "Back", + "renderer.components.mainPage.contextMenu.ariaLabel": "Context menu", + "renderer.components.mainPage.downloadingUpdate": "Downloading update. {percentDone}% of {total} @ {speed}/s", + "renderer.components.mainPage.updateAvailable": "Update available", + "renderer.components.mainPage.updateReady": "Update ready to install", + "renderer.components.newTeamModal.error.nameRequired": "Name is required.", + "renderer.components.newTeamModal.error.serverNameExists": "A server with the same name already exists.", + "renderer.components.newTeamModal.error.serverUrlExists": "A server with the same URL already exists.", + "renderer.components.newTeamModal.error.urlIncorrectFormatting": "URL is not formatted correctly.", + "renderer.components.newTeamModal.error.urlNeedsHttp": "URL should start with http:// or https://.", + "renderer.components.newTeamModal.error.urlRequired": "URL is required.", + "renderer.components.newTeamModal.serverDisplayName": "Server Display Name", + "renderer.components.newTeamModal.serverDisplayName.description": "The name of the server displayed on your desktop app tab bar.", + "renderer.components.newTeamModal.serverURL": "Server URL", + "renderer.components.newTeamModal.serverURL.description": "The URL of your Mattermost server. Must start with http:// or https://.", + "renderer.components.newTeamModal.title.add": "Add Server", + "renderer.components.newTeamModal.title.edit": "Edit Server", + "renderer.components.removeServerModal.body": "This will remove the server from your Desktop App but will not delete any of its data - you can add the server back to the app at any time.", + "renderer.components.removeServerModal.confirm": "Confirm you wish to remove the {serverName} server?", + "renderer.components.removeServerModal.title": "Remove Server", + "renderer.components.settingsPage.afterRestart": "Setting takes effect after restarting the app.", + "renderer.components.settingsPage.appLanguage": "Set app language (beta)", + "renderer.components.settingsPage.appLanguage.description": "Chooses the language that the Desktop App will use for menu items and popups. Still in beta, some languages will be missing translation strings.", + "renderer.components.settingsPage.appLanguage.useSystemDefault": "Use system default", + "renderer.components.settingsPage.appOptions": "App Options", + "renderer.components.settingsPage.bounceIcon": "Bounce the Dock icon", + "renderer.components.settingsPage.bounceIcon.description": "If enabled, the Dock icon bounces once or until the user opens the app when a new notification is received.", + "renderer.components.settingsPage.bounceIcon.once": "once", + "renderer.components.settingsPage.bounceIcon.untilOpenApp": "until I open the app", + "renderer.components.settingsPage.checkSpelling": "Check spelling", + "renderer.components.settingsPage.checkSpelling.description": "Highlight misspelled words in your messages based on your system language or language preference.", + "renderer.components.settingsPage.checkSpelling.editSpellcheckUrl": "Use an alternative dictionary URL", + "renderer.components.settingsPage.checkSpelling.preferredLanguages": "Select preferred language(s)", + "renderer.components.settingsPage.checkSpelling.revertToDefault": "Revert to default", + "renderer.components.settingsPage.checkSpelling.specifyURL": "Specify the url where dictionary definitions can be retrieved", + "renderer.components.settingsPage.downloadLocation": "Download Location", + "renderer.components.settingsPage.downloadLocation.description": "Specify the folder where files will download.", + "renderer.components.settingsPage.enableHardwareAcceleration": "Use GPU hardware acceleration", + "renderer.components.settingsPage.enableHardwareAcceleration.description": "If enabled, Mattermost UI is rendered more efficiently but can lead to decreased stability for some systems.", + "renderer.components.settingsPage.flashWindow": "Flash taskbar icon when a new message is received", + "renderer.components.settingsPage.flashWindow.description": "If enabled, the taskbar icon will flash for a few seconds when a new message is received.", + "renderer.components.settingsPage.flashWindow.description.linuxFunctionality": "This functionality may not work with all Linux window managers.", + "renderer.components.settingsPage.flashWindow.description.note": "NOTE: ", + "renderer.components.settingsPage.fullscreen": "Open app in fullscreen", + "renderer.components.settingsPage.fullscreen.description": "If enabled, the Mattermost application will always open in full screen", + "renderer.components.settingsPage.header": "Settings", + "renderer.components.settingsPage.launchAppMinimized": "Launch app minimized", + "renderer.components.settingsPage.launchAppMinimized.description": "If enabled, the app will start in system tray, and will not show the window on launch.", + "renderer.components.settingsPage.loadingConfig": "Loading configuration...", + "renderer.components.settingsPage.loggingLevel": "Logging level", + "renderer.components.settingsPage.loggingLevel.description": "Logging is helpful for developers and support to isolate issues you may be encountering with the desktop app.", + "renderer.components.settingsPage.loggingLevel.description.subtitle": "Increasing the log level increases disk space usage and can impact performance. We recommend only increasing the log level if you are having issues.", + "renderer.components.settingsPage.loggingLevel.level.debug": "Debug (debug)", + "renderer.components.settingsPage.loggingLevel.level.error": "Errors (error)", + "renderer.components.settingsPage.loggingLevel.level.info": "Info (info)", + "renderer.components.settingsPage.loggingLevel.level.silly": "Finest (silly)", + "renderer.components.settingsPage.loggingLevel.level.verbose": "Verbose (verbose)", + "renderer.components.settingsPage.loggingLevel.level.warn": "Errors and Warnings (warn)", + "renderer.components.settingsPage.minimizeToTray": "Leave app running in notification area when application window is closed", + "renderer.components.settingsPage.minimizeToTray.description": "If enabled, the app stays running in the notification area after app window is closed.", + "renderer.components.settingsPage.saving.error": "Can't save your changes. Please try again.", + "renderer.components.settingsPage.showUnreadBadge": "Show red badge on {taskbar} icon to indicate unread messages", + "renderer.components.settingsPage.showUnreadBadge.description": "Regardless of this setting, mentions are always indicated with a red badge and item count on the {taskbar} icon.", + "renderer.components.settingsPage.startAppOnLogin": "Start app on login", + "renderer.components.settingsPage.startAppOnLogin.description": "If enabled, the app starts automatically when you log in to your machine.", + "renderer.components.settingsPage.trayIcon.show": "Show icon in the notification area", + "renderer.components.settingsPage.trayIcon.show.darwin": "Show {appName} icon in the menu bar", + "renderer.components.settingsPage.trayIcon.theme": "Icon theme: ", + "renderer.components.settingsPage.trayIcon.theme.dark": "Dark", + "renderer.components.settingsPage.trayIcon.theme.light": "Light", + "renderer.components.settingsPage.trayIcon.theme.systemDefault": "Use system default", + "renderer.components.settingsPage.updates": "Updates", + "renderer.components.settingsPage.updates.automatic": "Automatically check for updates", + "renderer.components.settingsPage.updates.automatic.description": "If enabled, updates to the Desktop App will download automatically and you will be notified when ready to install.", + "renderer.components.settingsPage.updates.checkNow": "Check for Updates Now", + "renderer.components.showCertificateModal.algorithm": "Algorithm", + "renderer.components.showCertificateModal.commonName": "Common Name", + "renderer.components.showCertificateModal.issuerName": "Issuer Name", + "renderer.components.showCertificateModal.noCertSelected": "No certificate Selected", + "renderer.components.showCertificateModal.notValidAfter": "Not Valid After", + "renderer.components.showCertificateModal.notValidBefore": "Not Valid Before", + "renderer.components.showCertificateModal.publicKeyInfo": "Public Key Info", + "renderer.components.showCertificateModal.serialNumber": "Serial Number", + "renderer.components.showCertificateModal.subjectName": "Subject Name", + "renderer.components.teamDropdownButton.noServersConfigured": "No servers configured", + "renderer.dropdown.addAServer": "Add a server", + "renderer.dropdown.servers": "Servers", + "renderer.modals.certificate.certificateModal.certInfoButton": "Certificate Information", + "renderer.modals.certificate.certificateModal.issuer": "Issuer", + "renderer.modals.certificate.certificateModal.noCertsAvailable": "No certificates available", + "renderer.modals.certificate.certificateModal.serial": "Serial", + "renderer.modals.certificate.certificateModal.subject": "Subject", + "renderer.modals.certificate.certificateModal.subtitle": "Select a certificate to authenticate yourself to {url}", + "renderer.modals.certificate.certificateModal.title": "Select a certificate", + "renderer.modals.login.loginModal.message.proxy": "The proxy {host}:{port} requires a username and password.", + "renderer.modals.login.loginModal.message.server": "The server {url} requires a username and password.", + "renderer.modals.login.loginModal.password": "Password", + "renderer.modals.login.loginModal.title": "Authentication Required", + "renderer.modals.login.loginModal.username": "User Name", + "renderer.modals.permission.permissionModal.body": "A site that's not included in your Mattermost server configuration requires access for {permission}.", + "renderer.modals.permission.permissionModal.requestOriginatedFrom": "This request originated from ", + "renderer.modals.permission.permissionModal.title": "{permission} Required", + "renderer.modals.permission.permissionModal.unknownOrigin": "unknown origin" +} diff --git a/i18n/en_AU.json b/i18n/en_AU.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/en_AU.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/es.json b/i18n/es.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/es.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/fa.json b/i18n/fa.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/fa.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/fr.json b/i18n/fr.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/fr.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/hu.json b/i18n/hu.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/hu.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/i18n.ts b/i18n/i18n.ts new file mode 100644 index 00000000..b811e3f4 --- /dev/null +++ b/i18n/i18n.ts @@ -0,0 +1,161 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +/* eslint-disable import/order */ +import bg from './bg.json'; +import de from './de.json'; +import en from './en.json'; +import enAU from './en_AU.json'; +import es from './es.json'; +import fa from './fa.json'; +import fr from './fr.json'; +import hu from './hu.json'; +import it from './it.json'; +import ja from './ja.json'; +import ko from './ko.json'; +import nl from './nl.json'; +import pl from './pl.json'; +import ptBR from './pt-BR.json'; +import ro from './ro.json'; +import ru from './ru.json'; +import sv from './sv.json'; +import tr from './tr.json'; +import uk from './uk.json'; +import zhTW from './zh-TW.json'; +import zhCN from './zh-CN.json'; + +export type Language = { + value: string; + name: string; + order: number; + url: Record; +}; + +export const languages: Record = { + de: { + value: 'de', + name: 'Deutsch', + order: 0, + url: de, + }, + en: { + value: 'en', + name: 'English (US)', + order: 1, + url: en, + }, + 'en-AU': { + value: 'en-AU', + name: 'English (Australia)', + order: 2, + url: enAU, + }, + es: { + value: 'es', + name: 'Español', + order: 3, + url: es, + }, + fr: { + value: 'fr', + name: 'Français', + order: 4, + url: fr, + }, + it: { + value: 'it', + name: 'Italiano (Alpha)', + order: 5, + url: it, + }, + hu: { + value: 'hu', + name: 'Magyar', + order: 6, + url: hu, + }, + nl: { + value: 'nl', + name: 'Nederlands', + order: 7, + url: nl, + }, + pl: { + value: 'pl', + name: 'Polski', + order: 8, + url: pl, + }, + 'pt-BR': { + value: 'pt-BR', + name: 'Português (Brasil) (Beta)', + order: 9, + url: ptBR, + }, + ro: { + value: 'ro', + name: 'Română (Beta)', + order: 10, + url: ro, + }, + sv: { + value: 'sv', + name: 'Svenska', + order: 11, + url: sv, + }, + tr: { + value: 'tr', + name: 'Türkçe', + order: 12, + url: tr, + }, + bg: { + value: 'bg', + name: 'Български', + order: 13, + url: bg, + }, + ru: { + value: 'ru', + name: 'Pусский', + order: 14, + url: ru, + }, + uk: { + value: 'uk', + name: 'Yкраїнська (Alpha)', + order: 15, + url: uk, + }, + fa: { + value: 'fa', + name: 'فارسی (Beta)', + order: 16, + url: fa, + }, + ko: { + value: 'ko', + name: '한국어 (Alpha)', + order: 17, + url: ko, + }, + 'zh-CN': { + value: 'zh-CN', + name: '中文 (简体)', + order: 18, + url: zhCN, + }, + 'zh-TW': { + value: 'zh-TW', + name: '中文 (繁體)', + order: 19, + url: zhTW, + }, + ja: { + value: 'ja', + name: '日本語', + order: 20, + url: ja, + }, +}; diff --git a/i18n/it.json b/i18n/it.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/it.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/ja.json b/i18n/ja.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/ja.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/ko.json b/i18n/ko.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/ko.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/nl.json b/i18n/nl.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/nl.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/pl.json b/i18n/pl.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/pl.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/pt-BR.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/ro.json b/i18n/ro.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/ro.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/ru.json b/i18n/ru.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/ru.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/sv.json b/i18n/sv.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/sv.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/tr.json b/i18n/tr.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/tr.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/uk.json b/i18n/uk.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/uk.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/zh-CN.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/i18n/zh-TW.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 569aefce..8a55e595 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "react-beautiful-dnd": "13.1.0", "react-bootstrap": "1.6.4", "react-dom": "16.14.0", + "react-intl": "5.20.10", "react-select": "5.2.2", "sass": "1.49.11", "semver": "7.3.5", @@ -83,6 +84,7 @@ "jest": "27.5.1", "jest-junit": "13.1.0", "mini-css-extract-plugin": "2.6.0", + "mmjstool": "github:mattermost/mattermost-utilities#d849d3819112bd828f08caf0155bd7ed62f18950", "mocha-circleci-reporter": "0.0.3", "node-gyp": "9.0.0", "npm-run-all": "4.1.5", @@ -94,7 +96,7 @@ "ts-prune": "0.10.3", "typescript": "4.6.3", "webpack": "5.71.0", - "webpack-cli": "4.9.2", + "webpack-cli": "4.10.0", "webpack-dev-server": "4.8.0", "webpack-merge": "5.8.0" }, @@ -2458,6 +2460,133 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "1.9.8", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.9.8.tgz", + "integrity": "sha512-2U4n11bLmTij/k4ePCEFKJILPYwdMcJTdnKVBi+JMWBgu5O1N+XhCazlE6QXqVO1Agh2Doh0b/9Jf1mSmSVfhA==", + "dependencies": { + "@formatjs/intl-localematcher": "0.2.20", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@formatjs/fast-memoize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.0.tgz", + "integrity": "sha512-fObitP9Tlc31SKrPHgkPgQpGo4+4yXfQQITTCNH8AZdEqB7Mq4nPrjpUL/tNGN3lEeJcFxDbi0haX8HM7QvQ8w==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/fast-memoize/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.0.11.tgz", + "integrity": "sha512-5mWb8U8aulYGwnDZWrr+vdgn5PilvtrqQYQ1pvpgzQes/osi85TwmL2GqTGLlKIvBKD2XNA61kAqXYY95w4LWg==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/icu-skeleton-parser": "1.2.12", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.2.12.tgz", + "integrity": "sha512-DTFxWmEA02ZNW6fsYjGYSADvtrqqjCYF7DSgCmMfaaE0gLP4pCdAgOPE+lkXXU+jP8iCw/YhMT2Seyk/C5lBWg==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.9.8", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@formatjs/intl": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-1.14.1.tgz", + "integrity": "sha512-mtL8oBgFwTu0GHFnxaF93fk/zNzNkPzl+27Fwg5AZ88pWHWb7037dpODzoCBnaIVk4FBO5emUn/6jI9Byj8hOw==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/fast-memoize": "1.2.0", + "@formatjs/icu-messageformat-parser": "2.0.11", + "@formatjs/intl-displaynames": "5.2.3", + "@formatjs/intl-listformat": "6.3.3", + "intl-messageformat": "9.9.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@types/node": "14 || 16", + "typescript": "^4.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@formatjs/intl-displaynames": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-5.2.3.tgz", + "integrity": "sha512-5BmhSurLbfgdeo0OBcNPPkIS8ikMMYaHe2NclxEQZqcMvrnQzNMNnUE2dDF5vZx+mkvKq77aQYzpc8RfqVsRCQ==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/intl-localematcher": "0.2.20", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/intl-displaynames/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@formatjs/intl-listformat": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-6.3.3.tgz", + "integrity": "sha512-3nzAKgVS5rePDa5HiH0OwZgAhqxLtzlMc9Pg4QgajRHSP1TqFiMmQnnn52wd3+xVTb7cjZVm3JBnTv51/MhTOg==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/intl-localematcher": "0.2.20", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/intl-listformat/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.20.tgz", + "integrity": "sha512-/Ro85goRZnCojzxOegANFYL0LaDIpdPjAukR7xMTjOtRx+3yyjR0ifGTOW3/Kjhmab3t6GnyHBYWZSudxEOxPA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/intl-localematcher/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@formatjs/intl/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -4551,15 +4680,6 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true }, - "node_modules/@storybook/react/node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/@storybook/react/node_modules/@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.5.tgz", @@ -7174,27 +7294,6 @@ "@xtuc/long": "4.2.2" } }, - "node_modules/@storybook/react/node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/react/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@storybook/react/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7366,34 +7465,6 @@ "node": ">=4.0.0" } }, - "node_modules/@storybook/react/node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/@storybook/react/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@storybook/react/node_modules/find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", @@ -7495,38 +7566,6 @@ "node": ">=10" } }, - "node_modules/@storybook/react/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@storybook/react/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@storybook/react/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7566,15 +7605,6 @@ "node": ">= 0.10" } }, - "node_modules/@storybook/react/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/@storybook/react/node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -7649,19 +7679,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@storybook/react/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/@storybook/react/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7990,18 +8007,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "node_modules/@storybook/react/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/@storybook/react/node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -9035,8 +9040,7 @@ "node_modules/@types/node": { "version": "14.17.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.9.tgz", - "integrity": "sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g==", - "dev": true + "integrity": "sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g==" }, "node_modules/@types/node-fetch": { "version": "2.6.1", @@ -9525,130 +9529,6 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz", @@ -9939,6 +9819,42 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -14613,124 +14529,6 @@ "node": ">= 10" } }, - "node_modules/default-gateway/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/default-gateway/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/default-gateway/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-gateway/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-gateway/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/default-gateway/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/defaults": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", @@ -14879,6 +14677,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -17791,6 +17598,15 @@ "node": ">=8.3.0" } }, + "node_modules/estree-walk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/estree-walk/-/estree-walk-2.2.0.tgz", + "integrity": "sha512-6gUr3kGNVEfL6pcIiGBrSkhoEQPkv8laQy1lUDpaxT4AvlBxf/UYSueOEb6Wq1cG5Ct6xQSkFCHoJcZlijpUAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -17848,6 +17664,100 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -18318,6 +18228,44 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-js": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/file-js/-/file-js-0.3.0.tgz", + "integrity": "sha512-nZlX1pxpV6Mt8BghM3Z150bpsCT1zqil97UryusstZLSs9caYAe0Wph2UKPC3awfM2Dq4ri1Sv99KuK4EIImlA==", + "dev": true, + "dependencies": { + "bluebird": "^3.4.7", + "minimatch": "^3.0.3", + "proper-lockfile": "^1.2.0" + } + }, + "node_modules/file-js/node_modules/err-code": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "integrity": "sha512-CJAN+O0/yA1CKfRn9SXOGctSpEM7DCon/r/5r2eXFMY2zCCJBasFhcM5I+1kh3Ap11FsQCX+vGHceNPvpWKhoA==", + "dev": true + }, + "node_modules/file-js/node_modules/proper-lockfile": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-1.2.0.tgz", + "integrity": "sha512-YNjxtCoY3A+lohlLXWCYrHDhUdfU3MMnuC+ADhloDvJo586LKW23dPrjxGvRGuus05Amcf0cQy6vrjjtbJhWpw==", + "dev": true, + "dependencies": { + "err-code": "^1.0.0", + "extend": "^3.0.0", + "graceful-fs": "^4.1.2", + "retry": "^0.10.0" + } + }, + "node_modules/file-js/node_modules/retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -18410,6 +18358,20 @@ "dev": true, "optional": true }, + "node_modules/filehound": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/filehound/-/filehound-1.17.5.tgz", + "integrity": "sha512-BsNTM3xoscYKgv0quE9RWPVhu5ZTb7BNu3H/IbZQbOfQeA7ZyOV/hIYfo60H3Jhorw/J5vbg59KHS1UCHt4FAw==", + "dev": true, + "dependencies": { + "bluebird": "^3.7.2", + "file-js": "0.3.0", + "lodash": "^4.17.21", + "minimatch": "^3.0.4", + "moment": "^2.29.1", + "unit-compare": "^1.0.1" + } + }, "node_modules/filelist": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz", @@ -19296,6 +19258,130 @@ "node": ">= 0.4" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/globby/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/globby/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/globby/node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/globby/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/globby/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby/node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/globby/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/globby/node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -20146,6 +20232,37 @@ "node": ">=4" } }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -20219,6 +20336,21 @@ "node": ">= 0.10" } }, + "node_modules/intl-messageformat": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.9.1.tgz", + "integrity": "sha512-cuzS/XKHn//hvKka77JKU2dseiVY2dofQjIOZv6ZFxFt4Z9sPXnZ7KQ9Ak2r+4XBCjI04MqJ1PhKs/3X22AkfA==", + "dependencies": { + "@formatjs/fast-memoize": "1.2.0", + "@formatjs/icu-messageformat-parser": "2.0.11", + "tslib": "^2.1.0" + } + }, + "node_modules/intl-messageformat/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -20723,6 +20855,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -21103,124 +21247,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/jest-changed-files/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/jest-changed-files/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/jest-circus": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", @@ -21347,58 +21373,6 @@ "node": ">=8" } }, - "node_modules/jest-cli/node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-cli/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-cli/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-cli/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -22425,55 +22399,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runtime/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/jest-runtime/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-runtime/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -22483,60 +22408,6 @@ "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runtime/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runtime/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -22549,21 +22420,6 @@ "node": ">=8" } }, - "node_modules/jest-runtime/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/jest-serializer": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", @@ -22863,55 +22719,6 @@ "node": ">=8" } }, - "node_modules/jest/node_modules/import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/joi": { "version": "17.6.0", "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", @@ -24298,6 +24105,177 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "node_modules/mmjstool": { + "version": "1.0.0", + "resolved": "git+ssh://git@github.com/mattermost/mattermost-utilities.git#d849d3819112bd828f08caf0155bd7ed62f18950", + "integrity": "sha512-BGAxhpL3jZai7EZJKu1xcgeMPzvRUYRUWRmezw89iw6uEkYlEK4AiV0eM47O7ABS0n9aUH5QvuuiPccMtV+Yfg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.8.1", + "estree-walk": "2.2.0", + "filehound": "1.17.5", + "sort-json": "2.0.0", + "webpack-cli": "4.9.1", + "yargs": "17.3.1" + }, + "bin": { + "mmjstool": "bin/mmjstool" + } + }, + "node_modules/mmjstool/node_modules/@typescript-eslint/types": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.8.1.tgz", + "integrity": "sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/mmjstool/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz", + "integrity": "sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/visitor-keys": "5.8.1", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/mmjstool/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz", + "integrity": "sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.8.1", + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/mmjstool/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/mmjstool/node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/mmjstool/node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/mmjstool/node_modules/webpack-cli": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz", + "integrity": "sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.1.0", + "@webpack-cli/info": "^1.4.0", + "@webpack-cli/serve": "^1.6.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "execa": "^5.0.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/mmjstool/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/mmjstool/node_modules/yargs": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", @@ -24460,6 +24438,15 @@ "node": ">=0.10.0" } }, + "node_modules/moment": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", + "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -25141,6 +25128,27 @@ "node": ">=4" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -27203,6 +27211,37 @@ "react": "^16.8.4 || ^17.0.0" } }, + "node_modules/react-intl": { + "version": "5.20.10", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.20.10.tgz", + "integrity": "sha512-zy0ZQhpjkGsKcK1BFo2HbGM/q8GBVovzoXZGQ76DowR0yr6UzQuPLkrlIrObL2zxIYiDaxaz+hUJaoa2a1xqOQ==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/icu-messageformat-parser": "2.0.11", + "@formatjs/intl": "1.14.1", + "@formatjs/intl-displaynames": "5.2.3", + "@formatjs/intl-listformat": "6.3.3", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/react": "17", + "hoist-non-react-statics": "^3.3.2", + "intl-messageformat": "9.9.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "react": "^16.3.0 || 17", + "typescript": "^4.2" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-intl/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -27870,6 +27909,27 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "dev": true }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -28920,6 +28980,29 @@ "node": ">= 10" } }, + "node_modules/sort-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-json/-/sort-json-2.0.0.tgz", + "integrity": "sha512-OgXPErPJM/rBK5OhzIJ+etib/BmLQ1JY55Nb/ElhoWUec62pXNF/X6DrecHq3NW5OAGX0KxYD7m0HtgB9dvGeA==", + "dev": true, + "dependencies": { + "detect-indent": "^5.0.0", + "detect-newline": "^2.1.0", + "minimist": "^1.2.0" + }, + "bin": { + "sort-json": "app/cmd.js" + } + }, + "node_modules/sort-json/node_modules/detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", @@ -30645,6 +30728,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unit-compare": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unit-compare/-/unit-compare-1.0.1.tgz", + "integrity": "sha512-AeLMQr8gcen2WOTwV0Gvi1nKKbY4Mms79MoltZ6hrZV/VANgE/YQly3jtWZJA/fa9m4ajhynq3XMqh5rOyZclA==", + "dev": true, + "dependencies": { + "moment": "^2.14.1" + } + }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -31468,18 +31560,18 @@ } }, "node_modules/webpack-cli": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", - "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", "colorette": "^2.0.14", "commander": "^7.0.0", - "execa": "^5.0.0", + "cross-spawn": "^7.0.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", @@ -31492,6 +31584,10 @@ "engines": { "node": ">=10.13.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, "peerDependencies": { "webpack": "4.x.x || 5.x.x" }, @@ -31510,42 +31606,6 @@ } } }, - "node_modules/webpack-cli/node_modules/@webpack-cli/configtest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", - "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", - "dev": true, - "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" - } - }, - "node_modules/webpack-cli/node_modules/@webpack-cli/info": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", - "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", - "dev": true, - "dependencies": { - "envinfo": "^7.7.3" - }, - "peerDependencies": { - "webpack-cli": "4.x.x" - } - }, - "node_modules/webpack-cli/node_modules/@webpack-cli/serve": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", - "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", - "dev": true, - "peerDependencies": { - "webpack-cli": "4.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, "node_modules/webpack-cli/node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -31569,60 +31629,6 @@ "node": ">= 8" } }, - "node_modules/webpack-cli/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/webpack-cli/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-cli/node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/webpack-cli/node_modules/interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", @@ -31632,30 +31638,6 @@ "node": ">= 0.10" } }, - "node_modules/webpack-cli/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-cli/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/webpack-cli/node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -31665,39 +31647,6 @@ "node": ">=8" } }, - "node_modules/webpack-cli/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-cli/node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-cli/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/webpack-cli/node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -34139,6 +34088,140 @@ } } }, + "@formatjs/ecma402-abstract": { + "version": "1.9.8", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.9.8.tgz", + "integrity": "sha512-2U4n11bLmTij/k4ePCEFKJILPYwdMcJTdnKVBi+JMWBgu5O1N+XhCazlE6QXqVO1Agh2Doh0b/9Jf1mSmSVfhA==", + "requires": { + "@formatjs/intl-localematcher": "0.2.20", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@formatjs/fast-memoize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.0.tgz", + "integrity": "sha512-fObitP9Tlc31SKrPHgkPgQpGo4+4yXfQQITTCNH8AZdEqB7Mq4nPrjpUL/tNGN3lEeJcFxDbi0haX8HM7QvQ8w==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@formatjs/icu-messageformat-parser": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.0.11.tgz", + "integrity": "sha512-5mWb8U8aulYGwnDZWrr+vdgn5PilvtrqQYQ1pvpgzQes/osi85TwmL2GqTGLlKIvBKD2XNA61kAqXYY95w4LWg==", + "requires": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/icu-skeleton-parser": "1.2.12", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@formatjs/icu-skeleton-parser": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.2.12.tgz", + "integrity": "sha512-DTFxWmEA02ZNW6fsYjGYSADvtrqqjCYF7DSgCmMfaaE0gLP4pCdAgOPE+lkXXU+jP8iCw/YhMT2Seyk/C5lBWg==", + "requires": { + "@formatjs/ecma402-abstract": "1.9.8", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@formatjs/intl": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-1.14.1.tgz", + "integrity": "sha512-mtL8oBgFwTu0GHFnxaF93fk/zNzNkPzl+27Fwg5AZ88pWHWb7037dpODzoCBnaIVk4FBO5emUn/6jI9Byj8hOw==", + "requires": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/fast-memoize": "1.2.0", + "@formatjs/icu-messageformat-parser": "2.0.11", + "@formatjs/intl-displaynames": "5.2.3", + "@formatjs/intl-listformat": "6.3.3", + "intl-messageformat": "9.9.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@formatjs/intl-displaynames": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-5.2.3.tgz", + "integrity": "sha512-5BmhSurLbfgdeo0OBcNPPkIS8ikMMYaHe2NclxEQZqcMvrnQzNMNnUE2dDF5vZx+mkvKq77aQYzpc8RfqVsRCQ==", + "requires": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/intl-localematcher": "0.2.20", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@formatjs/intl-listformat": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-6.3.3.tgz", + "integrity": "sha512-3nzAKgVS5rePDa5HiH0OwZgAhqxLtzlMc9Pg4QgajRHSP1TqFiMmQnnn52wd3+xVTb7cjZVm3JBnTv51/MhTOg==", + "requires": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/intl-localematcher": "0.2.20", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, + "@formatjs/intl-localematcher": { + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.20.tgz", + "integrity": "sha512-/Ro85goRZnCojzxOegANFYL0LaDIpdPjAukR7xMTjOtRx+3yyjR0ifGTOW3/Kjhmab3t6GnyHBYWZSudxEOxPA==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -35653,12 +35736,6 @@ "webpack": "4" }, "dependencies": { - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, "@pmmmwh/react-refresh-webpack-plugin": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.5.tgz", @@ -37725,21 +37802,6 @@ "@xtuc/long": "4.2.2" } }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -37868,28 +37930,6 @@ "estraverse": "^4.1.1" } }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, "find-cache-dir": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", @@ -37958,29 +37998,6 @@ "universalify": "^2.0.0" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -38008,12 +38025,6 @@ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -38067,16 +38078,6 @@ } } }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -38317,15 +38318,6 @@ } } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -39297,8 +39289,7 @@ "@types/node": { "version": "14.17.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.9.tgz", - "integrity": "sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g==", - "dev": true + "integrity": "sha512-CMjgRNsks27IDwI785YMY0KLt3co/c0cQ5foxHYv/shC2w8oOnVwz5Ubq1QG5KzrcW+AXk6gzdnxIkDnTvzu3g==" }, "@types/node-fetch": { "version": "2.6.1", @@ -39704,99 +39695,6 @@ "is-glob": "^4.0.3", "semver": "^7.3.5", "tsutils": "^3.21.0" - }, - "dependencies": { - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } } }, "@typescript-eslint/visitor-keys": { @@ -40087,6 +39985,29 @@ "@xtuc/long": "4.2.2" } }, + "@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "requires": { + "envinfo": "^7.7.3" + } + }, + "@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "requires": {} + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -43701,87 +43622,6 @@ "dev": true, "requires": { "execa": "^5.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "defaults": { @@ -43903,6 +43743,12 @@ "repeat-string": "^1.5.4" } }, + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==", + "dev": true + }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -46141,6 +45987,12 @@ "c8": "^7.6.0" } }, + "estree-walk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/estree-walk/-/estree-walk-2.2.0.tgz", + "integrity": "sha512-6gUr3kGNVEfL6pcIiGBrSkhoEQPkv8laQy1lUDpaxT4AvlBxf/UYSueOEb6Wq1cG5Ct6xQSkFCHoJcZlijpUAg==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -46186,6 +46038,72 @@ "safe-buffer": "^5.1.1" } }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -46576,6 +46494,43 @@ "flat-cache": "^3.0.4" } }, + "file-js": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/file-js/-/file-js-0.3.0.tgz", + "integrity": "sha512-nZlX1pxpV6Mt8BghM3Z150bpsCT1zqil97UryusstZLSs9caYAe0Wph2UKPC3awfM2Dq4ri1Sv99KuK4EIImlA==", + "dev": true, + "requires": { + "bluebird": "^3.4.7", + "minimatch": "^3.0.3", + "proper-lockfile": "^1.2.0" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "integrity": "sha512-CJAN+O0/yA1CKfRn9SXOGctSpEM7DCon/r/5r2eXFMY2zCCJBasFhcM5I+1kh3Ap11FsQCX+vGHceNPvpWKhoA==", + "dev": true + }, + "proper-lockfile": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-1.2.0.tgz", + "integrity": "sha512-YNjxtCoY3A+lohlLXWCYrHDhUdfU3MMnuC+ADhloDvJo586LKW23dPrjxGvRGuus05Amcf0cQy6vrjjtbJhWpw==", + "dev": true, + "requires": { + "err-code": "^1.0.0", + "extend": "^3.0.0", + "graceful-fs": "^4.1.2", + "retry": "^0.10.0" + } + }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==", + "dev": true + } + } + }, "file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -46652,6 +46607,20 @@ "dev": true, "optional": true }, + "filehound": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/filehound/-/filehound-1.17.5.tgz", + "integrity": "sha512-BsNTM3xoscYKgv0quE9RWPVhu5ZTb7BNu3H/IbZQbOfQeA7ZyOV/hIYfo60H3Jhorw/J5vbg59KHS1UCHt4FAw==", + "dev": true, + "requires": { + "bluebird": "^3.7.2", + "file-js": "0.3.0", + "lodash": "^4.17.21", + "minimatch": "^3.0.4", + "moment": "^2.29.1", + "unit-compare": "^1.0.1" + } + }, "filelist": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz", @@ -47351,6 +47320,99 @@ "define-properties": "^1.1.3" } }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "dependencies": { + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -48006,6 +48068,27 @@ "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", "dev": true }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "dependencies": { + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -48067,6 +48150,23 @@ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, + "intl-messageformat": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.9.1.tgz", + "integrity": "sha512-cuzS/XKHn//hvKka77JKU2dseiVY2dofQjIOZv6ZFxFt4Z9sPXnZ7KQ9Ak2r+4XBCjI04MqJ1PhKs/3X22AkfA==", + "requires": { + "@formatjs/fast-memoize": "1.2.0", + "@formatjs/icu-messageformat-parser": "2.0.11", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -48434,6 +48534,12 @@ "call-bind": "^1.0.2" } }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, "is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -48692,42 +48798,6 @@ "@jest/core": "^27.5.1", "import-local": "^3.0.2", "jest-cli": "^27.5.1" - }, - "dependencies": { - "import-local": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.3.tgz", - "integrity": "sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } } }, "jest-changed-files": { @@ -48739,87 +48809,6 @@ "@jest/types": "^27.5.1", "execa": "^5.0.0", "throat": "^6.0.1" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } } }, "jest-circus": { @@ -48912,40 +48901,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -49724,82 +49679,12 @@ "supports-color": "^7.1.0" } }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -49808,15 +49693,6 @@ "requires": { "has-flag": "^4.0.0" } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } } } }, @@ -51170,6 +51046,112 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "mmjstool": { + "version": "git+ssh://git@github.com/mattermost/mattermost-utilities.git#d849d3819112bd828f08caf0155bd7ed62f18950", + "integrity": "sha512-BGAxhpL3jZai7EZJKu1xcgeMPzvRUYRUWRmezw89iw6uEkYlEK4AiV0eM47O7ABS0n9aUH5QvuuiPccMtV+Yfg==", + "dev": true, + "from": "mmjstool@github:mattermost/mattermost-utilities#d849d3819112bd828f08caf0155bd7ed62f18950", + "requires": { + "@typescript-eslint/typescript-estree": "5.8.1", + "estree-walk": "2.2.0", + "filehound": "1.17.5", + "sort-json": "2.0.0", + "webpack-cli": "4.9.1", + "yargs": "17.3.1" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.8.1.tgz", + "integrity": "sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz", + "integrity": "sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/visitor-keys": "5.8.1", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz", + "integrity": "sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.8.1", + "eslint-visitor-keys": "^3.0.0" + } + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true + }, + "webpack-cli": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.1.tgz", + "integrity": "sha512-JYRFVuyFpzDxMDB+v/nanUdQYcZtqFPGzmlW4s+UkPMFhSpfRNmf1z4AwYcHJVdvEFAM7FFCQdNTpsBYhDLusQ==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.1.0", + "@webpack-cli/info": "^1.4.0", + "@webpack-cli/serve": "^1.6.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "execa": "^5.0.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + } + } + }, "mocha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", @@ -51307,6 +51289,12 @@ "resolved": "https://registry.npmjs.org/modify-filename/-/modify-filename-1.1.0.tgz", "integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=" }, + "moment": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", + "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==", + "dev": true + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -51867,6 +51855,23 @@ } } }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + }, + "dependencies": { + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + } + } + }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -53473,6 +53478,30 @@ "prop-types": "^15.0.0" } }, + "react-intl": { + "version": "5.20.10", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.20.10.tgz", + "integrity": "sha512-zy0ZQhpjkGsKcK1BFo2HbGM/q8GBVovzoXZGQ76DowR0yr6UzQuPLkrlIrObL2zxIYiDaxaz+hUJaoa2a1xqOQ==", + "requires": { + "@formatjs/ecma402-abstract": "1.9.8", + "@formatjs/icu-messageformat-parser": "2.0.11", + "@formatjs/intl": "1.14.1", + "@formatjs/intl-displaynames": "5.2.3", + "@formatjs/intl-listformat": "6.3.3", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/react": "17", + "hoist-non-react-statics": "^3.3.2", + "intl-messageformat": "9.9.1", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -54005,6 +54034,23 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "dev": true }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -54865,6 +54911,25 @@ "socks": "^2.6.1" } }, + "sort-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-json/-/sort-json-2.0.0.tgz", + "integrity": "sha512-OgXPErPJM/rBK5OhzIJ+etib/BmLQ1JY55Nb/ElhoWUec62pXNF/X6DrecHq3NW5OAGX0KxYD7m0HtgB9dvGeA==", + "dev": true, + "requires": { + "detect-indent": "^5.0.0", + "detect-newline": "^2.1.0", + "minimist": "^1.2.0" + }, + "dependencies": { + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", + "dev": true + } + } + }, "sort-keys": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", @@ -56196,6 +56261,15 @@ "unist-util-is": "^4.0.0" } }, + "unit-compare": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unit-compare/-/unit-compare-1.0.1.tgz", + "integrity": "sha512-AeLMQr8gcen2WOTwV0Gvi1nKKbY4Mms79MoltZ6hrZV/VANgE/YQly3jtWZJA/fa9m4ajhynq3XMqh5rOyZclA==", + "dev": true, + "requires": { + "moment": "^2.14.1" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -56900,18 +56974,18 @@ } }, "webpack-cli": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", - "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", "colorette": "^2.0.14", "commander": "^7.0.0", - "execa": "^5.0.0", + "cross-spawn": "^7.0.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", @@ -56919,29 +56993,6 @@ "webpack-merge": "^5.7.3" }, "dependencies": { - "@webpack-cli/configtest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", - "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==", - "dev": true, - "requires": {} - }, - "@webpack-cli/info": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", - "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", - "dev": true, - "requires": { - "envinfo": "^7.7.3" - } - }, - "@webpack-cli/serve": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", - "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==", - "dev": true, - "requires": {} - }, "commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -56959,90 +57010,18 @@ "which": "^2.0.1" } }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", "dev": true }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index fb1c7b4d..a5ad7577 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,9 @@ "check-build-config:build": "babel ./src/common/config/buildConfig.ts -o ./dist/buildConfig.js", "check-build-config:run": "node -r @babel/register scripts/check_build_config.js", "check-types": "tsc", - "prune": "ts-prune" + "prune": "ts-prune", + "mmjstool": "mmjstool", + "i18n-extract": "npm run mmjstool -- i18n extract-desktop" }, "jest": { "clearMocks": true, @@ -160,6 +162,7 @@ "jest": "27.5.1", "jest-junit": "13.1.0", "mini-css-extract-plugin": "2.6.0", + "mmjstool": "github:mattermost/mattermost-utilities#d849d3819112bd828f08caf0155bd7ed62f18950", "mocha-circleci-reporter": "0.0.3", "node-gyp": "9.0.0", "npm-run-all": "4.1.5", @@ -171,7 +174,7 @@ "ts-prune": "0.10.3", "typescript": "4.6.3", "webpack": "5.71.0", - "webpack-cli": "4.9.2", + "webpack-cli": "4.10.0", "webpack-dev-server": "4.8.0", "webpack-merge": "5.8.0" }, @@ -193,6 +196,7 @@ "react-beautiful-dnd": "13.1.0", "react-bootstrap": "1.6.4", "react-dom": "16.14.0", + "react-intl": "5.20.10", "react-select": "5.2.2", "sass": "1.49.11", "semver": "7.3.5", diff --git a/src/common/communication.ts b/src/common/communication.ts index d021c13d..6d04a559 100644 --- a/src/common/communication.ts +++ b/src/common/communication.ts @@ -122,3 +122,7 @@ export const RELOAD_CURRENT_VIEW = 'reload-current-view'; export const PING_DOMAIN = 'ping-domain'; export const PING_DOMAIN_RESPONSE = 'ping-domain-response'; + +export const GET_LANGUAGE_INFORMATION = 'get-language-information'; +export const RETRIEVED_LANGUAGE_INFORMATION = 'retrieved-language-information'; +export const GET_AVAILABLE_LANGUAGES = 'get-available-languages'; diff --git a/src/common/config/index.ts b/src/common/config/index.ts index c2a06c70..80a86188 100644 --- a/src/common/config/index.ts +++ b/src/common/config/index.ts @@ -328,6 +328,10 @@ export class Config extends EventEmitter { return this.combinedData?.autoCheckForUpdates; } + get appLanguage() { + return this.combinedData?.appLanguage; + } + // initialization/processing methods /** diff --git a/src/common/permissions.ts b/src/common/permissions.ts index c95a2ec0..33ca36d6 100644 --- a/src/common/permissions.ts +++ b/src/common/permissions.ts @@ -6,5 +6,5 @@ export const BASIC_AUTH_PERMISSION = 'canBasicAuth'; // Permission descriptions export const PERMISSION_DESCRIPTION = { - [BASIC_AUTH_PERMISSION]: 'Web Authentication', + [BASIC_AUTH_PERMISSION]: 'common.permissions.canBasicAuth', }; diff --git a/src/common/tabs/TabView.ts b/src/common/tabs/TabView.ts index 3ffead6c..9cba3314 100644 --- a/src/common/tabs/TabView.ts +++ b/src/common/tabs/TabView.ts @@ -59,10 +59,6 @@ export function getServerView(srv: MattermostServer, tab: Tab) { } } -export function getTabViewName(serverName: string, tabType: string) { - return `${serverName}___${tabType}`; -} - export function getTabDisplayName(tabType: TabType) { switch (tabType) { case TAB_MESSAGING: @@ -76,6 +72,10 @@ export function getTabDisplayName(tabType: TabType) { } } +export function getTabViewName(serverName: string, tabType: string) { + return `${serverName}___${tabType}`; +} + export function canCloseTab(tabType: TabType) { return tabType !== TAB_MESSAGING; } diff --git a/src/common/utils/util.ts b/src/common/utils/util.ts index bfa01608..b7fdb8ea 100644 --- a/src/common/utils/util.ts +++ b/src/common/utils/util.ts @@ -60,6 +60,10 @@ function isVersionGreaterThanOrEqualTo(currentVersion: string, compareVersion: s return true; } +export function t(s: string) { + return s; +} + export default { getDisplayBoundaries, runMode, diff --git a/src/main/AutoLauncher.test.js b/src/main/AutoLauncher.test.js index a0faf8ba..3e8dde6c 100644 --- a/src/main/AutoLauncher.test.js +++ b/src/main/AutoLauncher.test.js @@ -14,6 +14,10 @@ jest.mock('electron', () => ({ jest.mock('electron-is-dev', () => false); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); + describe('main/AutoLauncher', () => { let autoLauncher; const isEnabled = jest.fn(); diff --git a/src/main/AutoLauncher.ts b/src/main/AutoLauncher.ts index 65931d0d..ccfda780 100644 --- a/src/main/AutoLauncher.ts +++ b/src/main/AutoLauncher.ts @@ -22,7 +22,7 @@ export class AutoLauncher { return; } const appLauncher = new AutoLaunch({ - name: 'Mattermost', + name: app.name, }); const enabled = await appLauncher.isEnabled(); if (enabled) { diff --git a/src/main/CriticalErrorHandler.test.js b/src/main/CriticalErrorHandler.test.js index e9bc29b3..984a57bb 100644 --- a/src/main/CriticalErrorHandler.test.js +++ b/src/main/CriticalErrorHandler.test.js @@ -36,6 +36,10 @@ jest.mock('child_process', () => ({ spawn: jest.fn(), })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); + describe('main/CriticalErrorHandler', () => { const criticalErrorHandler = new CriticalErrorHandler(); beforeEach(() => { diff --git a/src/main/CriticalErrorHandler.ts b/src/main/CriticalErrorHandler.ts index a88c798d..a66f5b0d 100644 --- a/src/main/CriticalErrorHandler.ts +++ b/src/main/CriticalErrorHandler.ts @@ -8,12 +8,9 @@ import os from 'os'; import path from 'path'; import {app, BrowserWindow, dialog} from 'electron'; - import log from 'electron-log'; -const BUTTON_OK = 'OK'; -const BUTTON_SHOW_DETAILS = 'Show Details'; -const BUTTON_REOPEN = 'Reopen'; +import {localizeMessage} from 'main/i18nManager'; function createErrorReport(err: Error) { // eslint-disable-next-line no-undef @@ -52,8 +49,11 @@ export class CriticalErrorHandler { dialog.showMessageBox(this.mainWindow, { type: 'warning', title: app.name, - message: 'The window is no longer responsive.\nDo you wait until the window becomes responsive again?', - buttons: ['No', 'Yes'], + message: localizeMessage('main.CriticalErrorHandler.unresponsive.dialog.message', 'The window is no longer responsive.\nDo you wait until the window becomes responsive again?'), + buttons: [ + localizeMessage('label.no', 'No'), + localizeMessage('label.yes', 'Yes'), + ], defaultId: 0, }).then(({response}) => { if (response === 0) { @@ -69,9 +69,17 @@ export class CriticalErrorHandler { fs.writeFileSync(file, report.replace(new RegExp('\\n', 'g'), os.EOL)); if (app.isReady()) { - const buttons = [BUTTON_SHOW_DETAILS, BUTTON_OK, BUTTON_REOPEN]; + const buttons = [ + localizeMessage('main.CriticalErrorHandler.uncaughtException.button.showDetails', 'Show Details'), + localizeMessage('label.ok', 'OK'), + localizeMessage('main.CriticalErrorHandler.uncaughtException.button.reopen', 'Reopen'), + ]; + let indexOfReopen = 2; + let indexOfShowDetails = 0; if (process.platform === 'darwin') { buttons.reverse(); + indexOfReopen = 0; + indexOfShowDetails = 2; } if (!this.mainWindow?.isVisible) { return; @@ -81,15 +89,24 @@ export class CriticalErrorHandler { { type: 'error', title: app.name, - message: `The ${app.name} app quit unexpectedly. Click "Show Details" to learn more or "Reopen" to open the application again.\n\nInternal error: ${err.message}`, + message: localizeMessage( + 'main.CriticalErrorHandler.uncaughtException.dialog.message', + 'The {appName} app quit unexpectedly. Click "{showDetails}" to learn more or "{reopen}" to open the application again.\n\nInternal error: {err}', + { + appName: app.name, + showDetails: localizeMessage('main.CriticalErrorHandler.uncaughtException.button.showDetails', 'Show Details'), + reopen: localizeMessage('main.CriticalErrorHandler.uncaughtException.button.reopen', 'Reopen'), + err: err.message, + }, + ), buttons, - defaultId: buttons.indexOf(BUTTON_REOPEN), + defaultId: indexOfReopen, noLink: true, }, ).then(({response}) => { let child; switch (response) { - case buttons.indexOf(BUTTON_SHOW_DETAILS): + case indexOfShowDetails: child = openDetachedExternal(file); if (child) { child.on( @@ -101,7 +118,7 @@ export class CriticalErrorHandler { child.unref(); } break; - case buttons.indexOf(BUTTON_REOPEN): + case indexOfReopen: app.relaunch(); break; } diff --git a/src/main/ParseArgs.ts b/src/main/ParseArgs.ts index f579b4a0..b1b6300e 100644 --- a/src/main/ParseArgs.ts +++ b/src/main/ParseArgs.ts @@ -28,6 +28,9 @@ function triageArgs(args: string[]) { // Note that yargs is able to exit the node process when handling // certain flags, like version or help. // https://github.com/yargs/yargs/blob/main/docs/api.md#exitprocessenable + +// TODO: Translations? + function parseArgs(args: string[]) { return yargs. alias('dataDir', 'd'). diff --git a/src/main/Validator.ts b/src/main/Validator.ts index 4ab82ae7..be411fb7 100644 --- a/src/main/Validator.ts +++ b/src/main/Validator.ts @@ -131,6 +131,7 @@ const configDataSchemaV3 = Joi.object({ alwaysMinimize: Joi.boolean(), alwaysClose: Joi.boolean(), logLevel: Joi.string().default('info'), + appLanguage: Joi.string().allow(''), }); // eg. data['community.mattermost.com'] = { data: 'certificate data', issuerName: 'COMODO RSA Domain Validation Secure Server CA'}; diff --git a/src/main/allowProtocolDialog.test.js b/src/main/allowProtocolDialog.test.js index 06032eee..09319210 100644 --- a/src/main/allowProtocolDialog.test.js +++ b/src/main/allowProtocolDialog.test.js @@ -49,6 +49,10 @@ jest.mock('./windows/windowManager', () => ({ getMainWindow: jest.fn(), })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); + describe('main/allowProtocolDialog', () => { describe('init', () => { it('should copy data from file when no error', () => { diff --git a/src/main/allowProtocolDialog.ts b/src/main/allowProtocolDialog.ts index 14531800..df0dcda7 100644 --- a/src/main/allowProtocolDialog.ts +++ b/src/main/allowProtocolDialog.ts @@ -8,6 +8,8 @@ import fs from 'fs'; import {dialog, shell} from 'electron'; import log from 'electron-log'; +import {localizeMessage} from 'main/i18nManager'; + import {protocols} from '../../electron-builder.json'; import * as Validator from './Validator'; @@ -54,15 +56,15 @@ export class AllowProtocolDialog { return; } dialog.showMessageBox(mainWindow, { - title: 'Non http(s) protocol', - message: `${protocol} link requires an external application.`, - detail: `The requested link is ${URL} . Do you want to continue?`, + title: localizeMessage('main.allowProtocolDialog.title', 'Non http(s) protocol'), + message: localizeMessage('main.allowProtocolDialog.message', '{protocol} link requires an external application.', {protocol}), + detail: localizeMessage('main.allowProtocolDialog.detail', 'The requested link is {URL}. Do you want to continue?', {URL}), defaultId: 2, type: 'warning', buttons: [ - 'Yes', - `Yes (Save ${protocol} as allowed)`, - 'No', + localizeMessage('label.yes', 'Yes'), + localizeMessage('main.allowProtocolDialog.button.saveProtocolAsAllowed', 'Yes (Save {protocol} as allowed)', {protocol}), + localizeMessage('label.no', 'No'), ], cancelId: 2, noLink: true, diff --git a/src/main/app/app.test.js b/src/main/app/app.test.js index c481fb79..ea8d81fd 100644 --- a/src/main/app/app.test.js +++ b/src/main/app/app.test.js @@ -33,6 +33,9 @@ jest.mock('main/certificateStore', () => ({ add: jest.fn(), save: jest.fn(), })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); jest.mock('main/tray/tray', () => ({})); jest.mock('main/windows/windowManager', () => ({ getMainWindow: jest.fn(), diff --git a/src/main/app/app.ts b/src/main/app/app.ts index 90a13a67..ad7bc6c8 100644 --- a/src/main/app/app.ts +++ b/src/main/app/app.ts @@ -8,6 +8,7 @@ import urlUtils from 'common/utils/url'; import updateManager from 'main/autoUpdater'; import CertificateStore from 'main/certificateStore'; +import {localizeMessage} from 'main/i18nManager'; import {destroyTray} from 'main/tray/tray'; import WindowManager from 'main/windows/windowManager'; @@ -96,8 +97,8 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo certificateErrorCallbacks.set(errorID, callback); return; } - const extraDetail = CertificateStore.isExisting(origin) ? 'Certificate is different from previous one.\n\n' : ''; - const detail = `${extraDetail}origin: ${origin}\nError: ${error}`; + const extraDetail = CertificateStore.isExisting(origin) ? localizeMessage('main.app.app.handleAppCertificateError.dialog.extraDetail', 'Certificate is different from previous one.\n\n') : ''; + const detail = localizeMessage('main.app.app.handleAppCertificateError.certError.dialog.detail', '{extraDetail}origin: {origin}\nError: {error}', {extraDetail, origin, error}); certificateErrorCallbacks.set(errorID, callback); @@ -109,21 +110,27 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo try { let result = await dialog.showMessageBox(mainWindow, { - title: 'Certificate Error', - message: 'There is a configuration issue with this Mattermost server, or someone is trying to intercept your connection. You also may need to sign into the Wi-Fi you are connected to using your web browser.', + title: localizeMessage('main.app.app.handleAppCertificateError.certError.dialog.title', 'Certificate Error'), + message: localizeMessage('main.app.app.handleAppCertificateError.certError.dialog.message', 'There is a configuration issue with this Mattermost server, or someone is trying to intercept your connection. You also may need to sign into the Wi-Fi you are connected to using your web browser.'), type: 'error', detail, - buttons: ['More Details', 'Cancel Connection'], + buttons: [ + localizeMessage('main.app.app.handleAppCertificateError.certError.button.moreDetails', 'More Details'), + localizeMessage('main.app.app.handleAppCertificateError.certError.button.cancelConnection', 'Cancel Connection'), + ], cancelId: 1, }); if (result.response === 0) { result = await dialog.showMessageBox(mainWindow, { - title: 'Certificate Not Trusted', - message: `Certificate from "${certificate.issuerName}" is not trusted.`, + title: localizeMessage('main.app.app.handleAppCertificateError.certNotTrusted.dialog.title', 'Certificate Not Trusted'), + message: localizeMessage('main.app.app.handleAppCertificateError.certNotTrusted.dialog.message', 'Certificate from "{issuerName}" is not trusted.', {issuerName: certificate.issuerName}), detail: extraDetail, type: 'error', - buttons: ['Trust Insecure Certificate', 'Cancel Connection'], + buttons: [ + localizeMessage('main.app.app.handleAppCertificateError.certNotTrusted.button.trustInsecureCertificate', 'Trust Insecure Certificate'), + localizeMessage('main.app.app.handleAppCertificateError.certNotTrusted.button.cancelConnection', 'Cancel Connection'), + ], cancelId: 1, checkboxChecked: false, checkboxLabel: "Don't ask again", diff --git a/src/main/app/initialize.test.js b/src/main/app/initialize.test.js index d83b3737..772a796d 100644 --- a/src/main/app/initialize.test.js +++ b/src/main/app/initialize.test.js @@ -39,6 +39,8 @@ jest.mock('electron', () => ({ setAppUserModelId: jest.fn(), getVersion: jest.fn(), whenReady: jest.fn(), + getLocale: jest.fn(), + getLocaleCountryCode: jest.fn(), }, ipcMain: { on: jest.fn(), @@ -54,6 +56,11 @@ jest.mock('electron', () => ({ }, })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), + setLocale: jest.fn(), +})); + jest.mock('electron-devtools-installer', () => { return () => ({ REACT_DEVELOPER_TOOLS: 'react-developer-tools', diff --git a/src/main/app/initialize.ts b/src/main/app/initialize.ts index d35dee19..9929c19f 100644 --- a/src/main/app/initialize.ts +++ b/src/main/app/initialize.ts @@ -47,6 +47,7 @@ import {setupBadge} from 'main/badge'; import CertificateManager from 'main/certificateManager'; import {updatePaths} from 'main/constants'; import CriticalErrorHandler from 'main/CriticalErrorHandler'; +import i18nManager, {localizeMessage} from 'main/i18nManager'; import {displayDownloadCompleted} from 'main/notifications'; import parseArgs from 'main/ParseArgs'; import TrustedOriginsStore from 'main/trustedOrigins'; @@ -359,7 +360,7 @@ function initializeAfterAppReady() { const filters = []; if (fileElements.length > 1) { filters.push({ - name: 'All files', + name: localizeMessage('main.app.initialize.downloadBox.allFiles', 'All files'), extensions: ['*'], }); } @@ -376,6 +377,14 @@ function initializeAfterAppReady() { }); }); + // needs to be done after app ready + // must be done before update menu + if (Config.appLanguage) { + i18nManager.setLocale(Config.appLanguage); + } else if (!i18nManager.setLocale(app.getLocale())) { + i18nManager.setLocale(app.getLocaleCountryCode()); + } + handleUpdateMenuEvent(); ipcMain.emit('update-dict'); diff --git a/src/main/app/utils.test.js b/src/main/app/utils.test.js index b6386b95..f7569955 100644 --- a/src/main/app/utils.test.js +++ b/src/main/app/utils.test.js @@ -49,6 +49,9 @@ jest.mock('main/autoUpdater', () => ({})); jest.mock('main/constants', () => ({ updatePaths: jest.fn(), })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); jest.mock('main/menus/app', () => ({})); jest.mock('main/menus/tray', () => ({})); jest.mock('main/server/serverInfo', () => ({ diff --git a/src/main/app/utils.ts b/src/main/app/utils.ts index b87b7c49..8ec1954e 100644 --- a/src/main/app/utils.ts +++ b/src/main/app/utils.ts @@ -21,6 +21,7 @@ import Utils from 'common/utils/util'; import updateManager from 'main/autoUpdater'; import {migrationInfoPath, updatePaths} from 'main/constants'; +import {localizeMessage} from 'main/i18nManager'; import {createMenu as createAppMenu} from 'main/menus/app'; import {createMenu as createTrayMenu} from 'main/menus/tray'; import {ServerInfo} from 'main/server/serverInfo'; @@ -224,11 +225,14 @@ export function migrateMacAppStore() { } const cancelImport = dialog.showMessageBoxSync({ - title: 'Mattermost', - message: 'Import Existing Configuration', - detail: 'It appears that an existing Mattermost configuration exists, would you like to import it? You will be asked to pick the correct configuration directory.', + title: app.name, + message: localizeMessage('main.app.utils.migrateMacAppStore.dialog.message', 'Import Existing Configuration'), + detail: localizeMessage('main.app.utils.migrateMacAppStore.dialog.detail', 'It appears that an existing {appName} configuration exists, would you like to import it? You will be asked to pick the correct configuration directory.', {appName: app.name}), icon: appIcon, - buttons: ['Select Directory and Import', 'Don\'t Import'], + buttons: [ + localizeMessage('main.app.utils.migrateMacAppStore.button.selectAndImport', 'Select Directory and Import'), + localizeMessage('main.app.utils.migrateMacAppStore.button.dontImport', 'Don\'t Import'), + ], type: 'info', defaultId: 0, cancelId: 1, diff --git a/src/main/autoUpdater.test.js b/src/main/autoUpdater.test.js index aa86bbd5..c6898d9b 100644 --- a/src/main/autoUpdater.test.js +++ b/src/main/autoUpdater.test.js @@ -45,6 +45,10 @@ jest.mock('main/windows/windowManager', () => ({ sendToRenderer: jest.fn(), })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); + describe('main/autoUpdater', () => { describe('constructor', () => { afterEach(() => { diff --git a/src/main/autoUpdater.ts b/src/main/autoUpdater.ts index 9bf2cd50..519d9cb6 100644 --- a/src/main/autoUpdater.ts +++ b/src/main/autoUpdater.ts @@ -8,6 +8,7 @@ import log from 'electron-log'; import {autoUpdater, ProgressInfo, UpdateInfo} from 'electron-updater'; +import {localizeMessage} from 'main/i18nManager'; import {displayUpgrade, displayRestartToUpgrade} from 'main/notifications'; import {CANCEL_UPGRADE, UPDATE_AVAILABLE, UPDATE_DOWNLOADED, CHECK_FOR_UPDATES, UPDATE_SHORTCUT_MENU, UPDATE_PROGRESS} from 'common/communication'; @@ -108,11 +109,14 @@ export class UpdateManager { clearTimeout(this.lastCheck); } dialog.showMessageBox({ - title: 'Mattermost', - message: 'New desktop version available', - detail: 'A new version of the Mattermost Desktop app is available for you to download and install now.', + title: app.name, + message: localizeMessage('main.autoUpdater.download.dialog.message', 'New desktop version available'), + detail: localizeMessage('main.autoUpdater.download.dialog.detail', 'A new version of the {appName} Desktop App is available for you to download and install now.', {appName: app.name}), icon: appIcon, - buttons: ['Download', 'Remind me Later'], + buttons: [ + localizeMessage('main.autoUpdater.download.dialog.button.download', 'Download'), + localizeMessage('main.autoUpdater.download.dialog.button.remindMeLater', 'Remind me Later'), + ], type: 'info', defaultId: 0, cancelId: 1, @@ -131,11 +135,14 @@ export class UpdateManager { handleUpdate = (): void => { dialog.showMessageBox({ - title: 'Mattermost', - message: 'A new version is ready to install', - detail: 'A new version of the Mattermost Desktop app is ready to install.', + title: app.name, + message: localizeMessage('main.autoUpdater.update.dialog.message', 'A new version is ready to install'), + detail: localizeMessage('main.autoUpdater.update.dialog.detail', 'A new version of the {appName} Desktop App is ready to install.', {appName: app.name}), icon: appIcon, - buttons: ['Restart and Update', 'Remind me Later'], + buttons: [ + localizeMessage('main.autoUpdater.update.dialog.button.restartAndUpdate', 'Restart and Update'), + localizeMessage('main.autoUpdater.update.dialog.button.remindMeLater', 'Remind me Later'), + ], type: 'info', defaultId: 0, cancelId: 1, @@ -149,12 +156,12 @@ export class UpdateManager { displayNoUpgrade = (): void => { const version = app.getVersion(); dialog.showMessageBox({ - title: 'Mattermost', + title: app.name, icon: appIcon, - message: 'You\'re up to date', + message: localizeMessage('main.autoUpdater.noUpdate.message', 'You\'re up to date'), type: 'info', - buttons: ['OK'], - detail: `You are using the latest version of the Mattermost Desktop App (version ${version}). You'll be notified when a new version is available to install.`, + buttons: [localizeMessage('label.ok', 'OK')], + detail: localizeMessage('main.autoUpdater.noUpdate.detail', 'You are using the latest version of the {appName} Desktop App (version {version}). You\'ll be notified when a new version is available to install.', {appName: app.name, version}), }); } diff --git a/src/main/badge.test.js b/src/main/badge.test.js index 6f4ea9c3..e0c208e8 100644 --- a/src/main/badge.test.js +++ b/src/main/badge.test.js @@ -24,6 +24,10 @@ jest.mock('./windows/windowManager', () => ({ setOverlayIcon: jest.fn(), })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn().mockReturnValue(''), +})); + describe('main/badge', () => { describe('showBadgeWindows', () => { it('should show dot when session expired', () => { diff --git a/src/main/badge.ts b/src/main/badge.ts index 6d79fa67..cbd1ee5f 100644 --- a/src/main/badge.ts +++ b/src/main/badge.ts @@ -7,6 +7,8 @@ import log from 'electron-log'; import {UPDATE_BADGE} from 'common/communication'; +import {localizeMessage} from 'main/i18nManager'; + import WindowManager from './windows/windowManager'; import * as AppState from './appState'; @@ -15,17 +17,17 @@ const MAX_WIN_COUNT = 99; let showUnreadBadgeSetting: boolean; export function showBadgeWindows(sessionExpired: boolean, mentionCount: number, showUnreadBadge: boolean) { - let description = 'You have no unread messages'; + let description = localizeMessage('main.badge.noUnreads', 'You have no unread messages'); let text; if (mentionCount > 0) { text = (mentionCount > MAX_WIN_COUNT) ? `${MAX_WIN_COUNT}+` : mentionCount.toString(); - description = `You have unread mentions (${mentionCount})`; + description = localizeMessage('main.badge.unreadMentions', 'You have unread mentions ({mentionCount})', {mentionCount}); } else if (showUnreadBadge && showUnreadBadgeSetting) { text = '•'; - description = 'You have unread channels'; + description = localizeMessage('main.badge.unreadChannels', 'You have unread channels'); } else if (sessionExpired) { text = '•'; - description = 'Session Expired: Please sign in to continue receiving notifications.'; + description = localizeMessage('main.badge.sessionExpired', 'Session Expired: Please sign in to continue receiving notifications.'); } WindowManager.setOverlayIcon(text, description, mentionCount > 99); } diff --git a/src/main/i18nManager.test.js b/src/main/i18nManager.test.js new file mode 100644 index 00000000..5de6cf8d --- /dev/null +++ b/src/main/i18nManager.test.js @@ -0,0 +1,72 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import i18nManager, {I18nManager, localizeMessage} from 'main/i18nManager'; + +jest.mock('electron', () => ({ + ipcMain: { + handle: jest.fn(), + }, +})); + +jest.mock('electron-log', () => ({ + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), +})); + +describe('main/i18nManager', () => { + it('should default to English', () => { + const i18n = new I18nManager(); + expect(i18n.currentLanguage.value).toBe('en'); + }); + + it('should set locale only if available', () => { + const i18n = new I18nManager(); + + expect(i18n.setLocale('fr')).toBe(true); + expect(i18n.currentLanguage.value).toBe('fr'); + expect(i18n.setLocale('zz')).toBe(false); + expect(i18n.currentLanguage.value).toBe('fr'); + }); +}); + +describe('main/i18nManager/localizeMessage', () => { + i18nManager.currentLanguage = { + url: { + simple_key: 'simple_translation', + simple_replace_key: 'simple_translation {key}', + replace_two_key: '{replace} {replace_again}', + nested_braces: '{{replace}}', + multiple_same: '{replace} {replace} {key}', + }, + }; + + it('should get a simple translation', () => { + expect(localizeMessage('simple_key', 'different_translation')).toBe('simple_translation'); + }); + + it('should default if does not exist', () => { + expect(localizeMessage('unsimple_key', 'different_translation')).toBe('different_translation'); + }); + + it('should replace', () => { + expect(localizeMessage('simple_replace_key', null, {key: 'replacement'})).toBe('simple_translation replacement'); + }); + + it('should not replace if key is missing', () => { + expect(localizeMessage('simple_replace_key', null, {})).toBe('simple_translation {key}'); + }); + + it('should replace twice', () => { + expect(localizeMessage('replace_two_key', null, {replace: 'replacement1', replace_again: 'replacement2'})).toBe('replacement1 replacement2'); + }); + + it('should ignore nested braces', () => { + expect(localizeMessage('nested_braces', null, {replace: 'replacement'})).toBe('{replacement}'); + }); + + it('should replace multiple of the same', () => { + expect(localizeMessage('multiple_same', null, {replace: 'replacement', key: 'key1'})).toBe('replacement replacement key1'); + }); +}); diff --git a/src/main/i18nManager.ts b/src/main/i18nManager.ts new file mode 100644 index 00000000..394d7b56 --- /dev/null +++ b/src/main/i18nManager.ts @@ -0,0 +1,60 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {ipcMain} from 'electron'; +import log from 'electron-log'; + +import {GET_AVAILABLE_LANGUAGES, GET_LANGUAGE_INFORMATION} from 'common/communication'; + +import {Language, languages} from '../../i18n/i18n'; + +export function localizeMessage(s: string, defaultString = '', values: any = {}) { + let str = i18nManager.currentLanguage.url[s] || defaultString; + for (const key of Object.keys(values)) { + str = str.replace(new RegExp(`{${key}}`, 'g'), values[key]); + } + return str; +} + +export class I18nManager { + currentLanguage: Language; + + constructor() { + this.currentLanguage = this.getLanguages().en; + + ipcMain.handle(GET_LANGUAGE_INFORMATION, this.getCurrentLanguage); + ipcMain.handle(GET_AVAILABLE_LANGUAGES, this.getAvailableLanguages); + } + + setLocale = (locale: string) => { + log.debug('i18nManager.setLocale', locale); + + if (this.isLanguageAvailable(locale)) { + this.currentLanguage = this.getLanguages()[locale]; + log.info('Set new language', locale); + return true; + } + + log.warn('Failed to set new language', locale); + return false; + } + + getLanguages = () => { + return languages; + } + + getAvailableLanguages = () => { + return Object.keys(languages); + } + + isLanguageAvailable = (locale: string) => { + return Boolean(this.getLanguages()[locale]); + } + + getCurrentLanguage = () => { + return this.currentLanguage; + } +} + +const i18nManager = new I18nManager(); +export default i18nManager; diff --git a/src/main/menus/app.test.js b/src/main/menus/app.test.js index aca33c5f..fb72e944 100644 --- a/src/main/menus/app.test.js +++ b/src/main/menus/app.test.js @@ -3,6 +3,7 @@ 'use strict'; +import {localizeMessage} from 'main/i18nManager'; import WindowManager from 'main/windows/windowManager'; import {createTemplate} from './app'; @@ -14,6 +15,10 @@ jest.mock('electron', () => ({ }, })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); + jest.mock('main/windows/windowManager', () => ({ getCurrentTeamName: jest.fn(), })); @@ -97,6 +102,12 @@ describe('main/menus/app', () => { }); it('should include About in menu on mac', () => { + localizeMessage.mockImplementation((id) => { + if (id === 'main.menus.app.file.about') { + return 'About AppName'; + } + return id; + }); const menu = createTemplate(config); const appNameMenu = menu.find((item) => item.label === '&AppName'); const menuItem = appNameMenu.submenu.find((item) => item.label === 'About AppName'); @@ -105,23 +116,45 @@ describe('main/menus/app', () => { }); it('should contain hide options', () => { + localizeMessage.mockImplementation((id) => { + if (id === 'main.menus.app.file') { + return '&AppName'; + } + return id; + }); const menu = createTemplate(config); const appNameMenu = menu.find((item) => item.label === '&AppName'); - expect(appNameMenu.submenu).toContainEqual({role: 'hide'}); - expect(appNameMenu.submenu).toContainEqual({role: 'unhide'}); - expect(appNameMenu.submenu).toContainEqual({role: 'hideOthers'}); + expect(appNameMenu.submenu).toContainEqual(expect.objectContaining({role: 'hide'})); + expect(appNameMenu.submenu).toContainEqual(expect.objectContaining({role: 'unhide'})); + expect(appNameMenu.submenu).toContainEqual(expect.objectContaining({role: 'hideOthers'})); }); it('should contain zoom and front options in Window', () => { + localizeMessage.mockImplementation((id) => { + if (id === 'main.menus.app.window') { + return '&Window'; + } + return id; + }); const menu = createTemplate(config); const windowMenu = menu.find((item) => item.label === '&Window'); expect(windowMenu.role).toBe('windowMenu'); - expect(windowMenu.submenu).toContainEqual({role: 'zoom'}); - expect(windowMenu.submenu).toContainEqual({role: 'front'}); + expect(windowMenu.submenu).toContainEqual(expect.objectContaining({role: 'zoom'})); + expect(windowMenu.submenu).toContainEqual(expect.objectContaining({role: 'front'})); }); }); it('should show `Sign in to Another Server` if `enableServerManagement` is true', () => { + localizeMessage.mockImplementation((id) => { + switch (id) { + case 'main.menus.app.file': + return '&File'; + case 'main.menus.app.file.signInToAnotherServer': + return 'Sign in to Another Server'; + default: + return id; + } + }); const menu = createTemplate(config); const fileMenu = menu.find((item) => item.label === '&AppName' || item.label === '&File'); const signInOption = fileMenu.submenu.find((item) => item.label === 'Sign in to Another Server'); @@ -129,6 +162,16 @@ describe('main/menus/app', () => { }); it('should not show `Sign in to Another Server` if `enableServerManagement` is false', () => { + localizeMessage.mockImplementation((id) => { + switch (id) { + case 'main.menus.app.file': + return '&File'; + case 'main.menus.app.file.signInToAnotherServer': + return 'Sign in to Another Server'; + default: + return ''; + } + }); const modifiedConfig = { ...config, enableServerManagement: false, @@ -140,6 +183,12 @@ describe('main/menus/app', () => { }); it('should show the first 9 servers (using order) in the Window menu', () => { + localizeMessage.mockImplementation((id) => { + if (id === 'main.menus.app.window') { + return '&Window'; + } + return id; + }); const modifiedConfig = { data: { ...config.data, @@ -174,6 +223,15 @@ describe('main/menus/app', () => { }); it('should show the first 9 tabs (using order) in the Window menu', () => { + localizeMessage.mockImplementation((id) => { + if (id === 'main.menus.app.window') { + return '&Window'; + } + if (id.startsWith('common.tabs')) { + return id.replace('common.tabs.', ''); + } + return id; + }); WindowManager.getCurrentTeamName.mockImplementation(() => config.data.teams[0].name); const modifiedConfig = { diff --git a/src/main/menus/app.ts b/src/main/menus/app.ts index ec6d2f76..02d377ce 100644 --- a/src/main/menus/app.ts +++ b/src/main/menus/app.ts @@ -6,9 +6,11 @@ import {app, ipcMain, Menu, MenuItemConstructorOptions, MenuItem, session, shell, WebContents, clipboard} from 'electron'; import {BROWSER_HISTORY_BUTTON, OPEN_TEAMS_DROPDOWN, SHOW_NEW_SERVER_MODAL} from 'common/communication'; +import {t} from 'common/utils/util'; +import {getTabDisplayName, TabType} from 'common/tabs/TabView'; import {Config} from 'common/config'; -import {TabType, getTabDisplayName} from 'common/tabs/TabView'; +import {localizeMessage} from 'main/i18nManager'; import WindowManager from 'main/windows/windowManager'; import {UpdateManager} from 'main/autoUpdater'; @@ -19,16 +21,16 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { const isMac = process.platform === 'darwin'; const appName = app.name; - const firstMenuName = isMac ? appName : 'File'; + const firstMenuName = isMac ? '&' + appName : localizeMessage('main.menus.app.file', '&File'); const template = []; - const settingsLabel = isMac ? 'Preferences...' : 'Settings...'; + const settingsLabel = isMac ? localizeMessage('main.menus.app.file.preferences', 'Preferences...') : localizeMessage('main.menus.app.file.settings', 'Settings...'); let platformAppMenu = []; if (isMac) { platformAppMenu.push( { - label: 'About ' + appName, + label: localizeMessage('main.menus.app.file.about', 'About {appName}', {appName}), role: 'about', }, ); @@ -44,7 +46,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { if (config.data?.enableServerManagement === true) { platformAppMenu.push({ - label: 'Sign in to Another Server', + label: localizeMessage('main.menus.app.file.signInToAnotherServer', 'Sign in to Another Server'), click() { ipcMain.emit(SHOW_NEW_SERVER_MODAL); }, @@ -55,67 +57,79 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { platformAppMenu = platformAppMenu.concat([ separatorItem, { role: 'hide', + label: localizeMessage('main.menus.app.file.hide', 'Hide {appName}', {appName}), }, { role: 'hideOthers', + label: localizeMessage('main.menus.app.file.hideOthers', 'Hide Others'), }, { role: 'unhide', + label: localizeMessage('main.menus.app.file.unhide', 'Show All'), }, separatorItem, { role: 'quit', + label: localizeMessage('main.menus.app.file.quit', 'Quit {appName}', {appName}), }]); } else { platformAppMenu = platformAppMenu.concat([ separatorItem, { role: 'quit', + label: localizeMessage('main.menus.app.file.exit', 'Exit'), accelerator: 'CmdOrCtrl+Q', }]); } template.push({ - label: '&' + firstMenuName, + label: firstMenuName, submenu: [ ...platformAppMenu, ], }); template.push({ - label: '&Edit', + label: localizeMessage('main.menus.app.edit', '&Edit'), submenu: [{ role: 'undo', + label: localizeMessage('main.menus.app.edit.undo', 'Undo'), accelerator: 'CmdOrCtrl+Z', }, { role: 'Redo', + label: localizeMessage('main.menus.app.edit.redo', 'Redo'), accelerator: 'CmdOrCtrl+SHIFT+Z', }, separatorItem, { role: 'cut', + label: localizeMessage('main.menus.app.edit.cut', 'Cut'), accelerator: 'CmdOrCtrl+X', }, { role: 'copy', + label: localizeMessage('main.menus.app.edit.copy', 'Copy'), accelerator: 'CmdOrCtrl+C', }, { role: 'paste', + label: localizeMessage('main.menus.app.edit.paste', 'Paste'), accelerator: 'CmdOrCtrl+V', }, { role: 'pasteAndMatchStyle', + label: localizeMessage('main.menus.app.edit.pasteAndMatchStyle', 'Paste and Match Style'), accelerator: 'CmdOrCtrl+SHIFT+V', }, { role: 'selectall', + label: localizeMessage('main.menus.app.edit.selectAll', 'Select All'), accelerator: 'CmdOrCtrl+A', }], }); const viewSubMenu = [{ - label: 'Find..', + label: localizeMessage('main.menus.app.view.find', 'Find..'), accelerator: 'CmdOrCtrl+F', click() { WindowManager.sendToFind(); }, }, { - label: 'Reload', + label: localizeMessage('main.menus.app.view.reload', 'Reload'), accelerator: 'CmdOrCtrl+R', click() { WindowManager.reload(); }, }, { - label: 'Clear Cache and Reload', + label: localizeMessage('main.menus.app.view.clearCacheAndReload', 'Clear Cache and Reload'), accelerator: 'Shift+CmdOrCtrl+R', click() { session.defaultSession.clearCache(); @@ -123,13 +137,15 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { }, }, { role: 'togglefullscreen', + label: localizeMessage('main.menus.app.view.fullscreen', 'Toggle Full Screen'), accelerator: isMac ? 'Ctrl+Cmd+F' : 'F11', }, separatorItem, { - label: 'Actual Size', + label: localizeMessage('main.menus.app.view.actualSize', 'Actual Size'), role: 'resetZoom', accelerator: 'CmdOrCtrl+0', }, { role: 'zoomIn', + label: localizeMessage('main.menus.app.view.zoomIn', 'Zoom In'), accelerator: 'CmdOrCtrl+=', }, { role: 'zoomIn', @@ -137,13 +153,14 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { accelerator: 'CmdOrCtrl+Shift+=', }, { role: 'zoomOut', + label: localizeMessage('main.menus.app.view.zoomOut', 'Zoom Out'), accelerator: 'CmdOrCtrl+-', }, { role: 'zoomOut', visible: false, accelerator: 'CmdOrCtrl+Shift+-', }, separatorItem, { - label: 'Developer Tools for Application Wrapper', + label: localizeMessage('main.menus.app.view.devToolsAppWrapper', 'Developer Tools for Application Wrapper'), accelerator: (() => { if (process.platform === 'darwin') { return 'Alt+Command+I'; @@ -161,7 +178,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { } }, }, { - label: 'Developer Tools for Current Server', + label: localizeMessage('main.menus.app.view.devToolsCurrentServer', 'Developer Tools for Current Server'), click() { WindowManager.openBrowserViewDevTools(); }, @@ -170,7 +187,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { if (process.platform !== 'darwin' && process.platform !== 'win32') { viewSubMenu.push(separatorItem); viewSubMenu.push({ - label: 'Toggle Dark Mode', + label: localizeMessage('main.menus.app.view.toggleDarkMode', 'Toggle Dark Mode'), click() { config.toggleDarkModeManually(); }, @@ -178,13 +195,13 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { } template.push({ - label: '&View', + label: localizeMessage('main.menus.app.view', '&View'), submenu: viewSubMenu, }); template.push({ - label: '&History', + label: localizeMessage('main.menus.app.history', '&History'), submenu: [{ - label: 'Back', + label: localizeMessage('main.menus.app.history.back', 'Back'), accelerator: process.platform === 'darwin' ? 'Cmd+[' : 'Alt+Left', click: () => { const view = WindowManager.viewManager?.getCurrentView(); @@ -194,7 +211,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { } }, }, { - label: 'Forward', + label: localizeMessage('main.menus.app.history.forward', 'Forward'), accelerator: process.platform === 'darwin' ? 'Cmd+]' : 'Alt+Right', click: () => { const view = WindowManager.viewManager?.getCurrentView(); @@ -208,21 +225,24 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { const teams = config.data?.teams || []; const windowMenu = { - label: '&Window', + label: localizeMessage('main.menus.app.window', '&Window'), role: isMac ? 'windowMenu' : null, submenu: [{ role: 'minimize', + label: localizeMessage('main.menus.app.window.minimize', 'Minimize'), // empty string removes shortcut on Windows; null will default by OS accelerator: process.platform === 'win32' ? '' : null, }, ...(isMac ? [{ role: 'zoom', + label: localizeMessage('main.menus.app.window.zoom', 'Zoom'), }, separatorItem, ] : []), { role: 'close', + label: isMac ? localizeMessage('main.menus.app.window.closeWindow', 'Close Window') : localizeMessage('main.menus.app.window.close', 'Close'), accelerator: 'CmdOrCtrl+W', }, separatorItem, { - label: 'Show Servers', + label: localizeMessage('main.menus.app.window.showServers', 'Show Servers'), accelerator: `${process.platform === 'darwin' ? 'Cmd+Ctrl' : 'Ctrl+Shift'}+S`, click() { ipcMain.emit(OPEN_TEAMS_DROPDOWN); @@ -239,7 +259,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { if (WindowManager.getCurrentTeamName() === team.name) { team.tabs.filter((tab) => tab.isOpen).sort((teamA, teamB) => teamA.order - teamB.order).slice(0, 9).forEach((tab, i) => { items.push({ - label: ` ${getTabDisplayName(tab.name as TabType)}`, + label: ` ${localizeMessage(`common.tabs.${tab.name}`, getTabDisplayName(tab.name as TabType))}`, accelerator: `CmdOrCtrl+${i + 1}`, click() { WindowManager.switchTab(team.name, tab.name); @@ -249,14 +269,14 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { } return items; }).flat(), separatorItem, { - label: 'Select Next Tab', + label: localizeMessage('main.menus.app.window.selectNextTab', 'Select Next Tab'), accelerator: 'Ctrl+Tab', click() { WindowManager.selectNextTab(); }, enabled: (teams.length > 1), }, { - label: 'Select Previous Tab', + label: localizeMessage('main.menus.app.window.selectPreviousTab', 'Select Previous Tab'), accelerator: 'Ctrl+Shift+Tab', click() { WindowManager.selectPreviousTab(); @@ -264,6 +284,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { enabled: (teams.length > 1), }, ...(isMac ? [separatorItem, { role: 'front', + label: localizeMessage('main.menus.app.window.bringAllToFront', 'Bring All to Front'), }] : []), ], }; @@ -272,21 +293,21 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { if (updateManager && config.canUpgrade) { if (updateManager.versionDownloaded) { submenu.push({ - label: 'Restart and Update', + label: localizeMessage('main.menus.app.help.restartAndUpdate', 'Restart and Update'), click() { updateManager.handleUpdate(); }, }); } else if (updateManager.versionAvailable) { submenu.push({ - label: 'Download Update', + label: localizeMessage('main.menus.app.help.downloadUpdate', 'Download Update'), click() { updateManager.handleDownload(); }, }); } else { submenu.push({ - label: 'Check for Updates', + label: localizeMessage('main.menus.app.help.checkForUpdates', 'Check for Updates'), click() { updateManager.checkForUpdates(true); }, @@ -295,7 +316,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { } if (config.data?.helpLink) { submenu.push({ - label: 'Learn More...', + label: localizeMessage('main.menus.app.help.learnMore', 'Learn More...'), click() { shell.openExternal(config.data!.helpLink); }, @@ -303,10 +324,13 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { submenu.push(separatorItem); } - // eslint-disable-next-line no-undef - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const version = `Version ${app.getVersion()}${__HASH_VERSION__ ? ` commit: ${__HASH_VERSION__}` : ''}`; + const version = localizeMessage('main.menus.app.help.versionString', 'Version {version}{commit}', { + version: app.getVersion(), + // eslint-disable-next-line no-undef + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + commit: __HASH_VERSION__ ? localizeMessage('main.menus.app.help.commitString', ' commit: {hashVersion}', {hashVersion: __HASH_VERSION__}) : '', + }); submenu.push({ label: version, enabled: true, @@ -315,7 +339,7 @@ export function createTemplate(config: Config, updateManager: UpdateManager) { }, }); - template.push({label: 'Hel&p', submenu}); + template.push({label: localizeMessage('main.menus.app.help', 'Hel&p'), submenu}); return template; } @@ -323,3 +347,7 @@ export function createMenu(config: Config, updateManager: UpdateManager) { // TODO: Electron is enforcing certain variables that it doesn't need return Menu.buildFromTemplate(createTemplate(config, updateManager) as Array); } + +t('common.tabs.TAB_MESSAGING'); +t('common.tabs.TAB_FOCALBOARD'); +t('common.tabs.TAB_PLAYBOOKS'); diff --git a/src/main/menus/tray.test.js b/src/main/menus/tray.test.js index 594efe94..5f8eb3b0 100644 --- a/src/main/menus/tray.test.js +++ b/src/main/menus/tray.test.js @@ -5,6 +5,10 @@ import {createTemplate} from './tray'; +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); + jest.mock('main/windows/windowManager', () => ({})); describe('main/menus/tray', () => { diff --git a/src/main/menus/tray.ts b/src/main/menus/tray.ts index 8fca16ab..4fd4343d 100644 --- a/src/main/menus/tray.ts +++ b/src/main/menus/tray.ts @@ -7,6 +7,7 @@ import {Menu, MenuItem, MenuItemConstructorOptions} from 'electron'; import {CombinedConfig} from 'types/config'; import WindowManager from 'main/windows/windowManager'; +import {localizeMessage} from 'main/i18nManager'; export function createTemplate(config: CombinedConfig) { const teams = config.teams; @@ -21,7 +22,7 @@ export function createTemplate(config: CombinedConfig) { }), { type: 'separator', }, { - label: process.platform === 'darwin' ? 'Preferences...' : 'Settings', + label: process.platform === 'darwin' ? localizeMessage('main.menus.tray.preferences', 'Preferences...') : localizeMessage('main.menus.tray.settings', 'Settings'), click: () => { WindowManager.showSettingsWindow(); }, diff --git a/src/main/notifications/Download.ts b/src/main/notifications/Download.ts index 05fcf437..fbd6fa9f 100644 --- a/src/main/notifications/Download.ts +++ b/src/main/notifications/Download.ts @@ -8,11 +8,13 @@ import {app, Notification} from 'electron'; import Utils from 'common/utils/util'; +import {localizeMessage} from 'main/i18nManager'; + const assetsDir = path.resolve(app.getAppPath(), 'assets'); const appIconURL = path.resolve(assetsDir, 'appicon_48.png'); const defaultOptions = { - title: 'Download Complete', + title: localizeMessage('main.notifications.download.complete.title', 'Download Complete'), silent: false, icon: appIconURL, urgency: 'normal' as Notification['urgency'], @@ -27,8 +29,8 @@ export class DownloadNotification extends Notification { Reflect.deleteProperty(options, 'icon'); } - options.title = process.platform === 'win32' ? serverName : 'Download Complete'; - options.body = process.platform === 'win32' ? `Download Complete \n ${fileName}` : fileName; + options.title = process.platform === 'win32' ? serverName : localizeMessage('main.notifications.download.complete.title', 'Download Complete'); + options.body = process.platform === 'win32' ? localizeMessage('main.notifications.download.complete.body', 'Download Complete \n {fileName}', {fileName}) : fileName; super(options); } diff --git a/src/main/notifications/Mention.ts b/src/main/notifications/Mention.ts index 4c14ce07..8dd01019 100644 --- a/src/main/notifications/Mention.ts +++ b/src/main/notifications/Mention.ts @@ -10,11 +10,13 @@ import {MentionOptions} from 'types/notification'; import Utils from 'common/utils/util'; +import {localizeMessage} from 'main/i18nManager'; + const assetsDir = path.resolve(app.getAppPath(), 'assets'); const appIconURL = path.resolve(assetsDir, 'appicon_48.png'); const defaultOptions = { - title: 'Someone mentioned you', + title: localizeMessage('main.notifications.mention.title', 'Someone mentioned you'), silent: false, icon: appIconURL, urgency: 'normal' as Notification['urgency'], diff --git a/src/main/notifications/Upgrade.ts b/src/main/notifications/Upgrade.ts index f0c1501f..3fb20a9d 100644 --- a/src/main/notifications/Upgrade.ts +++ b/src/main/notifications/Upgrade.ts @@ -5,12 +5,14 @@ import path from 'path'; import {app, Notification} from 'electron'; +import {localizeMessage} from 'main/i18nManager'; + const assetsDir = path.resolve(app.getAppPath(), 'assets'); const appIconURL = path.resolve(assetsDir, 'appicon_48.png'); const defaultOptions = { - title: 'New desktop version available', - body: 'A new version is available for you to download now.', + title: localizeMessage('main.notifications.upgrade.newVersion.title', 'New desktop version available'), + body: localizeMessage('main.notifications.upgrade.newVersion.body', 'A new version is available for you to download now.'), silent: false, icon: appIconURL, urgency: 'normal' as Notification['urgency'], @@ -33,8 +35,8 @@ export class NewVersionNotification extends Notification { export class UpgradeNotification extends Notification { constructor() { const options = {...defaultOptions}; - options.title = 'Click to restart and install update'; - options.body = 'A new desktop version is ready to install now.'; + options.title = localizeMessage('main.notifications.upgrade.readyToInstall.title', 'Click to restart and install update'); + options.body = localizeMessage('main.notifications.upgrade.readyToInstall.body', 'A new desktop version is ready to install now.'); if (process.platform === 'win32') { options.icon = appIconURL; } else if (process.platform === 'darwin') { diff --git a/src/main/notifications/index.test.js b/src/main/notifications/index.test.js index 052d495d..b0a3b772 100644 --- a/src/main/notifications/index.test.js +++ b/src/main/notifications/index.test.js @@ -8,6 +8,8 @@ import {Notification, shell} from 'electron'; import {PLAY_SOUND} from 'common/communication'; import {TAB_MESSAGING} from 'common/tabs/TabView'; +import {localizeMessage} from 'main/i18nManager'; + import WindowManager from '../windows/windowManager'; import {displayMention, displayDownloadCompleted, currentNotifications} from './index'; @@ -58,6 +60,10 @@ jest.mock('../windows/windowManager', () => ({ switchTab: jest.fn(), })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); + describe('main/notifications', () => { describe('displayMention', () => { beforeEach(() => { @@ -152,6 +158,7 @@ describe('main/notifications', () => { describe('displayDownloadCompleted', () => { it('should open file when clicked', () => { + localizeMessage.mockReturnValue('test_filename'); displayDownloadCompleted( 'test_filename', '/path/to/file', diff --git a/src/main/preload/dropdown.js b/src/main/preload/dropdown.js index cbea1a70..e7aaffff 100644 --- a/src/main/preload/dropdown.js +++ b/src/main/preload/dropdown.js @@ -16,6 +16,8 @@ import { SHOW_EDIT_SERVER_MODAL, SHOW_REMOVE_SERVER_MODAL, UPDATE_TEAMS, + GET_LANGUAGE_INFORMATION, + RETRIEVED_LANGUAGE_INFORMATION, } from 'common/communication'; console.log('preloaded for the dropdown!'); @@ -50,6 +52,9 @@ window.addEventListener('message', async (event) => { case UPDATE_TEAMS: ipcRenderer.invoke(UPDATE_TEAMS, event.data.data); break; + case GET_LANGUAGE_INFORMATION: + window.postMessage({type: RETRIEVED_LANGUAGE_INFORMATION, data: await ipcRenderer.invoke(GET_LANGUAGE_INFORMATION)}); + break; default: console.log(`got a message: ${event}`); console.log(event); diff --git a/src/main/preload/mainWindow.js b/src/main/preload/mainWindow.js index 38b8dd01..fccf3760 100644 --- a/src/main/preload/mainWindow.js +++ b/src/main/preload/mainWindow.js @@ -6,6 +6,11 @@ import {ipcRenderer, contextBridge} from 'electron'; +import { + GET_LANGUAGE_INFORMATION, + RETRIEVED_LANGUAGE_INFORMATION, +} from 'common/communication'; + contextBridge.exposeInMainWorld('ipcRenderer', { send: ipcRenderer.send, on: (channel, listener) => ipcRenderer.on(channel, (_, ...args) => listener(null, ...args)), @@ -24,3 +29,11 @@ contextBridge.exposeInMainWorld('timers', { setImmediate, }); +window.addEventListener('message', async (event) => { + switch (event.data.type) { + case GET_LANGUAGE_INFORMATION: + window.postMessage({type: RETRIEVED_LANGUAGE_INFORMATION, data: await ipcRenderer.invoke(GET_LANGUAGE_INFORMATION)}); + break; + } +}); + diff --git a/src/main/preload/modalPreload.js b/src/main/preload/modalPreload.js index 63e1d67f..5315442e 100644 --- a/src/main/preload/modalPreload.js +++ b/src/main/preload/modalPreload.js @@ -18,6 +18,8 @@ import { MODAL_UNCLOSEABLE, PING_DOMAIN, PING_DOMAIN_RESPONSE, + GET_LANGUAGE_INFORMATION, + RETRIEVED_LANGUAGE_INFORMATION, } from 'common/communication'; console.log('preloaded for the modal!'); @@ -70,6 +72,9 @@ window.addEventListener('message', async (event) => { window.postMessage({type: PING_DOMAIN_RESPONSE, data: error}, window.location.href); } break; + case GET_LANGUAGE_INFORMATION: + window.postMessage({type: RETRIEVED_LANGUAGE_INFORMATION, data: await ipcRenderer.invoke(GET_LANGUAGE_INFORMATION)}); + break; default: console.log(`got a message: ${event}`); console.log(event); diff --git a/src/main/tray/tray.ts b/src/main/tray/tray.ts index 7cdf5ed1..4eac47d5 100644 --- a/src/main/tray/tray.ts +++ b/src/main/tray/tray.ts @@ -7,6 +7,8 @@ import {app, nativeImage, Tray, systemPreferences, nativeTheme} from 'electron'; import {UPDATE_TRAY} from 'common/communication'; +import {localizeMessage} from 'main/i18nManager'; + import WindowManager from '../windows/windowManager'; import * as AppState from '../appState'; @@ -95,11 +97,11 @@ export function setupTray(icontheme: string) { AppState.on(UPDATE_TRAY, (anyExpired, anyMentions, anyUnreads) => { if (anyMentions) { - setTray('mention', 'You have been mentioned'); + setTray('mention', localizeMessage('main.tray.tray.mention', 'You have been mentioned')); } else if (anyUnreads) { - setTray('unread', 'You have unread channels'); + setTray('unread', localizeMessage('main.tray.tray.unread', 'You have unread channels')); } else if (anyExpired) { - setTray('mention', 'Session Expired: Please sign in to continue receiving notifications.'); + setTray('mention', localizeMessage('main.tray.tray.expired', 'Session Expired: Please sign in to continue receiving notifications.')); } else { setTray('normal', app.name); } diff --git a/src/main/views/viewManager.test.js b/src/main/views/viewManager.test.js index e642b95b..4bd60c78 100644 --- a/src/main/views/viewManager.test.js +++ b/src/main/views/viewManager.test.js @@ -48,6 +48,10 @@ jest.mock('common/utils/url', () => ({ getView: jest.fn(), })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); + jest.mock('main/server/serverInfo', () => ({ ServerInfo: jest.fn(), })); diff --git a/src/main/views/viewManager.ts b/src/main/views/viewManager.ts index a43624ee..d387223c 100644 --- a/src/main/views/viewManager.ts +++ b/src/main/views/viewManager.ts @@ -28,6 +28,7 @@ import Utils from 'common/utils/util'; import {MattermostServer} from 'common/servers/MattermostServer'; import {getServerView, getTabViewName, TabTuple, TabType} from 'common/tabs/TabView'; +import {localizeMessage} from 'main/i18nManager'; import {ServerInfo} from 'main/server/serverInfo'; import {getLocalURLString, getLocalPreload, getWindowBoundaries} from '../utils'; @@ -531,7 +532,10 @@ export class ViewManager { } } } else { - dialog.showErrorBox('No matching server', `there is no configured server in the app that matches the requested url: ${parsedURL.toString()}`); + dialog.showErrorBox( + localizeMessage('main.views.viewManager.handleDeepLink.error.title', 'No matching server'), + localizeMessage('main.views.viewManager.handleDeepLink.error.body', 'There is no configured server in the app that matches the requested url: {url}', {url: parsedURL.toString()}), + ); } } }; diff --git a/src/main/windows/mainWindow.test.js b/src/main/windows/mainWindow.test.js index a44b7045..956984c5 100644 --- a/src/main/windows/mainWindow.test.js +++ b/src/main/windows/mainWindow.test.js @@ -69,6 +69,10 @@ jest.mock('../utils', () => ({ getLocalURLString: jest.fn(), })); +jest.mock('main/i18nManager', () => ({ + localizeMessage: jest.fn(), +})); + 'use strict'; describe('main/windows/mainWindow', () => { diff --git a/src/main/windows/mainWindow.ts b/src/main/windows/mainWindow.ts index 19a21df2..446093d0 100644 --- a/src/main/windows/mainWindow.ts +++ b/src/main/windows/mainWindow.ts @@ -16,6 +16,7 @@ import {DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH, MINIMUM_WINDOW_HEIGHT, MINI import Utils from 'common/utils/util'; import {boundsInfoPath} from 'main/constants'; +import {localizeMessage} from 'main/i18nManager'; import * as Validator from '../Validator'; import ContextMenu from '../contextMenu'; @@ -156,11 +157,11 @@ function createMainWindow(options: {linuxAppIcon: string; fullscreen?: boolean}) hideWindow(mainWindow); } else { dialog.showMessageBox(mainWindow, { - title: 'Minimize to Tray', - message: 'Mattermost will continue to run in the system tray. This can be disabled in Settings.', + title: localizeMessage('main.windows.mainWindow.minimizeToTray.dialog.title', 'Minimize to Tray'), + message: localizeMessage('main.windows.mainWindow.minimizeToTray.dialog.message', '{appName} will continue to run in the system tray. This can be disabled in Settings.', {appName: app.name}), type: 'info', checkboxChecked: true, - checkboxLabel: 'Don\'t show again', + checkboxLabel: localizeMessage('main.windows.mainWindow.minimizeToTray.dialog.checkboxLabel', 'Don\'t show again'), }).then((result: {response: number; checkboxChecked: boolean}) => { Config.set('alwaysMinimize', result.checkboxChecked); hideWindow(mainWindow); @@ -170,13 +171,16 @@ function createMainWindow(options: {linuxAppIcon: string; fullscreen?: boolean}) app.quit(); } else { dialog.showMessageBox(mainWindow, { - title: 'Close Application', - message: 'Are you sure you want to quit?', - detail: 'You will no longer receive notifications for messages. If you want to leave Mattermost running in the system tray, you can enable this in Settings.', + title: localizeMessage('main.windows.mainWindow.closeApp.dialog.title', 'Close Application'), + message: localizeMessage('main.windows.mainWindow.closeApp.dialog.message', 'Are you sure you want to quit?'), + detail: localizeMessage('main.windows.mainWindow.closeApp.dialog.detail', 'You will no longer receive notifications for messages. If you want to leave {appName} running in the system tray, you can enable this in Settings.', {appName: app.name}), type: 'question', - buttons: ['Yes', 'No'], + buttons: [ + localizeMessage('label.yes', 'Yes'), + localizeMessage('label.no', 'No'), + ], checkboxChecked: true, - checkboxLabel: 'Don\'t ask again', + checkboxLabel: localizeMessage('main.windows.mainWindow.closeApp.dialog.checkboxLabel', 'Don\'t ask again'), }).then((result: {response: number; checkboxChecked: boolean}) => { Config.set('alwaysClose', result.checkboxChecked && result.response === 0); if (result.response === 0) { diff --git a/src/renderer/components/AutoSaveIndicator.tsx b/src/renderer/components/AutoSaveIndicator.tsx index c864c4b3..a1b79b93 100644 --- a/src/renderer/components/AutoSaveIndicator.tsx +++ b/src/renderer/components/AutoSaveIndicator.tsx @@ -4,6 +4,7 @@ import React from 'react'; import {Alert} from 'react-bootstrap'; +import {IntlShape, useIntl} from 'react-intl'; const baseClassName = 'AutoSaveIndicator'; const leaveClassName = `${baseClassName}-Leave`; @@ -15,16 +16,16 @@ export enum SavingState { SAVING_STATE_DONE = 'done', } -function getClassNameAndMessage(savingState: SavingState, errorMessage?: string) { +function getClassNameAndMessage(intl: IntlShape, savingState: SavingState, errorMessage?: React.ReactNode) { switch (savingState) { case SavingState.SAVING_STATE_SAVING: - return {className: baseClassName, message: 'Saving...'}; + return {className: baseClassName, message: intl.formatMessage({id: 'renderer.components.autoSaveIndicator.saving', defaultMessage: 'Saving...'})}; case SavingState.SAVING_STATE_SAVED: - return {className: baseClassName, message: 'Saved'}; + return {className: baseClassName, message: intl.formatMessage({id: 'renderer.components.autoSaveIndicator.saved', defaultMessage: 'Saved'})}; case SavingState.SAVING_STATE_ERROR: return {className: `${baseClassName}`, message: errorMessage}; case SavingState.SAVING_STATE_DONE: - return {className: `${baseClassName} ${leaveClassName}`, message: 'Saved'}; + return {className: `${baseClassName} ${leaveClassName}`, message: intl.formatMessage({id: 'renderer.components.autoSaveIndicator.saved', defaultMessage: 'Saved'})}; default: return {className: `${baseClassName} ${leaveClassName}`, message: ''}; } @@ -33,12 +34,13 @@ function getClassNameAndMessage(savingState: SavingState, errorMessage?: string) type Props = { id?: string; savingState: SavingState; - errorMessage?: string; + errorMessage?: React.ReactNode; }; -export default function AutoSaveIndicator(props: Props) { +const AutoSaveIndicator: React.FC = (props: Props) => { + const intl = useIntl(); const {savingState, errorMessage, ...rest} = props; - const {className, message} = getClassNameAndMessage(savingState, errorMessage); + const {className, message} = getClassNameAndMessage(intl, savingState, errorMessage); return ( ); -} +}; + +export default AutoSaveIndicator; diff --git a/src/renderer/components/ErrorView.tsx b/src/renderer/components/ErrorView.tsx index 1671a247..716cc9cc 100644 --- a/src/renderer/components/ErrorView.tsx +++ b/src/renderer/components/ErrorView.tsx @@ -6,6 +6,7 @@ import React from 'react'; import {Container, Row, Col} from 'react-bootstrap'; +import {FormattedMessage} from 'react-intl'; type Props = { errorInfo?: string; @@ -41,31 +42,74 @@ export default function ErrorView(props: Props) { md={10} lg={8} > -

{`Cannot connect to ${props.appName}`}

+

+ +


- {`We're having trouble connecting to ${props.appName}. We'll continue to try and establish a connection.`} +
- {'If refreshing this page (Ctrl+R or Command+R) does not work please verify that:'} +

    -
  • {'Your computer is connected to the internet.'}
  • -
  • {`The ${props.appName} URL `} - + +
  • +
  • + ( + - {props.url} - {' is correct.'}
  • -
  • {'You can reach '} - + {msg} + + ), + }} + /> +
  • +
  • + ( + - {props.url} - {' from a browser window.'}
  • + onClick={props.handleLink} + href='#' + > + {msg} + + ), + }} + /> +

diff --git a/src/renderer/components/ExtraBar.tsx b/src/renderer/components/ExtraBar.tsx index 1abf41ef..5359afea 100644 --- a/src/renderer/components/ExtraBar.tsx +++ b/src/renderer/components/ExtraBar.tsx @@ -3,6 +3,7 @@ import React from 'react'; import {Row, Button} from 'react-bootstrap'; +import {FormattedMessage} from 'react-intl'; type Props = { darkMode?: boolean; @@ -39,7 +40,10 @@ export default class ExtraBar extends React.PureComponent { > - {'Back'} +
diff --git a/src/renderer/components/MainPage.tsx b/src/renderer/components/MainPage.tsx index ffd0610a..dc32c379 100644 --- a/src/renderer/components/MainPage.tsx +++ b/src/renderer/components/MainPage.tsx @@ -6,6 +6,7 @@ import classNames from 'classnames'; import React, {Fragment} from 'react'; import {Container, Row} from 'react-bootstrap'; import {DropResult} from 'react-beautiful-dnd'; +import {injectIntl, IntlShape} from 'react-intl'; import {IpcRendererEvent} from 'electron/renderer'; import prettyBytes from 'pretty-bytes'; @@ -82,6 +83,7 @@ type Props = { darkMode: boolean; appName: string; useNativeWindow: boolean; + intl: IntlShape; }; type State = { @@ -115,7 +117,7 @@ type TabViewStatus = { }; } -export default class MainPage extends React.PureComponent { +class MainPage extends React.PureComponent { topBar: React.RefObject; threeDotMenu: React.RefObject; @@ -363,6 +365,7 @@ export default class MainPage extends React.PureComponent { } render() { + const {intl} = this.props; const currentTabs = this.props.teams.find((team) => team.name === this.state.activeServerName)?.tabs || []; const tabsRow = ( @@ -419,13 +422,20 @@ export default class MainPage extends React.PureComponent { let upgradeTooltip; switch (this.state.upgradeStatus) { case UpgradeStatus.AVAILABLE: - upgradeTooltip = 'Update available'; + upgradeTooltip = intl.formatMessage({id: 'renderer.components.mainPage.updateAvailable', defaultMessage: 'Update available'}); break; case UpgradeStatus.DOWNLOADED: - upgradeTooltip = 'Update ready to install'; + upgradeTooltip = intl.formatMessage({id: 'renderer.components.mainPage.updateReady', defaultMessage: 'Update ready to install'}); break; case UpgradeStatus.DOWNLOADING: - upgradeTooltip = `Downloading update. ${String(this.state.upgradeProgress?.percent).split('.')[0]}% of ${prettyBytes(this.state.upgradeProgress?.total || 0)} @ ${prettyBytes(this.state.upgradeProgress?.bytesPerSecond || 0)}/s`; + upgradeTooltip = intl.formatMessage({ + id: 'renderer.components.mainPage.downloadingUpdate', + defaultMessage: 'Downloading update. {percentDone}% of {total} @ {speed}/s', + }, { + percentDone: String(this.state.upgradeProgress?.percent).split('.')[0], + total: prettyBytes(this.state.upgradeProgress?.total || 0), + speed: prettyBytes(this.state.upgradeProgress?.bytesPerSecond || 0), + }); break; } @@ -516,7 +526,7 @@ export default class MainPage extends React.PureComponent { onClick={this.openMenu} tabIndex={0} ref={this.threeDotMenu} - aria-label='Context menu' + aria-label={intl.formatMessage({id: 'renderer.components.mainPage.contextMenu.ariaLabel', defaultMessage: 'Context menu'})} > @@ -593,3 +603,5 @@ export default class MainPage extends React.PureComponent { ); } } + +export default injectIntl(MainPage); diff --git a/src/renderer/components/NewTeamModal.tsx b/src/renderer/components/NewTeamModal.tsx index 49aa4a96..75cddaa3 100644 --- a/src/renderer/components/NewTeamModal.tsx +++ b/src/renderer/components/NewTeamModal.tsx @@ -4,6 +4,7 @@ import React from 'react'; import {Modal, Button, FormGroup, FormControl, FormLabel, FormText} from 'react-bootstrap'; +import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'; import {TeamWithIndex} from 'types/config'; @@ -20,6 +21,7 @@ type Props = { restoreFocus?: boolean; currentOrder?: number; setInputRef?: (inputRef: HTMLInputElement) => void; + intl: IntlShape; }; type State = { @@ -30,7 +32,7 @@ type State = { saveStarted: boolean; } -export default class NewTeamModal extends React.PureComponent { +class NewTeamModal extends React.PureComponent { wasShown?: boolean; teamNameInputRef?: HTMLInputElement; @@ -70,10 +72,20 @@ export default class NewTeamModal extends React.PureComponent { currentTeams.splice(this.props.team.index, 1); } if (currentTeams.find((team) => team.name === this.state.teamName)) { - return 'A server with the same name already exists.'; + return ( + + ); } } - return this.state.teamName.length > 0 ? null : 'Name is required.'; + return this.state.teamName.length > 0 ? null : ( + + ); } getTeamNameValidationState() { @@ -96,17 +108,37 @@ export default class NewTeamModal extends React.PureComponent { currentTeams.splice(this.props.team.index, 1); } if (currentTeams.find((team) => team.url === this.state.teamUrl)) { - return 'A server with the same URL already exists.'; + return ( + + ); } } if (this.state.teamUrl.length === 0) { - return 'URL is required.'; + return ( + + ); } if (!(/^https?:\/\/.*/).test(this.state.teamUrl.trim())) { - return 'URL should start with http:// or https://.'; + return ( + + ); } if (!urlUtils.isValidURL(this.state.teamUrl.trim())) { - return 'URL is not formatted correctly.'; + return ( + + ); } return null; } @@ -185,16 +217,36 @@ export default class NewTeamModal extends React.PureComponent { getSaveButtonLabel() { if (this.props.editMode) { - return 'Save'; + return ( + + ); } - return 'Add'; + return ( + + ); } getModalTitle() { if (this.props.editMode) { - return 'Edit Server'; + return ( + + ); } - return 'Add Server'; + return ( + + ); } render() { @@ -235,12 +287,17 @@ export default class NewTeamModal extends React.PureComponent {
- {'Server Display Name'} + + + { this.teamNameInputRef = ref; @@ -255,12 +312,22 @@ export default class NewTeamModal extends React.PureComponent { isInvalid={Boolean(this.getTeamNameValidationState())} /> - {'The name of the server displayed on your desktop app tab bar.'} + + + - {'Server URL'} + + + { isInvalid={Boolean(this.getTeamUrlValidationState())} /> - {'The URL of your Mattermost server. Must start with http:// or https://.'} + + +
@@ -291,7 +363,10 @@ export default class NewTeamModal extends React.PureComponent { onClick={this.props.onClose} variant='link' > - {'Cancel'} + } {this.props.onSave && @@ -310,3 +385,5 @@ export default class NewTeamModal extends React.PureComponent { ); } } + +export default injectIntl(NewTeamModal); diff --git a/src/renderer/components/RemoveServerModal.tsx b/src/renderer/components/RemoveServerModal.tsx index b9661e78..4084d83f 100644 --- a/src/renderer/components/RemoveServerModal.tsx +++ b/src/renderer/components/RemoveServerModal.tsx @@ -4,6 +4,7 @@ import React from 'react'; import {Modal} from 'react-bootstrap'; +import {FormattedMessage, useIntl} from 'react-intl'; import DestructiveConfirmationModal from './DestructiveConfirmModal'; @@ -13,27 +14,36 @@ type Props = { onHide: () => void; onAccept: React.MouseEventHandler; onCancel: React.MouseEventHandler; -} +}; -export default function RemoveServerModal(props: Props) { +function RemoveServerModal(props: Props) { + const intl = useIntl(); const {serverName, ...rest} = props; return (

- {'This will remove the server from your Desktop App but will not delete any of its data' + - ' - you can add the server back to the app at any time.'} +

- {'Confirm you wish to remove the '}{serverName}{' server?'} +

)} /> ); } + +export default RemoveServerModal; diff --git a/src/renderer/components/SettingsPage.tsx b/src/renderer/components/SettingsPage.tsx index f748a977..b21d7a78 100644 --- a/src/renderer/components/SettingsPage.tsx +++ b/src/renderer/components/SettingsPage.tsx @@ -8,6 +8,7 @@ import 'renderer/css/settings.css'; import React from 'react'; import {FormCheck, Col, FormGroup, FormText, Container, Row, Button, FormControl} from 'react-bootstrap'; +import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'; import ReactSelect, {ActionMeta, MultiValue} from 'react-select'; import {CombinedConfig, LocalConfiguration} from 'types/config'; @@ -23,6 +24,7 @@ import { RELOAD_CONFIGURATION, GET_AVAILABLE_SPELL_CHECKER_LANGUAGES, CHECK_FOR_UPDATES, + GET_AVAILABLE_LANGUAGES, } from 'common/communication'; import AutoSaveIndicator, {SavingState} from './AutoSaveIndicator'; @@ -32,6 +34,10 @@ const CONFIG_TYPE_APP_OPTIONS = 'appOptions'; type ConfigType = typeof CONFIG_TYPE_UPDATES | typeof CONFIG_TYPE_APP_OPTIONS; +type Props = { + intl: IntlShape; +} + type State = DeepPartial & { ready: boolean; maximized?: boolean; @@ -39,13 +45,14 @@ type State = DeepPartial & { userOpenedDownloadDialog: boolean; allowSaveSpellCheckerURL: boolean; availableLanguages: Array<{label: string; value: string}>; + availableSpellcheckerLanguages: Array<{label: string; value: string}>; canUpgrade?: boolean; } type SavingStateItems = { appOptions: SavingState; updates: SavingState; -}; +} type SaveQueueItem = { configType: ConfigType; @@ -53,7 +60,7 @@ type SaveQueueItem = { data: CombinedConfig[keyof CombinedConfig]; } -export default class SettingsPage extends React.PureComponent, State> { +class SettingsPage extends React.PureComponent { trayIconThemeRef: React.RefObject; downloadLocationRef: React.RefObject; showTrayIconRef: React.RefObject; @@ -69,6 +76,7 @@ export default class SettingsPage extends React.PureComponent; autoCheckForUpdatesRef: React.RefObject; logLevelRef: React.RefObject; + appLanguageRef: React.RefObject; saveQueue: SaveQueueItem[]; @@ -77,7 +85,7 @@ export default class SettingsPage extends React.PureComponent) { + constructor(props: Props) { super(props); this.state = { ready: false, @@ -88,6 +96,7 @@ export default class SettingsPage extends React.PureComponent { + const availableSpellcheckerLanguages = languages.filter((language) => localeTranslations[language]).map((language) => ({label: localeTranslations[language], value: language})); + availableSpellcheckerLanguages.sort((a, b) => a.label.localeCompare(b.label)); + this.setState({availableSpellcheckerLanguages}); + }); + + window.ipcRenderer.invoke(GET_AVAILABLE_LANGUAGES).then((languages: string[]) => { const availableLanguages = languages.filter((language) => localeTranslations[language]).map((language) => ({label: localeTranslations[language], value: language})); availableLanguages.sort((a, b) => a.label.localeCompare(b.label)); this.setState({availableLanguages}); @@ -318,6 +334,13 @@ export default class SettingsPage extends React.PureComponent { + window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_APP_OPTIONS, {key: 'appLanguage', data: this.appLanguageRef.current?.value}); + this.setState({ + appLanguage: this.appLanguageRef.current?.value, + }); + } + handleChangeAutoCheckForUpdates = () => { window.timers.setImmediate(this.saveSetting, CONFIG_TYPE_UPDATES, {key: 'autoCheckForUpdates', data: this.autoCheckForUpdatesRef.current?.checked}); this.setState({ @@ -415,6 +438,8 @@ export default class SettingsPage extends React.PureComponent - {'Start app on login'} + - {'If enabled, the app starts automatically when you log in to your machine.'} + ); @@ -512,9 +554,15 @@ export default class SettingsPage extends React.PureComponent - {'Launch app minimized'} + - {'If enabled, the app will start in system tray, and will not show the window on launch.'} + ); } @@ -530,10 +578,20 @@ export default class SettingsPage extends React.PureComponent - {'Check spelling'} + - {'Highlight misspelled words in your messages based on your system language or language preference. '} - {'Setting takes effect after restarting the app.'} + + {' '} + {this.state.useSpellChecker && @@ -541,12 +599,17 @@ export default class SettingsPage extends React.PureComponent + } /> } , @@ -559,7 +622,12 @@ export default class SettingsPage extends React.PureComponent this.setState({spellCheckerURL: '', allowSaveSpellCheckerURL: false})} variant='link' - >{'Use an alternative dictionary URL'}, + > + + , ); } else { options.push( @@ -583,22 +651,33 @@ export default class SettingsPage extends React.PureComponent - {'Save'} + - {'Specify the url where dictionary definitions can be retrieved'} + + > + + ); } } if (window.process.platform === 'darwin' || window.process.platform === 'win32') { - const TASKBAR = window.process.platform === 'win32' ? 'taskbar' : 'Dock'; + const taskbar = window.process.platform === 'win32' ? 'taskbar' : 'Dock'; options.push( - {`Show red badge on ${TASKBAR} icon to indicate unread messages`} + - {`Regardless of this setting, mentions are always indicated with a red badge and item count on the ${TASKBAR} icon.`} + ); } @@ -629,13 +716,30 @@ export default class SettingsPage extends React.PureComponent - {'Flash taskbar icon when a new message is received'} + - {'If enabled, the taskbar icon will flash for a few seconds when a new message is received.'} + {window.process.platform === 'linux' && ( <>
- {'NOTE: '}{'This functionality may not work with all Linux window managers.'} + + + + + + )}
@@ -656,7 +760,12 @@ export default class SettingsPage extends React.PureComponent + } /> + } /> {' '} + } /> - {'If enabled, the Dock icon bounces once or until the user opens the app when a new notification is received.'} + , ); @@ -704,9 +826,22 @@ export default class SettingsPage extends React.PureComponent - {window.process.platform === 'darwin' ? `Show ${this.state.appName} icon in the menu bar` : 'Show icon in the notification area'} + {window.process.platform === 'darwin' ? + : + + } - {'Setting takes effect after restarting the app.'} + ); } @@ -719,7 +854,10 @@ export default class SettingsPage extends React.PureComponent - {'Icon theme: '} + {window.process.platform === 'win32' && <> this.handleChangeTrayIconTheme('use_system')} - label={'Use system default'} + label={ + + } /> {' '} @@ -741,7 +884,12 @@ export default class SettingsPage extends React.PureComponent this.handleChangeTrayIconTheme('light')} - label={'Light'} + label={ + + } /> {' '} this.handleChangeTrayIconTheme('dark')} - label={'Dark'} + label={ + + } /> , ); @@ -771,10 +924,24 @@ export default class SettingsPage extends React.PureComponent - {'Leave app running in notification area when application window is closed'} + - {'If enabled, the app stays running in the notification area after app window is closed.'} - {this.state.showTrayIcon ? ' Setting takes effect after restarting the app.' : ''} + + {this.state.showTrayIcon && + <> + {' '} + + + } ); } @@ -790,10 +957,20 @@ export default class SettingsPage extends React.PureComponent - {'Use GPU hardware acceleration'} + - {'If enabled, Mattermost UI is rendered more efficiently but can lead to decreased stability for some systems.'} - {' Setting takes effect after restarting the app.'} + + {' '} + , ); @@ -809,9 +986,15 @@ export default class SettingsPage extends React.PureComponent - {'Open app in fullscreen'} + - {'If enabled, the Mattermost application will always open in full screen'} + , ); @@ -822,7 +1005,50 @@ export default class SettingsPage extends React.PureComponent
-
{'Download Location'}
+ + + + {this.state.availableLanguages.map((language) => { + return ( + + ); + })} + + + +
+ +
+
+
+ +
- {'Change'} + - {'Specify the folder where files will download.'} +
- {'Logging level'} + - - - - - - + + + + + + - {'Logging is helpful for developers and support to isolate issues you may be encountering with the desktop app.'} -
{'Increasing the log level increases disk space usage and can impact performance. We recommend only increasing the log level if you are having issues.'} + +
+
, ); @@ -871,12 +1125,22 @@ export default class SettingsPage extends React.PureComponent -

{'App Options'}

+

+ +

+ } />
{ options.map((opt) => ( @@ -895,12 +1159,22 @@ export default class SettingsPage extends React.PureComponent -

{'Updates'}

+

+ +

+ } />
- {'Automatically check for updates'} + - {'If enabled, updates to the Desktop App will download automatically and you will be notified when ready to install.'} + @@ -944,7 +1227,14 @@ export default class SettingsPage extends React.PureComponent ); } else { - waitForIpc = (

{'Loading configuration...'}

); + waitForIpc = ( +

+ +

+ ); } return ( @@ -962,7 +1252,12 @@ export default class SettingsPage extends React.PureComponent
-

{'Settings'}

+

+ +


void; tabsDisabled?: boolean; isMenuOpen?: boolean; + intl: IntlShape; }; function getStyle(style?: DraggingStyle | NotDraggingStyle) { @@ -38,7 +40,7 @@ function getStyle(style?: DraggingStyle | NotDraggingStyle) { return style; } -export default class TabBar extends React.PureComponent { +class TabBar extends React.PureComponent { onCloseTab = (name: string) => { return (event: React.MouseEvent) => { event.stopPropagation(); @@ -102,7 +104,7 @@ export default class TabBar extends React.PureComponent { as='li' id={`teamTabItem${index}`} draggable={false} - title={getTabDisplayName(tab.name as TabType)} + title={this.props.intl.formatMessage({id: `common.tabs.${tab.name}`, defaultMessage: getTabDisplayName(tab.name as TabType)})} className={classNames('teamTabItem', { active: this.props.activeTabName === tab.name, dragging: snapshot.isDragging, @@ -121,9 +123,10 @@ export default class TabBar extends React.PureComponent { }} >
- - {getTabDisplayName(tab.name as TabType)} - + { badgeDiv } {canCloseTab(tab.name as TabType) &&
- {activeServerName || 'No servers configured'} + {activeServerName && {activeServerName}} + {!activeServerName && + + } ); diff --git a/src/renderer/components/UpdaterPage.tsx b/src/renderer/components/UpdaterPage.tsx deleted file mode 100644 index c64d2826..00000000 --- a/src/renderer/components/UpdaterPage.tsx +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {Button, Navbar, ProgressBar} from 'react-bootstrap'; - -type InstallButtonProps = { - notifyOnly?: boolean; - onClickInstall?: React.MouseEventHandler; - onClickDownload?: React.MouseEventHandler; -}; - -function InstallButton(props: InstallButtonProps) { - if (props.notifyOnly) { - return ( - - ); - } - return ( - - ); -} - -type UpdaterPageProps = { - appName: string; - notifyOnly?: boolean; - isDownloading?: boolean; - progress?: number; - onClickInstall?: React.MouseEventHandler; - onClickDownload?: React.MouseEventHandler; - onClickReleaseNotes?: React.MouseEventHandler; - onClickRemind?: React.MouseEventHandler; - onClickSkip?: React.MouseEventHandler; - onClickCancel?: React.MouseEventHandler; -}; - -function UpdaterPage(props: UpdaterPageProps) { - let footer; - if (props.isDownloading) { - footer = ( - - -
- -
-
- ); - } else { - footer = ( - - -
- - -
-
- ); - } - - return ( -
- -

{'New update is available'}

-
-
-

{`A new version of the ${props.appName} is available!`}

-

{'Read the '} - {'release notes'} - {' to learn more.'} -

-
- {footer} -
- ); -} - -export default UpdaterPage; diff --git a/src/renderer/components/UpdaterPage/UpdaterPage.stories.tsx b/src/renderer/components/UpdaterPage/UpdaterPage.stories.tsx deleted file mode 100644 index b83ff2cd..00000000 --- a/src/renderer/components/UpdaterPage/UpdaterPage.stories.tsx +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React from 'react'; -import {storiesOf} from '@storybook/react'; - -import {action} from '@storybook/addon-actions'; - -import UpdaterPage from '../UpdaterPage'; -import '../../css/components/UpdaterPage.css'; - -/* -appName: propTypes.string.isRequired, -notifyOnly: propTypes.bool.isRequired, -isDownloading: propTypes.bool.isRequired, -progress: propTypes.number, -onClickInstall: propTypes.func.isRequired, -onClickDownload: propTypes.func.isRequired, -onClickReleaseNotes: propTypes.func.isRequired, -onClickRemind: propTypes.func.isRequired, -onClickSkip: propTypes.func.isRequired, -*/ -const appName = 'Storybook App'; - -storiesOf('UpdaterPage', module). - add('Normal', () => ( - - )). - add('NotifyOnly', () => ( - - )). - add('Downloading', () => ( - - )); diff --git a/src/renderer/components/showCertificateModal.tsx b/src/renderer/components/showCertificateModal.tsx index 8ef80143..1eb55e0e 100644 --- a/src/renderer/components/showCertificateModal.tsx +++ b/src/renderer/components/showCertificateModal.tsx @@ -3,6 +3,7 @@ import React, {Fragment} from 'react'; import {Modal, Button, Row, Col} from 'react-bootstrap'; +import {FormattedMessage} from 'react-intl'; import {Certificate} from 'electron/renderer'; type Props = { @@ -54,7 +55,10 @@ export default class ShowCertificateModal extends React.PureComponent {}} > - {'No certificate Selected'} + ); @@ -69,7 +73,7 @@ export default class ShowCertificateModal extends React.PureComponent

{'Details'}

- {certificateSection('Subject Name')} - {certificateItem('Common Name', this.state.certificate?.subject.commonName)} + {certificateSection( + , + )} + {certificateItem( + , + this.state.certificate?.subject.commonName, + )}
- {certificateSection('Issuer Name')} - {certificateItem('Common Name', this.state.certificate?.issuer.commonName)} + {certificateSection( + , + )} + {certificateItem( + , + this.state.certificate?.issuer.commonName, + )}
- {certificateItem('Serial Number', this.state.certificate?.serialNumber)} - {certificateItem('Not Valid Before', creation.toLocaleString(dateLocale, dateDisplayOptions))} - {certificateItem('Not Valid After', expiration.toLocaleString(dateLocale, dateDisplayOptions))} + {certificateItem( + , + this.state.certificate?.serialNumber, + )} + {certificateItem( + , + creation.toLocaleString(dateLocale, dateDisplayOptions), + )} + {certificateItem( + , + expiration.toLocaleString(dateLocale, dateDisplayOptions), + )}
- {certificateSection('Public Key Info')} - {certificateItem('Algorithm', this.state.certificate?.fingerprint.split('/')[0])} + {certificateSection( + , + )} + {certificateItem( + , + this.state.certificate?.fingerprint.split('/')[0], + )}
@@ -108,7 +163,12 @@ export default class ShowCertificateModal extends React.PureComponent{'Close'} + > + +
diff --git a/src/renderer/dropdown.tsx b/src/renderer/dropdown.tsx index cf190b5c..7581dfd1 100644 --- a/src/renderer/dropdown.tsx +++ b/src/renderer/dropdown.tsx @@ -3,6 +3,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import {FormattedMessage} from 'react-intl'; import classNames from 'classnames'; import {DragDropContext, Draggable, DraggingStyle, Droppable, DropResult, NotDraggingStyle} from 'react-beautiful-dnd'; @@ -23,6 +24,8 @@ import {TAB_BAR_HEIGHT, THREE_DOT_MENU_WIDTH_MAC} from 'common/utils/constants'; import './css/dropdown.scss'; +import IntlProvider from './intl_provider'; + type State = { teams?: TeamWithTabs[]; orderedTeams?: TeamWithTabs[]; @@ -227,137 +230,147 @@ class TeamDropdown extends React.PureComponent, State> { render() { return ( -
-
- {'Servers'} - - {window.process.platform === 'darwin' ? '⌃⌘S' : 'Ctrl + Shift + S'} - -
-
- +
- + + + + + {window.process.platform === 'darwin' ? '⌃⌘S' : 'Ctrl + Shift + S'} + +
+
+ - {(provided) => ( -
- {this.state.orderedTeams?.map((team, orderedIndex) => { - const index = this.state.teams?.indexOf(team); - const {sessionExpired, hasUnreads, mentionCount} = team.tabs.reduce((counts, tab) => { - const tabName = getTabViewName(team.name, tab.name); - counts.sessionExpired = this.state.expired?.get(tabName) || counts.sessionExpired; - counts.hasUnreads = this.state.unreads?.get(tabName) || counts.hasUnreads; - counts.mentionCount += this.state.mentions?.get(tabName) || 0; - return counts; - }, {sessionExpired: false, hasUnreads: false, mentionCount: 0}); + + {(provided) => ( +
+ {this.state.orderedTeams?.map((team, orderedIndex) => { + const index = this.state.teams?.indexOf(team); + const {sessionExpired, hasUnreads, mentionCount} = team.tabs.reduce((counts, tab) => { + const tabName = getTabViewName(team.name, tab.name); + counts.sessionExpired = this.state.expired?.get(tabName) || counts.sessionExpired; + counts.hasUnreads = this.state.unreads?.get(tabName) || counts.hasUnreads; + counts.mentionCount += this.state.mentions?.get(tabName) || 0; + return counts; + }, {sessionExpired: false, hasUnreads: false, mentionCount: 0}); - let badgeDiv: React.ReactNode; - if (sessionExpired) { - badgeDiv = ( -
- -
- ); - } else if (mentionCount && mentionCount > 0) { - badgeDiv = ( -
- {mentionCount > 99 ? '99+' : mentionCount} -
- ); - } else if (hasUnreads) { - badgeDiv = ( -
- ); - } + let badgeDiv: React.ReactNode; + if (sessionExpired) { + badgeDiv = ( +
+ +
+ ); + } else if (mentionCount && mentionCount > 0) { + badgeDiv = ( +
+ {mentionCount > 99 ? '99+' : mentionCount} +
+ ); + } else if (hasUnreads) { + badgeDiv = ( +
+ ); + } - return ( - - {(provided, snapshot) => ( -
-
- - - {badgeDiv &&
- {badgeDiv} -
} -
- - )} - - ); - })} - {provided.placeholder} -
- )} - - -
- {this.state.enableServerManagement && - - } -
+ + {this.isActiveTeam(team) ? : } + {team.name} +
+
+ + + {badgeDiv &&
+ {badgeDiv} +
} +
+ + )} + + ); + })} + {provided.placeholder} +
+ )} + + +
+ {this.state.enableServerManagement && + + } + + ); } } diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 56c02e7d..04d1afdc 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -13,6 +13,7 @@ import {CombinedConfig, Team} from 'types/config'; import {GET_CONFIGURATION, UPDATE_TEAMS, QUIT, RELOAD_CONFIGURATION} from 'common/communication'; import MainPage from './components/MainPage'; +import IntlProvider from './intl_provider'; type State = { config?: CombinedConfig; @@ -120,15 +121,17 @@ class Root extends React.PureComponent, State> { } return ( - + + + ); } } diff --git a/src/renderer/intl_provider.tsx b/src/renderer/intl_provider.tsx new file mode 100644 index 00000000..bee9b3d3 --- /dev/null +++ b/src/renderer/intl_provider.tsx @@ -0,0 +1,55 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {IntlProvider as BaseIntlProvider} from 'react-intl'; + +import {GET_LANGUAGE_INFORMATION, RETRIEVED_LANGUAGE_INFORMATION} from 'common/communication'; + +import {Language} from '../../i18n/i18n'; + +type State = { + language?: Language; +} + +export default class IntlProvider extends React.PureComponent { + constructor(props: any) { + super(props); + this.state = {}; + } + + componentDidMount() { + window.addEventListener('message', this.handleMessageEvent); + window.postMessage({type: GET_LANGUAGE_INFORMATION}); + } + + componentWillUnmount() { + window.removeEventListener('message', this.handleMessageEvent); + } + + handleMessageEvent = (event: MessageEvent<{type: string; data: Language}>) => { + if (event.data.type === RETRIEVED_LANGUAGE_INFORMATION) { + this.setState({ + language: event.data.data, + }); + } + } + + render() { + if (!this.state.language) { + return null; + } + + return ( + + {this.props.children} + + ); + } +} diff --git a/src/renderer/modals/certificate/certificateModal.tsx b/src/renderer/modals/certificate/certificateModal.tsx index 88e87c50..d52b9d2c 100644 --- a/src/renderer/modals/certificate/certificateModal.tsx +++ b/src/renderer/modals/certificate/certificateModal.tsx @@ -4,12 +4,15 @@ import {Certificate} from 'electron/renderer'; import React, {Fragment} from 'react'; import {Modal, Button, Table, Row, Col} from 'react-bootstrap'; +import {FormattedMessage} from 'react-intl'; import {CertificateModalData} from 'types/certificate'; import {ModalMessage} from 'types/modals'; import {MODAL_INFO} from 'common/communication'; +import IntlProvider from 'renderer/intl_provider'; + import ShowCertificateModal from '../../components/showCertificateModal'; type Props = { @@ -91,7 +94,12 @@ export default class SelectCertificateModal extends React.PureComponent ); } - return ({'No certificates available'}); + return ( + + ); } getSelectedCert = () => { @@ -128,65 +136,108 @@ export default class SelectCertificateModal extends React.PureComponent {}} - > - - {'Select a certificate'} - - -

{`Select a certificate to authenticate yourself to ${this.state.url}`}

- - - - - - - - - - {this.renderCerts(this.state.list!)} - - -
{'Subject'}{'Issuer'}{'Serial'}
-
- -
- - - - - - - - - -
-
- + + {}} + > + + + + + + +

+ +

+ + + + + + + + + + {this.renderCerts(this.state.list!)} + + +
+ + + + + +
+
+ +
+ + + + + + + + + +
+
+
+
); } } diff --git a/src/renderer/modals/editServer/editServer.tsx b/src/renderer/modals/editServer/editServer.tsx index 363c6cd7..3540c1f9 100644 --- a/src/renderer/modals/editServer/editServer.tsx +++ b/src/renderer/modals/editServer/editServer.tsx @@ -12,6 +12,8 @@ import {ModalMessage} from 'types/modals'; import {MODAL_CANCEL, MODAL_INFO, MODAL_RESULT, RETRIEVE_MODAL_INFO} from 'common/communication'; +import IntlProvider from 'renderer/intl_provider'; + import NewTeamModal from '../../components/NewTeamModal'; //'./addServer.jsx'; import setupDarkMode from '../darkMode'; @@ -48,14 +50,16 @@ const EditServerModalWrapper: React.FC = () => { }, []); return ( - + + + ); }; diff --git a/src/renderer/modals/login/loginModal.tsx b/src/renderer/modals/login/loginModal.tsx index bc7d9b98..e46c1cbc 100644 --- a/src/renderer/modals/login/loginModal.tsx +++ b/src/renderer/modals/login/loginModal.tsx @@ -4,6 +4,7 @@ import React from 'react'; import {Button, Col, FormLabel, Form, FormGroup, FormControl, Modal} from 'react-bootstrap'; +import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'; import {LoginModalData} from 'types/auth'; import {ModalMessage} from 'types/modals'; @@ -12,10 +13,13 @@ import {AuthenticationResponseDetails, AuthInfo} from 'electron/renderer'; import urlUtils from 'common/utils/url'; import {MODAL_INFO} from 'common/communication'; +import IntlProvider from 'renderer/intl_provider'; + type Props = { onCancel: (request: AuthenticationResponseDetails) => void; onLogin: (request: AuthenticationResponseDetails, username: string, password: string) => void; getAuthInfo: () => void; + intl: IntlShape; }; type State = { @@ -25,7 +29,7 @@ type State = { authInfo?: AuthInfo; }; -export default class LoginModal extends React.PureComponent { +class LoginModal extends React.PureComponent { constructor(props: Props) { super(props); this.state = { @@ -86,83 +90,126 @@ export default class LoginModal extends React.PureComponent { this.setState({password: e.target.value}); } - render() { - let theServer = ''; + renderLoginModalMessage = () => { if (!(this.state.request && this.state.authInfo)) { - theServer = ''; + return null; } else if (this.state.authInfo.isProxy) { - theServer = `The proxy ${this.state.authInfo.host}:${this.state.authInfo.port}`; - } else { - const tmpURL = urlUtils.parseURL(this.state.request.url); - theServer = `The server ${tmpURL?.protocol}//${tmpURL?.host}`; + return ( + + ); } - const message = `${theServer} requires a username and password.`; + const tmpURL = urlUtils.parseURL(this.state.request.url); return ( - - - {'Authentication Required'} - - -

- { message } -

-
- - {'User Name'} - - ) => { - e.stopPropagation(); - }} - /> - - - - {'Password'} - - ) => { - e.stopPropagation(); - }} - /> - - - - -
- - { ' ' } - -
- -
-
-
-
+ + ); + } + + render() { + const {intl} = this.props; + + return ( + + + + + + + + +

+ {this.renderLoginModalMessage()} +

+
+ + + + + + ) => { + e.stopPropagation(); + }} + /> + + + + + + + + ) => { + e.stopPropagation(); + }} + /> + + + + +
+ + { ' ' } + +
+ +
+
+
+
+
); } } + +export default injectIntl(LoginModal); diff --git a/src/renderer/modals/newServer/newServer.tsx b/src/renderer/modals/newServer/newServer.tsx index a0987993..5f2a3616 100644 --- a/src/renderer/modals/newServer/newServer.tsx +++ b/src/renderer/modals/newServer/newServer.tsx @@ -12,6 +12,8 @@ import {ModalMessage} from 'types/modals'; import {GET_MODAL_UNCLOSEABLE, MODAL_CANCEL, MODAL_INFO, MODAL_RESULT, MODAL_UNCLOSEABLE, RETRIEVE_MODAL_INFO} from 'common/communication'; +import IntlProvider from 'renderer/intl_provider'; + import NewTeamModal from '../../components/NewTeamModal'; //'./addServer.jsx'; import setupDarkMode from '../darkMode'; @@ -55,13 +57,15 @@ const NewServerModalWrapper: React.FC = () => { }, []); return ( - + + + ); }; diff --git a/src/renderer/modals/permission/permissionModal.tsx b/src/renderer/modals/permission/permissionModal.tsx index c8bf509a..263da57c 100644 --- a/src/renderer/modals/permission/permissionModal.tsx +++ b/src/renderer/modals/permission/permissionModal.tsx @@ -3,20 +3,24 @@ import React from 'react'; import {Modal, Button} from 'react-bootstrap'; +import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'; import {PermissionType} from 'types/trustedOrigin'; import {ModalMessage} from 'types/modals'; import urlUtil from 'common/utils/url'; +import {t} from 'common/utils/util'; import {MODAL_INFO} from 'common/communication'; import {PERMISSION_DESCRIPTION} from 'common/permissions'; +import IntlProvider from 'renderer/intl_provider'; type Props = { handleDeny: React.MouseEventHandler; handleGrant: React.MouseEventHandler; getPermissionInfo: () => void; openExternalLink: (protocol: string, url: string) => void; + intl: IntlShape; }; type State = { @@ -24,7 +28,7 @@ type State = { permission?: PermissionType; } -export default class PermissionModal extends React.PureComponent { +class PermissionModal extends React.PureComponent { constructor(props: Props) { super(props); this.state = {}; @@ -53,12 +57,13 @@ export default class PermissionModal extends React.PureComponent { } getModalTitle() { - return `${PERMISSION_DESCRIPTION[this.state.permission!]} Required`; + const permission = this.props.intl.formatMessage({id: `common.permissions.${PERMISSION_DESCRIPTION[this.state.permission!]}`}); + return this.props.intl.formatMessage({id: 'renderer.modals.permission.permissionModal.title', defaultMessage: '{permission} Required'}, {permission}); } getModalBody() { const {url, permission} = this.state; - const originDisplay = url ? urlUtil.getHost(url) : 'unknown origin'; + const originDisplay = url ? urlUtil.getHost(url) : this.props.intl.formatMessage({id: 'renderer.modals.permission.permissionModal.unknownOrigin', defaultMessage: 'unknown origin'}); const originLink = url ? originDisplay : ''; const click = (e: React.MouseEvent) => { @@ -75,10 +80,20 @@ export default class PermissionModal extends React.PureComponent { return (

- {`A site that's not included in your Mattermost server configuration requires access for ${PERMISSION_DESCRIPTION[permission!]}.`} + + {}

- {'This request originated from '} + {originDisplay}

@@ -87,32 +102,46 @@ export default class PermissionModal extends React.PureComponent { render() { return ( - {}} - > - - {this.getModalTitle()} - - - {this.getModalBody()} - - -
- - -
-
-
+ + {}} + > + + {this.getModalTitle()} + + + {this.getModalBody()} + + +
+ + +
+
+
+
); } } + +t('common.permissions.canBasicAuth'); + +export default injectIntl(PermissionModal); diff --git a/src/renderer/modals/removeServer/removeServer.tsx b/src/renderer/modals/removeServer/removeServer.tsx index 3b43e395..6f1909a6 100644 --- a/src/renderer/modals/removeServer/removeServer.tsx +++ b/src/renderer/modals/removeServer/removeServer.tsx @@ -11,6 +11,8 @@ import {ModalMessage} from 'types/modals'; import {MODAL_CANCEL, MODAL_INFO, MODAL_RESULT, RETRIEVE_MODAL_INFO} from 'common/communication'; +import IntlProvider from 'renderer/intl_provider'; + import RemoveServerModal from '../../components/RemoveServerModal'; import setupDarkMode from '../darkMode'; @@ -45,19 +47,21 @@ const RemoveServerModalWrapper: React.FC = () => { }, []); return ( - { - onClose(); - }} - onCancel={() => { - onSave(false); - }} - onAccept={() => { - onSave(true); - }} - /> + + { + onClose(); + }} + onCancel={() => { + onSave(false); + }} + onAccept={() => { + onSave(true); + }} + /> + ); }; diff --git a/src/renderer/settings.tsx b/src/renderer/settings.tsx index 9c292236..495215fa 100644 --- a/src/renderer/settings.tsx +++ b/src/renderer/settings.tsx @@ -14,6 +14,7 @@ import {DARK_MODE_CHANGE, GET_DARK_MODE} from 'common/communication'; import darkStyles from 'renderer/css/lazy/settings-dark.lazy.css'; import SettingsPage from './components/SettingsPage'; +import IntlProvider from './intl_provider'; const setDarkMode = (darkMode: boolean) => { if (darkMode) { @@ -28,7 +29,12 @@ window.ipcRenderer.invoke(GET_DARK_MODE).then(setDarkMode); const start = async () => { ReactDOM.render( - , + ( + + + + ) + , document.getElementById('app'), ); }; diff --git a/src/renderer/updater.html b/src/renderer/updater.html deleted file mode 100644 index 39247158..00000000 --- a/src/renderer/updater.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - Updater - - - - -
- - - diff --git a/src/renderer/updater.tsx b/src/renderer/updater.tsx deleted file mode 100644 index 7c039712..00000000 --- a/src/renderer/updater.tsx +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2015-2016 Yuya Ochiai -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -// TODO: Commented out for now since remote is removed, will be changed with the autoupdater changes anyway. - -// import url from 'url'; - -// import React from 'react'; -// import ReactDOM from 'react-dom'; -// //import {remote} from 'electron'; - -// import UpdaterPage from './components/UpdaterPage'; - -// const thisURL = url.parse(location.href, true); -// const notifyOnly = thisURL.query.notifyOnly === 'true'; - -// type Props = { -// notifyOnly: boolean; -// initialState: State; -// }; - -// type State = { - -// } - -// class UpdaterPageContainer extends React.PureComponent { -// constructor(props: Props) { -// super(props); -// this.state = props.initialState; -// } - -// getTabWebContents() { -// return null;//remote.webContents.getFocusedWebContents(); -// } - -// componentDidMount() { -// window.ipcRenderer.on('start-download', () => { -// this.setState({ -// isDownloading: true, -// }); -// }); -// window.ipcRenderer.on('progress', (event, progress) => { -// this.setState({ -// progress, -// }); -// }); -// window.ipcRenderer.on('zoom-in', () => { -// const activeTabWebContents = this.getTabWebContents(); -// if (!activeTabWebContents) { -// return; -// } -// if (activeTabWebContents.zoomLevel >= 9) { -// return; -// } -// activeTabWebContents.zoomLevel += 1; -// }); - -// window.ipcRenderer.on('zoom-out', () => { -// const activeTabWebContents = this.getTabWebContents(); -// if (!activeTabWebContents) { -// return; -// } -// if (activeTabWebContents.zoomLevel <= -8) { -// return; -// } -// activeTabWebContents.zoomLevel -= 1; -// }); - -// window.ipcRenderer.on('zoom-reset', () => { -// const activeTabWebContents = this.getTabWebContents(); -// if (!activeTabWebContents) { -// return; -// } -// activeTabWebContents.zoomLevel = 0; -// }); - -// window.ipcRenderer.on('undo', () => { -// const activeTabWebContents = this.getTabWebContents(); -// if (!activeTabWebContents) { -// return; -// } -// activeTabWebContents.undo(); -// }); - -// window.ipcRenderer.on('redo', () => { -// const activeTabWebContents = this.getTabWebContents(); -// if (!activeTabWebContents) { -// return; -// } -// activeTabWebContents.redo(); -// }); - -// window.ipcRenderer.on('cut', () => { -// const activeTabWebContents = this.getTabWebContents(); -// if (!activeTabWebContents) { -// return; -// } -// activeTabWebContents.cut(); -// }); - -// window.ipcRenderer.on('copy', () => { -// const activeTabWebContents = this.getTabWebContents(); -// if (!activeTabWebContents) { -// return; -// } -// activeTabWebContents.copy(); -// }); - -// window.ipcRenderer.on('paste', () => { -// const activeTabWebContents = this.getTabWebContents(); -// if (!activeTabWebContents) { -// return; -// } -// activeTabWebContents.paste(); -// }); - -// window.ipcRenderer.on('paste-and-match', () => { -// const activeTabWebContents = this.getTabWebContents(); -// if (!activeTabWebContents) { -// return; -// } -// activeTabWebContents.pasteAndMatchStyle(); -// }); -// } - -// render() { -// return ( -// { -// window.ipcRenderer.send('click-release-notes'); -// }} -// onClickSkip={() => { -// window.ipcRenderer.send('click-skip'); -// }} -// onClickRemind={() => { -// window.ipcRenderer.send('click-remind'); -// }} -// onClickInstall={() => { -// window.ipcRenderer.send('click-install'); -// }} -// onClickDownload={() => { -// window.ipcRenderer.send('click-download'); -// }} -// onClickCancel={() => { -// window.ipcRenderer.send('click-cancel'); -// }} -// /> -// ); -// } -// } - -// ReactDOM.render( -// , -// document.getElementById('content'), -// ); diff --git a/src/types/config.ts b/src/types/config.ts index d1b3844a..4703f741 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -45,6 +45,7 @@ export type ConfigV3 = { alwaysMinimize?: boolean; alwaysClose?: boolean; logLevel?: string; + appLanguage?: string; } export type ConfigV2 = {