Add translation module and lang switcher
- uses ngx-translate - extracts all strings marked with " XXX | translate " or <X translate> using ngx-translate-extract (npm run extract) - custom translation loader prevents empty strings - default language is english - will try to use the browsers language, will fallback to english - functional language switching menu - not compatible with current PO files - current JSON-translation can be re-used
This commit is contained in:
parent
0b6996b700
commit
e605649a9b
329
client/package-lock.json
generated
329
client/package-lock.json
generated
@ -391,6 +391,228 @@
|
|||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@biesbjerg/ngx-translate-extract": {
|
||||||
|
"version": "2.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@biesbjerg/ngx-translate-extract/-/ngx-translate-extract-2.3.4.tgz",
|
||||||
|
"integrity": "sha512-FzOdm5Jr2TMgdzTW+c6CGIgMQMCAXCyN6JYzz+hfnYjcvPrYbyR05AhM08W70nXD3a2RnbqjImNjEEcXY9pZ/g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"chalk": "2.0.1",
|
||||||
|
"cheerio": "1.0.0-rc.2",
|
||||||
|
"flat": "2.0.1",
|
||||||
|
"fs": "0.0.1-security",
|
||||||
|
"gettext-parser": "1.2.2",
|
||||||
|
"glob": "7.1.2",
|
||||||
|
"mkdirp": "0.5.1",
|
||||||
|
"path": "0.12.7",
|
||||||
|
"typescript": "2.4.1",
|
||||||
|
"yargs": "8.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"camelcase": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
|
||||||
|
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-Mp+FXEI+FrwY/XYV45b2YD3E8i3HwnEAoFcM0qlZzq/RZ9RwWitt2Y/c7cqRAz70U7hfekqx6qNYthuKFO6K0g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^3.1.0",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"supports-color": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cliui": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
|
||||||
|
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"string-width": "^1.0.1",
|
||||||
|
"strip-ansi": "^3.0.1",
|
||||||
|
"wrap-ansi": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"code-point-at": "^1.0.0",
|
||||||
|
"is-fullwidth-code-point": "^1.0.0",
|
||||||
|
"strip-ansi": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"load-json-file": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"graceful-fs": "^4.1.2",
|
||||||
|
"parse-json": "^2.2.0",
|
||||||
|
"pify": "^2.0.0",
|
||||||
|
"strip-bom": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"os-locale": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"execa": "^0.7.0",
|
||||||
|
"lcid": "^1.0.0",
|
||||||
|
"mem": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path-type": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"pify": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pify": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||||
|
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"read-pkg": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"load-json-file": "^2.0.0",
|
||||||
|
"normalize-package-data": "^2.3.2",
|
||||||
|
"path-type": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"read-pkg-up": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"find-up": "^2.0.0",
|
||||||
|
"read-pkg": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"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-bom": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.1.tgz",
|
||||||
|
"integrity": "sha1-w8yxbdqgsjFN4DHn5v7onlujRrw=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"which-module": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"y18n": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
|
||||||
|
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"yargs": {
|
||||||
|
"version": "8.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz",
|
||||||
|
"integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"camelcase": "^4.1.0",
|
||||||
|
"cliui": "^3.2.0",
|
||||||
|
"decamelize": "^1.1.1",
|
||||||
|
"get-caller-file": "^1.0.1",
|
||||||
|
"os-locale": "^2.0.0",
|
||||||
|
"read-pkg-up": "^2.0.0",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"require-main-filename": "^1.0.1",
|
||||||
|
"set-blocking": "^2.0.0",
|
||||||
|
"string-width": "^2.0.0",
|
||||||
|
"which-module": "^2.0.0",
|
||||||
|
"y18n": "^3.2.1",
|
||||||
|
"yargs-parser": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yargs-parser": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz",
|
||||||
|
"integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"camelcase": "^4.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@fortawesome/angular-fontawesome": {
|
"@fortawesome/angular-fontawesome": {
|
||||||
"version": "0.1.0-10",
|
"version": "0.1.0-10",
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.1.0-10.tgz",
|
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.1.0-10.tgz",
|
||||||
@ -431,6 +653,22 @@
|
|||||||
"webpack-sources": "^1.1.0"
|
"webpack-sources": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@ngx-translate/core": {
|
||||||
|
"version": "10.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-10.0.2.tgz",
|
||||||
|
"integrity": "sha512-7nM3DrJaqKswwtJlbu2kuKNl+hE8Isr18sKsKvGGpSxQk+G0gO0reDlx2PhUNus7TJTkA1C59vU/JoN8hIvZ4g==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@ngx-translate/http-loader": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-3.0.1.tgz",
|
||||||
|
"integrity": "sha1-ILD5i8bCUyESnT4zAqs8xInApCo=",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@schematics/angular": {
|
"@schematics/angular": {
|
||||||
"version": "0.6.8",
|
"version": "0.6.8",
|
||||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.6.8.tgz",
|
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.6.8.tgz",
|
||||||
@ -1917,6 +2155,54 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cheerio": {
|
||||||
|
"version": "1.0.0-rc.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz",
|
||||||
|
"integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"css-select": "~1.2.0",
|
||||||
|
"dom-serializer": "~0.1.0",
|
||||||
|
"entities": "~1.1.1",
|
||||||
|
"htmlparser2": "^3.9.1",
|
||||||
|
"lodash": "^4.15.0",
|
||||||
|
"parse5": "^3.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"domhandler": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"domelementtype": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"htmlparser2": {
|
||||||
|
"version": "3.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz",
|
||||||
|
"integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"domelementtype": "^1.3.0",
|
||||||
|
"domhandler": "^2.3.0",
|
||||||
|
"domutils": "^1.5.1",
|
||||||
|
"entities": "^1.1.1",
|
||||||
|
"inherits": "^2.0.1",
|
||||||
|
"readable-stream": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parse5": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz",
|
||||||
@ -2993,6 +3279,15 @@
|
|||||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"encoding": {
|
||||||
|
"version": "0.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||||
|
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"iconv-lite": "~0.4.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||||
@ -3809,6 +4104,15 @@
|
|||||||
"locate-path": "^2.0.0"
|
"locate-path": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flat": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/flat/-/flat-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-cOKRiKdL4MPIlAnu0fqVd5B64y8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"is-buffer": "~1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flush-write-stream": {
|
"flush-write-stream": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
|
||||||
@ -3914,6 +4218,12 @@
|
|||||||
"readable-stream": "^2.0.0"
|
"readable-stream": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fs": {
|
||||||
|
"version": "0.0.1-security",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
|
||||||
|
"integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"fs-access": {
|
"fs-access": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz",
|
||||||
@ -4619,6 +4929,15 @@
|
|||||||
"assert-plus": "^1.0.0"
|
"assert-plus": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"gettext-parser": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.2.2.tgz",
|
||||||
|
"integrity": "sha1-HvDadcHnWa4wicc++k0Z5AKYdI4=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"encoding": "0.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"glob": {
|
"glob": {
|
||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||||
@ -8192,6 +8511,16 @@
|
|||||||
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
|
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"path": {
|
||||||
|
"version": "0.12.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
|
||||||
|
"integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"process": "^0.11.1",
|
||||||
|
"util": "^0.10.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"path-browserify": {
|
"path-browserify": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
|
"extract": "ngx-translate-extract -i ./src -o ./src/assets/i18n/{en,de,fr}.json --clean --sort --format-indentation ' ' --format namespaced-json",
|
||||||
"format:fix": "pretty-quick --staged",
|
"format:fix": "pretty-quick --staged",
|
||||||
"precommit": "run-s format:fix lint"
|
"precommit": "run-s format:fix lint"
|
||||||
},
|
},
|
||||||
@ -27,6 +28,8 @@
|
|||||||
"@fortawesome/angular-fontawesome": "0.1.0-10",
|
"@fortawesome/angular-fontawesome": "0.1.0-10",
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.0",
|
"@fortawesome/fontawesome-svg-core": "^1.2.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.1.0",
|
"@fortawesome/free-solid-svg-icons": "^5.1.0",
|
||||||
|
"@ngx-translate/core": "^10.0.2",
|
||||||
|
"@ngx-translate/http-loader": "^3.0.1",
|
||||||
"core-js": "^2.5.4",
|
"core-js": "^2.5.4",
|
||||||
"rxjs": "^6.2.1",
|
"rxjs": "^6.2.1",
|
||||||
"zone.js": "^0.8.26"
|
"zone.js": "^0.8.26"
|
||||||
@ -36,6 +39,7 @@
|
|||||||
"@angular/cli": "~6.0.8",
|
"@angular/cli": "~6.0.8",
|
||||||
"@angular/compiler-cli": "^6.0.6",
|
"@angular/compiler-cli": "^6.0.6",
|
||||||
"@angular/language-service": "^6.0.6",
|
"@angular/language-service": "^6.0.6",
|
||||||
|
"@biesbjerg/ngx-translate-extract": "^2.3.4",
|
||||||
"@types/jasmine": "~2.8.6",
|
"@types/jasmine": "~2.8.6",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
"@types/node": "~8.9.4",
|
"@types/node": "~8.9.4",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { OpenslidesService } from './core/services/openslides.service';
|
import { OpenslidesService } from './core/services/openslides.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -7,7 +8,16 @@ import { OpenslidesService } from './core/services/openslides.service';
|
|||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
constructor(private openSlides: OpenslidesService) {}
|
constructor(private openSlides: OpenslidesService, public translate: TranslateService) {
|
||||||
|
// manually add the supported languages
|
||||||
|
translate.addLangs(['en', 'de', 'fr']);
|
||||||
|
// this language will be used as a fallback when a translation isn't found in the current language
|
||||||
|
translate.setDefaultLang('en');
|
||||||
|
// get the browsers default language
|
||||||
|
const browserLang = translate.getBrowserLang();
|
||||||
|
// try to use the browser language if it is available. If not, uses english.
|
||||||
|
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.openSlides.bootup();
|
this.openSlides.bootup();
|
||||||
|
@ -3,7 +3,7 @@ import { BrowserModule, Title } from '@angular/platform-browser';
|
|||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
|
import { HttpClientModule, HttpClient, HttpClientXsrfModule } from '@angular/common/http';
|
||||||
|
|
||||||
// MaterialUI modules
|
// MaterialUI modules
|
||||||
import {
|
import {
|
||||||
@ -40,6 +40,14 @@ import { WebsocketService } from './core/services/websocket.service';
|
|||||||
import { ProjectorContainerComponent } from './projector-container/projector-container.component';
|
import { ProjectorContainerComponent } from './projector-container/projector-container.component';
|
||||||
import { AlertComponent } from './core/directives/alert/alert.component';
|
import { AlertComponent } from './core/directives/alert/alert.component';
|
||||||
|
|
||||||
|
//translation module. TODO: Potetially a SharedModule and own files
|
||||||
|
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||||
|
import { PruningTranslationLoader } from './core/pruning-loader';
|
||||||
|
|
||||||
|
export function HttpLoaderFactory(http: HttpClient) {
|
||||||
|
return new PruningTranslationLoader(http);
|
||||||
|
}
|
||||||
|
|
||||||
//add font-awesome icons to library.
|
//add font-awesome icons to library.
|
||||||
//will blow up the code.
|
//will blow up the code.
|
||||||
library.add(fas);
|
library.add(fas);
|
||||||
@ -78,6 +86,13 @@ library.add(fas);
|
|||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useFactory: HttpLoaderFactory,
|
||||||
|
deps: [HttpClient]
|
||||||
|
}
|
||||||
|
}),
|
||||||
AppRoutingModule
|
AppRoutingModule
|
||||||
],
|
],
|
||||||
providers: [Title, ToastService, WebsocketService],
|
providers: [Title, ToastService, WebsocketService],
|
||||||
|
35
client/src/app/core/pruning-loader.ts
Normal file
35
client/src/app/core/pruning-loader.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { TranslateLoader } from '@ngx-translate/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { map } from 'rxjs/operators/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translation loader that replaces empty strings with nothing.
|
||||||
|
*
|
||||||
|
* ngx-translate-extract writes empty strings into json files.
|
||||||
|
* The problem is that these empty strings don't trigger
|
||||||
|
* the MissingTranslationHandler - they are simply empty strings...
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class PruningTranslationLoader implements TranslateLoader {
|
||||||
|
constructor(private http: HttpClient, private prefix: string = '/assets/i18n/', private suffix: string = '.json') {}
|
||||||
|
|
||||||
|
public getTranslation(lang: string): any {
|
||||||
|
return this.http.get(`${this.prefix}${lang}${this.suffix}`).pipe(map((res: Object) => this.process(res)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private process(object: any) {
|
||||||
|
const newObject = {};
|
||||||
|
for (const key in object) {
|
||||||
|
if (object.hasOwnProperty(key)) {
|
||||||
|
if (typeof object[key] === 'object') {
|
||||||
|
newObject[key] = this.process(object[key]);
|
||||||
|
} else if (typeof object[key] === 'string' && object[key] === '') {
|
||||||
|
// do not copy empty strings
|
||||||
|
} else {
|
||||||
|
newObject[key] = object[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newObject;
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,19 @@
|
|||||||
<mat-toolbar color='primary'>
|
<mat-toolbar color='primary'>
|
||||||
|
|
||||||
<button class='generic-plus-button' mat-fab>
|
<button class='generic-plus-button' mat-fab>
|
||||||
<fa-icon icon='plus'></fa-icon>
|
<fa-icon icon='plus'></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- TODO translate -->
|
<span class='app-name' translate>Agenda</span>
|
||||||
<span class='app-name'>Agenda</span>
|
|
||||||
|
|
||||||
<!-- download button on the right -->
|
<!-- download button on the right -->
|
||||||
<span class='spacer'></span>
|
<span class='spacer'></span>
|
||||||
<button mat-icon-button (click)='drawer.toggle()'>
|
<button mat-icon-button (click)='drawer.toggle()'>
|
||||||
<fa-icon icon='download'></fa-icon>
|
<fa-icon icon='download'></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
<div class="app-content">
|
<div class="app-content">
|
||||||
Agenda Works
|
Agenda Works
|
||||||
</div>
|
</div>
|
@ -1,20 +1,20 @@
|
|||||||
<mat-toolbar color='primary'>
|
<mat-toolbar color='primary'>
|
||||||
|
|
||||||
<button class='generic-plus-button' mat-fab>
|
<button class='generic-plus-button' mat-fab>
|
||||||
<fa-icon icon='plus'></fa-icon>
|
<fa-icon icon='plus'></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- TODO translate -->
|
<!-- TODO translate -->
|
||||||
<span class='app-name'>Motions</span>
|
<span class='app-name' translate>Motions</span>
|
||||||
|
|
||||||
<!-- download button on the right -->
|
<!-- download button on the right -->
|
||||||
<span class='spacer'></span>
|
<span class='spacer'></span>
|
||||||
<button mat-icon-button (click)='drawer.toggle()'>
|
<button mat-icon-button (click)='drawer.toggle()'>
|
||||||
<fa-icon icon='download'></fa-icon>
|
<fa-icon icon='download'></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
<div class="app-content">
|
<div class="app-content">
|
||||||
Motion Works
|
Motion Works
|
||||||
</div>
|
</div>
|
@ -1,64 +1,77 @@
|
|||||||
<mat-sidenav-container autosize>
|
<mat-sidenav-container autosize>
|
||||||
<mat-sidenav #sideNav [mode]="isMobile ? 'push' : 'side'" [opened]='!isMobile' disableClose='!isMobile'>
|
<mat-sidenav #sideNav [mode]="isMobile ? 'push' : 'side'" [opened]='!isMobile' disableClose='!isMobile'>
|
||||||
<mat-toolbar class='nav-toolbar' color='primary'>
|
<mat-toolbar class='nav-toolbar' color='primary'>
|
||||||
|
|
||||||
<!-- logo -->
|
<!-- logo -->
|
||||||
<mat-toolbar-row>
|
<mat-toolbar-row>
|
||||||
<img src='/assets/img/openslides-logo-dark.png' alt='OpenSlides-logo'>
|
<img src='/assets/img/openslides-logo-dark.png' alt='OpenSlides-logo'>
|
||||||
</mat-toolbar-row>
|
</mat-toolbar-row>
|
||||||
|
</mat-toolbar>
|
||||||
|
|
||||||
|
<!-- User Menu -->
|
||||||
|
<mat-expansion-panel>
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<!-- TODO use an operator service or get from whoami -->
|
||||||
|
UserName
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
<mat-action-row>
|
||||||
|
<button (click)='logOutButton()' mat-button>Logout</button>
|
||||||
|
</mat-action-row>
|
||||||
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<!-- navigation -->
|
||||||
|
<mat-nav-list>
|
||||||
|
<a mat-list-item routerLink='/' routerLinkActive='active' (click)='isMobile ? sideNav.toggle() : null'>
|
||||||
|
<fa-icon icon='home'></fa-icon>
|
||||||
|
<span translate>Home</span>
|
||||||
|
</a>
|
||||||
|
<a mat-list-item routerLink='/agenda' routerLinkActive='active' (click)='isMobile ? sideNav.toggle() : null'>
|
||||||
|
<fa-icon icon='calendar'></fa-icon>
|
||||||
|
<span translate>Agenda</span>
|
||||||
|
</a>
|
||||||
|
<a mat-list-item routerLink='/motions' routerLinkActive='active' (click)='isMobile ? sideNav.toggle() : null'>
|
||||||
|
<fa-icon icon='file-alt'></fa-icon>
|
||||||
|
<span translate>Motions</span>
|
||||||
|
</a>
|
||||||
|
</mat-nav-list>
|
||||||
|
|
||||||
|
</mat-sidenav>
|
||||||
|
|
||||||
|
<!-- the first toolbar row is (still) a global element
|
||||||
|
the second one shall be handled by the apps -->
|
||||||
|
<mat-toolbar color='primary'>
|
||||||
|
|
||||||
|
<!-- show/hide menu button -->
|
||||||
|
<!-- <button mat-icon-button (click)='sideNav.toggle()'> -->
|
||||||
|
<button mat-icon-button *ngIf="isMobile" (click)='sideNav.toggle()'>
|
||||||
|
<fa-icon icon='bars'></fa-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- glob search and generic menu on the right -->
|
||||||
|
<span class='spacer'></span>
|
||||||
|
<button mat-icon-button (click)='sideNav.toggle()'>
|
||||||
|
<fa-icon icon='search'></fa-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button (click)='sideNav.toggle()'>
|
||||||
|
<fa-icon icon='ellipsis-v'></fa-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button [matMenuTriggerFor]="languageMenu">
|
||||||
|
<fa-icon icon='language'></fa-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- TODO: Could use translate.getLangs() to fetch available languages-->
|
||||||
|
<mat-menu #languageMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)='selectLang("en")' translate>English</button>
|
||||||
|
<button mat-menu-item (click)='selectLang("de")' translate>German</button>
|
||||||
|
<button mat-menu-item (click)='selectLang("fr")' translate>French</button>
|
||||||
|
</mat-menu>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
<!-- User Menu -->
|
<!-- continue with <mat-toolbar> in the app-->
|
||||||
<mat-expansion-panel>
|
<router-outlet></router-outlet>
|
||||||
<mat-expansion-panel-header>
|
|
||||||
<!-- TODO use an operator service or get from whoami -->
|
|
||||||
UserName
|
|
||||||
</mat-expansion-panel-header>
|
|
||||||
<mat-action-row>
|
|
||||||
<button (click)='logOutButton()' mat-button>Logout</button>
|
|
||||||
</mat-action-row>
|
|
||||||
</mat-expansion-panel>
|
|
||||||
|
|
||||||
<!-- navigation -->
|
<!-- the global projector button -->
|
||||||
<mat-nav-list>
|
<button class='projector-button' mat-fab>
|
||||||
<a mat-list-item routerLink='/' routerLinkActive='active' (click)='isMobile ? sideNav.toggle() : null'>
|
<fa-icon icon='video'></fa-icon>
|
||||||
<fa-icon icon='home'></fa-icon> Home
|
|
||||||
</a>
|
|
||||||
<a mat-list-item routerLink='/agenda' routerLinkActive='active' (click)='isMobile ? sideNav.toggle() : null'>
|
|
||||||
<fa-icon icon='calendar'></fa-icon> Agenda
|
|
||||||
</a>
|
|
||||||
<a mat-list-item routerLink='/motions' routerLinkActive='active' (click)='isMobile ? sideNav.toggle() : null'>
|
|
||||||
<fa-icon icon='file-alt'></fa-icon> Motions
|
|
||||||
</a>
|
|
||||||
</mat-nav-list>
|
|
||||||
|
|
||||||
</mat-sidenav>
|
|
||||||
|
|
||||||
<!-- the first toolbar row is (still) a global element
|
|
||||||
the second one shall be handled by the apps -->
|
|
||||||
<mat-toolbar color='primary'>
|
|
||||||
|
|
||||||
<!-- show/hide menu button -->
|
|
||||||
<!-- <button mat-icon-button (click)='sideNav.toggle()'> -->
|
|
||||||
<button mat-icon-button *ngIf="isMobile" (click)='sideNav.toggle()'>
|
|
||||||
<fa-icon icon='bars'></fa-icon>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- glob search and generic menu on the right -->
|
|
||||||
<span class='spacer'></span>
|
|
||||||
<button mat-icon-button (click)='sideNav.toggle()'>
|
|
||||||
<fa-icon icon='search'></fa-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-icon-button (click)='sideNav.toggle()'>
|
|
||||||
<fa-icon icon='ellipsis-v'></fa-icon>
|
|
||||||
</button>
|
|
||||||
</mat-toolbar>
|
|
||||||
|
|
||||||
<!-- continue with <mat-toolbar> in the app-->
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
|
|
||||||
<!-- the global projector button -->
|
|
||||||
<button class='projector-button' mat-fab>
|
|
||||||
<fa-icon icon='video'></fa-icon>
|
|
||||||
</button>
|
|
||||||
</mat-sidenav-container>
|
</mat-sidenav-container>
|
@ -7,6 +7,8 @@ import { WebsocketService } from 'app/core/services/websocket.service';
|
|||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { tap } from 'rxjs/operators';
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core'; //showcase
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-site',
|
selector: 'app-site',
|
||||||
templateUrl: './site.component.html',
|
templateUrl: './site.component.html',
|
||||||
@ -19,7 +21,8 @@ export class SiteComponent implements OnInit {
|
|||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private breakpointObserver: BreakpointObserver
|
private breakpointObserver: BreakpointObserver,
|
||||||
|
private translate: TranslateService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -45,6 +48,20 @@ export class SiteComponent implements OnInit {
|
|||||||
socket.next(val => {
|
socket.next(val => {
|
||||||
console.log('socket.next: ', val);
|
console.log('socket.next: ', val);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//get a translation via code: use the translation service
|
||||||
|
this.translate.get('Motions').subscribe((res: string) => {
|
||||||
|
console.log(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectLang(lang: string): void {
|
||||||
|
console.log('selected langauge: ', lang);
|
||||||
|
console.log('get Langs : ', this.translate.getLangs());
|
||||||
|
|
||||||
|
this.translate.use(lang).subscribe(res => {
|
||||||
|
console.log('language changed : ', res);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logOutButton() {
|
logOutButton() {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<mat-toolbar color='primary'>
|
<mat-toolbar color='primary'>
|
||||||
|
<span class='app-name' translate>Home</span>
|
||||||
<!-- TODO translate -->
|
|
||||||
<span class='app-name'>Home</span>
|
|
||||||
|
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
||||||
<div class="app-content">
|
<div class="app-content" translate>
|
||||||
[Welcome to OpenSlide]
|
<span>{{'Welcome to OpenSlides' | translate}}</span>
|
||||||
|
<br/>
|
||||||
|
<p translate [translateParams]="{user: 'Tim'}">Hello user</p>
|
||||||
</div>
|
</div>
|
10
client/src/assets/i18n/de.json
Normal file
10
client/src/assets/i18n/de.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"Agenda": "Tagesordnung",
|
||||||
|
"English": "Englisch",
|
||||||
|
"French": "",
|
||||||
|
"German": "Deutsch",
|
||||||
|
"Hello user": "Hallo {{user}}",
|
||||||
|
"Home": "Startseite",
|
||||||
|
"Motions": "Anträge",
|
||||||
|
"Welcome to OpenSlides": "Willkommen bei OpenSlides"
|
||||||
|
}
|
10
client/src/assets/i18n/en.json
Normal file
10
client/src/assets/i18n/en.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"Agenda": "Agenda",
|
||||||
|
"English": "English",
|
||||||
|
"French": "",
|
||||||
|
"German": "German",
|
||||||
|
"Hello user": "Hello {{user}}",
|
||||||
|
"Home": "Home",
|
||||||
|
"Motions": "Motions",
|
||||||
|
"Welcome to OpenSlides": "Welcome to OpenSlides"
|
||||||
|
}
|
10
client/src/assets/i18n/fr.json
Normal file
10
client/src/assets/i18n/fr.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"Agenda": "",
|
||||||
|
"English": "",
|
||||||
|
"French": "",
|
||||||
|
"German": "",
|
||||||
|
"Hello user": "",
|
||||||
|
"Home": "",
|
||||||
|
"Motions": "",
|
||||||
|
"Welcome to OpenSlides": ""
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user