diff --git a/ui/src/app/app.component.spec.ts b/ui/src/app/app.component.spec.ts index d5a11f557..a86e7c1c9 100644 --- a/ui/src/app/app.component.spec.ts +++ b/ui/src/app/app.component.spec.ts @@ -7,7 +7,7 @@ import { AppComponent } from './app.component'; import * as fromRoot from './core/reducer'; import { NotificationModule } from './notification/notification.module'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; -import { MockI18nPipe, MockI18nService } from '../testing/i18n.stub'; +import { MockTranslatePipe, MockI18nService } from '../testing/i18n.stub'; import { I18nService } from './i18n/service/i18n.service'; @Component({ @@ -43,7 +43,7 @@ describe('AppComponent', () => { declarations: [ AppComponent, TestHostComponent, - MockI18nPipe + MockTranslatePipe ], }).compileComponents(); diff --git a/ui/src/app/i18n/directive/translate.directive.ts b/ui/src/app/i18n/directive/translate.directive.ts new file mode 100644 index 000000000..1922cedc7 --- /dev/null +++ b/ui/src/app/i18n/directive/translate.directive.ts @@ -0,0 +1,55 @@ +import { + AfterViewChecked, + ChangeDetectorRef, + Directive, + ElementRef, + Input, + OnDestroy +} from '@angular/core'; +import { Subscription } from 'rxjs'; +import { I18nService } from '../service/i18n.service'; +import * as fromI18n from '../reducer'; +import { Store } from '@ngrx/store'; +import { Messages } from '../model/Messages'; + +@Directive({ + selector: '[translate]' +}) +export class TranslateDirective implements OnDestroy { + key: string; + lastParams: any; + currentParams: any; + messages: Messages; + sub: Subscription; + + @Input() set translate(key: string) { + if (key) { + this.key = key; + this.update(); + } + } + + @Input() set translateParams(params: any) { + this.currentParams = params || {}; + this.update(); + } + + constructor( + private service: I18nService, + private store: Store, + private element: ElementRef, + private _ref: ChangeDetectorRef + ) { + this.sub = this.store.select(fromI18n.getMessages).subscribe(m => this.messages = m); + } + + update(): void { + this.element.nativeElement.textContent = this.service.translate(this.key, this.currentParams, this.messages); + } + + ngOnDestroy() { + if (this.sub) { + this.sub.unsubscribe(); + } + } +} diff --git a/ui/src/app/i18n/i18n.module.ts b/ui/src/app/i18n/i18n.module.ts index 0373c45c2..3823f7462 100644 --- a/ui/src/app/i18n/i18n.module.ts +++ b/ui/src/app/i18n/i18n.module.ts @@ -7,13 +7,16 @@ import { reducers } from './reducer'; import { HttpClientModule } from '@angular/common/http'; import { I18nService } from './service/i18n.service'; import { MessageEffects } from './effect/message.effect'; -import { I18nPipe } from './pipe/i18n.pipe'; +import { TranslatePipe } from './pipe/i18n.pipe'; import { CoreModule } from '../core/core.module'; +import { TranslateDirective } from './directive/translate.directive'; export const COMPONENTS = []; -export const DIRECTIVES = []; +export const DIRECTIVES = [ + TranslateDirective +]; export const PIPES = [ - I18nPipe + TranslatePipe ]; @NgModule({ diff --git a/ui/src/app/i18n/model/Messages.ts b/ui/src/app/i18n/model/Messages.ts new file mode 100644 index 000000000..e561dccbd --- /dev/null +++ b/ui/src/app/i18n/model/Messages.ts @@ -0,0 +1,3 @@ +export interface Messages { + [key: string]: string; +} diff --git a/ui/src/app/i18n/pipe/i18n.pipe.spec.ts b/ui/src/app/i18n/pipe/i18n.pipe.spec.ts index 840024ab9..c78cd4244 100644 --- a/ui/src/app/i18n/pipe/i18n.pipe.spec.ts +++ b/ui/src/app/i18n/pipe/i18n.pipe.spec.ts @@ -1,4 +1,4 @@ -import { I18nPipe } from './i18n.pipe'; +import { TranslatePipe } from './i18n.pipe'; import { I18nService } from '../service/i18n.service'; import { CommonModule } from '@angular/common'; import { StoreModule, combineReducers, Store } from '@ngrx/store'; @@ -10,7 +10,7 @@ import { MessagesLoadSuccessAction } from '../action/message.action'; @Component({ template: ` - {{ foo | i18n:{ foo: 'bar' } }} + {{ foo | translate:{ foo: 'bar' } }} ` }) class TestHostComponent { @@ -49,7 +49,7 @@ describe('Pipe: I18n translation', () => { }) ], declarations: [ - I18nPipe, + TranslatePipe, TestHostComponent ], }); diff --git a/ui/src/app/i18n/pipe/i18n.pipe.ts b/ui/src/app/i18n/pipe/i18n.pipe.ts index 20564a169..374f146f1 100644 --- a/ui/src/app/i18n/pipe/i18n.pipe.ts +++ b/ui/src/app/i18n/pipe/i18n.pipe.ts @@ -5,17 +5,15 @@ import { Subscription } from 'rxjs'; import { I18nService } from '../service/i18n.service'; @Pipe({ - name: 'i18n', + name: 'translate', pure: false }) -export class I18nPipe implements PipeTransform, OnDestroy { +export class TranslatePipe implements PipeTransform, OnDestroy { sub: Subscription; messages: { [propName: string]: string } = {}; - value: string; - constructor( private store: Store, private i18nService: I18nService, @@ -28,10 +26,7 @@ export class I18nPipe implements PipeTransform, OnDestroy { } transform(value: string, interpolated: { [prop: string]: string } = {}): any { - interpolated = interpolated || {}; - let val = this.messages.hasOwnProperty(value) ? this.messages[value] : value; - this.value = this.i18nService.interpolate(val, interpolated); - return this.value; + return this.i18nService.translate(value, interpolated, this.messages); } ngOnDestroy(): void { diff --git a/ui/src/app/i18n/service/i18n.service.ts b/ui/src/app/i18n/service/i18n.service.ts index f3e2d269d..16a613c1f 100644 --- a/ui/src/app/i18n/service/i18n.service.ts +++ b/ui/src/app/i18n/service/i18n.service.ts @@ -3,6 +3,7 @@ import { Observable } from 'rxjs'; import { HttpClient, HttpParams } from '@angular/common/http'; import { NavigatorService } from '../../core/service/navigator.service'; import { getCurrentLanguage, getCurrentCountry, getCurrentLocale } from '../../shared/util'; +import { Messages } from '../model/Messages'; @Injectable() export class I18nService { @@ -35,6 +36,12 @@ export class I18nService { return getCurrentLocale(this.navigator.native); } + translate(value: string, interpolated: any, messages: Messages): string { + interpolated = interpolated || {}; + let val = messages.hasOwnProperty(value) ? messages[value] : value; + return this.interpolate(val, interpolated); + } + interpolate(value: string, interpolated: { [prop: string]: string } = {}): string { return Object.entries(interpolated).reduce((current, interpolate) => { let reg = new RegExp(`{\\s*${interpolate[0]}\\s*}`, 'gm'); diff --git a/ui/src/testing/i18n.stub.ts b/ui/src/testing/i18n.stub.ts index 947f7a09d..0cc38e77f 100644 --- a/ui/src/testing/i18n.stub.ts +++ b/ui/src/testing/i18n.stub.ts @@ -9,7 +9,7 @@ import { Observable, of } from 'rxjs'; name: 'i18n', pure: false }) -export class MockI18nPipe implements PipeTransform { +export class MockTranslatePipe implements PipeTransform { constructor() {}