diff --git a/lib/core/src/lib/form/components/widgets/amount/amount.widget.html b/lib/core/src/lib/form/components/widgets/amount/amount.widget.html index 12aba25f05..6727eb3d12 100644 --- a/lib/core/src/lib/form/components/widgets/amount/amount.widget.html +++ b/lib/core/src/lib/form/components/widgets/amount/amount.widget.html @@ -12,7 +12,9 @@
@if ( (field.name || field?.required) && !field.leftLabels) { {{field.name | translate }} } - {{ currency }}  + @if(!enableDisplayBasedOnLocale) { + {{ currency }}  + } + (focus)="amountWidgetOnFocus()" + (blur)="amountWidgetOnBlur()" + />
diff --git a/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts b/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts index bc416e9e63..38a224d9d6 100644 --- a/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts +++ b/lib/core/src/lib/form/components/widgets/amount/amount.widget.spec.ts @@ -23,6 +23,10 @@ import { FormModel } from '../core/form.model'; import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { UnitTestingUtils } from '../../../../testing/unit-testing-utils'; +import { of } from 'rxjs'; +import { FormService } from '../../../services/form.service'; +import { FormFieldEvent } from '../../../events/form-field.event'; +import { TranslationService } from '../../../../translation/translation.service'; describe('AmountWidgetComponent', () => { let loader: HarnessLoader; @@ -32,8 +36,10 @@ describe('AmountWidgetComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [AmountWidgetComponent] + imports: [AmountWidgetComponent], + providers: [{ provide: TranslationService, useValue: { getLocale: () => 'en-US' } }] }); + fixture = TestBed.createComponent(AmountWidgetComponent); widget = fixture.componentInstance; loader = TestbedHarnessEnvironment.loader(fixture); @@ -94,6 +100,109 @@ describe('AmountWidgetComponent', () => { expect(widget.placeholder).toBe('1234'); }); + it('it should return locale based on browser', () => { + const expectedLanguage = 'en-US'; + widget.enableDisplayBasedOnLocale = true; + widget.field = new FormFieldModel(null, { id: 1, name: 'test', value: 25, currency: 'USD' }); + widget.setInitialValues(); + fixture.detectChanges(); + + expect(widget.locale).toBe(expectedLanguage); + }); + + it('should set initial values when enableDisplayBasedOnLocale is enabled', () => { + widget.field = new FormFieldModel(null, { id: 1, name: 'test', value: 25, currency: 'USD' }); + widget.enableDisplayBasedOnLocale = true; + widget.currency = 'USD'; + widget.setInitialValues(); + + expect(widget.amountWidgetValue).toBe('$25'); + expect(widget.decimalProperty).toBe('1.0-0'); + expect(widget.valueAsNumber).toBe(25); + }); + + it('should set initial values with correct currency', () => { + widget.field = new FormFieldModel(null, { id: 2, name: 'test', value: 25, currency: 'GBP' }); + widget.enableDisplayBasedOnLocale = true; + widget.currency = 'GBP'; + widget.setInitialValues(); + + expect(widget.amountWidgetValue).toBe('£25'); + expect(widget.decimalProperty).toBe('1.0-0'); + }); + + it('should set initial values with correct currency icon', () => { + widget.field = new FormFieldModel(null, { id: 2, name: 'test', value: 25, currency: '¥' }); + widget.enableDisplayBasedOnLocale = true; + widget.currency = '¥'; + widget.setInitialValues(); + + expect(widget.amountWidgetValue).toBe('¥25'); + expect(widget.decimalProperty).toBe('1.0-0'); + }); + + it('should set initial values without currency', () => { + widget.field = new FormFieldModel(null, { id: 3, name: 'test', value: 25, currency: '' }); + widget.enableDisplayBasedOnLocale = true; + widget.currency = ''; + widget.currencyDisplay = ''; + widget.setInitialValues(); + + expect(widget.amountWidgetValue).toBe('25'); + expect(widget.decimalProperty).toBe('1.0-0'); + }); + + it('should set initial values when enableDisplayBasedOnLocale is disabled', () => { + widget.field = new FormFieldModel(null, { id: 4, name: 'test', value: 25, enableFractions: false, className: '' }); + widget.enableDisplayBasedOnLocale = false; + widget.setInitialValues(); + + expect(widget.amountWidgetValue.toString()).toBe('25'); + }); + + it('should transform value from number to string', () => { + widget.enableDisplayBasedOnLocale = true; + widget.valueAsNumber = 123456; + widget.amountWidgetOnFocus(); + expect(widget.amountWidgetValue).toBe('123456'); + + widget.valueAsNumber = 123456.11; + widget.amountWidgetOnFocus(); + expect(widget.amountWidgetValue).toBe('123456.11'); + + widget.valueAsNumber = 0; + widget.amountWidgetOnFocus(); + expect(widget.amountWidgetValue).toBe('0'); + + widget.valueAsNumber = undefined; + widget.amountWidgetOnFocus(); + expect(widget.amountWidgetValue).toBe(null); + }); + + it('should update field.value on change', () => { + widget.field = new FormFieldModel(null, { id: 5, name: 'test', value: 25 }); + const mockValue = '1234.12'; + widget.amountWidgetValue = mockValue; + widget.onFieldChangedAmountWidget(); + + expect(widget.field.value).toBe(mockValue); + }); + + it('should transform values on blur', () => { + widget.enableDisplayBasedOnLocale = true; + widget.amountWidgetValue = '1234.56'; + widget.amountWidgetOnBlur(); + + expect(widget.valueAsNumber).toBe(1234.56); + expect(widget.amountWidgetValue).toBe('$1,234.56'); + + widget.amountWidgetValue = ''; + widget.amountWidgetOnBlur(); + + expect(widget.valueAsNumber).toBe(null); + expect(widget.amountWidgetValue).toBe(null); + }); + describe('when tooltip is set', () => { beforeEach(() => { widget.field = new FormFieldModel(new FormModel({ taskId: '' }), { @@ -145,6 +254,7 @@ describe('AmountWidgetComponent - rendering', () => { let widget: AmountWidgetComponent; let fixture: ComponentFixture; let testingUtils: UnitTestingUtils; + let formService: FormService; beforeEach(() => { TestBed.configureTestingModule({ @@ -154,6 +264,7 @@ describe('AmountWidgetComponent - rendering', () => { widget = fixture.componentInstance; loader = TestbedHarnessEnvironment.loader(fixture); testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + formService = TestBed.inject(FormService); }); it('[C289915] - Should be able to display different currency icons', async () => { @@ -345,6 +456,311 @@ describe('AmountWidgetComponent - rendering', () => { expect(asterisk.textContent).toEqual('*'); }); }); + + describe('Test widget with ADF_AMOUNT_SETTINGS as observable', () => { + beforeEach(() => { + TestBed.resetTestingModule(); + }); + + describe('set module for enableDisplayBasedOnLocale = true', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'TestAmount1', + name: 'Test Amount', + type: 'amount', + currency: 'USD', + enableFractions: true, + value: '1234.55' + }); + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [AmountWidgetComponent], + providers: [ + { provide: ADF_AMOUNT_SETTINGS, useValue: of({ enableDisplayBasedOnLocale: true }) }, + { provide: TranslationService, useValue: { getLocale: () => 'en-US' } } + ] + }); + fixture = TestBed.createComponent(AmountWidgetComponent); + widget = fixture.componentInstance; + fixture.componentRef.setInput('field', mockField); + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + fixture.detectChanges(); + }); + + it('should set enableDisplayBasedOnLocale to true', () => { + expect(widget.enableDisplayBasedOnLocale).toBeTrue(); + expect(widget.decimalProperty).toBe('1.2-2'); + expect(widget.locale).toBe('en-US'); + expect(widget.valueAsNumber).toBe('1234.55'); + expect(widget.amountWidgetValue).toBe('$1,234.55'); + }); + + it('should not display prefix with currency when enableDisplayBasedOnLocale = true', async () => { + const field = await testingUtils.getMatFormField(); + expect(await field.getPrefixText()).toBe(''); + }); + }); + }); + + describe('AmountWidgetComponent - subscribeToFieldChanges', () => { + it('should subscribe to formFieldValueChanged events for the specific field', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'amount-1', + name: 'Test Amount', + type: 'amount', + value: '100' + }); + const subscriptionSpy = spyOn(formService.formFieldValueChanged, 'subscribe').and.callThrough(); + widget.field = mockField; + widget.subscribeToFieldChanges(); + + expect(subscriptionSpy).toHaveBeenCalled(); + }); + + it('should update value when field value changes and input is not in focus with enableDisplayBasedOnLocale enabled', async () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'amount-1', + name: 'Test Amount', + type: 'amount', + value: '100', + currency: 'USD' + }); + widget.field = mockField; + widget.enableDisplayBasedOnLocale = true; + widget.isInputInFocus = false; + widget.currency = 'USD'; + widget.decimalProperty = '1.0-0'; + widget.ngOnInit(); + + const updateValueSpy = spyOn(widget, 'updateValue').and.callThrough(); + formService.formFieldValueChanged.next(new FormFieldEvent(mockField.form, { ...mockField, value: '200' } as FormFieldModel)); + await fixture.whenStable(); + + expect(updateValueSpy).toHaveBeenCalledWith('200'); + expect(widget.amountWidgetValue).toBe('$200'); + }); + + it('should update amountWidgetValue when field value changes and input is not in focus with enableDisplayBasedOnLocale disabled', () => { + const updateValueSpy = spyOn(widget, 'updateValue').and.callThrough(); + const mockField = new FormFieldModel(new FormModel(), { + id: 'amount-1', + name: 'Test Amount', + type: 'amount', + value: '100' + }); + widget.field = mockField; + widget.enableDisplayBasedOnLocale = false; + widget.isInputInFocus = false; + widget.ngOnInit(); + + formService.formFieldValueChanged.next(new FormFieldEvent(mockField.form, { ...mockField, value: '200' } as FormFieldModel)); + + expect(updateValueSpy).not.toHaveBeenCalled(); + expect(widget.amountWidgetValue).toBe('200'); + }); + + it('should not update value with formService.formFieldValueChanged when input is in focus', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'amount-1', + name: 'Test Amount', + type: 'amount', + value: '100' + }); + widget.field = mockField; + widget.enableDisplayBasedOnLocale = true; + widget.isInputInFocus = true; + widget.amountWidgetValue = '100'; + widget.ngOnInit(); + const updateValueSpy = spyOn(widget, 'updateValue'); + formService.formFieldValueChanged.next(new FormFieldEvent(mockField.form, { ...mockField, value: '200' } as FormFieldModel)); + + expect(updateValueSpy).not.toHaveBeenCalled(); + expect(widget.amountWidgetValue).toBe('100'); + }); + + it('should not react to events from different fields', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'amount-1', + name: 'Test Amount', + type: 'amount', + value: '100', + enableFractions: false + }); + const otherField = new FormFieldModel(new FormModel(), { + id: 'amount-2', + name: 'Other Amount', + type: 'amount', + value: '200' + }); + widget.field = mockField; + widget.currency = 'USD'; + widget.enableDisplayBasedOnLocale = true; + widget.isInputInFocus = false; + widget.amountWidgetValue = '100'; + widget.ngOnInit(); + + const updateValueSpy = spyOn(widget, 'updateValue').and.callThrough(); + + formService.formFieldValueChanged.next(new FormFieldEvent(otherField.form, otherField)); + + expect(updateValueSpy).not.toHaveBeenCalled(); + }); + + it('should use field.value when updating without enableDisplayBasedOnLocale and input not in focus', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'amount-1', + name: 'Test Amount', + type: 'amount', + value: '100' + }); + widget.field = mockField; + widget.enableDisplayBasedOnLocale = false; + widget.isInputInFocus = false; + widget.ngOnInit(); + + mockField.value = '300'; + formService.formFieldValueChanged.next(new FormFieldEvent(mockField.form, mockField)); + + expect(widget.amountWidgetValue).toBe('300'); + }); + }); + + describe('Test widget with different setting for enableDisplayBasedOnLocale', () => { + beforeEach(() => { + TestBed.resetTestingModule(); + }); + + describe('set module for enableDisplayBasedOnLocale = true', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'TestAmount1', + name: 'Test Amount', + type: 'amount', + currency: 'USD', + enableFractions: true, + value: '1234.55' + }); + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [AmountWidgetComponent], + providers: [{ provide: ADF_AMOUNT_SETTINGS, useValue: { enableDisplayBasedOnLocale: true } }] + }); + fixture = TestBed.createComponent(AmountWidgetComponent); + widget = fixture.componentInstance; + + fixture.componentRef.setInput('field', mockField); + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + fixture.detectChanges(); + }); + + it('should not display prefix with currency when enableDisplayBasedOnLocale = true', async () => { + const field = await testingUtils.getMatFormField(); + expect(await field.getPrefixText()).toBe(''); + }); + + it('should call method on focus and change input value', async () => { + const focusSpy = spyOn(widget, 'amountWidgetOnFocus').and.callThrough(); + fixture.detectChanges(); + + const field = await testingUtils.getMatInput(); + const fieldValueBeforeFocus = await field.getValue(); + await field.focus(); + const fieldValue = await field.getValue(); + + expect(field).toBeDefined(); + expect(widget.field.value).toBe('1234.55'); + expect(fieldValueBeforeFocus).toBe('$1,234.55'); + expect(focusSpy).toHaveBeenCalled(); + expect(fieldValue).toBe('1234.55'); + }); + + it('should transform value on blur', async () => { + const newValue = '456789'; + const blurSpy = spyOn(widget, 'amountWidgetOnBlur').and.callThrough(); + fixture.detectChanges(); + + const field = await testingUtils.getMatInput(); + const fieldValueBeforeBlur = await field.getValue(); + await field.setValue(newValue); + await field.blur(); + const fieldValue = await field.getValue(); + + expect(field).toBeDefined(); + expect(widget.field.value).toBe(newValue); + expect(fieldValueBeforeBlur).toBe('$1,234.55'); + expect(blurSpy).toHaveBeenCalled(); + expect(widget.valueAsNumber).toBe(parseFloat(newValue)); + expect(widget.amountWidgetValue).toBe('$456,789.00'); + expect(fieldValue).toBe('$456,789.00'); + }); + }); + describe('set module for enableDisplayBasedOnLocale = false', () => { + const mockField = new FormFieldModel(new FormModel(), { + id: 'TestAmount1', + name: 'Test Amount', + type: 'amount', + currency: 'USD', + enableFractions: true, + value: '1234.55' + }); + beforeEach(async () => { + TestBed.configureTestingModule({ + imports: [AmountWidgetComponent], + providers: [{ provide: ADF_AMOUNT_SETTINGS, useValue: { enableDisplayBasedOnLocale: false } }] + }); + fixture = TestBed.createComponent(AmountWidgetComponent); + widget = fixture.componentInstance; + + fixture.componentRef.setInput('field', mockField); + loader = TestbedHarnessEnvironment.loader(fixture); + testingUtils = new UnitTestingUtils(fixture.debugElement, loader); + fixture.detectChanges(); + }); + + it('should display prefix with currency when enableDisplayBasedOnLocale = true', async () => { + const field = await testingUtils.getMatFormField(); + expect(await field.getPrefixText()).toBe('USD'); + }); + + it('should call method on focus and not change input value', async () => { + const focusSpy = spyOn(widget, 'amountWidgetOnFocus').and.callThrough(); + fixture.detectChanges(); + + const field = await testingUtils.getMatInput(); + const fieldValueBeforeFocus = await field.getValue(); + await field.focus(); + const fieldValue = await field.getValue(); + + expect(field).toBeDefined(); + expect(widget.field.value).toBe('1234.55'); + expect(widget.valueAsNumber).toBeUndefined(); + expect(fieldValueBeforeFocus).toBe('1234.55'); + expect(focusSpy).toHaveBeenCalled(); + expect(fieldValue).toBe('1234.55'); + }); + + it('should call method on blur and not change input value', async () => { + const newValue = '456789'; + const blurSpy = spyOn(widget, 'amountWidgetOnBlur').and.callThrough(); + fixture.detectChanges(); + + const field = await testingUtils.getMatInput(); + const fieldValueBeforeBlur = await field.getValue(); + await field.setValue(newValue); + await field.blur(); + const fieldValue = await field.getValue(); + + expect(field).toBeDefined(); + expect(widget.field.value).toBe(newValue); + expect(widget.valueAsNumber).toBeUndefined(); + expect(fieldValueBeforeBlur).toBe('1234.55'); + expect(blurSpy).toHaveBeenCalled(); + expect(widget.valueAsNumber).toBeUndefined(); + expect(widget.amountWidgetValue).toBe('456789'); + expect(fieldValue).toBe('456789'); + }); + }); + }); }); describe('AmountWidgetComponent settings', () => { diff --git a/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts b/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts index 1a8e5bb014..db7929b8dc 100644 --- a/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts +++ b/lib/core/src/lib/form/components/widgets/amount/amount.widget.ts @@ -17,8 +17,8 @@ /* eslint-disable @angular-eslint/component-selector */ -import { NgIf } from '@angular/common'; -import { Component, OnInit, ViewEncapsulation, InjectionToken, Inject, Optional } from '@angular/core'; +import { CurrencyPipe, NgIf } from '@angular/common'; +import { Component, OnInit, ViewEncapsulation, InjectionToken, Inject, Optional, inject, DestroyRef } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; @@ -26,12 +26,17 @@ import { TranslatePipe } from '@ngx-translate/core'; import { FormService } from '../../../services/form.service'; import { ErrorWidgetComponent } from '../error/error.component'; import { WidgetComponent } from '../widget.component'; +import { filter, isObservable, Observable } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormFieldEvent } from '../../../events/form-field.event'; +import { TranslationService } from '../../../../translation/translation.service'; export interface AmountWidgetSettings { showReadonlyPlaceholder: boolean; + enableDisplayBasedOnLocale: boolean; } -export const ADF_AMOUNT_SETTINGS = new InjectionToken('adf-amount-settings'); +export const ADF_AMOUNT_SETTINGS = new InjectionToken | AmountWidgetSettings>('adf-amount-settings'); @Component({ selector: 'amount-widget', @@ -49,13 +54,25 @@ export const ADF_AMOUNT_SETTINGS = new InjectionToken('adf '(select)': 'event($event)' }, imports: [MatFormFieldModule, MatInputModule, FormsModule, ErrorWidgetComponent, TranslatePipe, NgIf], + providers: [CurrencyPipe], encapsulation: ViewEncapsulation.None }) export class AmountWidgetComponent extends WidgetComponent implements OnInit { static DEFAULT_CURRENCY: string = '$'; private showPlaceholder = true; + private readonly destroyRef = inject(DestroyRef); + amountWidgetValue: string; currency: string = AmountWidgetComponent.DEFAULT_CURRENCY; + currencyDisplay: string | boolean = 'symbol'; + decimalProperty: string; + enableDisplayBasedOnLocale: boolean; + isInputInFocus = false; + locale: string; + notShowDecimalDigits = '1.0-0'; + showDecimalDigits = '1.2-2'; + showReadonlyPlaceholder: boolean; + valueAsNumber: number; get placeholder(): string { return this.showPlaceholder ? this.field.placeholder : ''; @@ -63,22 +80,103 @@ export class AmountWidgetComponent extends WidgetComponent implements OnInit { constructor( public formService: FormService, - @Inject(ADF_AMOUNT_SETTINGS) - @Optional() - private settings: AmountWidgetSettings + @Optional() @Inject(ADF_AMOUNT_SETTINGS) settings: Observable | AmountWidgetSettings, + private currencyPipe: CurrencyPipe, + private translationService: TranslationService ) { super(formService); + if (isObservable(settings)) { + settings.pipe(takeUntilDestroyed()).subscribe((data: AmountWidgetSettings) => { + this.updateSettingsBasedProperties(data); + }); + } else { + this.updateSettingsBasedProperties(settings); + } } ngOnInit() { if (this.field) { if (this.field.currency) { this.currency = this.field.currency; + } else { + if (this.enableDisplayBasedOnLocale) { + this.currency = ''; + this.currencyDisplay = ''; + } } if (this.field.readOnly) { - this.showPlaceholder = this.settings?.showReadonlyPlaceholder; + this.showPlaceholder = this.showReadonlyPlaceholder; } + this.subscribeToFieldChanges(); + this.setInitialValues(); } } + + amountWidgetOnBlur(): void { + this.isInputInFocus = false; + if (this.enableDisplayBasedOnLocale) { + if (this.amountWidgetValue) { + this.valueAsNumber = parseFloat(this.amountWidgetValue); + this.amountWidgetValue = this.currencyPipe.transform( + this.amountWidgetValue, + this.currency, + this.currencyDisplay, + this.decimalProperty + ); + } else { + this.valueAsNumber = null; + this.amountWidgetValue = null; + } + } + this.markAsTouched(); + } + + amountWidgetOnFocus(): void { + this.isInputInFocus = true; + if (this.enableDisplayBasedOnLocale) { + const hasValue = this.valueAsNumber === 0 || this.valueAsNumber; + this.amountWidgetValue = hasValue ? this.valueAsNumber.toString() : null; + } + } + + onFieldChangedAmountWidget(): void { + this.field.value = this.amountWidgetValue; + super.onFieldChanged(this.field); + } + + setInitialValues(): void { + if (this.enableDisplayBasedOnLocale) { + this.decimalProperty = this.field.enableFractions ? this.showDecimalDigits : this.notShowDecimalDigits; + this.locale = this.translationService.getLocale(); + this.updateValue(this.field.value); + } else { + this.amountWidgetValue = this.field.value; + } + } + + subscribeToFieldChanges(): void { + this.formService.formFieldValueChanged + .pipe( + filter((ev: FormFieldEvent) => ev.field.id === this.field.id), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((ev: FormFieldEvent) => { + if (!this.isInputInFocus && this.enableDisplayBasedOnLocale) { + this.updateValue(ev.field.value); + } else if (!this.isInputInFocus) { + this.amountWidgetValue = ev.field.value; + } + }); + } + + updateValue(value: any): void { + this.valueAsNumber = value; + this.amountWidgetValue = this.currencyPipe.transform(value, this.currency, this.currencyDisplay, this.decimalProperty, this.locale); + } + + updateSettingsBasedProperties(data: AmountWidgetSettings): void { + this.enableDisplayBasedOnLocale = data?.enableDisplayBasedOnLocale ?? false; + this.showReadonlyPlaceholder = data?.showReadonlyPlaceholder; + } } diff --git a/lib/core/src/lib/mock/translation.service.mock.ts b/lib/core/src/lib/mock/translation.service.mock.ts index 5f3c0f219e..011d898bc3 100644 --- a/lib/core/src/lib/mock/translation.service.mock.ts +++ b/lib/core/src/lib/mock/translation.service.mock.ts @@ -47,6 +47,8 @@ export class TranslationMock implements TranslationService { return of(key); } + getLocale(): any {} + instant(key: string | Array): string | any { return key; } diff --git a/lib/core/src/lib/testing/noop-translate.module.ts b/lib/core/src/lib/testing/noop-translate.module.ts index bd906c7bd8..0e5f7a0196 100644 --- a/lib/core/src/lib/testing/noop-translate.module.ts +++ b/lib/core/src/lib/testing/noop-translate.module.ts @@ -41,6 +41,8 @@ export class NoopTranslationService implements TranslationService { return of(key); } + getLocale(): any {} + instant(key: string | Array): string | any { return key; } diff --git a/lib/core/src/lib/translation/translation.service.spec.ts b/lib/core/src/lib/translation/translation.service.spec.ts index 587d8a9c1b..f89fd22e9a 100644 --- a/lib/core/src/lib/translation/translation.service.spec.ts +++ b/lib/core/src/lib/translation/translation.service.spec.ts @@ -83,4 +83,37 @@ describe('TranslationService', () => { expect(translationService.instant('')).toEqual(''); expect(translationService.instant(undefined)).toEqual(''); }); + + describe('getLocale', () => { + it('returns the first language from navigator.languages when available', () => { + translationService.userLang = 'it'; + const returnedLanguages: string[] = ['fr-FR', 'en-US']; + const mockLanguages = spyOnProperty(window, 'navigator').and.returnValue({ + language: 'en-GB', + languages: returnedLanguages + } as any); + + expect(translationService.getLocale()).toBe('fr-FR'); + expect(mockLanguages).toHaveBeenCalled(); + }); + + it('falls back to navigator.language when languages list is absent', () => { + translationService.userLang = 'fr'; + const mockLanguages = spyOnProperty(window, 'navigator').and.returnValue({ + language: 'de-DE', + languages: [] + } as any); + + expect(translationService.getLocale()).toBe('de-DE'); + expect(mockLanguages).toHaveBeenCalled(); + }); + + it('falls back to the provided default locale when navigator is unavailable', () => { + translationService.userLang = 'en'; + const mockLanguages = spyOnProperty(window, 'navigator').and.returnValue(undefined); + + expect(translationService.getLocale()).toBe('en'); + expect(mockLanguages).toHaveBeenCalled(); + }); + }); }); diff --git a/lib/core/src/lib/translation/translation.service.ts b/lib/core/src/lib/translation/translation.service.ts index 4713f3be4a..b2f2b88798 100644 --- a/lib/core/src/lib/translation/translation.service.ts +++ b/lib/core/src/lib/translation/translation.service.ts @@ -151,6 +151,22 @@ export class TranslationService { return this.translate.get(key, interpolateParams); } + /** + * Determines the preferred locale for the current user. + * + * @returns Locale identifier resolved from the browser or the default translation locale + */ + getLocale(): string { + const defaultLocale = this.userLang || this.defaultLang; + if (typeof window?.navigator === 'undefined') { + return defaultLocale; + } + const wn = window.navigator as Navigator; + let lang = wn.languages ? wn.languages[0] : defaultLocale; + lang = lang || wn.language; + return lang; + } + /** * Directly returns the translation for the supplied key. * diff --git a/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts b/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts index dacb861699..f08dd3ca54 100644 --- a/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/form/services/form-cloud.service.ts @@ -15,8 +15,8 @@ * limitations under the License. */ -import { Inject, Injectable, InjectionToken, Optional } from '@angular/core'; -import { FormValues, FormModel, FormFieldOption, FormFieldValidator } from '@alfresco/adf-core'; +import { inject, Inject, Injectable, InjectionToken, Optional } from '@angular/core'; +import { FormValues, FormModel, FormFieldOption, FormFieldValidator, FormService } from '@alfresco/adf-core'; import { Observable, from, EMPTY } from 'rxjs'; import { expand, map, reduce, switchMap } from 'rxjs/operators'; import { TaskDetailsCloudModel } from '../../task/models/task-details-cloud.model'; @@ -35,6 +35,7 @@ export const FORM_CLOUD_SERVICE_FIELD_VALIDATORS_TOKEN = new InjectionToken