diff --git a/client/package-lock.json b/client/package-lock.json
index 8e0501f64..e38140ad1 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -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",
diff --git a/client/package.json b/client/package.json
index c557b70a9..60e374a6a 100644
--- a/client/package.json
+++ b/client/package.json
@@ -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",
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index a94cae2bd..4a716d26e 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -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();
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 79b7fadb5..96ecf55a5 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -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],
diff --git a/client/src/app/core/pruning-loader.ts b/client/src/app/core/pruning-loader.ts
new file mode 100644
index 000000000..4e264fd04
--- /dev/null
+++ b/client/src/app/core/pruning-loader.ts
@@ -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;
+ }
+}
diff --git a/client/src/app/site/agenda/agenda.component.html b/client/src/app/site/agenda/agenda.component.html
index 4a02263ad..e061ae779 100644
--- a/client/src/app/site/agenda/agenda.component.html
+++ b/client/src/app/site/agenda/agenda.component.html
@@ -1,20 +1,19 @@
Hello user