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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
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';
|
||||
|
||||
// Elementary App Components
|
||||
@ -11,25 +11,16 @@ import { AppComponent } from './app.component';
|
||||
import { CoreModule } from './core/core.module';
|
||||
|
||||
// translation module.
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { PruningTranslationLoader } from './core/pruning-loader';
|
||||
import { LoginModule } from './site/login/login.module';
|
||||
import { AppLoadService } from './core/services/app-load.service';
|
||||
import { ProjectorModule } from './site/projector/projector.module';
|
||||
import { SlidesModule } from './slides/slides.module';
|
||||
import { OpenSlidesTranslateModule } from './core/translate/openslides-translate-module';
|
||||
|
||||
// PWA
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
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.
|
||||
* @param appLoadService The service that loads the apps.
|
||||
@ -51,13 +42,7 @@ export function AppLoaderFactory(appLoadService: AppLoadService): () => Promise<
|
||||
headerName: 'X-CSRFToken'
|
||||
}),
|
||||
BrowserAnimationsModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient]
|
||||
}
|
||||
}),
|
||||
OpenSlidesTranslateModule.forRoot(),
|
||||
AppRoutingModule,
|
||||
CoreModule,
|
||||
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 {
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @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(
|
||||
private http: HttpClient,
|
||||
private prefix: string = '/assets/i18n/',
|
||||
private suffix: string = '.json'
|
||||
) {}
|
||||
public constructor(private http: HttpClient) {}
|
||||
|
||||
/**
|
||||
* 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 { ResizedDirective } from './directives/resized.directive';
|
||||
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.
|
||||
@ -128,7 +129,7 @@ import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-b
|
||||
MatTabsModule,
|
||||
MatSliderModule,
|
||||
DragDropModule,
|
||||
TranslateModule.forChild(),
|
||||
OpenSlidesTranslateModule.forChild(),
|
||||
RouterModule,
|
||||
NgxMatSelectSearchModule,
|
||||
FileDropModule,
|
||||
@ -171,6 +172,7 @@ import { MetaTextBlockComponent } from './components/meta-text-block/meta-text-b
|
||||
NgxMatSelectSearchModule,
|
||||
FileDropModule,
|
||||
TranslateModule,
|
||||
OpenSlidesTranslateModule,
|
||||
PermsDirective,
|
||||
DomChangeDirective,
|
||||
AutofocusDirective,
|
||||
|
@ -155,6 +155,10 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit {
|
||||
if (this.configItem.inputType === 'datetimepicker') {
|
||||
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.repo.update({ value: value }, this.configItem).then(() => {
|
||||
this.error = null;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
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 { LoginModule } from 'app/site/login/login.module';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { OpenSlidesTranslateModule } from 'app/core/translate/openslides-translate-module';
|
||||
|
||||
/**
|
||||
* Share Module for all "dumb" components and pipes.
|
||||
@ -24,18 +24,12 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
HttpClientModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient]
|
||||
}
|
||||
}),
|
||||
OpenSlidesTranslateModule.forRoot(),
|
||||
LoginModule,
|
||||
BrowserAnimationsModule,
|
||||
AppRoutingModule
|
||||
],
|
||||
exports: [CommonModule, SharedModule, HttpClientModule, TranslateModule, AppRoutingModule],
|
||||
exports: [CommonModule, SharedModule, HttpClientModule, OpenSlidesTranslateModule, AppRoutingModule],
|
||||
providers: [{ provide: APP_BASE_HREF, useValue: '/' }]
|
||||
})
|
||||
export class E2EImportsModule {}
|
||||
|
Loading…
Reference in New Issue
Block a user