Localization (Transloco) (#457)

* first transloco

* config

* app settings

* appsettings

* settings html

* first translations

* fix default loading

* lint

* use webpack

* some translations

* german terms + disable

* fix lint
This commit is contained in:
Tobias 2021-07-29 21:04:59 +02:00 committed by GitHub
commit 4639315ff5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 596 additions and 78 deletions

5
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"recommendations": [
"lokalise.i18n-ally"
]
}

View file

@ -2,5 +2,11 @@
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"tslint.autoFixOnSave": true
"tslint.autoFixOnSave": true,
"i18n-ally.localesPaths": "src/assets/i18n",
"i18n-ally.keystyle": "nested",
"i18n-ally.sourceLanguage": "en",
"i18n-ally.extract.keyPrefix": "{fileNameWithoutExt}.",
"i18n-ally.sortKeys": true,
"i18n-ally.extract.keyMaxLength": 60
}

340
package-lock.json generated
View file

@ -2191,6 +2191,143 @@
"tslib": "^2.0.0"
}
},
"@ngneat/transloco": {
"version": "2.22.0",
"resolved": "https://registry.npmjs.org/@ngneat/transloco/-/transloco-2.22.0.tgz",
"integrity": "sha512-mJ8KVm3nPoSyKlMqsP4D3nNClugwnwxmVrP3QtO16jEiLn8U0ipsAlj7SyQqjIWKSLxxVZuJX25lmk6Ye9iYoA==",
"requires": {
"@ngneat/transloco-utils": "^1.0.6",
"flat": "5.0.2",
"lodash.kebabcase": "^4.1.1",
"ora": "^3.4.0",
"replace-in-file": "^4.1.2",
"tslib": "^1.9.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"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=",
"requires": {
"restore-cursor": "^2.0.0"
}
},
"log-symbols": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
"requires": {
"chalk": "^2.0.1"
}
},
"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=="
},
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"requires": {
"mimic-fn": "^1.0.0"
}
},
"ora": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz",
"integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==",
"requires": {
"chalk": "^2.4.2",
"cli-cursor": "^2.1.0",
"cli-spinners": "^2.0.0",
"log-symbols": "^2.2.0",
"strip-ansi": "^5.2.0",
"wcwidth": "^1.0.1"
}
},
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"requires": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@ngneat/transloco-utils": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@ngneat/transloco-utils/-/transloco-utils-1.1.2.tgz",
"integrity": "sha512-yljMAoRNr+qr1z+DTUlY98SWm/ldbssCNmI3tE+A1183ppYKcnpdfSJQKrx1rg4rmwNfyKyexlmZ6EubosqZ8A==",
"requires": {
"cosmiconfig": "6.0.0",
"tslib": "^1.9.0"
},
"dependencies": {
"cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
}
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
}
},
"parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"requires": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
}
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"@ngtools/webpack": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-10.0.5.tgz",
@ -2414,6 +2551,11 @@
"integrity": "sha512-yzBInQFhdY8kaZmqoL2+3U5dSTMrKaYcb561VU+lDzAYvqt+2lojvBEy+hmpSNuXnPTx7m9+04CzWYOUqWME2A==",
"dev": true
},
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
},
"@types/q": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz",
@ -4368,8 +4510,7 @@
"cli-spinners": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.4.0.tgz",
"integrity": "sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA==",
"dev": true
"integrity": "sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA=="
},
"cli-width": {
"version": "2.2.1",
@ -5435,7 +5576,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
"dev": true,
"requires": {
"clone": "^1.0.2"
},
@ -5443,8 +5583,7 @@
"clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
"dev": true
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
}
}
},
@ -6366,7 +6505,6 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "^0.2.1"
}
@ -7081,6 +7219,11 @@
"locate-path": "^3.0.0"
}
},
"flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="
},
"flatted": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
@ -8299,8 +8442,7 @@
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
},
"is-binary-path": {
"version": "2.1.0",
@ -8846,6 +8988,11 @@
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true
},
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
@ -9286,6 +9433,11 @@
"immediate": "~3.0.5"
}
},
"lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA="
},
"loader-runner": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
@ -9338,6 +9490,11 @@
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"lodash.kebabcase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
"integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@ -11061,6 +11218,21 @@
"readable-stream": "^2.1.5"
}
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"requires": {
"callsites": "^3.0.0"
},
"dependencies": {
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
}
}
},
"parse-asn1": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
@ -11157,8 +11329,7 @@
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="
},
"pbkdf2": {
"version": "3.1.1",
@ -12789,6 +12960,149 @@
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
"dev": true
},
"replace-in-file": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-4.3.1.tgz",
"integrity": "sha512-FqVvfmpqGTD2JRGI1JjJ86b24P17x/WWwGdxExeyJxnh/2rVQz2+jXfD1507UnnhEQw092X0u0DPCBf1WC4ooQ==",
"requires": {
"chalk": "^2.4.2",
"glob": "^7.1.6",
"yargs": "^15.0.2"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
}
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"requires": {
"p-locate": "^4.1.0"
}
},
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"requires": {
"p-limit": "^2.2.0"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"string-width": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
}
},
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"requires": {
"ansi-regex": "^5.0.0"
}
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"request": {
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
@ -15764,7 +16078,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
"dev": true,
"requires": {
"defaults": "^1.0.3"
}
@ -16720,6 +17033,11 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
},
"yargs": {
"version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",

View file

@ -48,6 +48,7 @@
"@ledgerhq/hw-transport-webusb": "^5.39.0",
"@ledgerhq/logs": "^5.39.0",
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
"@ngneat/transloco": "^2.22.0",
"@types/crypto-js": "^3.1.38",
"@zxing/ngx-scanner": "^3.0.0",
"angular-password-strength-meter": "^3.0.0",

View file

@ -18,7 +18,7 @@
<div class="header">Currently Selected</div>
<div class="account active" *ngIf="wallet.selectedAccount !== null"> <!-- active nano_ account -->
<div class="name-column">
<div class="name">{{ wallet.selectedAccount.addressBookName ? wallet.selectedAccount.addressBookName : ('Account #' + wallet.selectedAccount.index) }}</div>
<div class="name">{{ wallet.selectedAccount.addressBookName ? wallet.selectedAccount.addressBookName : (('general.account' | transloco) + ' #' + wallet.selectedAccount.index) }}</div>
<div class="address nano-address-monospace">{{ wallet.selectedAccount.id }}</div>
</div>
<div class="balance-column">
@ -55,14 +55,14 @@
<ng-container *ngFor="let account of wallet.accounts"> <!-- inactive accounts -->
<div class="account inactive" (click)="selectAccount(account)" *ngIf="( (wallet.selectedAccount === null) || (wallet.selectedAccount.id !== account.id) )">
<div class="name-column">
<div class="name">{{ account.addressBookName ? account.addressBookName : ('Account #' + account.index) }}</div>
<div class="name">{{ account.addressBookName ? account.addressBookName : (('general.account' | transloco) + ' #' + account.index) }}</div>
<div class="address nano-address-monospace">{{ account.id }}</div>
</div>
<div class="balance-column">
<ng-container *ngIf="(walletService.wallet.updatingBalance === false) else balanceLoading">
<div class="balance primary">
<span class="incoming-label" *ngIf="account.pending.gt(0)">
<span class="text-snippet">New</span>
<span class="text-snippet">{{ 'general.new' | transloco }}</span>
<span class="text-full">+{{ account.pending | rai: 'mnano,true' | amountsplit: 0 }}{{ account.pending | rai: 'mnano,true' | amountsplit: 1 }} NANO</span>
</span>
{{ account.balance | rai: 'mnano,true' | amountsplit: 0 }}{{ account.balance | rai: 'mnano,true' | amountsplit: 1 }} NANO
@ -111,7 +111,7 @@
? (
wallet.selectedAccount.addressBookName
? wallet.selectedAccount.addressBookName
: ('Account #' + wallet.selectedAccount.index)
: (('general.account' | transloco) + '#' + wallet.selectedAccount.index)
)
: 'Total Balance'
}}</a>
@ -218,27 +218,27 @@
</div>
<ul class="uk-nav uk-nav-default uk-nav-parent-icon left-nav" uk-nav>
<li><a routerLink="/accounts" routerLinkActive="active"><div class="label">Accounts</div></a></li>
<li><a routerLink="/send" routerLinkActive="active"><div class="label">Send</div></a></li>
<li><a routerLink="/accounts" routerLinkActive="active"><div class="label">{{ 'general.accounts' | transloco }}</div></a></li>
<li><a routerLink="/send" routerLinkActive="active"><div class="label">{{ 'general.send' | transloco }}</div></a></li>
<li>
<a routerLink="/receive" routerLinkActive="active">
<div uk-grid>
<div class="uk-width-3-4">
<div class="label">Receive</div>
<div class="label">{{ 'general.receive' | transloco }}</div>
</div>
<div class="uk-width-1-4 uk-text-center label-new">
<span *ngIf="walletService.hasPendingTransactions()" class="uk-badge uk-text-top uk-align-center"><div class="label">New</div></span>
<span *ngIf="walletService.hasPendingTransactions()" class="uk-badge uk-text-top uk-align-center"><div class="label">{{ 'general.new' | transloco }}</div></span>
</div>
</div>
</a>
</li>
<li><a routerLink="/address-book" routerLinkActive="active"><div class="label">Address Book</div></a></li>
<li><a routerLink="/qr-scan" routerLinkActive="active"><div class="label">Scan QR Code</div></a></li>
<li><a routerLink="/address-book" routerLinkActive="active"><div class="label">{{ 'general.address-book' | transloco }}</div></a></li>
<li><a routerLink="/qr-scan" routerLinkActive="active"><div class="label">{{ 'general.scan-qr-code' | transloco }}</div></a></li>
<li class="uk-parent">
<a href="#">Settings</a>
<a href="#">{{ 'general.settings' | transloco }}</a>
<ul class="uk-nav-sub">
<li><a routerLink="/representatives" [queryParams]="" routerLinkActive="active"><div class="label">Representatives</div></a></li>
<li><a routerLink="/configure-app" routerLinkActive="active"><div class="label">App Settings</div></a></li>
<li><a routerLink="/representatives" [queryParams]="" routerLinkActive="active"><div class="label">{{ 'general.representatives' | transloco }}</div></a></li>
<li><a routerLink="/configure-app" routerLinkActive="active"><div class="label">{{ 'general.app-settings' | transloco }}</div></a></li>
<li *ngIf="isConfigured()"><a routerLink="/manage-wallet" routerLinkActive="active"><div class="label">Manage Wallet</div></a></li>
<li><a routerLink="/configure-wallet" routerLinkActive="active"><div class="label">Configure New Wallet</div></a></li>
</ul>

View file

@ -14,6 +14,7 @@ import {NodeService} from './services/node.service';
import { DesktopService, LedgerService } from './services';
import { environment } from 'environments/environment';
import { DeeplinkService } from './services/deeplink.service';
import { TranslocoService } from '@ngneat/transloco';
@Component({
@ -39,7 +40,8 @@ export class AppComponent implements OnInit {
private desktop: DesktopService,
private ledger: LedgerService,
private renderer: Renderer2,
private deeplinkService: DeeplinkService) {
private deeplinkService: DeeplinkService,
private translate: TranslocoService) {
router.events.subscribe(() => {
this.closeNav();
});
@ -86,6 +88,9 @@ export class AppComponent implements OnInit {
// New for v19: Patch saved xrb_ prefixes to nano_
await this.patchXrbToNanoPrefixData();
// set translation language
this.translate.setActiveLang(this.settings.settings.language);
this.addressBook.loadAddressBook();
this.workPool.loadWorkCache();

View file

@ -70,6 +70,7 @@ import { environment } from '../environments/environment';
import { MultisigComponent } from './components/multisig/multisig.component';
import { KeygeneratorComponent } from './components/keygenerator/keygenerator.component';
import { NanoTransactionMobileComponent } from './components/helpers/nano-transaction-mobile/nano-transaction-mobile.component';
import { TranslocoRootModule } from './transloco/transloco-root.module';
@NgModule({
declarations: [
@ -122,6 +123,7 @@ import { NanoTransactionMobileComponent } from './components/helpers/nano-transa
NgbModule,
PasswordStrengthMeterModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production && !environment.desktop }),
TranslocoRootModule,
],
providers: [
UtilService,

View file

@ -11,7 +11,7 @@
<div class="name">{{
addressBookEntry
? addressBookEntry
: ('Account #' + walletAccount.index)
: (('general.account' | transloco) + '#' + walletAccount.index)
}}</div>
<div #selectButton class="select-button closed" (click)="mobileAccountMenuModal.show()">
<div class="circle"></div>
@ -69,7 +69,7 @@
class="account-type-label"
[class.editing-name]="showEditAddressBook"
>
{{ 'Account #' + walletAccount.index }}
{{ ('general.account' | transloco) + '#' + walletAccount.index }}
</div>
<div
*ngIf="!walletAccount"
@ -97,7 +97,7 @@
? addressBookEntry
: (
walletAccount
? ('Account #' + walletAccount.index)
? (('general.account' | transloco) + '#' + walletAccount.index)
: 'No label set'
)
}}</h3>
@ -693,7 +693,7 @@
? addressBookEntry
: (
walletAccount
? ('Account #' + walletAccount.index)
? (('general.account' | transloco) + '#' + walletAccount.index)
: 'External address'
)
}}</div>
@ -723,7 +723,7 @@
? addressBookEntry
: (
walletAccount
? ('Account #' + walletAccount.index)
? (('general.account' | transloco) + '#' + walletAccount.index)
: 'External address'
)
}}</h3>

View file

@ -16,6 +16,7 @@ import {BehaviorSubject} from 'rxjs';
import * as nanocurrency from 'nanocurrency';
import {NinjaService} from '../../services/ninja.service';
import { QrModalService } from '../../services/qr-modal.service';
import { TranslocoService } from '@ngneat/transloco';
@Component({
selector: 'app-account-details',
@ -116,7 +117,8 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
public settings: AppSettingsService,
private nanoBlock: NanoBlockService,
private qrModalService: QrModalService,
private ninja: NinjaService) {
private ninja: NinjaService,
private translocoService: TranslocoService) {
// to detect when the account changes if the view is already active
route.events.subscribe((val) => {
if (val instanceof NavigationEnd) {
@ -391,7 +393,7 @@ export class AccountDetailsComponent implements OnInit, OnDestroy {
return defaultLabel;
}
return ('Account #' + walletAccount.index);
return (this.translocoService.translate('general.account') + '#' + walletAccount.index);
}
ngOnDestroy() {

View file

@ -93,7 +93,7 @@
: (
viewAdvanced
? 'Account'
: 'Account #' + account.index
: ('general.account' | transloco) + '#' + account.index
)
}}</div>
</div>

View file

@ -1,87 +1,86 @@
<div class="nav-representative-info" *ngIf="(displayedRepresentatives.length > 0) else noRepresentatives">
<div class="representative" *ngFor="let rep of displayedRepresentatives; let repIdx = index" (mouseover)="showRepHelp=rep.id" (mouseout)="showRepHelp=null">
<h2>Representative{{
<h2>{{
(repIdx !== 0)
? (
' for '
+ (
'change-rep-widget.representative-for-account' | transloco: { account: (
rep.accounts[0].addressBookName
? ( '"' + rep.accounts[0].addressBookName + '"' )
: ( rep.accounts[0].id.slice(0, 10) + '...' )
)
)}
)
: ''
: ('general.representative' | transloco)
}}</h2>
<div class="representative-name-row">
<a (click)="showRepSelectionForSpecificRep(rep)" class="name">{{ rep.label || 'Unknown Rep' }}</a>
<a (click)="showRepSelectionForSpecificRep(rep)" class="name">{{ rep.label || ('general.unknown' | transloco) }}</a>
<div class="weight-total" *ngIf="!rep.percent.isZero()">{{ rep.percent.toFixed(2) }}%</div>
</div>
<ng-container [ngSwitch]="true">
<div class="representative-health-row health-green" *ngSwitchCase="(rep.statusText == 'trusted')">
<div class="health-icon"></div>
<div class="label">Good Representative</div>
<div class="label">{{ 'change-rep-widget.good-representative' | transloco }}</div>
</div>
<div class="representative-health-row health-green" *ngSwitchCase="(rep.statusText == 'ok')">
<div class="health-icon"></div>
<div class="label">Good Representative</div>
<div class="label">{{ 'change-rep-widget.good-representative' | transloco }}</div>
</div>
<div class="representative-health-row health-yellow" *ngSwitchCase="(rep.statusText == 'warn')">
<div class="health-icon"></div>
<div class="label">Acceptable Representative</div>
<div class="label">{{ 'change-rep-widget.acceptable-representative' | transloco }}</div>
</div>
<div class="representative-health-row health-red" *ngSwitchCase="(rep.statusText == 'alert')">
<div class="health-icon"></div>
<div class="label">Bad Representative</div>
<div class="label">{{ 'change-rep-widget.bad-representative' | transloco }}</div>
</div>
<div class="representative-health-row health-unknown" *ngSwitchDefault>
<div class="health-icon"></div>
<div class="label">Unknown Status</div>
<div class="label">{{ 'change-rep-widget.unknown-status' | transloco }}</div>
</div>
</ng-container>
<div [class]="[ 'representative-help-tooltip', showRepHelp==rep.id ? 'visible' : 'hidden' ]">
<p class="primary"><b>{{ rep.label || 'Unknown Rep' }}</b> represents you in the Nano consensus protocol by voting on your behalf.</p>
<p>You can change representative at any time by clicking on its name above.</p>
<p class="primary">{{ 'change-rep-widget.rep-represents-you' | transloco: { name: (rep.label || ('general.unknown' | transloco)) } }}</p>
<p>{{ 'change-rep-widget.you-can-change-representative-at-any-time' | transloco }}</p>
<div class="header-row">
<div class="separator"></div>
<h2>Health</h2>
<h2>{{ 'change-rep-widget.health' | transloco }}</h2>
<div class="separator"></div>
</div>
<p class="uk-text-danger" *ngIf="rep.status.markedToAvoid">
<span uk-icon="icon: warning;"></span>This representative is <b>marked as "avoid"</b> in the list of known reps.
<span uk-icon="icon: warning;"></span>{{ 'change-rep-widget.this-representative-is-marked-as-avoid' | transloco }}
</p>
<p class="uk-text-danger" *ngIf="rep.status.closing">
<span uk-icon="icon: warning;"></span>This representative has announced plans to <b>permanently shutdown</b>.
<span uk-icon="icon: warning;"></span>{{ 'change-rep-widget.this-representative-has-announced-plans-to-permanently-shutdown' | transloco }}
</p>
<p class="uk-text-danger" *ngIf="rep.status.veryHighWeight">
<span uk-icon="icon: warning;"></span>This representative has a <b>very high voting weight</b> (over 3%).
<span uk-icon="icon: warning;"></span>{{ 'change-rep-widget.this-representative-has-a-very-high-voting-weight' | transloco: { percent: 5 } }}
</p>
<p class="uk-text-warning" *ngIf="rep.status.highWeight">
<span uk-icon="icon: warning;"></span>This representative has a <b>high voting weight</b> (over 2%).
<span uk-icon="icon: warning;"></span>{{ 'change-rep-widget.this-representative-has-a-high-voting-weight' | transloco: { percent: 3 } }}
</p>
<p class="uk-text-danger" *ngIf="rep.status.veryLowUptime && rep.status.uptime > 0">
<span uk-icon="icon: warning;"></span>This representative is <b>often offline</b> ({{ rep.status.uptime.toFixed(1) }}% uptime).
<span uk-icon="icon: warning;"></span>{{ 'change-rep-widget.this-representative-is-often-offline' | transloco: { percent: rep.status.uptime.toFixed(1) } }}
</p>
<p class="uk-text-warning" *ngIf="rep.status.lowUptime && rep.status.uptime > 0">
<span uk-icon="icon: warning;"></span>This representative is <b>sometimes offline</b> ({{ rep.status.uptime.toFixed(1) }}% uptime).
<span uk-icon="icon: warning;"></span>{{ 'change-rep-widget.this-representative-is-sometimes-offline' | transloco: { percent: rep.status.uptime.toFixed(1) } }}
</p>
<p class="uk-text-danger" *ngIf="!rep.status.online && rep.status.uptime === 0">
<span uk-icon="icon: warning;"></span>This representative has been <b>offline for the past {{ rep.status.daysSinceLastVoted }} days</b>.
<span uk-icon="icon: warning;"></span>{{ 'change-rep-widget.this-representative-has-been-offline-for-the-past-days' | transloco: { days: rep.status.daysSinceLastVoted } }}
</p>
<ng-container [ngSwitch]="true">
<p *ngSwitchCase="(rep.statusText == 'trusted')">
You have marked this representative as trusted, meaning its status will remain "Good" even in case of severe issues with uptime or weight distribution.
{{ 'change-rep-widget.you-have-marked-this-representative-as-trusted' | transloco }}
</p>
<p class="uk-text-success" *ngSwitchCase="(rep.statusText == 'ok')">
<span uk-icon="icon: check;"></span>We found no issues with uptime or weight distribution of your current representative.
<span uk-icon="icon: check;"></span>{{ 'change-rep-widget.we-found-no-issues-with-uptime-or-weight' | transloco }}
</p>
<p *ngSwitchCase="(rep.statusText == 'warn')">
Switching to a different representative could improve security and decentralization of the Nano network.
{{ 'change-rep-widget.switching-to-a-different-representative-could-improve' | transloco }}
</p>
<p *ngSwitchCase="(rep.statusText == 'alert')">
It is <b>highly advised</b> to switch to a different representative, in order to improve security and decentralization of the Nano network.
{{ 'change-rep-widget.it-is-highly-advised-to-switch-to-a-different-representative' | transloco }}
</p>
<p *ngSwitchDefault>
<span uk-icon="icon: question;"></span>We could not determine status of this representative.
<span uk-icon="icon: question;"></span>{{ 'change-rep-widget.we-could-not-determine-status-of-this-representative' | transloco }}
</p>
</ng-container>
</div>
@ -90,26 +89,26 @@
<ng-template #noRepresentatives>
<div class="nav-representative-info" *ngIf="(!initialLoadComplete || selectedAccountHasRepresentative) else selectedAccountHasNoRep">
<div class="representative">
<h2>Representative</h2>
<h2>{{ 'general.representative' | transloco }}</h2>
<div class="representative-name-row">
<div class="name no-interact">Loading...</div>
<div class="name no-interact">{{ 'general.loading' | transloco }}</div>
</div>
<div class="representative-health-row health-unknown health-loading">
<div uk-spinner="ratio: 0.5;"></div>
<div class="label">Checking Status...</div>
<div class="label">{{ 'change-rep-widget.checking-status' | transloco }}</div>
</div>
</div>
</div>
<ng-template #selectedAccountHasNoRep>
<div class="nav-representative-info">
<div class="representative">
<h2>Representative</h2>
<h2>{{ 'general.representative' | transloco }}</h2>
<div class="representative-name-row">
<div class="name no-interact">None</div>
<div class="name no-interact">{{ 'change-rep-widget.representative-none' | transloco }}</div>
</div>
<div class="representative-health-row health-unknown">
<div class="health-icon"></div>
<div class="label">Receive a transaction to configure</div>
<div class="label">{{ 'change-rep-widget.receive-a-transaction-to-configure' | transloco }}</div>
</div>
</div>
</div>
@ -121,6 +120,6 @@
<span class="uk-text-warning" uk-icon="icon: warning; ratio: 1.2;"></span>
</div>
<div class="status-labels">
<div class="label primary uk-text-warning">Representative Change Required</div>
<div class="label primary uk-text-warning">{{ 'change-rep-widget.representative-change-required' | transloco }}</div>
</div>
</div>

View file

@ -8,6 +8,23 @@
</div>
<div class="uk-card-body">
<div uk-grid>
<div class="uk-width-1-1">
<div class="uk-form-horizontal">
<div class="uk-margin">
<label class="uk-form-label" for="languageselector">
{{ 'configure-app.language' | transloco }}
</label>
<div class="uk-form-controls">
<select class="uk-select" [(ngModel)]="selectedLanguage" id="languageselector">
<option *ngFor="let language of languages" [value]="language.id">{{ language.label }}</option>
</select>
</div>
</div>
</div>
</div>
<div class="uk-width-1-1">
<div class="uk-form-horizontal">

View file

@ -14,6 +14,7 @@ import {BehaviorSubject} from 'rxjs';
import {RepresentativeService} from '../../services/representative.service';
import {NinjaService} from '../../services/ninja.service';
import {QrModalService} from '../../services/qr-modal.service';
import { TranslocoService } from '@ngneat/transloco';
@Component({
selector: 'app-configure-app',
@ -38,9 +39,13 @@ export class ConfigureAppComponent implements OnInit {
private price: PriceService,
private ninja: NinjaService,
private renderer: Renderer2,
private qrModalService: QrModalService) { }
private qrModalService: QrModalService,
private translate: TranslocoService) { }
wallet = this.walletService.wallet;
languages = this.translate.getAvailableLangs() as [{id: string, label: string}];
selectedLanguage = this.languages[0].id;
denominations = [
{ name: 'NANO', value: 'mnano' },
{ name: 'knano', value: 'knano' },
@ -247,6 +252,9 @@ export class ConfigureAppComponent implements OnInit {
loadFromSettings() {
const settings = this.appSettings.settings;
const matchingLanguage = this.languages.find(language => language.id === settings.language);
this.selectedLanguage = matchingLanguage.id || this.languages[0].id;
const matchingCurrency = this.currencies.find(d => d.value === settings.displayCurrency);
this.selectedCurrency = matchingCurrency.value || this.currencies[0].value;
@ -313,6 +321,9 @@ export class ConfigureAppComponent implements OnInit {
this.walletService.reloadFiatBalances();
}
this.appSettings.setAppSetting('language', this.selectedLanguage);
this.translate.setActiveLang(this.selectedLanguage);
// if (updatePrefixes) {
// this.appSettings.setAppSetting('displayPrefix', this.selectedPrefix);
// Go through accounts?

View file

@ -13,6 +13,7 @@ import {PriceService} from '../../services/price.service';
import {WebsocketService} from '../../services/websocket.service';
import * as QRCode from 'qrcode';
import BigNumber from 'bignumber.js';
import { TranslocoService } from '@ngneat/transloco';
@Component({
selector: 'app-receive',
@ -61,7 +62,8 @@ export class ReceiveComponent implements OnInit, OnDestroy {
private nanoBlock: NanoBlockService,
public price: PriceService,
private websocket: WebsocketService,
private util: UtilService) { }
private util: UtilService,
private translocoService: TranslocoService) { }
async ngOnInit() {
const UIkit = window['UIkit'];
@ -174,7 +176,7 @@ export class ReceiveComponent implements OnInit, OnDestroy {
return defaultLabel;
}
return ('Account #' + walletAccount.index);
return (this.translocoService.translate('general.account') + '#' + walletAccount.index);
}
async getPending() {

View file

@ -15,6 +15,7 @@ import {
WalletService,
NinjaService
} from '../../services';
import { TranslocoService } from '@ngneat/transloco';
@Component({
selector: 'app-representatives',
@ -61,7 +62,8 @@ export class RepresentativesComponent implements OnInit {
private representativeService: RepresentativeService,
public settings: AppSettingsService,
private ninja: NinjaService,
private qrModalService: QrModalService) { }
private qrModalService: QrModalService,
private translocoService: TranslocoService) { }
async ngOnInit() {
this.representativeService.loadRepresentativeList();
@ -131,10 +133,10 @@ export class RepresentativesComponent implements OnInit {
const walletAccount = this.walletService.wallet.accounts.find(a => a.id === account.id);
if (walletAccount == null) {
return 'Account';
return this.translocoService.translate('general.account');
}
return ('Account #' + walletAccount.index);
return (this.translocoService.translate('general.account') + '#' + walletAccount.index);
}
addSelectedAccounts(accounts) {

View file

@ -13,6 +13,7 @@ import {PriceService} from '../../services/price.service';
import {NanoBlockService} from '../../services/nano-block.service';
import { QrModalService } from '../../services/qr-modal.service';
import { environment } from 'environments/environment';
import { TranslocoService } from '@ngneat/transloco';
const nacl = window['nacl'];
@ -67,7 +68,8 @@ export class SendComponent implements OnInit {
private workPool: WorkPoolService,
public settings: AppSettingsService,
private util: UtilService,
private qrModalService: QrModalService, ) { }
private qrModalService: QrModalService,
private translocoService: TranslocoService) { }
async ngOnInit() {
const params = this.router.snapshot.queryParams;
@ -250,7 +252,7 @@ export class SendComponent implements OnInit {
return defaultLabel;
}
return ('Account #' + walletAccount.index);
return (this.translocoService.translate('general.account') + '#' + walletAccount.index);
}
validateAmount() {

View file

@ -6,6 +6,7 @@ import {NotificationService} from '../../services/notification.service';
import {AppSettingsService} from '../../services/app-settings.service';
import BigNumber from 'bignumber.js';
import {AddressBookService} from '../../services/address-book.service';
import { TranslocoService } from '@ngneat/transloco';
@Component({
selector: 'app-transaction-details',
@ -41,7 +42,8 @@ export class TransactionDetailsComponent implements OnInit {
private addressBook: AddressBookService,
private api: ApiService,
private notifications: NotificationService,
public settings: AppSettingsService
public settings: AppSettingsService,
private translocoService: TranslocoService
) { }
async ngOnInit() {
@ -171,7 +173,7 @@ export class TransactionDetailsComponent implements OnInit {
return defaultLabel;
}
return ('Account #' + walletAccount.index);
return (this.translocoService.translate('general.account') + '#' + walletAccount.index);
}
getBalanceFromHex(balance) {

View file

@ -1,11 +1,13 @@
import { Injectable } from '@angular/core';
import * as url from 'url';
import { TranslocoService, getBrowserCultureLang, getBrowserLang } from '@ngneat/transloco';
export type WalletStore = 'localStorage'|'none';
export type PoWSource = 'server'|'clientCPU'|'clientWebGL'|'best'|'custom';
export type LedgerConnectionType = 'usb'|'bluetooth';
interface AppSettings {
language: string;
displayDenomination: string;
// displayPrefix: string | null;
walletStore: string;
@ -33,6 +35,7 @@ export class AppSettingsService {
storeKey = `nanovault-appsettings`;
settings: AppSettings = {
language: null,
displayDenomination: 'mnano',
// displayPrefix: 'xrb',
walletStore: 'localStorage',
@ -140,7 +143,9 @@ export class AppSettingsService {
'node.somenano.com'
]);
constructor() { }
constructor(
private translate: TranslocoService
) { }
loadAppSettings() {
let settings: AppSettings = this.settings;
@ -150,6 +155,23 @@ export class AppSettingsService {
}
this.settings = Object.assign(this.settings, settings);
if (this.settings.language === null) {
const browserCultureLang = getBrowserCultureLang();
const browserLang = getBrowserLang();
if (this.translate.getAvailableLangs().some(lang => lang['id'] === browserCultureLang)) {
this.settings.language = browserCultureLang;
} else if (this.translate.getAvailableLangs().some(lang => lang['id'] === browserCultureLang)) {
this.settings.language = browserLang;
} else {
this.settings.language = this.translate.getDefaultLang();
}
console.log('No language configured, setting to: ' + this.settings.language);
console.log('Browser culture language: ' + browserCultureLang);
console.log('Browser language: ' + browserLang);
}
this.loadServerSettings();
return this.settings;
@ -206,6 +228,7 @@ export class AppSettingsService {
clearAppSettings() {
localStorage.removeItem(this.storeKey);
this.settings = {
language: 'en',
displayDenomination: 'mnano',
// displayPrefix: 'xrb',
walletStore: 'localStorage',

View file

@ -0,0 +1,44 @@
import {
TRANSLOCO_LOADER,
TranslocoLoader,
TRANSLOCO_CONFIG,
translocoConfig,
TranslocoModule
} from '@ngneat/transloco';
import { Injectable, NgModule } from '@angular/core';
import { environment } from '../../environments/environment';
@Injectable({ providedIn: 'root' })
export class TranslocoHttpLoader implements TranslocoLoader {
constructor() {}
getTranslation(lang: string) {
return import(/* webpackChunkName: "translation" */ `../../assets/i18n/${lang}.json`).then(res => res.default);
}
}
@NgModule({
exports: [ TranslocoModule ],
providers: [
{
provide: TRANSLOCO_CONFIG,
useValue: translocoConfig({
availableLangs: [
{ id: 'en', label: 'English' },
// { id: 'de', label: 'Deutsch' }
],
defaultLang: 'en',
fallbackLang: 'en',
missingHandler: {
// It will use the first language set in the `fallbackLang` property
useFallbackTranslation: true
},
// Remove this option if your application doesn't support changing language in runtime.
reRenderOnLangChange: true,
prodMode: environment.production,
})
},
{ provide: TRANSLOCO_LOADER, useClass: TranslocoHttpLoader }
]
})
export class TranslocoRootModule {}

31
src/assets/i18n/de.json Normal file
View file

@ -0,0 +1,31 @@
{
"change-rep-widget": {
"acceptable-representative": "Akzeptabler Vertreter",
"bad-representative": "Schlechter Vertreter",
"checking-status": "Prüfe Status...",
"good-representative": "Guter Vertreter",
"health": "Status",
"representative-change-required": "Vetreterwechsel nötig",
"representative-for-account": "Vetreter für {{ account }}",
"representative-none": "Keinen",
"unknown-status": "Unbekannter Status"
},
"configure-app": {
"language": "Sprache"
},
"general": {
"account": "Konto",
"accounts": "Konten",
"address-book": "Adressbuch",
"app-settings": "App Einstellungen",
"loading": "Lade...",
"new": "Neu",
"receive": "Empfangen",
"representative": "Vertreter",
"representatives": "Vertreter",
"scan-qr-code": "QR Code scannen",
"send": "Senden",
"settings": "Einstellungen",
"unknown": "Unbekannt"
}
}

46
src/assets/i18n/en.json Normal file
View file

@ -0,0 +1,46 @@
{
"change-rep-widget": {
"acceptable-representative": "Acceptable Representative",
"bad-representative": "Bad Representative",
"checking-status": "Checking Status...",
"good-representative": "Good Representative",
"health": "Health",
"it-is-highly-advised-to-switch-to-a-different-representative": "It is highly advised to switch to a different representative, in order to improve security and decentralization of the Nano network.",
"receive-a-transaction-to-configure": "Receive a transaction to configure",
"rep-represents-you": "{{ name }} represents you in the Nano consensus protocol by voting on your behalf.",
"representative-change-required": "Representative Change Required",
"representative-for-account": "Representative for {{ account }}",
"representative-none": "None",
"switching-to-a-different-representative-could-improve": "Switching to a different representative could improve security and decentralization of the Nano network.",
"this-representative-has-a-high-voting-weight": "This representative has a high voting weight (over {{ percent }}%).",
"this-representative-has-a-very-high-voting-weight": "This representative has a very high voting weight (over {{ percent }}%).",
"this-representative-has-announced-plans-to-permanently-shutdown": "This representative has announced plans to permanently shutdown.",
"this-representative-has-been-offline-for-the-past-days": "This representative has been offline for the past {{ days }} days.",
"this-representative-is-marked-as-avoid": "This representative is marked as \"avoid\" in the list of known reps.",
"this-representative-is-often-offline": "This representative is often offline ({{ percent }}% uptime)",
"this-representative-is-sometimes-offline": "This representative is sometimes offline ({{ percent }}% uptime).",
"unknown-status": "Unknown Status",
"we-could-not-determine-status-of-this-representative": "We could not determine status of this representative.",
"we-found-no-issues-with-uptime-or-weight": "We found no issues with uptime or weight distribution of your current representative.",
"you-can-change-representative-at-any-time": "You can change representative at any time by clicking on its name above.",
"you-have-marked-this-representative-as-trusted": "You have marked this representative as trusted, meaning its status will remain \"Good\" even in case of severe issues with uptime or weight distribution."
},
"configure-app": {
"language": "Language"
},
"general": {
"account": "Account",
"accounts": "Accounts",
"address-book": "Address Book",
"app-settings": "App Settings",
"loading": "Loading...",
"new": "New",
"receive": "Receive",
"representative": "Representative",
"representatives": "Representatives",
"scan-qr-code": "Scan QR Code",
"send": "Send",
"settings": "Settings",
"unknown": "Unknown"
}
}