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"
|
||||
}
|
||||
},
|
||||
"@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": {
|
||||
"version": "0.1.0-10",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.1.0-10.tgz",
|
||||
@ -431,6 +653,22 @@
|
||||
"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": {
|
||||
"version": "0.6.8",
|
||||
"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": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz",
|
||||
@ -2993,6 +3279,15 @@
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||
"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": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||
@ -3809,6 +4104,15 @@
|
||||
"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": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
|
||||
@ -3914,6 +4218,12 @@
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz",
|
||||
@ -4619,6 +4929,15 @@
|
||||
"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": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||
@ -8192,6 +8511,16 @@
|
||||
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
|
||||
"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": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
|
||||
|
@ -8,6 +8,7 @@
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"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",
|
||||
"precommit": "run-s format:fix lint"
|
||||
},
|
||||
@ -27,6 +28,8 @@
|
||||
"@fortawesome/angular-fontawesome": "0.1.0-10",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.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",
|
||||
"rxjs": "^6.2.1",
|
||||
"zone.js": "^0.8.26"
|
||||
@ -36,6 +39,7 @@
|
||||
"@angular/cli": "~6.0.8",
|
||||
"@angular/compiler-cli": "^6.0.6",
|
||||
"@angular/language-service": "^6.0.6",
|
||||
"@biesbjerg/ngx-translate-extract": "^2.3.4",
|
||||
"@types/jasmine": "~2.8.6",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/node": "~8.9.4",
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { OpenslidesService } from './core/services/openslides.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -7,7 +8,16 @@ import { OpenslidesService } from './core/services/openslides.service';
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
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() {
|
||||
this.openSlides.bootup();
|
||||
|
@ -3,7 +3,7 @@ import { BrowserModule, Title } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
|
||||
import { HttpClientModule, HttpClient, HttpClientXsrfModule } from '@angular/common/http';
|
||||
|
||||
// MaterialUI modules
|
||||
import {
|
||||
@ -40,6 +40,14 @@ import { WebsocketService } from './core/services/websocket.service';
|
||||
import { ProjectorContainerComponent } from './projector-container/projector-container.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.
|
||||
//will blow up the code.
|
||||
library.add(fas);
|
||||
@ -78,6 +86,13 @@ library.add(fas);
|
||||
MatMenuModule,
|
||||
MatSnackBarModule,
|
||||
FontAwesomeModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient]
|
||||
}
|
||||
}),
|
||||
AppRoutingModule
|
||||
],
|
||||
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'>
|
||||
|
||||
<button class='generic-plus-button' mat-fab>
|
||||
<fa-icon icon='plus'></fa-icon>
|
||||
</button>
|
||||
<button class='generic-plus-button' mat-fab>
|
||||
<fa-icon icon='plus'></fa-icon>
|
||||
</button>
|
||||
|
||||
<!-- TODO translate -->
|
||||
<span class='app-name'>Agenda</span>
|
||||
<span class='app-name' translate>Agenda</span>
|
||||
|
||||
<!-- download button on the right -->
|
||||
<span class='spacer'></span>
|
||||
<button mat-icon-button (click)='drawer.toggle()'>
|
||||
<fa-icon icon='download'></fa-icon>
|
||||
</button>
|
||||
<!-- download button on the right -->
|
||||
<span class='spacer'></span>
|
||||
<button mat-icon-button (click)='drawer.toggle()'>
|
||||
<fa-icon icon='download'></fa-icon>
|
||||
</button>
|
||||
|
||||
</mat-toolbar>
|
||||
|
||||
<div class="app-content">
|
||||
Agenda Works
|
||||
Agenda Works
|
||||
</div>
|
@ -1,20 +1,20 @@
|
||||
<mat-toolbar color='primary'>
|
||||
|
||||
<button class='generic-plus-button' mat-fab>
|
||||
<fa-icon icon='plus'></fa-icon>
|
||||
</button>
|
||||
<button class='generic-plus-button' mat-fab>
|
||||
<fa-icon icon='plus'></fa-icon>
|
||||
</button>
|
||||
|
||||
<!-- TODO translate -->
|
||||
<span class='app-name'>Motions</span>
|
||||
<!-- TODO translate -->
|
||||
<span class='app-name' translate>Motions</span>
|
||||
|
||||
<!-- download button on the right -->
|
||||
<span class='spacer'></span>
|
||||
<button mat-icon-button (click)='drawer.toggle()'>
|
||||
<fa-icon icon='download'></fa-icon>
|
||||
</button>
|
||||
<!-- download button on the right -->
|
||||
<span class='spacer'></span>
|
||||
<button mat-icon-button (click)='drawer.toggle()'>
|
||||
<fa-icon icon='download'></fa-icon>
|
||||
</button>
|
||||
|
||||
</mat-toolbar>
|
||||
|
||||
<div class="app-content">
|
||||
Motion Works
|
||||
Motion Works
|
||||
</div>
|
@ -1,64 +1,77 @@
|
||||
<mat-sidenav-container autosize>
|
||||
<mat-sidenav #sideNav [mode]="isMobile ? 'push' : 'side'" [opened]='!isMobile' disableClose='!isMobile'>
|
||||
<mat-toolbar class='nav-toolbar' color='primary'>
|
||||
<mat-sidenav #sideNav [mode]="isMobile ? 'push' : 'side'" [opened]='!isMobile' disableClose='!isMobile'>
|
||||
<mat-toolbar class='nav-toolbar' color='primary'>
|
||||
|
||||
<!-- logo -->
|
||||
<mat-toolbar-row>
|
||||
<img src='/assets/img/openslides-logo-dark.png' alt='OpenSlides-logo'>
|
||||
</mat-toolbar-row>
|
||||
<!-- logo -->
|
||||
<mat-toolbar-row>
|
||||
<img src='/assets/img/openslides-logo-dark.png' alt='OpenSlides-logo'>
|
||||
</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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- continue with <mat-toolbar> in the app-->
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<!-- navigation -->
|
||||
<mat-nav-list>
|
||||
<a mat-list-item routerLink='/' routerLinkActive='active' (click)='isMobile ? sideNav.toggle() : null'>
|
||||
<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>
|
||||
<!-- the global projector button -->
|
||||
<button class='projector-button' mat-fab>
|
||||
<fa-icon icon='video'></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>
|
||||
</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>
|
@ -7,6 +7,8 @@ import { WebsocketService } from 'app/core/services/websocket.service';
|
||||
import { Subject } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core'; //showcase
|
||||
|
||||
@Component({
|
||||
selector: 'app-site',
|
||||
templateUrl: './site.component.html',
|
||||
@ -19,7 +21,8 @@ export class SiteComponent implements OnInit {
|
||||
private authService: AuthService,
|
||||
private websocketService: WebsocketService,
|
||||
private router: Router,
|
||||
private breakpointObserver: BreakpointObserver
|
||||
private breakpointObserver: BreakpointObserver,
|
||||
private translate: TranslateService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -45,6 +48,20 @@ export class SiteComponent implements OnInit {
|
||||
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() {
|
||||
|
@ -1,10 +1,9 @@
|
||||
<mat-toolbar color='primary'>
|
||||
|
||||
<!-- TODO translate -->
|
||||
<span class='app-name'>Home</span>
|
||||
|
||||
<span class='app-name' translate>Home</span>
|
||||
</mat-toolbar>
|
||||
|
||||
<div class="app-content">
|
||||
[Welcome to OpenSlide]
|
||||
<div class="app-content" translate>
|
||||
<span>{{'Welcome to OpenSlides' | translate}}</span>
|
||||
<br/>
|
||||
<p translate [translateParams]="{user: 'Tim'}">Hello user</p>
|
||||
</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