From fb0cc81d1db0d88c90bb112a0caec66095fcc0f0 Mon Sep 17 00:00:00 2001 From: André Oliveira <37463445+SpecialAro@users.noreply.github.com> Date: Wed, 17 Aug 2022 22:54:41 +0100 Subject: Feature: Add Ferdium Translator (#548) Add feature to translate text natively using https://github.com/shikar/NODE_GOOGLE_TRANSLATE package and a LibreTranslate self-hosted option (already running on our server on https://translator.ferdium.org). --- package-lock.json | 816 ++++++++++++++++++++- package.json | 1 + src/@types/stores.types.ts | 3 + .../settings/settings/EditSettingsForm.jsx | 12 + src/config.ts | 139 ++++ src/containers/settings/EditSettingsScreen.tsx | 54 ++ src/helpers/translation-helpers.ts | 47 ++ src/i18n/locales/en-US.json | 3 + src/index.ts | 13 + src/models/IContextMenuParams.ts | 7 + src/stores/ServicesStore.ts | 21 + src/webview/contextMenu.ts | 6 + src/webview/contextMenuBuilder.ts | 255 ++++++- src/webview/recipe.js | 6 + 14 files changed, 1328 insertions(+), 55 deletions(-) create mode 100644 src/helpers/translation-helpers.ts create mode 100644 src/models/IContextMenuParams.ts diff --git a/package-lock.json b/package-lock.json index ed75ec4ef..7c21ac15a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,6 +82,7 @@ "semver": "7.3.7", "sqlite3": "5.0.8", "tar": "6.1.11", + "translate-google": "1.5.0", "tslib": "2.4.0", "useragent-generator": "1.1.1-amkt-22079-finish.0", "uuid": "8.3.2", @@ -4675,6 +4676,17 @@ "@types/node": "*" } }, + "node_modules/@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "node_modules/@types/color": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.3.tgz", @@ -4820,6 +4832,11 @@ "hoist-non-react-statics": "^3.3.0" } }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "node_modules/@types/http-proxy": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", @@ -4863,6 +4880,11 @@ "pretty-format": "^28.0.0" } }, + "node_modules/@types/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==" + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -4881,6 +4903,14 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -4930,8 +4960,7 @@ "node_modules/@types/node": { "version": "16.11.33", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.33.tgz", - "integrity": "sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA==", - "dev": true + "integrity": "sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -4998,6 +5027,14 @@ "@types/react": "^17" } }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -8004,6 +8041,14 @@ "node": ">=0.10.0" } }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, "node_modules/cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", @@ -8559,7 +8604,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", - "dev": true, "dependencies": { "mimic-response": "^1.0.0" } @@ -8787,6 +8831,23 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, + "node_modules/compress-brotli": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.8.tgz", + "integrity": "sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ==", + "dependencies": { + "@types/json-buffer": "~3.0.0", + "json-buffer": "~3.0.1" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/compress-brotli/node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -9746,6 +9807,14 @@ "node": ">=0.10.0" } }, + "node_modules/detect-indent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", + "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -9905,6 +9974,14 @@ "node": ">=6" } }, + "node_modules/docopt": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/docopt/-/docopt-0.6.2.tgz", + "integrity": "sha512-NqTbaYeE4gA/wU1hdKFdU+AFahpDOpgGLzHP42k6H6DKExJd0A55KEVWYhL9FEmHmgeLvEU2vuKXDuU+4yToOw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -9925,6 +10002,19 @@ "@babel/runtime": "^7.1.2" } }, + "node_modules/dot-json": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/dot-json/-/dot-json-1.2.2.tgz", + "integrity": "sha512-AKL+GsO4wSEU4LU+fAk/PqN4nQ6PB1vT3HpMiZous9xCzK5S0kh4DzfUY0EfU67jsIXLlu0ty71659N9Nmg+Tw==", + "dependencies": { + "detect-indent": "~6.0.0", + "docopt": "~0.6.2", + "underscore-keypath": "~0.0.22" + }, + "bin": { + "dot-json": "bin/dot-json.js" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -14085,8 +14175,7 @@ "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "devOptional": true + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "node_modules/http-deceiver": { "version": "1.2.7", @@ -14194,6 +14283,29 @@ "npm": ">=1.3.7" } }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/http2-wrapper/node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -14429,7 +14541,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true, "engines": { "node": ">=0.8.19" } @@ -14939,6 +15050,14 @@ "node": ">=10" } }, + "node_modules/is-keyword-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-keyword-js/-/is-keyword-js-1.0.3.tgz", + "integrity": "sha512-EW8wNCNvomPa/jsH1g0DmLfPakkRCRTcTML1v1fZMLiVCvQ/1YB+tKsRzShBiWQhqrYCi5a+WsepA4Z8TA9iaA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -15019,7 +15138,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, "engines": { "node": ">=8" } @@ -15152,8 +15270,7 @@ "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "devOptional": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "node_modules/is-unc-path": { "version": "1.0.0", @@ -15166,6 +15283,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "node_modules/is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -17921,8 +18043,7 @@ "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -18667,7 +18788,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, "engines": { "node": ">=4" } @@ -19456,6 +19576,14 @@ "set-blocking": "^2.0.0" } }, + "node_modules/num-or-not": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/num-or-not/-/num-or-not-1.0.1.tgz", + "integrity": "sha512-IMyEpYE7hBjD/fKvZu7/jhy05scXUYy0KXOoKVjoFNU6di56wpGjCok3SoC9k51993v9N7GSLPf+9PuWq220cg==", + "dependencies": { + "trim": "0.0.1" + } + }, "node_modules/number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -22501,6 +22629,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -22712,6 +22845,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/safe-eval": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/safe-eval/-/safe-eval-0.4.1.tgz", + "integrity": "sha512-wmiu4RSYVZ690RP1+cv/LxfPK1dIlEN35aW7iv4SMYdqDrHbkll4+NJcHmKm7PbCuI1df1otOcPwgcc2iFR85g==" + }, "node_modules/safe-json-parse": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", @@ -25051,6 +25189,274 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/translate-google": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/translate-google/-/translate-google-1.5.0.tgz", + "integrity": "sha512-wI/oX3U3t78PthVkvO7BdvKRcjb03JaUs53oAhs0NVmzFJUEm5ROiYWH7gomRHFZ5bL7LKxrFRx8Qy/l1eB46g==", + "dependencies": { + "configstore": "^6.0.0", + "got": "^11.8.2", + "is-keyword-js": "^1.0.3", + "is-url": "^1.2.4", + "lodash": "^4.17.21", + "num-or-not": "^1.0.1", + "safe-eval": "^0.4.1", + "user-agents": "^1.0.779" + } + }, + "node_modules/translate-google/node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/translate-google/node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/translate-google/node_modules/cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/translate-google/node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/translate-google/node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/translate-google/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/translate-google/node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/translate-google/node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/translate-google/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/translate-google/node_modules/got": { + "version": "11.8.5", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", + "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/translate-google/node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/translate-google/node_modules/keyv": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.3.3.tgz", + "integrity": "sha512-AcysI17RvakTh8ir03+a3zJr5r0ovnAH/XTXei/4HIv3bL2K/jzvgivLK9UuI/JbU1aJjM3NSAnVvVVd3n+4DQ==", + "dependencies": { + "compress-brotli": "^1.3.8", + "json-buffer": "3.0.1" + } + }, + "node_modules/translate-google/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/translate-google/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/translate-google/node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/translate-google/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/translate-google/node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/translate-google/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/translate-google/node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/translate-google/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/translate-google/node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -25068,6 +25474,11 @@ "tree-kill": "cli.js" } }, + "node_modules/trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==" + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -25415,7 +25826,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -25479,6 +25889,19 @@ "node": ">=0.10.0" } }, + "node_modules/underscore": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", + "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==" + }, + "node_modules/underscore-keypath": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/underscore-keypath/-/underscore-keypath-0.0.22.tgz", + "integrity": "sha512-fU7aYj1J2LQd+jqdQ67AlCOZKK3Pl+VErS8fGYcgZG75XB9/bY+RLM+F2xEcKHhHNtLvqqFyXAoZQlLYfec3Xg==", + "dependencies": { + "underscore": "*" + } + }, "node_modules/undertaker": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", @@ -26010,6 +26433,15 @@ "node": ">=0.10.0" } }, + "node_modules/user-agents": { + "version": "1.0.1099", + "resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.1099.tgz", + "integrity": "sha512-udTGqrQs7qbdOGfzFyZAvuRlx/TSZij3k+LRT7TnaWRfWkPGbDj/mIJRSe2BJfc9RXdbNO+qos6C9xdXXVi9kA==", + "dependencies": { + "dot-json": "^1.2.2", + "lodash.clonedeep": "^4.5.0" + } + }, "node_modules/useragent": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", @@ -30892,6 +31324,17 @@ "@types/node": "*" } }, + "@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, "@types/color": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.3.tgz", @@ -31037,6 +31480,11 @@ "hoist-non-react-statics": "^3.3.0" } }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "@types/http-proxy": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", @@ -31080,6 +31528,11 @@ "pretty-format": "^28.0.0" } }, + "@types/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==" + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -31098,6 +31551,14 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "requires": { + "@types/node": "*" + } + }, "@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -31147,8 +31608,7 @@ "@types/node": { "version": "16.11.33", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.33.tgz", - "integrity": "sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA==", - "dev": true + "integrity": "sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -31215,6 +31675,14 @@ "@types/react": "^17" } }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, "@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -33527,6 +33995,11 @@ } } }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, "cacheable-request": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", @@ -33965,7 +34438,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", - "dev": true, "requires": { "mimic-response": "^1.0.0" } @@ -34166,6 +34638,22 @@ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, + "compress-brotli": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.8.tgz", + "integrity": "sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ==", + "requires": { + "@types/json-buffer": "~3.0.0", + "json-buffer": "~3.0.1" + }, + "dependencies": { + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + } + } + }, "compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -34908,6 +35396,11 @@ "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==" }, + "detect-indent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", + "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==" + }, "detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -35031,6 +35524,11 @@ "@leichtgewicht/ip-codec": "^2.0.1" } }, + "docopt": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/docopt/-/docopt-0.6.2.tgz", + "integrity": "sha512-NqTbaYeE4gA/wU1hdKFdU+AFahpDOpgGLzHP42k6H6DKExJd0A55KEVWYhL9FEmHmgeLvEU2vuKXDuU+4yToOw==" + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -35048,6 +35546,16 @@ "@babel/runtime": "^7.1.2" } }, + "dot-json": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/dot-json/-/dot-json-1.2.2.tgz", + "integrity": "sha512-AKL+GsO4wSEU4LU+fAk/PqN4nQ6PB1vT3HpMiZous9xCzK5S0kh4DzfUY0EfU67jsIXLlu0ty71659N9Nmg+Tw==", + "requires": { + "detect-indent": "~6.0.0", + "docopt": "~0.6.2", + "underscore-keypath": "~0.0.22" + } + }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -38372,8 +38880,7 @@ "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "devOptional": true + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" }, "http-deceiver": { "version": "1.2.7", @@ -38453,6 +38960,22 @@ "sshpk": "^1.7.0" } }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "dependencies": { + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + } + } + }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -38611,8 +39134,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, "indent-string": { "version": "3.2.0", @@ -38988,6 +39510,11 @@ } } }, + "is-keyword-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-keyword-js/-/is-keyword-js-1.0.3.tgz", + "integrity": "sha512-EW8wNCNvomPa/jsH1g0DmLfPakkRCRTcTML1v1fZMLiVCvQ/1YB+tKsRzShBiWQhqrYCi5a+WsepA4Z8TA9iaA==" + }, "is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -39042,8 +39569,7 @@ "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" }, "is-path-inside": { "version": "3.0.3", @@ -39131,8 +39657,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "devOptional": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "is-unc-path": { "version": "1.0.0", @@ -39142,6 +39667,11 @@ "unc-path-regex": "^0.1.2" } }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -41244,8 +41774,7 @@ "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, "lodash.debounce": { "version": "4.0.8", @@ -41852,8 +42381,7 @@ "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, "min-indent": { "version": "1.0.1", @@ -42447,6 +42975,14 @@ "set-blocking": "^2.0.0" } }, + "num-or-not": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/num-or-not/-/num-or-not-1.0.1.tgz", + "integrity": "sha512-IMyEpYE7hBjD/fKvZu7/jhy05scXUYy0KXOoKVjoFNU6di56wpGjCok3SoC9k51993v9N7GSLPf+9PuWq220cg==", + "requires": { + "trim": "0.0.1" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -44824,6 +45360,11 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -44974,6 +45515,11 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "safe-eval": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/safe-eval/-/safe-eval-0.4.1.tgz", + "integrity": "sha512-wmiu4RSYVZ690RP1+cv/LxfPK1dIlEN35aW7iv4SMYdqDrHbkll4+NJcHmKm7PbCuI1df1otOcPwgcc2iFR85g==" + }, "safe-json-parse": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", @@ -46860,6 +47406,192 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "translate-google": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/translate-google/-/translate-google-1.5.0.tgz", + "integrity": "sha512-wI/oX3U3t78PthVkvO7BdvKRcjb03JaUs53oAhs0NVmzFJUEm5ROiYWH7gomRHFZ5bL7LKxrFRx8Qy/l1eB46g==", + "requires": { + "configstore": "^6.0.0", + "got": "^11.8.2", + "is-keyword-js": "^1.0.3", + "is-url": "^1.2.4", + "lodash": "^4.17.21", + "num-or-not": "^1.0.1", + "safe-eval": "^0.4.1", + "user-agents": "^1.0.779" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + } + }, + "configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "requires": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + } + }, + "crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "requires": { + "type-fest": "^1.0.1" + } + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "11.8.5", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", + "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.3.3.tgz", + "integrity": "sha512-AcysI17RvakTh8ir03+a3zJr5r0ovnAH/XTXei/4HIv3bL2K/jzvgivLK9UuI/JbU1aJjM3NSAnVvVVd3n+4DQ==", + "requires": { + "compress-brotli": "^1.3.8", + "json-buffer": "3.0.1" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" + }, + "unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "requires": { + "crypto-random-string": "^4.0.0" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==" + } + } + }, "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -46871,6 +47603,11 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==" + }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -47130,7 +47867,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, "requires": { "is-typedarray": "^1.0.0" } @@ -47172,6 +47908,19 @@ "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==" }, + "underscore": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.4.tgz", + "integrity": "sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ==" + }, + "underscore-keypath": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/underscore-keypath/-/underscore-keypath-0.0.22.tgz", + "integrity": "sha512-fU7aYj1J2LQd+jqdQ67AlCOZKK3Pl+VErS8fGYcgZG75XB9/bY+RLM+F2xEcKHhHNtLvqqFyXAoZQlLYfec3Xg==", + "requires": { + "underscore": "*" + } + }, "undertaker": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", @@ -47581,6 +48330,15 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "user-agents": { + "version": "1.0.1099", + "resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.1099.tgz", + "integrity": "sha512-udTGqrQs7qbdOGfzFyZAvuRlx/TSZij3k+LRT7TnaWRfWkPGbDj/mIJRSe2BJfc9RXdbNO+qos6C9xdXXVi9kA==", + "requires": { + "dot-json": "^1.2.2", + "lodash.clonedeep": "^4.5.0" + } + }, "useragent": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", diff --git a/package.json b/package.json index d7f1fba89..863db5413 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "semver": "7.3.7", "sqlite3": "5.0.8", "tar": "6.1.11", + "translate-google": "1.5.0", "tslib": "2.4.0", "useragent-generator": "1.1.1-amkt-22079-finish.0", "uuid": "8.3.2", diff --git a/src/@types/stores.types.ts b/src/@types/stores.types.ts index eec18c11e..13870a43a 100644 --- a/src/@types/stores.types.ts +++ b/src/@types/stores.types.ts @@ -90,6 +90,7 @@ interface AppStore extends TypedStore { darkMode: boolean; dictionaries: []; enableSpellchecking: boolean; + enableTranslator: boolean; fetchDataInterval: 4; get(key: string): any; getAppCacheSizeRequest: () => void; @@ -106,6 +107,8 @@ interface AppStore extends TypedStore { reloadAfterResume: boolean; reloadAfterResumeTime: number; searchEngine: string; + translatorEngine: string; + translatorLanguage: string; spellcheckerLanguage: string; splitMode: boolean; splitColumns: number; diff --git a/src/components/settings/settings/EditSettingsForm.jsx b/src/components/settings/settings/EditSettingsForm.jsx index 0e5be38ed..e6cba922b 100644 --- a/src/components/settings/settings/EditSettingsForm.jsx +++ b/src/components/settings/settings/EditSettingsForm.jsx @@ -870,12 +870,24 @@ class EditSettingsForm extends Component { {intl.formatMessage(messages.spellCheckerLanguageInfo)}

)} +

{intl.formatMessage(messages.appRestartRequired)}


+ + + {form.$('enableTranslator').value && ( + + )} + +
+ `https://www.startpage.com/sp/search?query=${searchTerm}`, @@ -222,6 +358,7 @@ export const DEFAULT_APP_SETTINGS = { showMessageBadgeWhenMuted: true, showDragArea: false, enableSpellchecking: true, + enableTranslator: false, spellcheckerLanguage: 'en-us', darkMode: false, navigationBarManualActive: false, @@ -261,6 +398,8 @@ export const DEFAULT_APP_SETTINGS = { iconSize: iconSizeBias, navigationBarBehaviour: 'custom', searchEngine: SEARCH_ENGINE_STARTPAGE, + translatorLanguage: 'en', + translatorEngine: TRANSLATOR_ENGINE_LIBRETRANSLATE, useHorizontalStyle: false, hideCollapseButton: false, isMenuCollapsed: false, diff --git a/src/containers/settings/EditSettingsScreen.tsx b/src/containers/settings/EditSettingsScreen.tsx index fbbed629a..a4d7ba0eb 100644 --- a/src/containers/settings/EditSettingsScreen.tsx +++ b/src/containers/settings/EditSettingsScreen.tsx @@ -15,6 +15,10 @@ import { ICON_SIZES, NAVIGATION_BAR_BEHAVIOURS, SEARCH_ENGINE_NAMES, + TRANSLATOR_ENGINE_NAMES, + GOOGLE_TRANSLATOR_LANGUAGES, + TRANSLATOR_ENGINE_GOOGLE, + LIBRETRANSLATE_TRANSLATOR_LANGUAGES, TODO_APPS, DEFAULT_SETTING_KEEP_ALL_WORKSPACES_LOADED, DEFAULT_IS_FEATURE_ENABLED_BY_USER, @@ -103,6 +107,14 @@ const messages = defineMessages({ id: 'settings.app.form.searchEngine', defaultMessage: 'Search engine', }, + translatorEngine: { + id: 'settings.app.form.translatorEngine', + defaultMessage: 'Translator Engine', + }, + translatorLanguage: { + id: 'settings.app.form.translatorLanguage', + defaultMessage: 'Default Translator language', + }, hibernateOnStartup: { id: 'settings.app.form.hibernateOnStartup', defaultMessage: 'Keep services in hibernation on startup', @@ -267,6 +279,10 @@ const messages = defineMessages({ id: 'settings.app.form.enableSpellchecking', defaultMessage: 'Enable spell checking', }, + enableTranslator: { + id: 'settings.app.form.enableTranslator', + defaultMessage: 'Enable Translator', + }, enableGPUAcceleration: { id: 'settings.app.form.enableGPUAcceleration', defaultMessage: 'Enable GPU Acceleration', @@ -342,6 +358,8 @@ class EditSettingsScreen extends Component { notifyTaskBarOnMessage: Boolean(settingsData.notifyTaskBarOnMessage), navigationBarBehaviour: settingsData.navigationBarBehaviour, searchEngine: settingsData.searchEngine, + translatorEngine: settingsData.translatorEngine, + translatorLanguage: settingsData.translatorLanguage, hibernateOnStartup: Boolean(settingsData.hibernateOnStartup), hibernationStrategy: Number(settingsData.hibernationStrategy), wakeUpStrategy: Number(settingsData.wakeUpStrategy), @@ -394,6 +412,7 @@ class EditSettingsScreen extends Component { ), showDragArea: Boolean(settingsData.showDragArea), enableSpellchecking: Boolean(settingsData.enableSpellchecking), + enableTranslator: Boolean(settingsData.enableTranslator), spellcheckerLanguage: settingsData.spellcheckerLanguage, userAgentPref: settingsData.userAgentPref, beta: Boolean(settingsData.beta), // we need this info in the main process as well @@ -451,6 +470,16 @@ class EditSettingsScreen extends Component { sort: false, }); + const translatorEngines = getSelectOptions({ + locales: TRANSLATOR_ENGINE_NAMES, + sort: false, + }); + + const translatorLanguages = getSelectOptions({ + locales: LIBRETRANSLATE_TRANSLATOR_LANGUAGES, + sort: false, + }); + const hibernationStrategies = getSelectOptions({ locales: HIBERNATION_STRATEGIES, sort: false, @@ -574,6 +603,18 @@ class EditSettingsScreen extends Component { default: DEFAULT_APP_SETTINGS.searchEngine, options: searchEngines, }, + translatorEngine: { + label: intl.formatMessage(messages.translatorEngine), + value: settings.all.app.translatorEngine, + default: DEFAULT_APP_SETTINGS.translatorEngine, + options: translatorEngines, + }, + translatorLanguage: { + label: intl.formatMessage(messages.translatorLanguage), + value: settings.all.app.translatorLanguage, + default: DEFAULT_APP_SETTINGS.translatorLanguage, + options: translatorLanguages, + }, hibernateOnStartup: { label: intl.formatMessage(messages.hibernateOnStartup), value: settings.all.app.hibernateOnStartup, @@ -677,6 +718,11 @@ class EditSettingsScreen extends Component { value: settings.all.app.enableSpellchecking, default: DEFAULT_APP_SETTINGS.enableSpellchecking, }, + enableTranslator: { + label: intl.formatMessage(messages.enableTranslator), + value: settings.all.app.enableTranslator, + default: DEFAULT_APP_SETTINGS.enableTranslator, + }, spellcheckerLanguage: { label: intl.formatMessage(globalMessages.spellcheckerLanguage), value: settings.all.app.spellcheckerLanguage, @@ -828,6 +874,14 @@ class EditSettingsScreen extends Component { }, }; + if (settings.app.translatorEngine === TRANSLATOR_ENGINE_GOOGLE) { + const translatorGoogleLanguages = getSelectOptions({ + locales: GOOGLE_TRANSLATOR_LANGUAGES, + sort: false, + }); + config.fields.translatorLanguage.options = translatorGoogleLanguages; + } + if (workspaces.isFeatureActive) { config.fields.keepAllWorkspacesLoaded = { label: intl.formatMessage(messages.keepAllWorkspacesLoaded), diff --git a/src/helpers/translation-helpers.ts b/src/helpers/translation-helpers.ts new file mode 100644 index 000000000..215b2a49c --- /dev/null +++ b/src/helpers/translation-helpers.ts @@ -0,0 +1,47 @@ +import fetch from 'node-fetch'; +import translateGoogle from 'translate-google'; +import { LIVE_API_FERDIUM_LIBRETRANSLATE } from '../config'; + +export async function translateTo( + text: string, + translateToLanguage: string, + translatorEngine: string, +): Promise<{ text: string; error: boolean }> { + const errorText = + // TODO: Need to support i18n + 'FERDIUM ERROR: An error occured. Please select less text to translate or try again later.'; + + if (translatorEngine === 'Google') { + try { + const res = await translateGoogle(text, { + to: translateToLanguage, + }); + + return { text: res, error: false }; + } catch { + return { text: errorText, error: true }; + } + } else if (translatorEngine === 'LibreTranslate') { + try { + const res = await fetch(LIVE_API_FERDIUM_LIBRETRANSLATE, { + method: 'POST', + body: JSON.stringify({ + q: text, + source: 'auto', + target: translateToLanguage, + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + + const response = await res.json(); + + return { text: response.translatedText, error: false }; + } catch { + return { text: errorText, error: true }; + } + } + + return { text: errorText, error: true }; +} diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index a98bc2f77..4dc8bad82 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -219,6 +219,7 @@ "settings.app.form.enableSpellchecking": "Enable spell checking", "settings.app.form.enableSystemTray": "Always show Ferdium in System Tray", "settings.app.form.enableTodos": "Enable Ferdium Todos", + "settings.app.form.enableTranslator": "Enable Translator", "settings.app.form.grayscaleServicesDim": "Grayscale dim level", "settings.app.form.hibernateOnStartup": "Keep services in hibernation on startup", "settings.app.form.hibernationStrategy": "Hibernation strategy", @@ -256,6 +257,8 @@ "settings.app.form.splitColumns": "Number of columns", "settings.app.form.splitMode": "Enable Split View Mode", "settings.app.form.startMinimized": "Start minimized", + "settings.app.form.translatorEngine": "Translator Engine", + "settings.app.form.translatorLanguage": "Default Translator language", "settings.app.form.universalDarkMode": "Enable universal Dark Mode", "settings.app.form.useGrayscaleServices": "Use grayscale services", "settings.app.form.useHorizontalStyle": "Use horizontal style", diff --git a/src/index.ts b/src/index.ts index 05e222a25..7c80ca955 100644 --- a/src/index.ts +++ b/src/index.ts @@ -45,6 +45,7 @@ import './electron/exception'; import { asarPath } from './helpers/asar-helpers'; import { openExternalUrl } from './helpers/url-helpers'; import userAgent from './helpers/userAgent-helpers'; +import { translateTo } from './helpers/translation-helpers'; const debug = require('./preload-safe-debug')('Ferdium:App'); @@ -500,6 +501,18 @@ app.on('login', (event, _webContents, _request, authInfo, callback) => { } }); +ipcMain.handle( + 'translate', + async (_e, { text, translateToLanguage, translatorEngine }) => { + const response = await translateTo( + text, + translateToLanguage, + translatorEngine, + ); + return response; + }, +); + // TODO: evaluate if we need to store the authCallback for every service ipcMain.on('feature-basic-auth-credentials', (_e, { user, password }) => { debug('Received basic auth credentials', user, '********'); diff --git a/src/models/IContextMenuParams.ts b/src/models/IContextMenuParams.ts new file mode 100644 index 000000000..c661b4595 --- /dev/null +++ b/src/models/IContextMenuParams.ts @@ -0,0 +1,7 @@ +export default interface IContextMenuParams extends Electron.ContextMenuParams { + enableTranslator: boolean; + clipboardNotifications: boolean; + searchEngine: string; + translatorEngine: string; + translatorLanguage: string; +} diff --git a/src/stores/ServicesStore.ts b/src/stores/ServicesStore.ts index 83ec7d18e..00cf33b17 100644 --- a/src/stores/ServicesStore.ts +++ b/src/stores/ServicesStore.ts @@ -157,6 +157,13 @@ export default class ServicesStore extends TypedStore { }, ); + reaction( + () => this.stores.settings.app.enableTranslator, + () => { + this._shareSettingsWithServiceProcess(); + }, + ); + reaction( () => this.stores.settings.app.spellcheckerLanguage, () => { @@ -206,6 +213,20 @@ export default class ServicesStore extends TypedStore { }, ); + reaction( + () => this.stores.settings.app.translatorEngine, + () => { + this._shareSettingsWithServiceProcess(); + }, + ); + + reaction( + () => this.stores.settings.app.translatorLanguage, + () => { + this._shareSettingsWithServiceProcess(); + }, + ); + reaction( () => this.stores.settings.app.clipboardNotifications, () => { diff --git a/src/webview/contextMenu.ts b/src/webview/contextMenu.ts index 72f927ef4..61771854f 100644 --- a/src/webview/contextMenu.ts +++ b/src/webview/contextMenu.ts @@ -9,6 +9,9 @@ export default async function setupContextMenu( getSpellcheckerLanguage: () => void, getSearchEngine: () => void, getClipboardNotifications: () => void, + getEnableTranslator: () => void, + getTranslatorEngine: () => void, + getTranslatorLanguage: () => void, ) { const contextMenuBuilder = new ContextMenuBuilder(webContents); @@ -19,6 +22,9 @@ export default async function setupContextMenu( ...props, searchEngine: getSearchEngine(), clipboardNotifications: getClipboardNotifications(), + enableTranslator: getEnableTranslator(), + translatorEngine: getTranslatorEngine(), + translatorLanguage: getTranslatorLanguage(), }, // @ts-expect-error Expected 1 arguments, but got 4. isSpellcheckEnabled(), diff --git a/src/webview/contextMenuBuilder.ts b/src/webview/contextMenuBuilder.ts index 7bd86556e..2e64977c1 100644 --- a/src/webview/contextMenuBuilder.ts +++ b/src/webview/contextMenuBuilder.ts @@ -11,8 +11,16 @@ import { clipboard, ipcRenderer, nativeImage, WebContents } from 'electron'; import { Menu, MenuItem } from '@electron/remote'; import { cmdOrCtrlShortcutKey, isMac } from '../environment'; -import { SEARCH_ENGINE_NAMES, SEARCH_ENGINE_URLS } from '../config'; +import { + SEARCH_ENGINE_NAMES, + SEARCH_ENGINE_URLS, + GOOGLE_TRANSLATOR_LANGUAGES, + TRANSLATOR_ENGINE_GOOGLE, + TRANSLATOR_ENGINE_LIBRETRANSLATE, + LIBRETRANSLATE_TRANSLATOR_LANGUAGES, +} from '../config'; import { openExternalUrl } from '../helpers/url-helpers'; +import IContextMenuParams from '../models/IContextMenuParams'; function matchesWord(string: string) { const regex = @@ -21,6 +29,83 @@ function matchesWord(string: string) { return string.match(regex); } +function childOf(node, ancestor) { + let child = node; + while (child !== null) { + if (child === ancestor) return true; + child = child.parentNode; + } + return false; +} + +function translatePopup(res, isError: boolean = false) { + const elementExists = document.querySelector('#container-ferdium-translator'); + if (elementExists) { + elementExists.remove(); + } + + const style = document.createElement('style'); + style.innerHTML = ` + .container-ferdium-translator { + position: fixed; + opacity: 0.9; + z-index: 999999; + ${ + isError + ? `background: rgb(255 37 37);` + : `background: rgb(131 131 131);` + } + border-radius: 8px; + top: 5%; + left: 50%; + transform: translate(-50%, -5%); + display: flex; + flex-direction: row; + -webkit-box-shadow: 0px 10px 13px -7px #000000, 5px 5px 13px 9px rgba(0,0,0,0); + overflow: auto; + max-height: 95%; + max-width: 90%; + width: max-content; + height: max-content; + -webkit-animation: scale-up-center 0.4s cubic-bezier(0.390, 0.575, 0.565, 1.000) both; + } + .container-ferdium-translator > p { + color: white; + margin: 10px; + text-align: justify; + } + + @-webkit-keyframes scale-up-center { + 0% { + -webkit-transform: translate(-50%, -50%) scale(0.5); + } + 100% { + -webkit-transform: translate(-50%, -50%) scale(1); + } + } + `; + document.head.append(style); + + const para = document.createElement('p'); + + const node = document.createTextNode(res); + para.append(node); + + const div = document.createElement('div'); + div.setAttribute('id', 'container-ferdium-translator'); + div.setAttribute('class', 'container-ferdium-translator'); + + div.append(para); + + document.body.insertBefore(div, document.body.firstChild); + + document.addEventListener('click', e => { + if (div !== e.target && !childOf(e.target, div)) { + div?.remove(); + } + }); +} + interface ContextMenuStringTable { lookUpDefinition: ({ word }: { word: string }) => string; cut: () => string; @@ -28,6 +113,17 @@ interface ContextMenuStringTable { paste: () => string; pasteAndMatchStyle: () => string; searchWith: ({ searchEngine }: { searchEngine: string }) => string; + translate: () => string; + quickTranslate: ({ + translatorLanguage, + }: { + translatorLanguage: string; + }) => string; + translateLanguage: ({ + translatorLanguage, + }: { + translatorLanguage: string; + }) => string; openLinkUrl: () => string; openLinkInFerdiumUrl: () => string; openInBrowser: () => string; @@ -52,6 +148,10 @@ const contextMenuStringTable: ContextMenuStringTable = { paste: () => 'Paste', pasteAndMatchStyle: () => 'Paste and match style', searchWith: ({ searchEngine }) => `Search with ${searchEngine}`, + translate: () => `Translate to ...`, + quickTranslate: ({ translatorLanguage }) => + `Translate to ${translatorLanguage}`, + translateLanguage: ({ translatorLanguage }) => `${translatorLanguage}`, openLinkUrl: () => 'Open Link', openLinkInFerdiumUrl: () => 'Open Link in Ferdium', openInBrowser: () => 'Open in Browser', @@ -127,7 +227,7 @@ export class ContextMenuBuilder { * * @param contextInfo The object returned from the 'context-menu' Electron event. */ - async showPopupMenu(contextInfo: Electron.ContextMenuParams): Promise { + async showPopupMenu(contextInfo: IContextMenuParams): Promise { const menu = await this.buildMenuForElement(contextInfo); if (!menu) return; menu.popup(); @@ -139,7 +239,7 @@ export class ContextMenuBuilder { * the list but use most of the default behavior. */ async buildMenuForElement( - info: Electron.ContextMenuParams, + info: IContextMenuParams, ): Promise { if (info.linkURL && info.linkURL.length > 0) { return this.buildMenuForLink(info); @@ -165,12 +265,17 @@ export class ContextMenuBuilder { * @return {Menu} The `Menu` */ buildMenuForTextInput( - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ): Electron.CrossProcessExports.Menu { const menu = new Menu(); + const { enableTranslator } = menuInfo; + this.addSpellingItems(menu, menuInfo); this.addSearchItems(menu, menuInfo); + if (enableTranslator) { + this.addTranslateItems(menu, menuInfo); + } this.addCut(menu, menuInfo); this.addCopy(menu, menuInfo); @@ -194,7 +299,7 @@ export class ContextMenuBuilder { * @return {Menu} The `Menu` */ buildMenuForLink( - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ): Electron.CrossProcessExports.Menu { const menu = new Menu(); const isEmailAddress = menuInfo.linkURL.startsWith('mailto:'); @@ -208,7 +313,6 @@ export class ContextMenuBuilder { const url = isEmailAddress ? menuInfo.linkText : menuInfo.linkURL; clipboard.writeText(url); this._sendNotificationOnClipboardEvent( - // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. menuInfo.clipboardNotifications, () => `Link URL copied: ${url}`, ); @@ -257,11 +361,16 @@ export class ContextMenuBuilder { * Builds a menu applicable to a text field. */ buildMenuForText( - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ): Electron.CrossProcessExports.Menu { const menu = new Menu(); + const { enableTranslator } = menuInfo; + this.addSearchItems(menu, menuInfo); + if (enableTranslator) { + this.addTranslateItems(menu, menuInfo); + } this.addCopy(menu, menuInfo); this.addInspectElement(menu, menuInfo); // @ts-expect-error Expected 1 arguments, but got 2. @@ -283,7 +392,7 @@ export class ContextMenuBuilder { * @return {Menu} The `Menu` */ buildMenuForImage( - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ): Electron.CrossProcessExports.Menu { const menu = new Menu(); @@ -303,7 +412,7 @@ export class ContextMenuBuilder { */ addSpellingItems( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { const webContents = this.getWebContents(); // Add each spelling suggestion @@ -338,7 +447,7 @@ export class ContextMenuBuilder { */ addSearchItems( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { if (!menuInfo.selectionText || menuInfo.selectionText.length === 0) { return menu; @@ -364,11 +473,9 @@ export class ContextMenuBuilder { const search = new MenuItem({ label: this.stringTable.searchWith({ - // @ts-expect-error Property 'searchEngine' does not exist on type 'ContextMenuParams'. searchEngine: SEARCH_ENGINE_NAMES[menuInfo.searchEngine], }), click: () => { - // @ts-expect-error Property 'searchEngine' does not exist on type 'ContextMenuParams'. const url = SEARCH_ENGINE_URLS[menuInfo.searchEngine]({ searchTerm: encodeURIComponent(menuInfo.selectionText), }); @@ -382,7 +489,106 @@ export class ContextMenuBuilder { return menu; } - isSrcUrlValid(menuInfo: Electron.ContextMenuParams) { + /** + * Adds translate-related menu items. + */ + addTranslateItems( + menu: Electron.CrossProcessExports.Menu, + menuInfo: IContextMenuParams, + ) { + const { translatorEngine } = menuInfo; + + if (!menuInfo.selectionText || menuInfo.selectionText.length === 0) { + return menu; + } + + const match = matchesWord(menuInfo.selectionText); + if (!match || match.length === 0) { + return menu; + } + + const generateTranslationItems = async ( + translateToLanguage: string, + translatorEngine: string, + ) => { + // TODO: Need to support i18n + translatePopup('Loading...', false); + + const translatedText = await ipcRenderer.invoke('translate', { + text: menuInfo.selectionText, + translateToLanguage, + translatorEngine, + }); + + translatePopup(translatedText.text, translatedText.error); + }; + + let arrayLanguagesAfterEngine; + if (translatorEngine === TRANSLATOR_ENGINE_LIBRETRANSLATE) { + arrayLanguagesAfterEngine = LIBRETRANSLATE_TRANSLATOR_LANGUAGES; + } else if (translatorEngine === TRANSLATOR_ENGINE_GOOGLE) { + arrayLanguagesAfterEngine = GOOGLE_TRANSLATOR_LANGUAGES; + } + + const arrayLanguages = Object.keys(arrayLanguagesAfterEngine).map( + code => arrayLanguagesAfterEngine[code], + ); + + const menuLanguages = new Menu(); + + const getLanguageCode = (obj, val): string => + Object.keys(obj).find(key => obj[key] === val)!; + + for (const language in arrayLanguages) { + if (arrayLanguages[language]) { + const languageItem = new MenuItem({ + label: this.stringTable.translateLanguage({ + translatorLanguage: arrayLanguages[language], + }), + click: () => { + const translateToLanguageCode = getLanguageCode( + arrayLanguagesAfterEngine, + arrayLanguages[language], + ); + generateTranslationItems(translateToLanguageCode, translatorEngine); + }, + }); + menuLanguages.append(languageItem); + } + } + + const translateToLanguage = + arrayLanguagesAfterEngine[menuInfo.translatorLanguage]; + + const translateToLanguageCode = getLanguageCode( + arrayLanguagesAfterEngine, + translateToLanguage, + ); + const quickTranslateItem = new MenuItem({ + label: this.stringTable.quickTranslate({ + translatorLanguage: translateToLanguage, + }), + click: () => { + generateTranslationItems(translateToLanguageCode, translatorEngine); + }, + }); + + const translateItem = new MenuItem({ + label: this.stringTable.translate(), + submenu: menuLanguages, + click: () => { + generateTranslationItems(translateToLanguageCode, translatorEngine); + }, + }); + + menu.append(translateItem); + menu.append(quickTranslateItem); + this.addSeparator(menu); + + return menu; + } + + isSrcUrlValid(menuInfo: IContextMenuParams) { return menuInfo.srcURL && menuInfo.srcURL.length > 0; } @@ -391,7 +597,7 @@ export class ContextMenuBuilder { */ addImageItems( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { const copyImage = new MenuItem({ label: this.stringTable.copyImage(), @@ -403,7 +609,6 @@ export class ContextMenuBuilder { ); this._sendNotificationOnClipboardEvent( - // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. menuInfo.clipboardNotifications, () => `Image copied from URL: ${menuInfo.srcURL}`, ); @@ -418,7 +623,6 @@ export class ContextMenuBuilder { click: () => { const result = clipboard.writeText(menuInfo.srcURL); this._sendNotificationOnClipboardEvent( - // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. menuInfo.clipboardNotifications, () => `Image URL copied: ${menuInfo.srcURL}`, ); @@ -446,7 +650,6 @@ export class ContextMenuBuilder { }); }); this._sendNotificationOnClipboardEvent( - // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. menuInfo.clipboardNotifications, () => `Image downloaded: ${urlWithoutBlob}`, ); @@ -464,7 +667,7 @@ export class ContextMenuBuilder { */ addCut( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { const webContents = this.getWebContents(); menu.append( @@ -484,7 +687,7 @@ export class ContextMenuBuilder { */ addCopy( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { const webContents = this.getWebContents(); menu.append( @@ -504,7 +707,7 @@ export class ContextMenuBuilder { */ addPaste( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { const webContents = this.getWebContents(); menu.append( @@ -521,7 +724,7 @@ export class ContextMenuBuilder { addPastePlain( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { if ( menuInfo.editFlags.canPaste && @@ -552,7 +755,7 @@ export class ContextMenuBuilder { */ addInspectElement( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, needsSeparator = true, ) { const webContents = this.getWebContents(); @@ -609,6 +812,7 @@ export class ContextMenuBuilder { */ goBack(menu: Electron.CrossProcessExports.Menu) { const webContents = this.getWebContents(); + menu.append( new MenuItem({ label: this.stringTable.goBack(), @@ -643,7 +847,7 @@ export class ContextMenuBuilder { */ copyPageUrl( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { menu.append( new MenuItem({ @@ -652,7 +856,6 @@ export class ContextMenuBuilder { click: () => { clipboard.writeText(window.location.href); this._sendNotificationOnClipboardEvent( - // @ts-expect-error Property 'clipboardNotifications' does not exist on type 'ContextMenuParams'. menuInfo?.clipboardNotifications, () => `Page URL copied: ${window.location.href}`, ); @@ -668,7 +871,7 @@ export class ContextMenuBuilder { */ goToHomePage( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { const baseURL = new window.URL(menuInfo.pageURL); menu.append( @@ -691,7 +894,7 @@ export class ContextMenuBuilder { */ openInBrowser( menu: Electron.CrossProcessExports.Menu, - menuInfo: Electron.ContextMenuParams, + menuInfo: IContextMenuParams, ) { menu.append( new MenuItem({ diff --git a/src/webview/recipe.js b/src/webview/recipe.js index 8c93da202..acf4f9f31 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js @@ -188,6 +188,9 @@ class RecipeController { () => this.spellcheckerLanguage, () => this.settings.app.searchEngine, () => this.settings.app.clipboardNotifications, + () => this.settings.app.enableTranslator, + () => this.settings.app.translatorEngine, + () => this.settings.app.translatorLanguage, ); autorun(() => this.update()); @@ -293,6 +296,9 @@ class RecipeController { ); debug('darkReaderSettigs', this.settings.service.darkReaderSettings); debug('searchEngine', this.settings.app.searchEngine); + debug('enableTranslator', this.settings.app.enableTranslator); + debug('translatorEngine', this.settings.app.translatorEngine); + debug('translatorLanguage', this.settings.app.translatorLanguage); if (this.userscript && this.userscript.internal_setSettings) { this.userscript.internal_setSettings(this.settings); -- cgit v1.2.3-54-g00ecf