From 9c0fdc3717207a36a6e4b0bbadfd25cea1808602 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 00:24:18 +0100 Subject: [PATCH 01/10] project: Prepare for npm release --- .gitignore | 1 + package-lock.json | 928 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 28 ++ 3 files changed, 957 insertions(+) create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6e67e8c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,928 @@ +{ + "name": "i18n", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "acorn": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", + "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", + "dev": true + }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true + }, + "ajv": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", + "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.9.0.tgz", + "integrity": "sha512-g4KWpPdqN0nth+goDNICNXGfJF7nNnepthp46CAlJoJtC5K/cLu3NgCM3AHu1CkJ5Hzt9V0Y0PBAO6Ay/gGb+w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "imurmurhash": "^0.1.4", + "inquirer": "^6.1.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" + } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", + "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", + "dev": true, + "requires": { + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", + "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/table/-/table-5.1.0.tgz", + "integrity": "sha512-e542in22ZLhD/fOIuXs/8yDZ9W61ltF8daM88rkRNtgTIct+vI2fTnAyu/Db2TCfEcI8i7mjZz6meLq0nW7TYg==", + "dev": true, + "requires": { + "ajv": "^6.5.3", + "lodash": "^4.17.10", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..12aa2bb --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "i18n", + "version": "0.0.1", + "description": "Simple Internationalization provider for JavaScript", + "main": "i18n.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Xaymar/i18n-js.git" + }, + "keywords": [ + "i18n", + "internationalization", + "translation", + "translate" + ], + "author": "Michael Fabian 'Xaymar' Dirks ", + "license": "AGPL-3.0", + "bugs": { + "url": "https://github.com/Xaymar/i18n-js/issues" + }, + "homepage": "https://github.com/Xaymar/i18n-js#readme", + "devDependencies": { + "eslint": "^5.9.0" + } +} -- 2.52.0 From 9039b7e7649f77eaad414fd4e242617e714db558 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 00:27:33 +0100 Subject: [PATCH 02/10] i18n: Massive rework to modernize features * Added support for multiple base languages. * Added support for per-language base languages. * Added caching for language translation chain. * Changed language functionality to allow creating, loading, saving and destroying a language. * Added functionality to set, get and clear keys for a language. * Added functionality to override resolver and applier for domTranslate (previously autoTranslate). --- i18n.js | 584 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 484 insertions(+), 100 deletions(-) diff --git a/i18n.js b/i18n.js index f7941d1..a4e27ee 100644 --- a/i18n.js +++ b/i18n.js @@ -15,152 +15,536 @@ along with this program. If not, see . */ -let group = "i18n"; -let group_style = "font-weight: bold;"; -let text_style = "font-weight: inherit;"; +/* +Dead Simple I18n Library + +Languages are stored in a Map() to allow for any kind of name to be used. In + addition to that there is a global base language, and each language can + specify an override base language which will be used only if that language + is actually loaded during translation. The base language can be an array + instead of a string, in which each language is tested in order of appearance. + +The key can only be a string, while the Value is allowed to be anything. In + case you want to implement pluralization in your translation layer, you would + be doing it with an object value or similar. This creates a simple structure + that can be used for various kinds of translations, and is as fast as the + underlying Map implementation. + +# Example: Evaluation of the language chain [en-US < en-GB < de-DE < de-BE] +Searching for the key will first happen in de-BE, then in de-DE, then in en-GB + and finally in en-US. If de-BE, de-DE or en-GB is not loaded, that specific + lookup step skips straight to the global base language, in this case en-US. If + en-US is not loaded, the original i18n key is returned instead. + +# Example: Evaluation of the language chain [en-US < en-GB < de-DE, en-GB < de-BE] +Checks first happen in de-BE, if de-BE does not have the key checks move on to + de-DE. If de-DE is not loaded, en-GB is instead checked. If de-DE does not + have the key, checks progress to en-GB. If en-GB does not have the key, checks + progress to en-US. If any of them are not loaded or does not have the key, the + next language in the base languages is checked, until the base languages are + exhausted, in which case the i18n key is returned. + +# Structure +Map{ + languageName: Map{ + // Base language override (optional) + _base: {string}, + // Key Value storage for lookup + {string key}: {any value}, + } +} + +# Internal behavior +The library will build a chain of languages to check in a loop for, to avoid + costly recursive calls that aren't well protected against loops in base + language definitions (i.e. de-DE depends on en-GB, en-GB depends on de-DE). +This cache is refreshed when first attempting to translate to that language, + and flagged as dirty every time a language is loaded or a base language is + changed. + +*/ + +let group = 'i18n'; +let group_style = 'font-weight: bold;'; +let text_style = 'font-weight: inherit;'; class I18n { /** Create a new object, ready to be used. * - * @param {string} urlFormat URL from which to asynchronously load translation data from in the format 'url/file{0}.extension' ({0} is replaced by the lowercase language name). + * @param {string} language Initial base language to base all translations on. + * @param {string} baseLanguageKey Key to use for base language overrides. (Default = _base) + * @throws Exception on invalid parameters. */ - constructor(urlFormat) { - console.log("%c[%s]%c Initializing ...", group_style, group, text_style); - if (typeof(urlFormat) != "string") { - throw "urlFormat must be of type string"; + constructor(language, baseLanguageKey = '_base') { + this._sanitizeLanguage(language); + this._verifyKey(baseLanguageKey); + + console.log('%c[%s]%c Initializing ...', group_style, group, text_style); + + this.languages = new Map(); + this.chains = new Map(); + this.baseLanguage = language; + this.baseLanguageKey = baseLanguageKey; + + this.dirtyTs = performance.now(); + this.chainsTs = performance.now(); + } + + _verifyLanguageKnown(language) { + if (!this.languages.has(language)) { + throw 'language unknown'; } - this.url = urlFormat; - this.languages = {}; - this.languages.base = ''; + } + + _verifyKey(key) { + if (typeof (key) == 'string') { + return; + } + throw 'key must be of type string'; + } + + _verifyKeyKnown(language, key) { + this._verifyLanguageKnown(language); + this._verifyKey(key); + + if (!this.languages.get(language).has(key)) { + throw 'key unknown in language'; + } + } + + _verifyData(data) { + if (typeof (data) == 'string') { + return; + } + if (typeof (data) == 'object') { + return; + } + if (data instanceof File) { + return; + } + if (data instanceof Blob) { + return; + } + throw 'data must be of type string, object, File or Blob'; } _sanitizeLanguage(language) { - if (typeof(language) != "string") { - throw "language must be of type string"; + try { + this._verifyKey(language); + } catch (e) { + throw 'language must be of type string'; } return language.toLowerCase(); } + _defaultResolver(language, key, element) { + return this.translate(key, language); + } + + _defaultApplier(language, key, translation, element) { + try { + element.textContent = translation; + return true; + } catch (e) { + return false; + } + } + + /** Caches the necessary language chain for translation. + * + * Creates and returns a language chain required for translation, avoiding + * recursive loops that never end in the process. The chain will not + * evalute the entire branch first, instead it will check all branches + * and then list the childs of those branches. This is an expensive + * operation and should only be done once on every language update. + * + * Example: + * de-DE +- en-GB - en-US - de-DE (- en-GB, en-US, ... [recursive]) + * \- en-US - de-DE (- en-GB, en-US, ... [recursive]) + * Result: [de-DE, en-GB, en-US] + * Explanation: de-DE depends on both en-GB and en-US, and will check both + * before considering the next branch. Since all dependencies are resolved + * early, a recursive lookup is prevented here. + * + * Example: + * + * + * @param {string} language + * @returns {array} Translation chain + */ + _cacheChain(language) { + // This caches a chain so that we do not have to rebuild this every + // lookup. It is important that there are no recursive loops in this + // code, which means we can't rely on this function to work until the + // cache is completed. + + // Have there been changes to the timestamp? + if (this.chainsTs < this.dirtyTs) { + // If yes, clear all chains for rebuilding. + this.chainsTs = this.dirtyTs; + this.chains.clear(); + } + + // Do we have a chain cached? + if (this.languageChains.has(language)) { + // Yes, return the cached chain and go back to translation. + return this.languageChains.get(language); + } + + // Create a new chain without relying on our own function. + let chain = [language]; // Chains always contain the language itself. + + // Now we walk through the chain manually, modifying it as we go. + for (let pos = 0; pos < chain.length; pos++) { + // Check if the language is loaded, if not skip it. + if (!this.languages.has(chain[pos])) { + continue; + } + + let languageMap = this.languages.get(chain[pos]); + + // Check if there is a base language override. + if (!languageMap.has(this.baseLanguageKey)) { + continue; + } + + // If yes, walk it. + let baseLanguages = languageMap.get(this.baseLanguageKey); + if (typeof (baseLanguages) == 'string') { + baseLanguages = [baseLanguages]; + } else if (typeof (baseLanguages) == 'array') { + + } else { + continue; + } + + for (baseLanguage in baseLanguages) { + if (!chain.includes(baseLanguage)) { + chain = chain.push(baseLanguage); + } + } + } + + // We are now through with the actual language chain, so we now have to check for the base language chain. + // The logic for this is identical to the above. + let baseChain = this.baseLanguage; + if (typeof (this.baseLanguage) == 'string') { + baseChain = [baseChain]; + } + for (let pos = 0; pos < baseChain.length; pos++) { + // Check if the language is loaded, if not skip it. + if (!this.languages.has(baseChain[pos])) { + continue; + } + + let languageMap = this.languages.get(baseChain[pos]); + + // Check if there is a base language override. + if (!languageMap.has(this.baseLanguageKey)) { + continue; + } + + // If yes, walk it. + let baseLanguages = languageMap.get(this.baseLanguageKey); + if (typeof (baseLanguages) == 'string') { + baseLanguages = [baseLanguages]; + } else if (typeof (baseLanguages) == 'array') { + + } else { + continue; + } + + for (baseLanguage in baseLanguages) { + if ((!chain.includes(baseLanguage)) && (!baseChain.includes(baseLanguage))) { + baseChain = baseChain.push(baseLanguage); + } + } + } + + // Concat normal walk and base chain walk. + chain = chain.concat(baseChain); + + // Store. + this.chains.set(language, chain); + + // Return. + return chain; + } + + /** Create a new language. + * + * @param {string} language Name of the language. + * @throws Exception on invalid parameters. + */ + createLanguage(language) { + language = this._sanitizeLanguage(language); + + console.debug('%c[%s]%c Creating language "%s"...', + group_style, group, text_style, + language); + this.languages.set(language, new Map()); + console.debug('%c[%s]%c Created language "%s".', + group_style, group, text_style, + language); + this.dirtyTs = performance.now(); + } + /** Load a new language. * - * @param {string} language Name of the language file. - * @param {boolean} isBaseLanguage Use this language as the new base language. + * @param {string} language Name of the language. + * @param {File/Blob/object/string} data Data containing a JSON representation of the language. + * @param {string} encoding (Optional) Encoding to use when reading File or Blob. * @returns {Promise} + * @throws Exception on invalid parameters or invalid data. */ - load(language, isBaseLanguage) { + loadLanguage(language, data, encoding = 'utf-8') { + // Verify input. language = this._sanitizeLanguage(language); - if (isBaseLanguage == true) { - this.languages.base = language; - } - if (this.languages[language] == undefined) { - return new Promise((resolve, reject) => { - let self = this; - let url = this.url.replace('{0}', language); - console.debug("%c[%s]%c Loading language '%s' from url '%s'...", + _verifyData(data); + + return new Promise(async (resolve, reject) => { + console.debug('%c[%s]%c Loading language "%s"...', + group_style, group, text_style, + language); + + try { + let json_data; + + // Decode File, Blob and string to JSON object. + if ((data instanceof File) || (data instanceof Blob)) { + await new Promise((resolve2, reject2) => { + let freader = new FileReader(); + freader.onload((ev) => { + resolve2(); + }); + freader.onabort((ev) => { + reject2(ev); + }); + freader.onerror((ev) => { + reject2(ev); + }); + freader.readAsText(data, encoding); + }); + json_data = JSON.parse(freader.result); + } else if (typeof (data) == 'string') { + json_data = JSON.parse(data); + } else if (typeof (data) == 'object') { + json_data = data; + } + + let language_map = new Map(); + for (key in json_data) { + language_map.set(key, json_data[key]); + } + + this.languages.set(language, language_map); + this.dirtyTs = performance.now(); + + console.debug('%c[%s]%c Loaded language "%s".', + group_style, group, text_style, language); + resolve(language); + } catch (e) { + console.error('%c[%s]%c Failed to load language "%s": %o', group_style, group, text_style, - language, url); + language, e); + reject(e); + return; + } + }); + } - let req = new XMLHttpRequest(); - req.addEventListener("error", function (event) { - console.log("%c[%s]%c Failed to load language '%s': ", - group_style, group, text_style, - language, event); + /** Save a language. + * + * @param {string} language Name of the language. + * @returns {Promise} Promise that eventually returns the JSON data of the language. + * @throws Exception on invalid parameters and missing language. + */ + saveLanguage(language) { + language = this._sanitizeLanguage(language); + this._verifyLanguageKnown(language); - }); - req.addEventListener("progress", function (event) { - let prc = event.loaded / event.total; - if (event.loaded == event.total) { - prc = 100.0; - } - if (event.total == 0) { - prc = 0.0; - } - console.debug("%c[%s]%c Loading language '%s'... [%6.2f%%]", - group_style, group, text_style, - language, prc); - }); - req.addEventListener("load", function (event) { - console.debug("%c[%s]%c Parsing language '%s'...", - group_style, group, text_style, - language); - try { - self.languages[language] = JSON.parse(req.responseText); - } catch (e) { - console.log("%c[%s]%c Failed to load language '%s': ", - group_style, group, text_style, - language, e); - reject(e); - return; - } - resolve(true); - console.log("%c[%s]%c Loaded language '%s'.", - group_style, group, text_style, - language); - return; - }); - req.open("GET", url); - req.overrideMimeType("text/plain; charset=utf-8"); - req.send(); + return new Promise((resolve, reject) => { + console.debug('%c[%s]%c Saving language "%s"...', + group_style, group, text_style, + language); + + this._verifyLanguageKnown(language); + + let language_data = {}; + let language_map = this.languages.get(language); + language_map.forEach((value, key, map) => { + language_data[key] = value; }); - } else { - console.log("%c[%s]%c Loaded language '%s'...", - group_style, group, text_style, language); - return new Promise((resolve, reject) => { resolve(true); }); + let json_data = JSON.stringify(language_data); + + console.debug('%c[%s]%c Saved language "%s".', + group_style, group, text_style, + language); + resolve(json_data); + }); + } + + /** Destroy/unload a language. + * + * @param {string} language Name of the language. + * @throws Exception on invalid parameters and missing language. + */ + destroyLanguage(language) { + language = this._sanitizeLanguage(language); + this._verifyLanguageKnown(language); + + console.debug('%c[%s]%c Destroying language "%s"...', + group_style, group, text_style, + language); + + this.languages.delete(language); + this.dirtyTs = performance.now(); + + console.debug('%c[%s]%c Destroyed language "%s".', + group_style, group, text_style, + language); + } + + /** Clear a key from a language. + * + * @param {string} language Language to edit. + * @param {string} key Key to clear. + * @return {bool} true on success. + * @throws Exception on invalid parameters and missing language. + */ + clearKey(language, key) { + // Verify and sanitize input. + language = this._sanitizeLanguage(language); + this._verifyKey(key); + + // Check if language exists. + this._verifyLanguageKnown(language); + + // Delete key if exists. + let language_map = this.languages.get(language); + if (!language_map.has(key)) { + return false; } + language_map.delete(key); + + // If the key was the base language key, set dirty timestamp. + if (key == this.baseLanguageKey) { + this.dirtyTs = performance.now(); + } + + return true; + } + + /** Set a key in a language + * + * @param {string} language Language to edit. + * @param {string} key Key to set. + * @param {*} value New value to set. + * @param {bool} force Force the update if the key exists. (Default = true) + * @return {bool} true on success. + * @throws Exception on invalid parameters and missing language. + */ + setKey(language, key, value, force = true) { + // Verify and sanitize input. + language = this._sanitizeLanguage(language); + this._verifyKey(key); + + // Check if language exists. + this._verifyLanguageKnown(language); + + // Set key. + let language_map = this.languages.get(language); + if ((language_map.has(key)) && !force) { + return false; + } + language_map.set(key, value); + + // If the key was the base language key, set dirty timestamp. + if (key == this.baseLanguageKey) { + this.dirtyTs = performance.now(); + } + + return true; + } + + /** Get a key in a language + * + * @param {string} language + * @param {string} key + * @return {*} the value + * @throws Exception on invalid parameters, missing language and missing key. + */ + getKey(language, key) { + // Verify and sanitize input. + language = this._sanitizeLanguage(language); + this._verifyKeyKnown(language, key); + + // Get Key + let language_map = this.languages.get(language); + return language_map.get(key); } /** Translate a single string to any loaded language. * - * @param {string} string String to translate + * @param {string} key String to translate * @param {string} language Language to translate to * @return {string} Translated string, or if failed the string plus the language appended. + * @throws Exception on invalid parameters. */ - translate(string, language) { + translate(key, language) { + // Verify and sanitize input. language = this._sanitizeLanguage(language); - if (typeof(string) != "string") { - throw "string must be of type string"; - } - let str = this.languages[language][string]; - if (str == undefined) { - let str = this.languages[this.languages.base][string]; - if (str == undefined) { - console.error("%c[%s]%c Language '%s' and base language '%s' contain no translation for '%s'.", - group_style, group, text_style, - language, this.languages.base, string - ) - return string + language; - } else { - console.debug("%c[%s]%c Language '%s' contains no translation for '%s', falling back to base language '%s'.", - group_style, group, text_style, - language, string, this.languages.base - ) - return str; + this._verifyKey(key); + + // Translate using the translation chain. + let chain = this._cacheChain(language); + let translated = key; + for (language in chain) { + let languageMap = this.languages.get(language); + if (languageMap.has(key)) { + translated = languageMap.get(key); + break; } - } else { - return str; } + + return translated; } /** Automatically translate the entire page using the specified property on elements. * - * @param {*} language Language to translate to - * @param {*} property Property to search for (default 'data-i18n') + * @param {string} language Language to translate to + * @param {string} property Property to search for (default 'data-i18n') + * @param {function} resolver Function to call to find the proper translation, called with parameters ({string}language, {string}key, {Node}element) and must return a string. + * @param {function} applier Function to call to apply the proper translation, called with parameters ({string}language, {string}key, {string}translation, {Node}element) and must return a boolean. + * @returns {Promise} resolved when successful, rejected if applier returns false. + * @throws Exception on invalid parameters. */ - autoTranslate(language, property = 'data-i18n') { + domTranslate(language, property = 'data-i18n', resolver = this._defaultResolver, applier = this._defaultApplier) { language = this._sanitizeLanguage(language); + if (typeof (resolver) != 'function') { + throw 'resolver must be a function'; + } + if (typeof (applier) != 'function') { + throw 'applier must be a function'; + } + return new Promise((resolve, reject) => { - console.debug("%c[%s]%c Starting automatic translation to language '%s'...", + console.debug('%c[%s]%c Starting DOM translation to language "%s" using property "%s"...', group_style, group, text_style, - language); + language, property); + let els = document.querySelectorAll(`[${property}]`); for (let el of els) { - let string = el.getAttribute(property); - el.textContent = string; - el.textContent = this.translate(string, language); + let key = el.getAttribute(property); + if (!applier(language, key, resolver(language, key, el), el)) { + reject(); + return; + } } - console.log("%c[%s]%c Translated to language '%s'.", + console.log('%c[%s]%c Translated to language "%s".', group_style, group, text_style, language); + resolve(); }); } } -- 2.52.0 From db3b0cd94b53ebb19d55509dba413e14348e2957 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 01:04:15 +0100 Subject: [PATCH 03/10] git: Change .js files to always be LF --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..937b84a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.ts text eol=lf \ No newline at end of file -- 2.52.0 From a2bac12c364f9116b4453cc1dee6024dd3efd8b9 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 01:04:26 +0100 Subject: [PATCH 04/10] project: Add ESLint support --- .eslintrc.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..610a563 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,30 @@ +{ + "env": { + "browser": true, + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "rules": { + "indent": [ + "error", + "tab" + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ] + } +} \ No newline at end of file -- 2.52.0 From 3309cb20fb3c44750cd47f9468ffffc511504259 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 01:05:41 +0100 Subject: [PATCH 05/10] i18n: Fix double-language entry and various undefined problems Additionally removes all console commands and adds node.js/electron support. --- i18n.js | 93 ++++++++++++++++++++------------------------------------- 1 file changed, 32 insertions(+), 61 deletions(-) diff --git a/i18n.js b/i18n.js index a4e27ee..c175b31 100644 --- a/i18n.js +++ b/i18n.js @@ -64,10 +64,6 @@ This cache is refreshed when first attempting to translate to that language, */ -let group = 'i18n'; -let group_style = 'font-weight: bold;'; -let text_style = 'font-weight: inherit;'; - class I18n { /** Create a new object, ready to be used. * @@ -79,8 +75,6 @@ class I18n { this._sanitizeLanguage(language); this._verifyKey(baseLanguageKey); - console.log('%c[%s]%c Initializing ...', group_style, group, text_style); - this.languages = new Map(); this.chains = new Map(); this.baseLanguage = language; @@ -186,9 +180,9 @@ class I18n { } // Do we have a chain cached? - if (this.languageChains.has(language)) { + if (this.chains.has(language)) { // Yes, return the cached chain and go back to translation. - return this.languageChains.get(language); + return this.chains.get(language); } // Create a new chain without relying on our own function. @@ -218,8 +212,9 @@ class I18n { continue; } - for (baseLanguage in baseLanguages) { - if (!chain.includes(baseLanguage)) { + for (let baseLanguage in baseLanguages) { + baseLanguage = this._sanitizeLanguage(baseLanguage); + if (!chain.includes(baseLanguage) && (this.languages.has(baseLanguage))) { chain = chain.push(baseLanguage); } } @@ -254,16 +249,15 @@ class I18n { continue; } - for (baseLanguage in baseLanguages) { - if ((!chain.includes(baseLanguage)) && (!baseChain.includes(baseLanguage))) { + for (let baseLanguage in baseLanguages) { + baseLanguage = this._sanitizeLanguage(baseLanguage); + if ((!chain.includes(baseLanguage)) && (!baseChain.includes(baseLanguage)) && (this.languages.has(baseLanguage))) { baseChain = baseChain.push(baseLanguage); + chain.push(baseLanguage); } } } - // Concat normal walk and base chain walk. - chain = chain.concat(baseChain); - // Store. this.chains.set(language, chain); @@ -278,14 +272,7 @@ class I18n { */ createLanguage(language) { language = this._sanitizeLanguage(language); - - console.debug('%c[%s]%c Creating language "%s"...', - group_style, group, text_style, - language); this.languages.set(language, new Map()); - console.debug('%c[%s]%c Created language "%s".', - group_style, group, text_style, - language); this.dirtyTs = performance.now(); } @@ -300,13 +287,9 @@ class I18n { loadLanguage(language, data, encoding = 'utf-8') { // Verify input. language = this._sanitizeLanguage(language); - _verifyData(data); + this._verifyData(data); return new Promise(async (resolve, reject) => { - console.debug('%c[%s]%c Loading language "%s"...', - group_style, group, text_style, - language); - try { let json_data; @@ -333,20 +316,14 @@ class I18n { } let language_map = new Map(); - for (key in json_data) { + for (let key in json_data) { language_map.set(key, json_data[key]); } this.languages.set(language, language_map); this.dirtyTs = performance.now(); - - console.debug('%c[%s]%c Loaded language "%s".', - group_style, group, text_style, language); resolve(language); } catch (e) { - console.error('%c[%s]%c Failed to load language "%s": %o', - group_style, group, text_style, - language, e); reject(e); return; } @@ -364,10 +341,6 @@ class I18n { this._verifyLanguageKnown(language); return new Promise((resolve, reject) => { - console.debug('%c[%s]%c Saving language "%s"...', - group_style, group, text_style, - language); - this._verifyLanguageKnown(language); let language_data = {}; @@ -376,10 +349,6 @@ class I18n { language_data[key] = value; }); let json_data = JSON.stringify(language_data); - - console.debug('%c[%s]%c Saved language "%s".', - group_style, group, text_style, - language); resolve(json_data); }); } @@ -393,16 +362,8 @@ class I18n { language = this._sanitizeLanguage(language); this._verifyLanguageKnown(language); - console.debug('%c[%s]%c Destroying language "%s"...', - group_style, group, text_style, - language); - this.languages.delete(language); this.dirtyTs = performance.now(); - - console.debug('%c[%s]%c Destroyed language "%s".', - group_style, group, text_style, - language); } /** Clear a key from a language. @@ -499,7 +460,7 @@ class I18n { // Translate using the translation chain. let chain = this._cacheChain(language); let translated = key; - for (language in chain) { + for (let language of chain) { let languageMap = this.languages.get(language); if (languageMap.has(key)) { translated = languageMap.get(key); @@ -519,20 +480,26 @@ class I18n { * @returns {Promise} resolved when successful, rejected if applier returns false. * @throws Exception on invalid parameters. */ - domTranslate(language, property = 'data-i18n', resolver = this._defaultResolver, applier = this._defaultApplier) { + domTranslate(language, property = 'data-i18n', resolver = undefined, applier = undefined) { language = this._sanitizeLanguage(language); - if (typeof (resolver) != 'function') { + if ((resolver != undefined) && (typeof (resolver) != 'function')) { throw 'resolver must be a function'; + } else if (resolver == undefined) { + let self = this; + resolver = (language, key, el) => { + return self._defaultResolver(language, key, el); + }; } - if (typeof (applier) != 'function') { + if ((applier != undefined) && (typeof (applier) != 'function')) { throw 'applier must be a function'; + } else if (applier == undefined) { + let self = this; + applier = (language, key, translation, el) => { + return self._defaultApplier(language, key, translation, el); + }; } return new Promise((resolve, reject) => { - console.debug('%c[%s]%c Starting DOM translation to language "%s" using property "%s"...', - group_style, group, text_style, - language, property); - let els = document.querySelectorAll(`[${property}]`); for (let el of els) { let key = el.getAttribute(property); @@ -541,10 +508,14 @@ class I18n { return; } } - console.log('%c[%s]%c Translated to language "%s".', - group_style, group, text_style, - language); resolve(); }); } } + +// Compatible with Node.js and Browsers +if (typeof (module) != 'undefined') { + module.exports = exports = { + 'I18n': I18n + } +} -- 2.52.0 From d87dc241f56a2d0dcf84630f7926817b63461edd Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 01:19:21 +0100 Subject: [PATCH 06/10] i18n: Add method to check for existing language --- i18n.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/i18n.js b/i18n.js index c175b31..5b2437e 100644 --- a/i18n.js +++ b/i18n.js @@ -265,6 +265,16 @@ class I18n { return chain; } + /** Check if a language is known. + * + * @param {string} language Name of the language + * @returns {bool} true if known. + */ + hasLanguage(language) { + language = this._sanitizeLanguage(language); + return this.languages.has(language); + } + /** Create a new language. * * @param {string} language Name of the language. -- 2.52.0 From fd80444782826de3391ba4b6f086ab9e7eaab834 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 02:36:26 +0100 Subject: [PATCH 07/10] i18n: Fix incorrect chain generation --- i18n.js | 628 +++++++++++++++++++++++++++----------------------------- 1 file changed, 302 insertions(+), 326 deletions(-) diff --git a/i18n.js b/i18n.js index 5b2437e..2cbccc8 100644 --- a/i18n.js +++ b/i18n.js @@ -65,86 +65,87 @@ This cache is refreshed when first attempting to translate to that language, */ class I18n { - /** Create a new object, ready to be used. + /** Create a new object, ready to be used. * * @param {string} language Initial base language to base all translations on. * @param {string} baseLanguageKey Key to use for base language overrides. (Default = _base) * @throws Exception on invalid parameters. */ - constructor(language, baseLanguageKey = '_base') { - this._sanitizeLanguage(language); - this._verifyKey(baseLanguageKey); + constructor(language, baseLanguageKey = '_base') { + this._sanitizeLanguage(language); + this._verifyKey(baseLanguageKey); - this.languages = new Map(); - this.chains = new Map(); - this.baseLanguage = language; - this.baseLanguageKey = baseLanguageKey; + this.languages = new Map(); + this.chains = new Map(); + this.baseLanguage = language; + this.baseLanguageKey = baseLanguageKey; - this.dirtyTs = performance.now(); - this.chainsTs = performance.now(); - } + this.dirtyTs = performance.now(); + this.chainsTs = performance.now(); + } - _verifyLanguageKnown(language) { - if (!this.languages.has(language)) { - throw 'language unknown'; - } - } + _verifyLanguageKnown(language) { + if (!this.languages.has(language)) { + throw 'language unknown'; + } + } - _verifyKey(key) { - if (typeof (key) == 'string') { - return; - } - throw 'key must be of type string'; - } + _verifyKey(key) { + if (typeof (key) == 'string') { + return; + } + throw 'key must be of type string'; + } - _verifyKeyKnown(language, key) { - this._verifyLanguageKnown(language); - this._verifyKey(key); + _verifyKeyKnown(language, key) { + this._verifyLanguageKnown(language); + this._verifyKey(key); - if (!this.languages.get(language).has(key)) { - throw 'key unknown in language'; - } - } + if (!this.languages.get(language).has(key)) { + throw 'key unknown in language'; + } + } - _verifyData(data) { - if (typeof (data) == 'string') { - return; - } - if (typeof (data) == 'object') { - return; - } - if (data instanceof File) { - return; - } - if (data instanceof Blob) { - return; - } - throw 'data must be of type string, object, File or Blob'; - } + _verifyData(data) { + if (typeof (data) == 'string') { + return; + } + if (typeof (data) == 'object') { + return; + } + if (data instanceof File) { + return; + } + if (data instanceof Blob) { + return; + } + throw 'data must be of type string, object, File or Blob'; + } - _sanitizeLanguage(language) { - try { - this._verifyKey(language); - } catch (e) { - throw 'language must be of type string'; - } - return language.toLowerCase(); - } + _sanitizeLanguage(language) { + try { + this._verifyKey(language); + } catch (e) { + throw 'language must be of type string'; + } + return language.toLowerCase(); + } - _defaultResolver(language, key, element) { - return this.translate(key, language); - } + _defaultResolver(language, key, element) { + element; + return this.translate(key, language); + } - _defaultApplier(language, key, translation, element) { - try { - element.textContent = translation; - return true; - } catch (e) { - return false; - } - } + _defaultApplier(language, key, translation, element) { + try { + element.textContent = translation; + return true; + } catch (e) { + return false; + } + } - /** Caches the necessary language chain for translation. + /** Caches the necessary language chain for translation. * * Creates and returns a language chain required for translation, avoiding * recursive loops that never end in the process. The chain will not @@ -166,127 +167,100 @@ class I18n { * @param {string} language * @returns {array} Translation chain */ - _cacheChain(language) { - // This caches a chain so that we do not have to rebuild this every - // lookup. It is important that there are no recursive loops in this - // code, which means we can't rely on this function to work until the - // cache is completed. + _cacheChain(language) { + // This caches a chain so that we do not have to rebuild this every + // lookup. It is important that there are no recursive loops in this + // code, which means we can't rely on this function to work until the + // cache is completed. - // Have there been changes to the timestamp? - if (this.chainsTs < this.dirtyTs) { - // If yes, clear all chains for rebuilding. - this.chainsTs = this.dirtyTs; - this.chains.clear(); - } + // Have there been changes to the timestamp? + if (this.chainsTs < this.dirtyTs) { + // If yes, clear all chains for rebuilding. + this.chainsTs = this.dirtyTs; + this.chains.clear(); + } - // Do we have a chain cached? - if (this.chains.has(language)) { - // Yes, return the cached chain and go back to translation. - return this.chains.get(language); - } + // Do we have a chain cached? + if (this.chains.has(language)) { + // Yes, return the cached chain and go back to translation. + return this.chains.get(language); + } - // Create a new chain without relying on our own function. - let chain = [language]; // Chains always contain the language itself. + // Create a new chain without relying on our own function. + let chain = new Array(); + chain.push(language); // Chains always contain the language itself. - // Now we walk through the chain manually, modifying it as we go. - for (let pos = 0; pos < chain.length; pos++) { - // Check if the language is loaded, if not skip it. - if (!this.languages.has(chain[pos])) { - continue; - } + // Now we walk through the chain manually, modifying it as we go. + for (let pos = 0; pos < chain.length; pos++) { + // Check if the language is loaded, if not skip it. + if (!this.languages.has(chain[pos])) { + continue; + } + let languageMap = this.languages.get(chain[pos]); - let languageMap = this.languages.get(chain[pos]); + // Check if there is a base language override. + if (!languageMap.has(this.baseLanguageKey)) { + continue; + } + let baseLanguages = languageMap.get(this.baseLanguageKey); - // Check if there is a base language override. - if (!languageMap.has(this.baseLanguageKey)) { - continue; - } + // Convert to array for for...in. + if (typeof (baseLanguages) == 'string') { + baseLanguages = [baseLanguages]; + } else if (!(baseLanguages instanceof Array)) { + continue; + } - // If yes, walk it. - let baseLanguages = languageMap.get(this.baseLanguageKey); - if (typeof (baseLanguages) == 'string') { - baseLanguages = [baseLanguages]; - } else if (typeof (baseLanguages) == 'array') { + for (let base of baseLanguages) { + base = this._sanitizeLanguage(base); + if (!chain.includes(base) && (this.languages.has(base))) { + chain.push(base); + } + } + + // Append the global base languages if there are no other languages left. + if (pos == (chain.length - 1)) { + let baseLanguages = this.baseLanguage; + if (typeof(this.baseLanguage) == 'string') { + baseLanguages = [this.baseLanguage]; + } + for (let base of baseLanguages) { + if (!chain.includes(base) && (this.languages.has(base))) { + chain.push(base); + } + } + } + } - } else { - continue; - } + // Store. + this.chains.set(language, chain); - for (let baseLanguage in baseLanguages) { - baseLanguage = this._sanitizeLanguage(baseLanguage); - if (!chain.includes(baseLanguage) && (this.languages.has(baseLanguage))) { - chain = chain.push(baseLanguage); - } - } - } + // Return. + return chain; + } - // We are now through with the actual language chain, so we now have to check for the base language chain. - // The logic for this is identical to the above. - let baseChain = this.baseLanguage; - if (typeof (this.baseLanguage) == 'string') { - baseChain = [baseChain]; - } - for (let pos = 0; pos < baseChain.length; pos++) { - // Check if the language is loaded, if not skip it. - if (!this.languages.has(baseChain[pos])) { - continue; - } - - let languageMap = this.languages.get(baseChain[pos]); - - // Check if there is a base language override. - if (!languageMap.has(this.baseLanguageKey)) { - continue; - } - - // If yes, walk it. - let baseLanguages = languageMap.get(this.baseLanguageKey); - if (typeof (baseLanguages) == 'string') { - baseLanguages = [baseLanguages]; - } else if (typeof (baseLanguages) == 'array') { - - } else { - continue; - } - - for (let baseLanguage in baseLanguages) { - baseLanguage = this._sanitizeLanguage(baseLanguage); - if ((!chain.includes(baseLanguage)) && (!baseChain.includes(baseLanguage)) && (this.languages.has(baseLanguage))) { - baseChain = baseChain.push(baseLanguage); - chain.push(baseLanguage); - } - } - } - - // Store. - this.chains.set(language, chain); - - // Return. - return chain; - } - - /** Check if a language is known. + /** Check if a language is known. * * @param {string} language Name of the language * @returns {bool} true if known. */ - hasLanguage(language) { - language = this._sanitizeLanguage(language); - return this.languages.has(language); - } + hasLanguage(language) { + language = this._sanitizeLanguage(language); + return this.languages.has(language); + } - /** Create a new language. + /** Create a new language. * * @param {string} language Name of the language. * @throws Exception on invalid parameters. */ - createLanguage(language) { - language = this._sanitizeLanguage(language); - this.languages.set(language, new Map()); - this.dirtyTs = performance.now(); - } + createLanguage(language) { + language = this._sanitizeLanguage(language); + this.languages.set(language, new Map()); + this.dirtyTs = performance.now(); + } - /** Load a new language. + /** Load a new language. * * @param {string} language Name of the language. * @param {File/Blob/object/string} data Data containing a JSON representation of the language. @@ -294,119 +268,121 @@ class I18n { * @returns {Promise} * @throws Exception on invalid parameters or invalid data. */ - loadLanguage(language, data, encoding = 'utf-8') { - // Verify input. - language = this._sanitizeLanguage(language); - this._verifyData(data); + loadLanguage(language, data, encoding = 'utf-8') { + // Verify input. + language = this._sanitizeLanguage(language); + this._verifyData(data); - return new Promise(async (resolve, reject) => { - try { - let json_data; + return new Promise(async (resolve, reject) => { + try { + let json_data; - // Decode File, Blob and string to JSON object. - if ((data instanceof File) || (data instanceof Blob)) { - await new Promise((resolve2, reject2) => { - let freader = new FileReader(); - freader.onload((ev) => { - resolve2(); - }); - freader.onabort((ev) => { - reject2(ev); - }); - freader.onerror((ev) => { - reject2(ev); - }); - freader.readAsText(data, encoding); - }); - json_data = JSON.parse(freader.result); - } else if (typeof (data) == 'string') { - json_data = JSON.parse(data); - } else if (typeof (data) == 'object') { - json_data = data; - } + // Decode File, Blob and string to JSON object. + if ((data instanceof File) || (data instanceof Blob)) { + let freader = new FileReader(); + await new Promise((resolve2, reject2) => { + freader.onload(() => { + resolve2(); + }); + freader.onabort((ev) => { + reject2(ev); + }); + freader.onerror((ev) => { + reject2(ev); + }); + freader.readAsText(data, encoding); + }); + json_data = JSON.parse(freader.result); + } else if (typeof (data) == 'string') { + json_data = JSON.parse(data); + } else if (typeof (data) == 'object') { + json_data = data; + } - let language_map = new Map(); - for (let key in json_data) { - language_map.set(key, json_data[key]); - } + let language_map = new Map(); + for (let key in json_data) { + language_map.set(key, json_data[key]); + } - this.languages.set(language, language_map); - this.dirtyTs = performance.now(); - resolve(language); - } catch (e) { - reject(e); - return; - } - }); - } + this.languages.set(language, language_map); + this.dirtyTs = performance.now(); + resolve(language); + } catch (e) { + reject(e); + return; + } + }); + } - /** Save a language. + /** Save a language. * * @param {string} language Name of the language. * @returns {Promise} Promise that eventually returns the JSON data of the language. * @throws Exception on invalid parameters and missing language. */ - saveLanguage(language) { - language = this._sanitizeLanguage(language); - this._verifyLanguageKnown(language); + saveLanguage(language) { + language = this._sanitizeLanguage(language); + this._verifyLanguageKnown(language); - return new Promise((resolve, reject) => { - this._verifyLanguageKnown(language); + return new Promise((resolve, reject) => { + reject; + this._verifyLanguageKnown(language); - let language_data = {}; - let language_map = this.languages.get(language); - language_map.forEach((value, key, map) => { - language_data[key] = value; - }); - let json_data = JSON.stringify(language_data); - resolve(json_data); - }); - } + let language_data = {}; + let language_map = this.languages.get(language); + language_map.forEach((value, key, map) => { + map; + language_data[key] = value; + }); + let json_data = JSON.stringify(language_data); + resolve(json_data); + }); + } - /** Destroy/unload a language. + /** Destroy/unload a language. * * @param {string} language Name of the language. * @throws Exception on invalid parameters and missing language. */ - destroyLanguage(language) { - language = this._sanitizeLanguage(language); - this._verifyLanguageKnown(language); + destroyLanguage(language) { + language = this._sanitizeLanguage(language); + this._verifyLanguageKnown(language); - this.languages.delete(language); - this.dirtyTs = performance.now(); - } + this.languages.delete(language); + this.dirtyTs = performance.now(); + } - /** Clear a key from a language. + /** Clear a key from a language. * * @param {string} language Language to edit. * @param {string} key Key to clear. * @return {bool} true on success. * @throws Exception on invalid parameters and missing language. */ - clearKey(language, key) { - // Verify and sanitize input. - language = this._sanitizeLanguage(language); - this._verifyKey(key); + clearKey(language, key) { + // Verify and sanitize input. + language = this._sanitizeLanguage(language); + this._verifyKey(key); - // Check if language exists. - this._verifyLanguageKnown(language); + // Check if language exists. + this._verifyLanguageKnown(language); - // Delete key if exists. - let language_map = this.languages.get(language); - if (!language_map.has(key)) { - return false; - } - language_map.delete(key); + // Delete key if exists. + let language_map = this.languages.get(language); + if (!language_map.has(key)) { + return false; + } + language_map.delete(key); - // If the key was the base language key, set dirty timestamp. - if (key == this.baseLanguageKey) { - this.dirtyTs = performance.now(); - } + // If the key was the base language key, set dirty timestamp. + if (key == this.baseLanguageKey) { + this.dirtyTs = performance.now(); + } - return true; - } + return true; + } - /** Set a key in a language + /** Set a key in a language * * @param {string} language Language to edit. * @param {string} key Key to set. @@ -415,73 +391,73 @@ class I18n { * @return {bool} true on success. * @throws Exception on invalid parameters and missing language. */ - setKey(language, key, value, force = true) { - // Verify and sanitize input. - language = this._sanitizeLanguage(language); - this._verifyKey(key); + setKey(language, key, value, force = true) { + // Verify and sanitize input. + language = this._sanitizeLanguage(language); + this._verifyKey(key); - // Check if language exists. - this._verifyLanguageKnown(language); + // Check if language exists. + this._verifyLanguageKnown(language); - // Set key. - let language_map = this.languages.get(language); - if ((language_map.has(key)) && !force) { - return false; - } - language_map.set(key, value); + // Set key. + let language_map = this.languages.get(language); + if ((language_map.has(key)) && !force) { + return false; + } + language_map.set(key, value); - // If the key was the base language key, set dirty timestamp. - if (key == this.baseLanguageKey) { - this.dirtyTs = performance.now(); - } + // If the key was the base language key, set dirty timestamp. + if (key == this.baseLanguageKey) { + this.dirtyTs = performance.now(); + } - return true; - } + return true; + } - /** Get a key in a language + /** Get a key in a language * * @param {string} language * @param {string} key * @return {*} the value * @throws Exception on invalid parameters, missing language and missing key. */ - getKey(language, key) { - // Verify and sanitize input. - language = this._sanitizeLanguage(language); - this._verifyKeyKnown(language, key); + getKey(language, key) { + // Verify and sanitize input. + language = this._sanitizeLanguage(language); + this._verifyKeyKnown(language, key); - // Get Key - let language_map = this.languages.get(language); - return language_map.get(key); - } + // Get Key + let language_map = this.languages.get(language); + return language_map.get(key); + } - /** Translate a single string to any loaded language. + /** Translate a single string to any loaded language. * * @param {string} key String to translate * @param {string} language Language to translate to * @return {string} Translated string, or if failed the string plus the language appended. * @throws Exception on invalid parameters. */ - translate(key, language) { - // Verify and sanitize input. - language = this._sanitizeLanguage(language); - this._verifyKey(key); + translate(key, language) { + // Verify and sanitize input. + language = this._sanitizeLanguage(language); + this._verifyKey(key); - // Translate using the translation chain. - let chain = this._cacheChain(language); - let translated = key; - for (let language of chain) { - let languageMap = this.languages.get(language); - if (languageMap.has(key)) { - translated = languageMap.get(key); - break; - } - } + // Translate using the translation chain. + let chain = this._cacheChain(language); + let translated = key; + for (let language of chain) { + let languageMap = this.languages.get(language); + if (languageMap.has(key)) { + translated = languageMap.get(key); + break; + } + } - return translated; - } + return translated; + } - /** Automatically translate the entire page using the specified property on elements. + /** Automatically translate the entire page using the specified property on elements. * * @param {string} language Language to translate to * @param {string} property Property to search for (default 'data-i18n') @@ -490,42 +466,42 @@ class I18n { * @returns {Promise} resolved when successful, rejected if applier returns false. * @throws Exception on invalid parameters. */ - domTranslate(language, property = 'data-i18n', resolver = undefined, applier = undefined) { - language = this._sanitizeLanguage(language); - if ((resolver != undefined) && (typeof (resolver) != 'function')) { - throw 'resolver must be a function'; - } else if (resolver == undefined) { - let self = this; - resolver = (language, key, el) => { - return self._defaultResolver(language, key, el); - }; - } - if ((applier != undefined) && (typeof (applier) != 'function')) { - throw 'applier must be a function'; - } else if (applier == undefined) { - let self = this; - applier = (language, key, translation, el) => { - return self._defaultApplier(language, key, translation, el); - }; - } + domTranslate(language, property = 'data-i18n', resolver = undefined, applier = undefined) { + language = this._sanitizeLanguage(language); + if ((resolver != undefined) && (typeof (resolver) != 'function')) { + throw 'resolver must be a function'; + } else if (resolver == undefined) { + let self = this; + resolver = (language, key, el) => { + return self._defaultResolver(language, key, el); + }; + } + if ((applier != undefined) && (typeof (applier) != 'function')) { + throw 'applier must be a function'; + } else if (applier == undefined) { + let self = this; + applier = (language, key, translation, el) => { + return self._defaultApplier(language, key, translation, el); + }; + } - return new Promise((resolve, reject) => { - let els = document.querySelectorAll(`[${property}]`); - for (let el of els) { - let key = el.getAttribute(property); - if (!applier(language, key, resolver(language, key, el), el)) { - reject(); - return; - } - } - resolve(); - }); - } + return new Promise((resolve, reject) => { + let els = document.querySelectorAll(`[${property}]`); + for (let el of els) { + let key = el.getAttribute(property); + if (!applier(language, key, resolver(language, key, el), el)) { + reject(); + return; + } + } + resolve(); + }); + } } // Compatible with Node.js and Browsers if (typeof (module) != 'undefined') { - module.exports = exports = { - 'I18n': I18n - } + module.exports = exports = { + 'I18n': I18n + }; } -- 2.52.0 From 0ee0f282a07059107d07ec27fcfcbdec9e013908 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 02:36:48 +0100 Subject: [PATCH 08/10] examples: Basic sample --- examples/sample.html | 94 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 examples/sample.html diff --git a/examples/sample.html b/examples/sample.html new file mode 100644 index 0000000..bde8850 --- /dev/null +++ b/examples/sample.html @@ -0,0 +1,94 @@ + + + + + + i18n-js - Sample + + + +
+ +
+
+

+

+

+
+ + + + + + \ No newline at end of file -- 2.52.0 From b734696e6ca1cab31e3b5ec3dc2c4834e5fe7b9d Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 03:00:33 +0100 Subject: [PATCH 09/10] project: Update README --- i18n.js | 8 ++++---- readme.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/i18n.js b/i18n.js index 2cbccc8..d6e2bde 100644 --- a/i18n.js +++ b/i18n.js @@ -67,17 +67,17 @@ This cache is refreshed when first attempting to translate to that language, class I18n { /** Create a new object, ready to be used. * - * @param {string} language Initial base language to base all translations on. + * @param {string} defaultLanguage Initial base language to base all translations on. * @param {string} baseLanguageKey Key to use for base language overrides. (Default = _base) * @throws Exception on invalid parameters. */ - constructor(language, baseLanguageKey = '_base') { - this._sanitizeLanguage(language); + constructor(defaultLanguage, baseLanguageKey = '_base') { + this._sanitizeLanguage(defaultLanguage); this._verifyKey(baseLanguageKey); this.languages = new Map(); this.chains = new Map(); - this.baseLanguage = language; + this.baseLanguage = defaultLanguage; this.baseLanguageKey = baseLanguageKey; this.dirtyTs = performance.now(); diff --git a/readme.md b/readme.md index e563b44..a12fd30 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,49 @@ # I18n (JavaScript) +Very simple JavaScript library for handling translations. Supports Node.js, Electron and other JavaScript based code runners. -Simple JavaScript library to load translation data in the browser and apply translations automatically to a full page. +## Features: +* Simple modern API with Promises and full Documentation. +* Support for languages with more than one base language and per language base language overrides. +* Cached translation chains for quick and recursion free key lookups. # License -Licensed under Affero GNU GPLv3. See license.txt for more information. \ No newline at end of file +Licensed under Affero GNU GPLv3. See license.txt for more information. + +# Documentation +`i18n.js` is fully documented using JSDoc, a format IDEs should be able to display. If in any case your IDE is not showing this information, your first reference should be `i18n.js`, not this readme. + +## I18n +The class that contains all the translation data. + +### constructor(string defaultLanguage, string baseLanguageKey = '_base') +Creates a new I18n object when called with `new I18n('mylanguage')`. Will set up internal structures and variables for later use. Throws exceptions. + +### bool hasLanguage(string language) +Check if a language is known to this I18n object. Returns true if known. Throws exceptions. + +### createLanguage(string language) +Create a new empty language, or replaces a loaded one with an empty one. Throws exceptions. + +### Promise(string) loadLanguage(string language, File|Blob|object|string data, string encoding = 'utf-8') +Load a language from a File, Blob, object or string as JSON data, and store it as the language. Returns a Promise, which will resolve once it is done loading and parsing and the result will contain the loaded language name. Throws exceptions. + +### Promise(string) saveLanguage(string language) +Save a language to a string as JSON data. Returns a Promise which will resolve with the JSON data string once done. Modifying the language before the returned Promise is done can result in corrupt data. Throws exceptions. + +### destroyLanguage(string language) +Destroy a language, thus clearing any references to data in it. Throws exceptions. + +### bool clearKey(string language, string key) +Remove a key from a language. Returns true if the key was removed. Throws exceptions. + +### bool setKey(string language, string key, any value, bool force = true) +Set a key in a language to a new value, optionally overwriting the previous content (defaults to true). Returns true if successful. Throws exceptions. + +### any getKey(string language, string key) +Get the value of a key in a language. Returns the value of the key if known, otherwise throws an exception. + +### string translate(string key, string language) +Translate a given key to the language selected. Returns the translated string or the original key if it was not found. Throws exceptions. + +### Promise domTranslate(string language, string property = 'data-i18n', function resolver = string function(string language, string key, Node element), function applier = bool function(string language, string key, string translated, Node element)) +Automatically translate all elements in the current DOM which have the attribute `property`. Returns a Promise which resolves when the translation is complete. Throws exceptions. -- 2.52.0 From ed9efdd5f7cb012eee5438089a1f932b580feb72 Mon Sep 17 00:00:00 2001 From: Michael Fabian 'Xaymar' Dirks Date: Thu, 29 Nov 2018 03:06:41 +0100 Subject: [PATCH 10/10] project: Improve readme --- readme.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/readme.md b/readme.md index a12fd30..fbb0b5b 100644 --- a/readme.md +++ b/readme.md @@ -1,49 +1,52 @@ # I18n (JavaScript) Very simple JavaScript library for handling translations. Supports Node.js, Electron and other JavaScript based code runners. -## Features: +# Features: * Simple modern API with Promises and full Documentation. * Support for languages with more than one base language and per language base language overrides. * Cached translation chains for quick and recursion free key lookups. +# Maintenance, Donations, etc. +This codes maintenance is funded through [Patreon](https://www.patreon.com/Xaymar). If you think this code is useful to you, consider donating a few bucks. + # License Licensed under Affero GNU GPLv3. See license.txt for more information. # Documentation `i18n.js` is fully documented using JSDoc, a format IDEs should be able to display. If in any case your IDE is not showing this information, your first reference should be `i18n.js`, not this readme. -## I18n +### I18n The class that contains all the translation data. -### constructor(string defaultLanguage, string baseLanguageKey = '_base') +##### constructor(string defaultLanguage, string baseLanguageKey = '\_base') Creates a new I18n object when called with `new I18n('mylanguage')`. Will set up internal structures and variables for later use. Throws exceptions. -### bool hasLanguage(string language) +##### bool hasLanguage(string language) Check if a language is known to this I18n object. Returns true if known. Throws exceptions. -### createLanguage(string language) +##### createLanguage(string language) Create a new empty language, or replaces a loaded one with an empty one. Throws exceptions. -### Promise(string) loadLanguage(string language, File|Blob|object|string data, string encoding = 'utf-8') +##### Promise(string) loadLanguage(string language, File|Blob|object|string data, string encoding = 'utf-8') Load a language from a File, Blob, object or string as JSON data, and store it as the language. Returns a Promise, which will resolve once it is done loading and parsing and the result will contain the loaded language name. Throws exceptions. -### Promise(string) saveLanguage(string language) +##### Promise(string) saveLanguage(string language) Save a language to a string as JSON data. Returns a Promise which will resolve with the JSON data string once done. Modifying the language before the returned Promise is done can result in corrupt data. Throws exceptions. -### destroyLanguage(string language) +##### destroyLanguage(string language) Destroy a language, thus clearing any references to data in it. Throws exceptions. -### bool clearKey(string language, string key) +##### bool clearKey(string language, string key) Remove a key from a language. Returns true if the key was removed. Throws exceptions. -### bool setKey(string language, string key, any value, bool force = true) +##### bool setKey(string language, string key, any value, bool force = true) Set a key in a language to a new value, optionally overwriting the previous content (defaults to true). Returns true if successful. Throws exceptions. -### any getKey(string language, string key) +##### any getKey(string language, string key) Get the value of a key in a language. Returns the value of the key if known, otherwise throws an exception. -### string translate(string key, string language) +##### string translate(string key, string language) Translate a given key to the language selected. Returns the translated string or the original key if it was not found. Throws exceptions. -### Promise domTranslate(string language, string property = 'data-i18n', function resolver = string function(string language, string key, Node element), function applier = bool function(string language, string key, string translated, Node element)) +##### Promise domTranslate(string language, string property = 'data-i18n', function resolver = string function(string language, string key, Node element), function applier = bool function(string language, string key, string translated, Node element)) Automatically translate all elements in the current DOM which have the attribute `property`. Returns a Promise which resolves when the translation is complete. Throws exceptions. -- 2.52.0