From 522f4c52534c3480c85bf8f94e0295ef08ac02d4 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Mon, 12 Jan 2026 16:29:07 +0100 Subject: [PATCH 1/6] feat(frontend): add translationScope --- frontend/src/app/app.component.html | 1 + .../member-detail-view.component.html | 15 ++------ .../member-detail-view.component.ts | 6 +++ .../member/form/member-form.component.ts | 2 +- .../src/app/features/member/member.model.ts | 2 +- .../directives/translation-scope.spec.ts | 9 +++++ .../shared/directives/translation-scope.ts | 29 +++++++++++++++ .../src/app/shared/i18n-prefix.provider.ts | 37 ++++++++++++++++++- .../shared/modal/base-modal.component.html | 5 +-- .../app/shared/modal/base-modal.component.ts | 10 +++-- .../app/shared/pipes/null-fallback.pipe.ts | 13 +++++++ .../pipes/translated-model-suffix.pipe.ts | 17 +++++++++ 12 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 frontend/src/app/shared/directives/translation-scope.spec.ts create mode 100644 frontend/src/app/shared/directives/translation-scope.ts create mode 100644 frontend/src/app/shared/pipes/null-fallback.pipe.ts create mode 100644 frontend/src/app/shared/pipes/translated-model-suffix.pipe.ts diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index c4a4b12fa..72f2840f6 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -5,6 +5,7 @@ {{'PCTS_LOGO_ALT' | translate}} diff --git a/frontend/src/app/features/member/detail-view/member-detail-view.component.html b/frontend/src/app/features/member/detail-view/member-detail-view.component.html index 9097098a3..2ce190083 100644 --- a/frontend/src/app/features/member/detail-view/member-detail-view.component.html +++ b/frontend/src/app/features/member/detail-view/member-detail-view.component.html @@ -18,7 +18,7 @@

{{ 'ABBREVIATION' | scopedTranslation }}:

- {{ m.abbreviation ? (m.abbreviation) : ('ATTRIBUTE_NULL' | scopedTranslation)}} + {{ m.abbreviation | nullFallback }}

@@ -30,12 +30,8 @@

{{ 'DATE_OF_HIRE' | scopedTranslation }}:

- {{ m.dateOfHire - ? (m.dateOfHire | date) - : ('ATTRIBUTE_NULL' | scopedTranslation) - }} + {{ m.dateOfHire | date | nullFallback }}

-
@@ -46,11 +42,7 @@

{{ 'ORGANISATION_UNIT' | scopedTranslation }}:

- @if (m.organisationUnitName) { - {{m.organisationUnitName}} - } @else { - {{'ATTRIBUTE_NULL' | scopedTranslation}} - } + {{m.organisationUnit?.name | nullFallback}}

@@ -99,3 +91,4 @@

} +
diff --git a/frontend/src/app/features/member/detail-view/member-detail-view.component.ts b/frontend/src/app/features/member/detail-view/member-detail-view.component.ts index fcf061ec5..88c942c5d 100644 --- a/frontend/src/app/features/member/detail-view/member-detail-view.component.ts +++ b/frontend/src/app/features/member/detail-view/member-detail-view.component.ts @@ -2,6 +2,7 @@ import { Component, inject, OnInit, signal, WritableSignal } from '@angular/core import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { MemberService } from '../member.service'; +import { MemberModel } from '../member.model'; import { ScopedTranslationPipe } from '../../../shared/pipes/scoped-translation-pipe'; import { CrudButtonComponent } from '../../../shared/crud-button/crud-button.component'; import { GenericCvContentComponent } from './generic-cv-content/generic-cv-content.component'; @@ -18,6 +19,8 @@ import { getLeadershipExperienceTable } from './cv/member-detail-cv-table-definition'; import { MemberOverviewModel } from '../member-overview.model'; +import { NullFallbackPipe } from '../../../shared/pipes/null-fallback.pipe'; +import { TranslationScope } from '../../../shared/directives/translation-scope'; @Component({ selector: 'app-member-detail-view', @@ -26,6 +29,9 @@ import { MemberOverviewModel } from '../member-overview.model'; CommonModule, ScopedTranslationPipe, CrudButtonComponent, + NullFallbackPipe, + TranslationScope + CrudButtonComponent, GenericCvContentComponent, MatTabGroup, MatTab, diff --git a/frontend/src/app/features/member/form/member-form.component.ts b/frontend/src/app/features/member/form/member-form.component.ts index 68da57c2a..b1fd8a8c8 100644 --- a/frontend/src/app/features/member/form/member-form.component.ts +++ b/frontend/src/app/features/member/form/member-form.component.ts @@ -119,7 +119,7 @@ export class MemberFormComponent implements OnInit { this.memberForm.get('organisationUnit') ?.setValue(this.organisationUnitsOptions() - .find((orgUnit) => orgUnit.id === this.member().organisationUnit.id)); + .find((orgUnit) => orgUnit.id === this.member().organisationUnit?.id)); }); } diff --git a/frontend/src/app/features/member/member.model.ts b/frontend/src/app/features/member/member.model.ts index 8ceacfdf9..62c3fb00a 100644 --- a/frontend/src/app/features/member/member.model.ts +++ b/frontend/src/app/features/member/member.model.ts @@ -8,6 +8,6 @@ export interface MemberModel { birthDate: Date; abbreviation: string | null; employmentState: EmploymentState; - organisationUnit: OrganisationUnitModel; + organisationUnit: OrganisationUnitModel | null; dateOfHire: Date | null; } diff --git a/frontend/src/app/shared/directives/translation-scope.spec.ts b/frontend/src/app/shared/directives/translation-scope.spec.ts new file mode 100644 index 000000000..c95a56354 --- /dev/null +++ b/frontend/src/app/shared/directives/translation-scope.spec.ts @@ -0,0 +1,9 @@ +import { TranslationScope } from './translation-scope'; + +describe('TranslationScope', () => { + it('should create an instance', () => { + const directive = new TranslationScope(); + expect(directive) + .toBeTruthy(); + }); +}); diff --git a/frontend/src/app/shared/directives/translation-scope.ts b/frontend/src/app/shared/directives/translation-scope.ts new file mode 100644 index 000000000..9f2550714 --- /dev/null +++ b/frontend/src/app/shared/directives/translation-scope.ts @@ -0,0 +1,29 @@ +import { computed, Directive, effect, inject, input } from '@angular/core'; +import { isArray } from '@ngx-translate/core'; +import { provideI18nScope, ScopedTranslationService } from '../i18n-prefix.provider'; + + +@Directive({ + selector: '[translationScope]', + providers: [provideI18nScope(TranslationScope), + ScopedTranslationService] +}) +export class TranslationScope { + scopedTranslationService = inject(ScopedTranslationService); + // i18nPrefix = inject(I18N_PREFIX); + + translationScope = input.required(); + + scope = computed(() => { + const scope = this.translationScope(); + return isArray(scope) ? scope.join('.') : scope; + }); + + constructor() { + effect(() => { + // console.log(this.i18nPrefix) + console.log(this.scopedTranslationService.prefix); + console.log(this.scope()); + }); + } +} diff --git a/frontend/src/app/shared/i18n-prefix.provider.ts b/frontend/src/app/shared/i18n-prefix.provider.ts index 1f013f148..b77b9b253 100644 --- a/frontend/src/app/shared/i18n-prefix.provider.ts +++ b/frontend/src/app/shared/i18n-prefix.provider.ts @@ -1,4 +1,11 @@ -import { inject, Injectable, Provider } from '@angular/core'; +import { + inject, + Injectable, + Optional, + Provider, + SkipSelf, + Type +} from '@angular/core'; import { InterpolationParameters } from '@ngx-translate/core'; import { I18N_PREFIX } from './i18n-prefix.token'; import { ScopedTranslationCoreService } from './services/scoped-translation-core.service'; @@ -21,6 +28,34 @@ export function provideI18nPrefix(suffix: string, separator = '.'): Provider[] { ScopedTranslationService]; } + +export function provideI18nScope(tokenSourceType: Type, separator = '.'): Provider { + return { + provide: I18N_PREFIX, + useFactory: (parentPrefix: string | null, instance: any) => { + console.log('tasdf'); + // Access the computed signal from the injected directive instance + const currentSuffix = instance.scope(); + console.log(currentSuffix); + if (!currentSuffix) { + return parentPrefix ?? ''; + } + const var234 = parentPrefix + ? parentPrefix + separator + currentSuffix + : currentSuffix; + console.log('parent prefix:', parentPrefix); + console.log(instance.scope()); + console.log(var234); + return var234; + }, + // We inject the parent token (optional) AND the directive instance itself + deps: [[new Optional(), + new SkipSelf(), + I18N_PREFIX], + tokenSourceType] + }; +} + @Injectable() export class ScopedTranslationService { private readonly core = inject(ScopedTranslationCoreService); diff --git a/frontend/src/app/shared/modal/base-modal.component.html b/frontend/src/app/shared/modal/base-modal.component.html index 334490b8c..97e149771 100644 --- a/frontend/src/app/shared/modal/base-modal.component.html +++ b/frontend/src/app/shared/modal/base-modal.component.html @@ -1,7 +1,7 @@
-

+

{{ "MODEL_NAME" | scopedTranslation }} {{ "ACTION" | scopedTranslation }}

@@ -9,8 +9,7 @@

close diff --git a/frontend/src/app/shared/modal/base-modal.component.ts b/frontend/src/app/shared/modal/base-modal.component.ts index 377d55f02..9e7fcdd8b 100644 --- a/frontend/src/app/shared/modal/base-modal.component.ts +++ b/frontend/src/app/shared/modal/base-modal.component.ts @@ -4,10 +4,10 @@ import { MatDivider } from '@angular/material/divider'; import { MatIconButton } from '@angular/material/button'; import { MatIcon } from '@angular/material/icon'; - -import { ScopedTranslationPipe } from '../pipes/scoped-translation-pipe'; import { BaseFormActionsComponent } from '../base-form-actions/base-form-actions.component'; import { FormGroup } from '@angular/forms'; +import { TranslatedModelSuffixPipe } from '../pipes/translated-model-suffix.pipe'; +import { ScopedTranslationPipe } from '../pipes/scoped-translation-pipe'; @Component({ selector: 'app-base-modal', @@ -19,8 +19,10 @@ import { FormGroup } from '@angular/forms'; MatIcon, MatDialogClose, MatIconButton, - ScopedTranslationPipe, - BaseFormActionsComponent + BaseFormActionsComponent, + TranslatedModelSuffixPipe, + TranslatedModelSuffixPipe, + ScopedTranslationPipe ], templateUrl: './base-modal.component.html' }) diff --git a/frontend/src/app/shared/pipes/null-fallback.pipe.ts b/frontend/src/app/shared/pipes/null-fallback.pipe.ts new file mode 100644 index 000000000..3fbd2f2b6 --- /dev/null +++ b/frontend/src/app/shared/pipes/null-fallback.pipe.ts @@ -0,0 +1,13 @@ +import { inject, Pipe, PipeTransform } from '@angular/core'; +import { ScopedTranslationService } from '../i18n-prefix.provider'; + +@Pipe({ + name: 'nullFallback' +}) +export class NullFallbackPipe implements PipeTransform { + scopedTranslationService = inject(ScopedTranslationService); + + transform(value: any, fallback = 'ATTRIBUTE_NULL'): string { + return value ?? this.scopedTranslationService.instant(fallback); + } +} diff --git a/frontend/src/app/shared/pipes/translated-model-suffix.pipe.ts b/frontend/src/app/shared/pipes/translated-model-suffix.pipe.ts new file mode 100644 index 000000000..bf5f4af4c --- /dev/null +++ b/frontend/src/app/shared/pipes/translated-model-suffix.pipe.ts @@ -0,0 +1,17 @@ +import { inject, Pipe, PipeTransform } from '@angular/core'; +import { ScopedTranslationService } from '../i18n-prefix.provider'; + +@Pipe({ + name: 'translatedModelSuffix' +}) +export class TranslatedModelSuffixPipe implements PipeTransform { + pctsTranslationService = inject(ScopedTranslationService); + + transform(prefix: string, suffix = ''): string { + const modelKey = 'MODEL_NAME'; + const translatedModelName = this.pctsTranslationService.instant(modelKey); + return [prefix, + translatedModelName, + suffix].join('-'); + } +} From d8101d89cc073e3806fe72856c9687eecbffa7f5 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Mon, 12 Jan 2026 22:28:15 +0100 Subject: [PATCH 2/6] refactor(frontend): simplify TranslationScopeDirective --- .../member-detail-view.component.html | 1 - .../member-detail-view.component.ts | 2 +- .../shared/directives/translation-scope.ts | 45 ++++++++++--------- .../src/app/shared/i18n-prefix.provider.ts | 33 +------------- 4 files changed, 27 insertions(+), 54 deletions(-) diff --git a/frontend/src/app/features/member/detail-view/member-detail-view.component.html b/frontend/src/app/features/member/detail-view/member-detail-view.component.html index 2ce190083..97fd5c506 100644 --- a/frontend/src/app/features/member/detail-view/member-detail-view.component.html +++ b/frontend/src/app/features/member/detail-view/member-detail-view.component.html @@ -91,4 +91,3 @@

} -
diff --git a/frontend/src/app/features/member/detail-view/member-detail-view.component.ts b/frontend/src/app/features/member/detail-view/member-detail-view.component.ts index 88c942c5d..17e3e6501 100644 --- a/frontend/src/app/features/member/detail-view/member-detail-view.component.ts +++ b/frontend/src/app/features/member/detail-view/member-detail-view.component.ts @@ -20,7 +20,7 @@ import { } from './cv/member-detail-cv-table-definition'; import { MemberOverviewModel } from '../member-overview.model'; import { NullFallbackPipe } from '../../../shared/pipes/null-fallback.pipe'; -import { TranslationScope } from '../../../shared/directives/translation-scope'; +import { TranslationScopeDirective } from '../../../shared/directives/translation-scope'; @Component({ selector: 'app-member-detail-view', diff --git a/frontend/src/app/shared/directives/translation-scope.ts b/frontend/src/app/shared/directives/translation-scope.ts index 9f2550714..5e5c16525 100644 --- a/frontend/src/app/shared/directives/translation-scope.ts +++ b/frontend/src/app/shared/directives/translation-scope.ts @@ -1,29 +1,34 @@ -import { computed, Directive, effect, inject, input } from '@angular/core'; -import { isArray } from '@ngx-translate/core'; -import { provideI18nScope, ScopedTranslationService } from '../i18n-prefix.provider'; - +import { + Directive, + input, + inject, + TemplateRef, + ViewContainerRef, + Injector, + OnInit +} from '@angular/core'; +import { provideI18nPrefix } from '../i18n-prefix.provider'; @Directive({ - selector: '[translationScope]', - providers: [provideI18nScope(TranslationScope), - ScopedTranslationService] + selector: '[appTranslationScope]', + standalone: true }) -export class TranslationScope { - scopedTranslationService = inject(ScopedTranslationService); - // i18nPrefix = inject(I18N_PREFIX); +export class TranslationScopeDirective implements OnInit { + readonly scopeInput = input.required({ alias: 'appTranslationScope' }); + + // 4. Structural Directive Dependencies + private readonly vcr = inject(ViewContainerRef); - translationScope = input.required(); + private readonly tpl = inject(TemplateRef); - scope = computed(() => { - const scope = this.translationScope(); - return isArray(scope) ? scope.join('.') : scope; - }); + private readonly injector = inject(Injector); - constructor() { - effect(() => { - // console.log(this.i18nPrefix) - console.log(this.scopedTranslationService.prefix); - console.log(this.scope()); + ngOnInit(): void { + const customInjector = Injector.create({ + providers: [provideI18nPrefix(this.scopeInput())], + parent: this.injector // Link to the ElementInjector of this anchor }); + + this.vcr.createEmbeddedView(this.tpl, null, { injector: customInjector }); } } diff --git a/frontend/src/app/shared/i18n-prefix.provider.ts b/frontend/src/app/shared/i18n-prefix.provider.ts index b77b9b253..2df09646a 100644 --- a/frontend/src/app/shared/i18n-prefix.provider.ts +++ b/frontend/src/app/shared/i18n-prefix.provider.ts @@ -1,10 +1,7 @@ import { inject, Injectable, - Optional, - Provider, - SkipSelf, - Type + Provider } from '@angular/core'; import { InterpolationParameters } from '@ngx-translate/core'; import { I18N_PREFIX } from './i18n-prefix.token'; @@ -28,34 +25,6 @@ export function provideI18nPrefix(suffix: string, separator = '.'): Provider[] { ScopedTranslationService]; } - -export function provideI18nScope(tokenSourceType: Type, separator = '.'): Provider { - return { - provide: I18N_PREFIX, - useFactory: (parentPrefix: string | null, instance: any) => { - console.log('tasdf'); - // Access the computed signal from the injected directive instance - const currentSuffix = instance.scope(); - console.log(currentSuffix); - if (!currentSuffix) { - return parentPrefix ?? ''; - } - const var234 = parentPrefix - ? parentPrefix + separator + currentSuffix - : currentSuffix; - console.log('parent prefix:', parentPrefix); - console.log(instance.scope()); - console.log(var234); - return var234; - }, - // We inject the parent token (optional) AND the directive instance itself - deps: [[new Optional(), - new SkipSelf(), - I18N_PREFIX], - tokenSourceType] - }; -} - @Injectable() export class ScopedTranslationService { private readonly core = inject(ScopedTranslationCoreService); From 56e3fdd47ab2a6c90a8688cd197a30e3c72417a2 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Tue, 13 Jan 2026 15:12:31 +0100 Subject: [PATCH 3/6] refactor(frontend): fix ussages of scoped translation pipe in the entire applicatoin --- .../overview/member-overview.component.html | 17 ++++++++--------- .../overview/member-overview.component.ts | 15 ++++++++------- frontend/src/app/shared/format/date-format.ts | 2 -- .../services/scoped-translation-core.service.ts | 7 +++++-- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/features/member/overview/member-overview.component.html b/frontend/src/app/features/member/overview/member-overview.component.html index 1099a16a9..b3b4d9c1c 100644 --- a/frontend/src/app/features/member/overview/member-overview.component.html +++ b/frontend/src/app/features/member/overview/member-overview.component.html @@ -41,8 +41,9 @@

[class.active]="isFilterActive(state)" (click)="toggleFilter(state)" [attr.data-testid]="state" + *appTranslationScope="'EMPLOYMENT_STATUS_VALUES'" > - {{ 'EMPLOYMENT_STATUS_VALUES.' + state | scopedTranslation }} + {{ state | scopedTranslation }} }

@@ -67,7 +68,7 @@

mat-sort-header="firstName" data-testid="first-name-sort" class="col-range" > - {{ 'MEMBER.FIRST_NAME' | translate }} + {{ 'FIRST_NAME' | scopedTranslation }} {{ 'BIRTH_DATE' | scopedTranslation }} - {{ member.birthDate | date: GLOBAL_DATE_FORMAT }} + {{ member.birthDate | date }} @@ -103,11 +104,7 @@

{{ 'ORGANISATION_UNIT' | scopedTranslation }} - @if (member.organisationUnit) { - {{ member.organisationUnit.name }} - } @else { - {{ 'MEMBER.ATTRIBUTE_NULL' | translate }} - } + {{member.organisationUnit?.name | nullFallback}} @@ -116,7 +113,9 @@

{{ 'EMPLOYMENT_STATE' | scopedTranslation }} - {{ "MEMBER.EMPLOYMENT_STATUS_VALUES." + member.employmentState | translate }} + + {{ member.employmentState | scopedTranslation }} + diff --git a/frontend/src/app/features/member/overview/member-overview.component.ts b/frontend/src/app/features/member/overview/member-overview.component.ts index b0cca1d79..648f98b72 100644 --- a/frontend/src/app/features/member/overview/member-overview.component.ts +++ b/frontend/src/app/features/member/overview/member-overview.component.ts @@ -6,17 +6,18 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSort, MatSortModule } from '@angular/material/sort'; -import { DatePipe } from '@angular/common'; +import { DatePipe, JsonPipe } from '@angular/common'; import { MatIcon } from '@angular/material/icon'; import { MatButton } from '@angular/material/button'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; -import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { TranslateService } from '@ngx-translate/core'; import { EmploymentState } from '../../../shared/enum/employment-state.enum'; import { debounceTime } from 'rxjs/operators'; -import { GLOBAL_DATE_FORMAT } from '../../../shared/format/date-format'; import sortingDataAccessor from '../../../shared/utils/sortingDataAccessor'; import { ScopedTranslationPipe } from '../../../shared/pipes/scoped-translation-pipe'; import { CrudButtonComponent } from '../../../shared/crud-button/crud-button.component'; +import { TranslationScopeDirective } from '../../../shared/directives/translation-scope'; +import { NullFallbackPipe } from '../../../shared/pipes/null-fallback.pipe'; @Component({ @@ -32,10 +33,12 @@ import { CrudButtonComponent } from '../../../shared/crud-button/crud-button.com DatePipe, MatIcon, MatButton, - TranslatePipe, RouterLink, ScopedTranslationPipe, - CrudButtonComponent + CrudButtonComponent, + TranslationScopeDirective, + NullFallbackPipe, + JsonPipe ], templateUrl: './member-overview.component.html', styleUrl: './member-overview.component.scss' @@ -51,8 +54,6 @@ export class MemberOverviewComponent implements OnInit { private readonly translate = inject(TranslateService); - protected readonly GLOBAL_DATE_FORMAT = GLOBAL_DATE_FORMAT; - displayedColumns: string[] = [ 'first_name', 'last_name', diff --git a/frontend/src/app/shared/format/date-format.ts b/frontend/src/app/shared/format/date-format.ts index a383dfda3..acbf00ab2 100644 --- a/frontend/src/app/shared/format/date-format.ts +++ b/frontend/src/app/shared/format/date-format.ts @@ -2,8 +2,6 @@ import { MatDateFormats } from '@angular/material/core'; import { format } from 'date-fns'; import { de } from 'date-fns/locale'; -export const GLOBAL_DATE_FORMAT = 'dd.MM.yyyy'; - export const GLOBAL_DATE_FORMATS: MatDateFormats = { parse: { dateInput: { month: 'short', diff --git a/frontend/src/app/shared/services/scoped-translation-core.service.ts b/frontend/src/app/shared/services/scoped-translation-core.service.ts index 3af8dcb76..1b30e198f 100644 --- a/frontend/src/app/shared/services/scoped-translation-core.service.ts +++ b/frontend/src/app/shared/services/scoped-translation-core.service.ts @@ -87,8 +87,11 @@ export class ScopedTranslationCoreService { for (const key of keyList) { const translation = this.translateService.instant(key, params); - // instant() returns the key itself if no translation is found - if (translation !== key) { + /* + * instant() returns the key itself if no translation is found + * prevent returning anything else than a string since ngx-translate return an obj whenever a key is translated that contains sub keys + */ + if (translation !== key && typeof translation === 'string') { return translation; } } From 6510b39eef95ccd39c588db1183a48d6afa85bf6 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Tue, 13 Jan 2026 16:45:04 +0100 Subject: [PATCH 4/6] refactor(frontend): use effect instead of on init to set providers of providedScope directive --- .../directives/translation-scope.spec.ts | 4 ++-- .../shared/directives/translation-scope.ts | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/shared/directives/translation-scope.spec.ts b/frontend/src/app/shared/directives/translation-scope.spec.ts index c95a56354..bbda989c0 100644 --- a/frontend/src/app/shared/directives/translation-scope.spec.ts +++ b/frontend/src/app/shared/directives/translation-scope.spec.ts @@ -1,8 +1,8 @@ -import { TranslationScope } from './translation-scope'; +import { TranslationScopeDirective } from './translation-scope'; describe('TranslationScope', () => { it('should create an instance', () => { - const directive = new TranslationScope(); + const directive = new TranslationScopeDirective(); expect(directive) .toBeTruthy(); }); diff --git a/frontend/src/app/shared/directives/translation-scope.ts b/frontend/src/app/shared/directives/translation-scope.ts index 5e5c16525..e536d93f6 100644 --- a/frontend/src/app/shared/directives/translation-scope.ts +++ b/frontend/src/app/shared/directives/translation-scope.ts @@ -4,8 +4,7 @@ import { inject, TemplateRef, ViewContainerRef, - Injector, - OnInit + Injector, effect } from '@angular/core'; import { provideI18nPrefix } from '../i18n-prefix.provider'; @@ -13,22 +12,23 @@ import { provideI18nPrefix } from '../i18n-prefix.provider'; selector: '[appTranslationScope]', standalone: true }) -export class TranslationScopeDirective implements OnInit { +export class TranslationScopeDirective { readonly scopeInput = input.required({ alias: 'appTranslationScope' }); - // 4. Structural Directive Dependencies private readonly vcr = inject(ViewContainerRef); private readonly tpl = inject(TemplateRef); private readonly injector = inject(Injector); - ngOnInit(): void { - const customInjector = Injector.create({ - providers: [provideI18nPrefix(this.scopeInput())], - parent: this.injector // Link to the ElementInjector of this anchor + constructor() { + effect(() => { + this.vcr.clear(); + const customInjector = Injector.create({ + providers: [provideI18nPrefix(this.scopeInput())], + parent: this.injector + }); + this.vcr.createEmbeddedView(this.tpl, null, { injector: customInjector }); }); - - this.vcr.createEmbeddedView(this.tpl, null, { injector: customInjector }); } } From 269a5e99520946e9e073c23341a45b8d3c54ac21 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Thu, 22 Jan 2026 13:25:18 +0100 Subject: [PATCH 5/6] refactor(frontend): rebase and clean up --- .../member-detail-view.component.html | 2 +- .../member-detail-view.component.ts | 4 +-- .../overview/member-overview.component.ts | 14 ++------ .../directives/translation-scope.spec.ts | 9 ----- .../shared/directives/translation-scope.ts | 34 ------------------- 5 files changed, 5 insertions(+), 58 deletions(-) delete mode 100644 frontend/src/app/shared/directives/translation-scope.spec.ts delete mode 100644 frontend/src/app/shared/directives/translation-scope.ts diff --git a/frontend/src/app/features/member/detail-view/member-detail-view.component.html b/frontend/src/app/features/member/detail-view/member-detail-view.component.html index 97fd5c506..0410b0232 100644 --- a/frontend/src/app/features/member/detail-view/member-detail-view.component.html +++ b/frontend/src/app/features/member/detail-view/member-detail-view.component.html @@ -42,7 +42,7 @@

{{ 'ORGANISATION_UNIT' | scopedTranslation }}:

- {{m.organisationUnit?.name | nullFallback}} + {{m.organisationUnitName | nullFallback}}

diff --git a/frontend/src/app/features/member/detail-view/member-detail-view.component.ts b/frontend/src/app/features/member/detail-view/member-detail-view.component.ts index 17e3e6501..7b2b1ca3c 100644 --- a/frontend/src/app/features/member/detail-view/member-detail-view.component.ts +++ b/frontend/src/app/features/member/detail-view/member-detail-view.component.ts @@ -2,7 +2,6 @@ import { Component, inject, OnInit, signal, WritableSignal } from '@angular/core import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { MemberService } from '../member.service'; -import { MemberModel } from '../member.model'; import { ScopedTranslationPipe } from '../../../shared/pipes/scoped-translation-pipe'; import { CrudButtonComponent } from '../../../shared/crud-button/crud-button.component'; import { GenericCvContentComponent } from './generic-cv-content/generic-cv-content.component'; @@ -20,7 +19,6 @@ import { } from './cv/member-detail-cv-table-definition'; import { MemberOverviewModel } from '../member-overview.model'; import { NullFallbackPipe } from '../../../shared/pipes/null-fallback.pipe'; -import { TranslationScopeDirective } from '../../../shared/directives/translation-scope'; @Component({ selector: 'app-member-detail-view', @@ -30,7 +28,7 @@ import { TranslationScopeDirective } from '../../../shared/directives/translatio ScopedTranslationPipe, CrudButtonComponent, NullFallbackPipe, - TranslationScope + TranslationScopeDirective, CrudButtonComponent, GenericCvContentComponent, MatTabGroup, diff --git a/frontend/src/app/features/member/overview/member-overview.component.ts b/frontend/src/app/features/member/overview/member-overview.component.ts index 648f98b72..efe40cbee 100644 --- a/frontend/src/app/features/member/overview/member-overview.component.ts +++ b/frontend/src/app/features/member/overview/member-overview.component.ts @@ -6,7 +6,7 @@ import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSort, MatSortModule } from '@angular/material/sort'; -import { DatePipe, JsonPipe } from '@angular/common'; +import { DatePipe } from '@angular/common'; import { MatIcon } from '@angular/material/icon'; import { MatButton } from '@angular/material/button'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; @@ -16,14 +16,13 @@ import { debounceTime } from 'rxjs/operators'; import sortingDataAccessor from '../../../shared/utils/sortingDataAccessor'; import { ScopedTranslationPipe } from '../../../shared/pipes/scoped-translation-pipe'; import { CrudButtonComponent } from '../../../shared/crud-button/crud-button.component'; -import { TranslationScopeDirective } from '../../../shared/directives/translation-scope'; import { NullFallbackPipe } from '../../../shared/pipes/null-fallback.pipe'; +import { TranslationScopeDirective } from '../../../shared/translation-scope/translation-scope.directive'; @Component({ selector: 'app-member-overview', standalone: true, - providers: [DatePipe], imports: [ ReactiveFormsModule, MatFormFieldModule, @@ -37,8 +36,7 @@ import { NullFallbackPipe } from '../../../shared/pipes/null-fallback.pipe'; ScopedTranslationPipe, CrudButtonComponent, TranslationScopeDirective, - NullFallbackPipe, - JsonPipe + NullFallbackPipe ], templateUrl: './member-overview.component.html', styleUrl: './member-overview.component.scss' @@ -46,8 +44,6 @@ import { NullFallbackPipe } from '../../../shared/pipes/null-fallback.pipe'; export class MemberOverviewComponent implements OnInit { private readonly service: MemberService = inject(MemberService); - private readonly datePipe: DatePipe = inject(DatePipe); - private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); @@ -175,8 +171,4 @@ export class MemberOverviewComponent implements OnInit { } return all; } - - handleAddMemberClick(): void { - this.router.navigate(['/member/add']); - } } diff --git a/frontend/src/app/shared/directives/translation-scope.spec.ts b/frontend/src/app/shared/directives/translation-scope.spec.ts deleted file mode 100644 index bbda989c0..000000000 --- a/frontend/src/app/shared/directives/translation-scope.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TranslationScopeDirective } from './translation-scope'; - -describe('TranslationScope', () => { - it('should create an instance', () => { - const directive = new TranslationScopeDirective(); - expect(directive) - .toBeTruthy(); - }); -}); diff --git a/frontend/src/app/shared/directives/translation-scope.ts b/frontend/src/app/shared/directives/translation-scope.ts deleted file mode 100644 index e536d93f6..000000000 --- a/frontend/src/app/shared/directives/translation-scope.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - Directive, - input, - inject, - TemplateRef, - ViewContainerRef, - Injector, effect -} from '@angular/core'; -import { provideI18nPrefix } from '../i18n-prefix.provider'; - -@Directive({ - selector: '[appTranslationScope]', - standalone: true -}) -export class TranslationScopeDirective { - readonly scopeInput = input.required({ alias: 'appTranslationScope' }); - - private readonly vcr = inject(ViewContainerRef); - - private readonly tpl = inject(TemplateRef); - - private readonly injector = inject(Injector); - - constructor() { - effect(() => { - this.vcr.clear(); - const customInjector = Injector.create({ - providers: [provideI18nPrefix(this.scopeInput())], - parent: this.injector - }); - this.vcr.createEmbeddedView(this.tpl, null, { injector: customInjector }); - }); - } -} From e8e3f9395a8cc596db9be067da9f114f5b8e2f27 Mon Sep 17 00:00:00 2001 From: Yanick Minder Date: Mon, 26 Jan 2026 10:09:34 +0100 Subject: [PATCH 6/6] style(frontend): remove useless priority from logo --- frontend/src/app/app.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 72f2840f6..c4a4b12fa 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -5,7 +5,6 @@ {{'PCTS_LOGO_ALT' | translate}}