Use own translation service wrapper for using custom translations
This commit is contained in:
parent
d705b2a137
commit
d20f8d6f44
@ -2,7 +2,7 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { NgModule, APP_INITIALIZER } from '@angular/core';
|
import { NgModule, APP_INITIALIZER } from '@angular/core';
|
||||||
import { HttpClientModule, HttpClient, HttpClientXsrfModule } from '@angular/common/http';
|
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
|
||||||
import { PapaParseModule } from 'ngx-papaparse';
|
import { PapaParseModule } from 'ngx-papaparse';
|
||||||
|
|
||||||
// Elementary App Components
|
// Elementary App Components
|
||||||
@ -11,25 +11,16 @@ import { AppComponent } from './app.component';
|
|||||||
import { CoreModule } from './core/core.module';
|
import { CoreModule } from './core/core.module';
|
||||||
|
|
||||||
// translation module.
|
// translation module.
|
||||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
|
||||||
import { PruningTranslationLoader } from './core/pruning-loader';
|
|
||||||
import { LoginModule } from './site/login/login.module';
|
import { LoginModule } from './site/login/login.module';
|
||||||
import { AppLoadService } from './core/services/app-load.service';
|
import { AppLoadService } from './core/services/app-load.service';
|
||||||
import { ProjectorModule } from './site/projector/projector.module';
|
import { ProjectorModule } from './site/projector/projector.module';
|
||||||
import { SlidesModule } from './slides/slides.module';
|
import { SlidesModule } from './slides/slides.module';
|
||||||
|
import { OpenSlidesTranslateModule } from './core/translate/openslides-translate-module';
|
||||||
|
|
||||||
// PWA
|
// PWA
|
||||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
|
|
||||||
/**
|
|
||||||
* For the translation module. Loads a Custom 'translation loader' and provides it as loader.
|
|
||||||
* @param http Just the HttpClient to load stuff
|
|
||||||
*/
|
|
||||||
export function HttpLoaderFactory(http: HttpClient): PruningTranslationLoader {
|
|
||||||
return new PruningTranslationLoader(http);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a function that returns a promis that will be resolved, if all apps are loaded.
|
* Returns a function that returns a promis that will be resolved, if all apps are loaded.
|
||||||
* @param appLoadService The service that loads the apps.
|
* @param appLoadService The service that loads the apps.
|
||||||
@ -51,13 +42,7 @@ export function AppLoaderFactory(appLoadService: AppLoadService): () => Promise<
|
|||||||
headerName: 'X-CSRFToken'
|
headerName: 'X-CSRFToken'
|
||||||
}),
|
}),
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
TranslateModule.forRoot({
|
OpenSlidesTranslateModule.forRoot(),
|
||||||
loader: {
|
|
||||||
provide: TranslateLoader,
|
|
||||||
useFactory: HttpLoaderFactory,
|
|
||||||
deps: [HttpClient]
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
CoreModule,
|
CoreModule,
|
||||||
LoginModule,
|
LoginModule,
|
||||||
|
61
client/src/app/core/translate/openslides-translate-module.ts
Normal file
61
client/src/app/core/translate/openslides-translate-module.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { ModuleWithProviders, NgModule } from '@angular/core';
|
||||||
|
import {
|
||||||
|
TranslateModule,
|
||||||
|
TranslateLoader,
|
||||||
|
TranslateFakeCompiler,
|
||||||
|
TranslateParser,
|
||||||
|
TranslateCompiler,
|
||||||
|
FakeMissingTranslationHandler,
|
||||||
|
MissingTranslationHandler,
|
||||||
|
TranslateStore,
|
||||||
|
USE_STORE,
|
||||||
|
USE_DEFAULT_LANG,
|
||||||
|
TranslateService,
|
||||||
|
TranslatePipe,
|
||||||
|
TranslateDirective
|
||||||
|
} from '@ngx-translate/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { OpenSlidesTranslateParser } from './translation-parser';
|
||||||
|
import { PruningTranslationLoader } from './translation-pruning-loader';
|
||||||
|
import { OpenSlidesTranslateService } from './translation-service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is analog to the TranslateModule from ngx-translate, but with our own classes.
|
||||||
|
*/
|
||||||
|
@NgModule({
|
||||||
|
imports: [TranslateModule],
|
||||||
|
exports: [TranslatePipe, TranslateDirective]
|
||||||
|
})
|
||||||
|
export class OpenSlidesTranslateModule {
|
||||||
|
public static forRoot(): ModuleWithProviders {
|
||||||
|
return {
|
||||||
|
ngModule: TranslateModule,
|
||||||
|
providers: [
|
||||||
|
{ provide: TranslateLoader, useClass: PruningTranslationLoader, deps: [HttpClient] },
|
||||||
|
{ provide: TranslateCompiler, useClass: TranslateFakeCompiler },
|
||||||
|
{ provide: TranslateParser, useClass: OpenSlidesTranslateParser },
|
||||||
|
{ provide: MissingTranslationHandler, useClass: FakeMissingTranslationHandler },
|
||||||
|
TranslateStore,
|
||||||
|
{ provide: USE_STORE, useValue: false },
|
||||||
|
{ provide: USE_DEFAULT_LANG, useValue: true },
|
||||||
|
{ provide: TranslateService, useClass: OpenSlidesTranslateService }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// no config store for child.
|
||||||
|
public static forChild(): ModuleWithProviders {
|
||||||
|
return {
|
||||||
|
ngModule: TranslateModule,
|
||||||
|
providers: [
|
||||||
|
{ provide: TranslateLoader, useClass: PruningTranslationLoader, deps: [HttpClient] },
|
||||||
|
{ provide: TranslateCompiler, useClass: TranslateFakeCompiler },
|
||||||
|
{ provide: TranslateParser, useClass: OpenSlidesTranslateParser },
|
||||||
|
{ provide: MissingTranslationHandler, useClass: FakeMissingTranslationHandler },
|
||||||
|
{ provide: USE_STORE, useValue: false },
|
||||||
|
{ provide: USE_DEFAULT_LANG, useValue: true },
|
||||||
|
{ provide: TranslateService, useClass: OpenSlidesTranslateService }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
67
client/src/app/core/translate/translation-parser.ts
Normal file
67
client/src/app/core/translate/translation-parser.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { TranslateDefaultParser, TranslateStore } from '@ngx-translate/core';
|
||||||
|
import { ConfigService } from '../services/config.service';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
interface CustomTranslation {
|
||||||
|
original: string;
|
||||||
|
translation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomTranslations = CustomTranslation[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom translate parser. Intercepts and use custom translations from the configservice.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class OpenSlidesTranslateParser extends TranslateDefaultParser {
|
||||||
|
/**
|
||||||
|
* Saves the custom translations retrieved from the config service
|
||||||
|
*/
|
||||||
|
private customTranslations: CustomTranslations = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to the config services and watches for updated custom translations.
|
||||||
|
*
|
||||||
|
* @param config
|
||||||
|
* @param translateStore
|
||||||
|
*/
|
||||||
|
public constructor(config: ConfigService, private translateStore: TranslateStore) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
config.get<CustomTranslations>('translations').subscribe(ct => {
|
||||||
|
if (!ct) {
|
||||||
|
ct = [];
|
||||||
|
}
|
||||||
|
this.customTranslations = ct;
|
||||||
|
|
||||||
|
// trigger reload of all languages. This does not hurt performance,
|
||||||
|
// in fact the directives and pipes just listen to the selected language.
|
||||||
|
this.translateStore.langs.forEach(lang => {
|
||||||
|
this.translateStore.onTranslationChange.emit({
|
||||||
|
lang: lang,
|
||||||
|
translations: this.translateStore.translations[lang]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here, we actually intercept getting translations. This method is called from the
|
||||||
|
* TranslateService trying to retrieve a translation to the key.
|
||||||
|
*
|
||||||
|
* Here, the translation is searched and then overwritten by our custom translations, if
|
||||||
|
* the value exist.
|
||||||
|
*
|
||||||
|
* @param target The translation dict
|
||||||
|
* @param key The key to find the translation
|
||||||
|
*/
|
||||||
|
public getValue(target: any, key: string): any {
|
||||||
|
const translation = super.getValue(target, key);
|
||||||
|
const customTranslation = this.customTranslations.find(c => c.original === translation);
|
||||||
|
if (customTranslation) {
|
||||||
|
return customTranslation.translation;
|
||||||
|
} else {
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,18 +12,22 @@ import { Observable } from 'rxjs';
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class PruningTranslationLoader implements TranslateLoader {
|
export class PruningTranslationLoader implements TranslateLoader {
|
||||||
|
/**
|
||||||
|
* Path to the language files. Can be adjusted of needed
|
||||||
|
*/
|
||||||
|
private prefix = '/assets/i18n/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suffix of the translation files. Usually '.json'.
|
||||||
|
*/
|
||||||
|
private suffix = '.json';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor to load the HttpClient
|
* Constructor to load the HttpClient
|
||||||
*
|
*
|
||||||
* @param http httpClient to load the translation files.
|
* @param http httpClient to load the translation files.
|
||||||
* @param prefix Path to the language files. Can be adjusted of needed
|
|
||||||
* @param suffix Suffix of the translation files. Usually '.json'.
|
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(private http: HttpClient) {}
|
||||||
private http: HttpClient,
|
|
||||||
private prefix: string = '/assets/i18n/',
|
|
||||||
private suffix: string = '.json'
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a language file, stores the content, give it to the process function.
|
* Loads a language file, stores the content, give it to the process function.
|
82
client/src/app/core/translate/translation-service.ts
Normal file
82
client/src/app/core/translate/translation-service.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import {
|
||||||
|
TranslateStore,
|
||||||
|
TranslateService,
|
||||||
|
TranslateLoader,
|
||||||
|
TranslateCompiler,
|
||||||
|
TranslateParser,
|
||||||
|
MissingTranslationHandler,
|
||||||
|
USE_DEFAULT_LANG,
|
||||||
|
USE_STORE
|
||||||
|
} from '@ngx-translate/core';
|
||||||
|
import { Inject, Injectable } from '@angular/core';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom translate service. Wraps the get, stream and instant method not to throw an error, if null or undefined
|
||||||
|
* is passed as keys to them. This happens, if yet not resolved properties should be translated in the templates.
|
||||||
|
* Returns empty strings instead.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class OpenSlidesTranslateService extends TranslateService {
|
||||||
|
/**
|
||||||
|
* See the ngx-translate TranslateService for docs.
|
||||||
|
*
|
||||||
|
* @param store
|
||||||
|
* @param currentLoader
|
||||||
|
* @param compiler
|
||||||
|
* @param parser
|
||||||
|
* @param missingTranslationHandler
|
||||||
|
* @param useDefaultLang
|
||||||
|
* @param isolate
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
store: TranslateStore,
|
||||||
|
currentLoader: TranslateLoader,
|
||||||
|
compiler: TranslateCompiler,
|
||||||
|
parser: TranslateParser,
|
||||||
|
missingTranslationHandler: MissingTranslationHandler,
|
||||||
|
@Inject(USE_DEFAULT_LANG) useDefaultLang: boolean = true,
|
||||||
|
@Inject(USE_STORE) isolate: boolean = false
|
||||||
|
) {
|
||||||
|
super(store, currentLoader, compiler, parser, missingTranslationHandler, useDefaultLang, isolate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the original get function and returns an empty string instead of throwing an error.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
public get(key: string | Array<string>, interpolateParams?: Object): Observable<string | any> {
|
||||||
|
try {
|
||||||
|
return super.get(key, interpolateParams);
|
||||||
|
} catch {
|
||||||
|
return of('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the original key function and returns an empty string instead of throwing an error.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
public stream(key: string | Array<string>, interpolateParams?: Object): Observable<string | any> {
|
||||||
|
try {
|
||||||
|
return super.stream(key, interpolateParams);
|
||||||
|
} catch {
|
||||||
|
return of('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the original instant function and returns an empty string instead of throwing an error.
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
public instant(key: string | Array<string>, interpolateParams?: Object): string | any {
|
||||||
|
try {
|
||||||
|
return super.instant(key, interpolateParams);
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -78,6 +78,7 @@ import { ProjectorButtonComponent } from './components/projector-button/projecto
|
|||||||
import { ProjectionDialogComponent } from './components/projection-dialog/projection-dialog.component';
|
import { ProjectionDialogComponent } from './components/projection-dialog/projection-dialog.component';
|
||||||
import { ResizedDirective } from './directives/resized.directive';
|
import { ResizedDirective } from './directives/resized.directive';
|
||||||
import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-block.component';
|
import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-block.component';
|
||||||
|
import { OpenSlidesTranslateModule } from '../core/translate/openslides-translate-module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share Module for all "dumb" components and pipes.
|
* Share Module for all "dumb" components and pipes.
|
||||||
@ -128,7 +129,7 @@ import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-b
|
|||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatSliderModule,
|
MatSliderModule,
|
||||||
DragDropModule,
|
DragDropModule,
|
||||||
TranslateModule.forChild(),
|
OpenSlidesTranslateModule.forChild(),
|
||||||
RouterModule,
|
RouterModule,
|
||||||
NgxMatSelectSearchModule,
|
NgxMatSelectSearchModule,
|
||||||
FileDropModule,
|
FileDropModule,
|
||||||
@ -171,6 +172,7 @@ import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-b
|
|||||||
NgxMatSelectSearchModule,
|
NgxMatSelectSearchModule,
|
||||||
FileDropModule,
|
FileDropModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
|
OpenSlidesTranslateModule,
|
||||||
PermsDirective,
|
PermsDirective,
|
||||||
DomChangeDirective,
|
DomChangeDirective,
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
|
@ -155,6 +155,10 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
|
|||||||
if (this.configItem.inputType === 'datetimepicker') {
|
if (this.configItem.inputType === 'datetimepicker') {
|
||||||
value = Date.parse(value);
|
value = Date.parse(value);
|
||||||
}
|
}
|
||||||
|
// TODO: Solve this via a custom input form.
|
||||||
|
if (this.configItem.inputType === 'translations') {
|
||||||
|
value = JSON.parse(value);
|
||||||
|
}
|
||||||
this.debounceTimeout = null;
|
this.debounceTimeout = null;
|
||||||
this.repo.update({ value: value }, this.configItem).then(() => {
|
this.repo.update({ value: value }, this.configItem).then(() => {
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { SharedModule } from 'app/shared/shared.module';
|
import { SharedModule } from 'app/shared/shared.module';
|
||||||
import { AppModule, HttpLoaderFactory } from 'app/app.module';
|
import { AppModule } from 'app/app.module';
|
||||||
import { AppRoutingModule } from 'app/app-routing.module';
|
import { AppRoutingModule } from 'app/app-routing.module';
|
||||||
import { LoginModule } from 'app/site/login/login.module';
|
import { LoginModule } from 'app/site/login/login.module';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { OpenSlidesTranslateModule } from 'app/core/translate/openslides-translate-module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share Module for all "dumb" components and pipes.
|
* Share Module for all "dumb" components and pipes.
|
||||||
@ -24,18 +24,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
TranslateModule.forRoot({
|
OpenSlidesTranslateModule.forRoot(),
|
||||||
loader: {
|
|
||||||
provide: TranslateLoader,
|
|
||||||
useFactory: HttpLoaderFactory,
|
|
||||||
deps: [HttpClient]
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
LoginModule,
|
LoginModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
AppRoutingModule
|
AppRoutingModule
|
||||||
],
|
],
|
||||||
exports: [CommonModule, SharedModule, HttpClientModule, TranslateModule, AppRoutingModule],
|
exports: [CommonModule, SharedModule, HttpClientModule, OpenSlidesTranslateModule, AppRoutingModule],
|
||||||
providers: [{ provide: APP_BASE_HREF, useValue: '/' }]
|
providers: [{ provide: APP_BASE_HREF, useValue: '/' }]
|
||||||
})
|
})
|
||||||
export class E2EImportsModule {}
|
export class E2EImportsModule {}
|
||||||
|
Loading…
Reference in New Issue
Block a user