diff --git a/projects/igniteui-angular/action-strip/src/action-strip/action-strip.component.spec.ts b/projects/igniteui-angular/action-strip/src/action-strip/action-strip.component.spec.ts index e7fb05f77c4..52db4b966c7 100644 --- a/projects/igniteui-angular/action-strip/src/action-strip/action-strip.component.spec.ts +++ b/projects/igniteui-angular/action-strip/src/action-strip/action-strip.component.spec.ts @@ -4,6 +4,7 @@ import { TestBed, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IgxIconComponent } from 'igniteui-angular/icon'; +import { ActionStripResourceStringsEN, changei18n } from 'igniteui-angular/core'; import { wait } from '../../../test-utils/ui-interactions.spec'; const ACTION_STRIP_CONTAINER_CSS = 'igx-action-strip__actions'; @@ -153,6 +154,41 @@ describe('igxActionStrip', () => { expect(dropDownList.nativeElement.getAttribute('aria-hidden')).toBe('true'); }); }); + + describe('Resource Strings', () => { + it('should update resource strings when global i18n changes and no custom strings are set', () => { + const fix = TestBed.createComponent(IgxActionStripMenuTestingComponent); + fix.detectChanges(); + actionStrip = fix.componentInstance.actionStrip; + + try { + changei18n({ igx_action_strip_button_more_title: 'More Options' }); + fix.detectChanges(); + + expect(actionStrip.resourceStrings.igx_action_strip_button_more_title).toBe('More Options'); + } finally { + changei18n(ActionStripResourceStringsEN); + } + }); + + it('should preserve custom resource strings when global i18n changes', () => { + const fix = TestBed.createComponent(IgxActionStripMenuTestingComponent); + fix.detectChanges(); + actionStrip = fix.componentInstance.actionStrip; + + actionStrip.resourceStrings = { igx_action_strip_button_more_title: 'Custom More' }; + fix.detectChanges(); + + try { + changei18n({ igx_action_strip_button_more_title: 'Global More' }); + fix.detectChanges(); + + expect(actionStrip.resourceStrings.igx_action_strip_button_more_title).toBe('Custom More'); + } finally { + changei18n(ActionStripResourceStringsEN); + } + }); + }); }); @Component({ diff --git a/projects/igniteui-angular/action-strip/src/action-strip/action-strip.component.ts b/projects/igniteui-angular/action-strip/src/action-strip/action-strip.component.ts index 5d3f8c2ea2d..389721eac3e 100644 --- a/projects/igniteui-angular/action-strip/src/action-strip/action-strip.component.ts +++ b/projects/igniteui-angular/action-strip/src/action-strip/action-strip.component.ts @@ -177,11 +177,12 @@ export class IgxActionStripComponent implements IgxActionStripToken, AfterViewIn */ @Input() public set resourceStrings(value: IActionStripResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } public get resourceStrings(): IActionStripResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -233,12 +234,14 @@ export class IgxActionStripComponent implements IgxActionStripToken, AfterViewIn private _hidden = signal(true); private _context = signal(undefined); private _resourceStrings: IActionStripResourceStrings = null!; + private _customResourceStrings: IActionStripResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(ActionStripResourceStringsEN); private _originalParent!: HTMLElement; constructor() { onResourceChangeHandle(this._destroyRef, () => { this._defaultResourceStrings = getCurrentResourceStrings(ActionStripResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/banner/src/banner/banner.component.spec.ts b/projects/igniteui-angular/banner/src/banner/banner.component.spec.ts index 0251c95f420..b441f07ab78 100644 --- a/projects/igniteui-angular/banner/src/banner/banner.component.spec.ts +++ b/projects/igniteui-angular/banner/src/banner/banner.component.spec.ts @@ -3,6 +3,7 @@ import { TestBed, ComponentFixture, tick, fakeAsync, waitForAsync } from '@angul import { By } from '@angular/platform-browser'; import { IgxBannerComponent } from './banner.component'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { BannerResourceStringsEN, changei18n } from 'igniteui-angular/core'; import { IgxIconComponent } from 'igniteui-angular/icon'; import { IgxBannerActionsDirective } from './banner.directives'; import { IgxCardComponent, IgxCardContentDirective, IgxCardHeaderComponent } from 'igniteui-angular/card'; @@ -525,6 +526,52 @@ describe('igxBanner', () => { })); }); + describe('Resource Strings', () => { + it('should return full resource strings when partial resourceStrings are set', () => { + const fix = TestBed.createComponent(SimpleBannerEventsComponent); + fix.detectChanges(); + const banner = fix.componentInstance.banner; + + banner.resourceStrings = { igx_banner_button_dismiss: 'Close' }; + fix.detectChanges(); + + expect(banner.resourceStrings.igx_banner_button_dismiss).toBe('Close'); + }); + + it('should update resource strings when global i18n changes and no custom strings are set', () => { + const fix = TestBed.createComponent(SimpleBannerEventsComponent); + fix.detectChanges(); + const banner = fix.componentInstance.banner; + + try { + changei18n({ igx_banner_button_dismiss: 'Dismiss Global' }); + fix.detectChanges(); + + expect(banner.resourceStrings.igx_banner_button_dismiss).toBe('Dismiss Global'); + } finally { + changei18n(BannerResourceStringsEN); + } + }); + + it('should preserve custom resource strings when global i18n changes', () => { + const fix = TestBed.createComponent(SimpleBannerEventsComponent); + fix.detectChanges(); + const banner = fix.componentInstance.banner; + + banner.resourceStrings = { igx_banner_button_dismiss: 'Custom Dismiss' }; + fix.detectChanges(); + + try { + changei18n({ igx_banner_button_dismiss: 'Global Dismiss' }); + fix.detectChanges(); + + expect(banner.resourceStrings.igx_banner_button_dismiss).toBe('Custom Dismiss'); + } finally { + changei18n(BannerResourceStringsEN); + } + }); + }); + const getBaseClassElements = (fixture: ComponentFixture) => { bannerElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_BANNER)); bannerMessageElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_BANNER_MESSAGE)); diff --git a/projects/igniteui-angular/banner/src/banner/banner.component.ts b/projects/igniteui-angular/banner/src/banner/banner.component.ts index 7722fb4c6ec..a8bc2f5c628 100644 --- a/projects/igniteui-angular/banner/src/banner/banner.component.ts +++ b/projects/igniteui-angular/banner/src/banner/banner.component.ts @@ -160,11 +160,12 @@ export class IgxBannerComponent implements IToggleView { */ @Input() public set resourceStrings(value: IBannerResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } public get resourceStrings(): IBannerResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -248,11 +249,13 @@ export class IgxBannerComponent implements IToggleView { private _bannerEvent!: BannerEventArgs; private _animationSettings!: ToggleAnimationSettings; private _resourceStrings: IBannerResourceStrings = null!; + private _customResourceStrings: IBannerResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(BannerResourceStringsEN); constructor() { onResourceChangeHandle(this._destroyRef, () => { this._defaultResourceStrings = getCurrentResourceStrings(BannerResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/calendar/src/calendar/calendar-base.ts b/projects/igniteui-angular/calendar/src/calendar/calendar-base.ts index 16c7574dbd0..97fb6ee57a4 100644 --- a/projects/igniteui-angular/calendar/src/calendar/calendar-base.ts +++ b/projects/igniteui-angular/calendar/src/calendar/calendar-base.ts @@ -256,6 +256,7 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor { */ private _selection: CalendarSelection | string = CalendarSelection.SINGLE; private _resourceStrings: ICalendarResourceStrings = null!; + private _customResourceStrings: ICalendarResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(CalendarResourceStringsEN); /** @@ -283,14 +284,15 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor { */ @Input() public set resourceStrings(value: ICalendarResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * An accessor that returns the resource strings. */ public get resourceStrings(): ICalendarResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -328,7 +330,7 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor { this._locale = this.i18nFormatter.verifyLocale(value); // changing locale runtime needs to update the `weekStart` too this._localeWeekStart = this.i18nFormatter.getLocaleFirstDayOfWeek(this._locale); - this._defaultResourceStrings = getCurrentResourceStrings(CalendarResourceStringsEN, false, this._locale); + this.updateResources(this._locale); } /** @@ -1038,8 +1040,14 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor { private onResourceChange(args?: CustomEvent) { this._defaultLocale = args!.detail.newLocale; if (!this._locale) { - this._defaultResourceStrings = getCurrentResourceStrings(CalendarResourceStringsEN, false); + // Avoid unnecessary fetch of resources, since they should be already retrieved when setting custom locale. + this.updateResources(); } this._localeWeekStart = this.i18nFormatter.getLocaleFirstDayOfWeek(this.locale); } + + private updateResources(locale?: string) { + this._defaultResourceStrings = getCurrentResourceStrings(CalendarResourceStringsEN, false, locale); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; + } } diff --git a/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts b/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts index 27651b6d7ec..b44566c05ff 100644 --- a/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts +++ b/projects/igniteui-angular/carousel/src/carousel/carousel.component.spec.ts @@ -1,5 +1,5 @@ import { Component, ViewChild, TemplateRef, ChangeDetectionStrategy, ElementRef, provideZonelessChangeDetection, inject, ChangeDetectorRef } from '@angular/core'; -import { TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { IgxCarouselComponent, @@ -10,6 +10,7 @@ import { IgxSlideComponent } from './slide.component'; import { IgxCarouselIndicatorDirective, IgxCarouselNextButtonDirective, IgxCarouselPrevButtonDirective } from './carousel.directives'; import { CarouselIndicatorsOrientation, CarouselAnimationType } from './enums'; import { UIInteractions, wait } from 'igniteui-angular/test-utils/ui-interactions.spec'; +import { CarouselResourceStringsEN, changei18n } from 'igniteui-angular/core'; describe('Carousel', () => { let fixture; @@ -1069,6 +1070,49 @@ describe('Carousel', () => { expect(carousel.current).toEqual(2); }); }); + + describe('Resource Strings', () => { + let fix: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule, CarouselTestComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fix = TestBed.createComponent(CarouselTestComponent); + fix.detectChanges(); + carousel = fix.componentInstance.carousel; + }); + + it('should return full resource strings when partial resourceStrings are set', () => { + carousel.resourceStrings = { igx_carousel_of: 'out of' }; + fix.detectChanges(); + + expect(carousel.resourceStrings.igx_carousel_of).toBe('out of'); + expect(carousel.resourceStrings.igx_carousel_slide).toBe('slide'); + expect(carousel.resourceStrings.igx_carousel_previous_slide).toBe('previous slide'); + expect(carousel.resourceStrings.igx_carousel_next_slide).toBe('next slide'); + }); + + it('should update non-overridden resource strings when global i18n changes', () => { + carousel.resourceStrings = { igx_carousel_of: 'custom of' }; + fix.detectChanges(); + + try { + changei18n({ igx_carousel_slide: 'foto' }); + fix.detectChanges(); + + expect(carousel.resourceStrings.igx_carousel_of).toBe('custom of'); + expect(carousel.resourceStrings.igx_carousel_slide).toBe('foto'); + expect(carousel.resourceStrings.igx_carousel_previous_slide).toBe('previous slide'); + expect(carousel.resourceStrings.igx_carousel_next_slide).toBe('next slide'); + } finally { + changei18n(CarouselResourceStringsEN); + } + }); + }); }); describe('Carousel Zoneless Tests:', () => { @@ -1112,6 +1156,7 @@ describe('Carousel Zoneless Tests:', () => { }); }); + class HelperTestFunctions { public static NEXT_BUTTON_CLASS = '.igx-carousel__arrow--next'; public static PRIV_BUTTON_CLASS = '.igx-carousel__arrow--prev'; diff --git a/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts b/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts index 0cbb261d88d..864eb9c47b0 100644 --- a/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts +++ b/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts @@ -390,6 +390,7 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On protected override previousItem!: IgxSlideComponent; private _interval!: number; private _resourceStrings: ICarouselResourceStrings = null!; + private _customResourceStrings: ICarouselResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN); private lastInterval: any; private playing!: boolean; @@ -405,14 +406,15 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On */ @Input() public set resourceStrings(value: ICarouselResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * An accessor that returns the resource strings. */ public get resourceStrings(): ICarouselResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @hidden */ @@ -557,6 +559,7 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On this.differ = this.iterableDiffers.find([]).create(null!); onResourceChangeHandle(this.destroy$, () => { this._defaultResourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/chips/src/chips/chip.component.ts b/projects/igniteui-angular/chips/src/chips/chip.component.ts index a2fba20d4ce..ac32439e3dd 100644 --- a/projects/igniteui-angular/chips/src/chips/chip.component.ts +++ b/projects/igniteui-angular/chips/src/chips/chip.component.ts @@ -363,14 +363,15 @@ export class IgxChipComponent implements OnInit, OnDestroy { */ @Input() public set resourceStrings(value: IChipResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * An accessor that returns the resource strings. */ public get resourceStrings(): IChipResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -627,11 +628,13 @@ export class IgxChipComponent implements OnInit, OnDestroy { protected _movedWhileRemoving = false; protected computedStyles?: CSSStyleDeclaration; private _resourceStrings: IChipResourceStrings | null = null; + private _customResourceStrings: IChipResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(ChipResourceStringsEN); constructor() { onResourceChangeHandle(this.destroy$, () => { this._defaultResourceStrings = getCurrentResourceStrings(ChipResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/chips/src/chips/chip.spec.ts b/projects/igniteui-angular/chips/src/chips/chip.spec.ts index e36cd3f98f3..49911df1a22 100644 --- a/projects/igniteui-angular/chips/src/chips/chip.spec.ts +++ b/projects/igniteui-angular/chips/src/chips/chip.spec.ts @@ -7,7 +7,7 @@ import { IgxPrefixDirective } from '../../../input-group/src/public_api'; import { IgxLabelDirective } from '../../../input-group/src/public_api'; import { IgxSuffixDirective } from '../../../input-group/src/public_api'; import { IgxIconComponent } from 'igniteui-angular/icon'; -import { getComponentSize } from 'igniteui-angular/core'; +import { ChipResourceStringsEN, changei18n, getComponentSize } from 'igniteui-angular/core'; import { ControlsFunction } from 'igniteui-angular/test-utils/controls-functions.spec'; import { UIInteractions, wait } from 'igniteui-angular/test-utils/ui-interactions.spec'; @@ -411,6 +411,46 @@ describe('IgxChip', () => { expect(firstChipSuffixText).toEqual('suf'); }); }); + + describe('Resource Strings', () => { + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TestChipComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fix = TestBed.createComponent(TestChipComponent); + fix.detectChanges(); + }); + + it('should return full resource strings when partial resourceStrings are set', () => { + const chip = fix.componentInstance.chips.first; + + chip.resourceStrings = { igx_chip_remove: 'Custom Remove' }; + fix.detectChanges(); + + expect(chip.resourceStrings.igx_chip_remove).toBe('Custom Remove'); + expect(chip.resourceStrings.igx_chip_select).toBe('select chip'); + }); + + it('should update non-overridden resource strings when global i18n changes', () => { + const chip = fix.componentInstance.chips.first; + + chip.resourceStrings = { igx_chip_remove: 'Custom Remove' }; + fix.detectChanges(); + + try { + changei18n({ igx_chip_select: 'Global Select' }); + fix.detectChanges(); + + expect(chip.resourceStrings.igx_chip_remove).toBe('Custom Remove'); + expect(chip.resourceStrings.igx_chip_select).toBe('Global Select'); + } finally { + changei18n(ChipResourceStringsEN); + } + }); + }); }); class HelperTestFunctions { diff --git a/projects/igniteui-angular/combo/src/combo/combo.common.ts b/projects/igniteui-angular/combo/src/combo/combo.common.ts index 8f0def50c5c..0d61396c6af 100644 --- a/projects/igniteui-angular/combo/src/combo/combo.common.ts +++ b/projects/igniteui-angular/combo/src/combo/combo.common.ts @@ -496,10 +496,11 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh */ @Input() public get resourceStrings(): IComboResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } public set resourceStrings(value: IComboResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** @@ -978,6 +979,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh protected _displayKey!: string; protected _remoteSelection = {}; protected _resourceStrings: IComboResourceStrings = null!; + protected _customResourceStrings: IComboResourceStrings = getCurrentResourceStrings(ComboResourceStringsEN); protected _defaultResourceStrings = getCurrentResourceStrings(ComboResourceStringsEN); protected _valid = IgxInputState.INITIAL; protected ngControl: NgControl = null!; @@ -1006,6 +1008,7 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh constructor() { onResourceChangeHandle(this.destroy$, () => { this._defaultResourceStrings = getCurrentResourceStrings(ComboResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts b/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts index 921ec9cc46f..20ed6074bab 100644 --- a/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/combo/src/combo/combo.component.spec.ts @@ -14,6 +14,7 @@ import { IForOfState } from '../../../directives/src/directives/for-of/for_of.di import { IgxInputState } from '../../../input-group/src/public_api'; import { IGX_INPUT_GROUP_TYPE, IgxLabelDirective } from '../../../input-group/src/public_api'; import { AbsoluteScrollStrategy, ConnectedPositioningStrategy } from 'igniteui-angular/core'; +import { ComboResourceStringsEN, changei18n } from 'igniteui-angular/core'; import { IgxComboAddItemComponent } from './combo-add-item.component'; import { IgxComboDropDownComponent } from './combo-dropdown.component'; import { IgxComboItemComponent } from './combo-item.component'; @@ -3772,6 +3773,50 @@ describe('igxCombo', () => { })); }); }); + + describe('Resource Strings', () => { + let fix: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule, IgxComboSampleComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fix = TestBed.createComponent(IgxComboSampleComponent); + fix.detectChanges(); + }); + + it('should return full resource strings when partial resourceStrings are set', () => { + combo = fix.componentInstance.combo; + + combo.resourceStrings = { igx_combo_empty_message: 'Nothing here' }; + fix.detectChanges(); + + expect(combo.resourceStrings.igx_combo_empty_message).toBe('Nothing here'); + expect(combo.resourceStrings.igx_combo_filter_search_placeholder).toBe('Enter a Search Term'); + expect(combo.resourceStrings.igx_combo_clearItems_placeholder).toBe('Clear Selection'); + }); + + it('should update non-overridden resource strings when global i18n changes', () => { + combo = fix.componentInstance.combo; + + combo.resourceStrings = { igx_combo_empty_message: 'Custom Empty' }; + fix.detectChanges(); + + try { + changei18n({ igx_combo_filter_search_placeholder: 'Suchen...' }); + fix.detectChanges(); + + expect(combo.resourceStrings.igx_combo_empty_message).toBe('Custom Empty'); + expect(combo.resourceStrings.igx_combo_filter_search_placeholder).toBe('Suchen...'); + expect(combo.resourceStrings.igx_combo_clearItems_placeholder).toBe('Clear Selection'); + } finally { + changei18n(ComboResourceStringsEN); + } + }); + }); }); @Component({ diff --git a/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.spec.ts b/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.spec.ts index 81d0984881f..3efc986a445 100644 --- a/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.spec.ts +++ b/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.spec.ts @@ -8,7 +8,7 @@ import { By } from '@angular/platform-browser'; import { ControlsFunction } from '../../../test-utils/controls-functions.spec'; import { UIInteractions } from '../../../test-utils/ui-interactions.spec'; import { HelperTestFunctions } from '../../../test-utils/calendar-helper-utils'; -import { CancelableEventArgs, WEEKDAYS } from 'igniteui-angular/core'; +import { CancelableEventArgs, WEEKDAYS, DateRangePickerResourceStringsEN, changei18n } from 'igniteui-angular/core'; import { IgxDateRangeSeparatorDirective, IgxDateRangeStartComponent } from './date-range-picker-inputs.common'; import { IgxDateTimeEditorDirective } from '../../../directives/src/directives/date-time-editor/date-time-editor.directive'; import { DateRangeType } from 'igniteui-angular/core'; @@ -2269,6 +2269,50 @@ describe('IgxDateRangePicker', () => { }); }); }); + + describe('Resource Strings', () => { + let fix: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule, DateRangeDefaultComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fix = TestBed.createComponent(DateRangeDefaultComponent); + fix.detectChanges(); + }); + + it('should return full resource strings when partial resourceStrings are set', () => { + const drp = fix.componentInstance.dateRange; + + drp.resourceStrings = { igx_date_range_picker_done_button: 'OK' }; + fix.detectChanges(); + + expect(drp.resourceStrings.igx_date_range_picker_done_button).toBe('OK'); + expect(drp.resourceStrings.igx_date_range_picker_cancel_button).toBe('Cancel'); + expect(drp.resourceStrings.igx_date_range_picker_date_separator).toBe('to'); + }); + + it('should update non-overridden resource strings when global i18n changes', () => { + const drp = fix.componentInstance.dateRange; + + drp.resourceStrings = { igx_date_range_picker_done_button: 'Fertig' }; + fix.detectChanges(); + + try { + changei18n({ igx_date_range_picker_cancel_button: 'Abbrechen' }); + fix.detectChanges(); + + expect(drp.resourceStrings.igx_date_range_picker_done_button).toBe('Fertig'); + expect(drp.resourceStrings.igx_date_range_picker_cancel_button).toBe('Abbrechen'); + expect(drp.resourceStrings.igx_date_range_picker_date_separator).toBe('to'); + } finally { + changei18n(DateRangePickerResourceStringsEN); + } + }); + }); }); @Component({ diff --git a/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.ts b/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.ts index e29fd242a16..3edfb2216d4 100644 --- a/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.ts +++ b/projects/igniteui-angular/date-picker/src/date-range-picker/date-range-picker.component.ts @@ -373,14 +373,15 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective */ @Input() public set resourceStrings(value: IDateRangePickerResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * An accessor that returns the resource strings. */ public get resourceStrings(): IDateRangePickerResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -632,6 +633,7 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective } private _resourceStrings: IDateRangePickerResourceStrings = null!; + private _customResourceStrings: IDateRangePickerResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN); private _doneButtonText: string = null!; private _cancelButtonText: string = null!; @@ -1344,6 +1346,7 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective protected override updateResources(): void { this._defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN, false, this._locale); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; } private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) { diff --git a/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts b/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts index 92628641bce..0bb8231f798 100644 --- a/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts +++ b/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts @@ -71,11 +71,12 @@ export class IgxPivotDateDimension implements IPivotDimension { * By default it uses EN resources. */ public set resourceStrings(value: IGridResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, getCurrentResourceStrings(GridResourceStringsEN, false), this._resourceStrings); } public get resourceStrings(): IGridResourceStrings { - return this._resourceStrings || getCurrentResourceStrings(GridResourceStringsEN, false); + return this._resourceStrings ? this._customResourceStrings : getCurrentResourceStrings(GridResourceStringsEN, false); } /** @@ -111,6 +112,7 @@ export class IgxPivotDateDimension implements IPivotDimension { public locale?: string; public displayName!: string; private _resourceStrings: IGridResourceStrings = null!; + private _customResourceStrings: IGridResourceStrings = null!; private _baseDimension: IPivotDimension; private _options: IPivotDateDimensionOptions = {}; diff --git a/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts b/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts index 75c43fc92b8..0593e2620fb 100644 --- a/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts +++ b/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts @@ -1816,12 +1816,13 @@ export abstract class IgxGridBaseDirective implements GridType, */ @Input() public set resourceStrings(value: IGridResourceStrings) { - this._resourceStrings = Object.assign({}, this.resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); this.notifyChanges(); } public get resourceStrings(): IGridResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -1960,7 +1961,7 @@ export abstract class IgxGridBaseDirective implements GridType, public set locale(value: string) { if (value !== this._locale) { this._locale = this.i18nFormatter.verifyLocale(value); - this._defaultResourceStrings = getCurrentResourceStrings(GridResourceStringsEN, false, this._locale); + this.updateResources(this._locale); this._currencyPositionLeft = undefined!; this.summaryService.clearSummaryCache(); this.pipeTrigger++; @@ -3172,6 +3173,7 @@ export abstract class IgxGridBaseDirective implements GridType, protected _hGridSchema!: EntityType[]; protected gridComputedStyles!: CSSStyleDeclaration; protected _resourceStrings: IGridResourceStrings = null!; + protected _customResourceStrings: IGridResourceStrings = getCurrentResourceStrings(GridResourceStringsEN); /** @hidden @internal */ public get paginator(): IgxPaginatorComponent | undefined { @@ -8281,7 +8283,8 @@ export abstract class IgxGridBaseDirective implements GridType, private onResourceChange(args: CustomEvent) { this._defaultLocale = args.detail.newLocale; if (!this._locale) { - this._defaultResourceStrings = getCurrentResourceStrings(GridResourceStringsEN, false); + // Avoid unnecessary fetch of resources, since they should be already retrieved when setting custom locale. + this.updateResources(); } // Reset currency position because of new locale. this._currencyPositionLeft = undefined!; @@ -8290,4 +8293,9 @@ export abstract class IgxGridBaseDirective implements GridType, this.notifyChanges(true); } } + + private updateResources(locale?: string) { + this._defaultResourceStrings = getCurrentResourceStrings(GridResourceStringsEN, false, locale); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; + } } diff --git a/projects/igniteui-angular/grids/grid/src/grid.component.spec.ts b/projects/igniteui-angular/grids/grid/src/grid.component.spec.ts index 747caa4e14c..59f5d0bd35f 100644 --- a/projects/igniteui-angular/grids/grid/src/grid.component.spec.ts +++ b/projects/igniteui-angular/grids/grid/src/grid.component.spec.ts @@ -1,5 +1,5 @@ import { AfterViewInit, ChangeDetectorRef, Component, Injectable, OnInit, ViewChild, TemplateRef, inject, ChangeDetectionStrategy, provideZonelessChangeDetection } from '@angular/core'; -import { TestBed, fakeAsync, tick, flush, waitForAsync } from '@angular/core/testing'; +import { TestBed, fakeAsync, tick, flush, waitForAsync, ComponentFixture } from '@angular/core/testing'; import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -16,7 +16,7 @@ import { IgxGridRowComponent } from './grid-row.component'; import { GRID_SCROLL_CLASS, GridFunctions } from '../../../test-utils/grid-functions.spec'; import { AsyncPipe } from '@angular/common'; import { setElementSize, ymd } from '../../../test-utils/helper-utils.spec'; -import { FilteringExpressionsTree, FilteringLogic, getComponentSize, GridColumnDataType, IgxNumberFilteringOperand, IgxStringFilteringOperand, ISortingExpression, ɵSize, SortingDirection } from 'igniteui-angular/core'; +import { FilteringExpressionsTree, FilteringLogic, getComponentSize, GridColumnDataType, IgxNumberFilteringOperand, IgxStringFilteringOperand, ISortingExpression, ɵSize, SortingDirection, GridResourceStringsEN, changei18n } from 'igniteui-angular/core'; import { IgxPaginatorComponent, IgxPaginatorContentDirective } from 'igniteui-angular/paginator'; import { SCROLL_THROTTLE_TIME_MULTIPLIER } from './../src/grid-base.directive'; @@ -3434,6 +3434,49 @@ describe('IgxGrid Component Tests #grid', () => { expect(() => fix.detectChanges()).not.toThrow(); }); }); + + describe('Resource Strings', () => { + let fix: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule, IgxGridTestComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fix = TestBed.createComponent(IgxGridTestComponent); + fix.detectChanges(); + }); + + it('should return full resource strings when partial resourceStrings are set', () => { + const grid = fix.componentInstance.grid; + + grid.resourceStrings = { igx_grid_emptyFilteredGrid_message: 'No results' }; + fix.detectChanges(); + + expect(grid.resourceStrings.igx_grid_emptyFilteredGrid_message).toBe('No results'); + expect(grid.resourceStrings.igx_grid_groupByArea_message).toBe( + 'Drag a column header and drop it here to group by that column.'); + }); + + it('should update non-overridden resource strings when global i18n changes', () => { + const grid = fix.componentInstance.grid; + + grid.resourceStrings = { igx_grid_emptyFilteredGrid_message: 'Custom Empty' }; + fix.detectChanges(); + + try { + changei18n({ igx_grid_groupByArea_message: 'Hier ablegen' }); + fix.detectChanges(); + + expect(grid.resourceStrings.igx_grid_emptyFilteredGrid_message).toBe('Custom Empty'); + expect(grid.resourceStrings.igx_grid_groupByArea_message).toBe('Hier ablegen'); + } finally { + changei18n(GridResourceStringsEN); + } + }); + }); }); @Component({ diff --git a/projects/igniteui-angular/input-group/src/input-group/input-group.component.spec.ts b/projects/igniteui-angular/input-group/src/input-group/input-group.component.spec.ts index b10d4829a94..f6c8aff22be 100644 --- a/projects/igniteui-angular/input-group/src/input-group/input-group.component.spec.ts +++ b/projects/igniteui-angular/input-group/src/input-group/input-group.component.spec.ts @@ -5,6 +5,7 @@ import { IgxInputGroupComponent } from './input-group.component'; import { UIInteractions } from '../../../test-utils/ui-interactions.spec'; import { IgxInputDirective, IgxPrefixDirective, IgxSuffixDirective } from '../public_api'; import { IGX_INPUT_GROUP_TYPE, IgxInputGroupType } from './inputGroupType'; +import { InputResourceStringsEN, changei18n } from 'igniteui-angular/core'; const INPUT_GROUP_CSS_CLASS = 'igx-input-group'; const INPUT_GROUP_BOX_CSS_CLASS = 'igx-input-group--box'; @@ -239,6 +240,39 @@ describe('IgxInputGroup', () => { inputGroupDebugElement.triggerEventHandler('click', pointerEvent); expect(document.activeElement).toEqual(input.nativeElement); }); + + describe('Resource Strings', () => { + it('should return full resource strings when partial resourceStrings are set', () => { + const fix = TestBed.createComponent(InputGroupComponent); + fix.detectChanges(); + const inputGroup = fix.componentInstance.igxInputGroup; + + inputGroup.resourceStrings = { igx_input_upload_button: 'Upload' }; + fix.detectChanges(); + + expect(inputGroup.resourceStrings.igx_input_upload_button).toBe('Upload'); + expect(inputGroup.resourceStrings.igx_input_file_placeholder).toBe('No file chosen'); + }); + + it('should update non-overridden resource strings when global i18n changes', () => { + const fix = TestBed.createComponent(InputGroupComponent); + fix.detectChanges(); + const inputGroup = fix.componentInstance.igxInputGroup; + + inputGroup.resourceStrings = { igx_input_upload_button: 'Custom Browse' }; + fix.detectChanges(); + + try { + changei18n({ igx_input_file_placeholder: 'Keine Datei ausgewählt' }); + fix.detectChanges(); + + expect(inputGroup.resourceStrings.igx_input_upload_button).toBe('Custom Browse'); + expect(inputGroup.resourceStrings.igx_input_file_placeholder).toBe('Keine Datei ausgewählt'); + } finally { + changei18n(InputResourceStringsEN); + } + }); + }); }); @Component({ diff --git a/projects/igniteui-angular/input-group/src/input-group/input-group.component.ts b/projects/igniteui-angular/input-group/src/input-group/input-group.component.ts index 947c0509754..10f082748e7 100644 --- a/projects/igniteui-angular/input-group/src/input-group/input-group.component.ts +++ b/projects/igniteui-angular/input-group/src/input-group/input-group.component.ts @@ -54,14 +54,15 @@ export class IgxInputGroupComponent implements IgxInputGroupBase, AfterContentCh */ @Input() public set resourceStrings(value: IInputResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * Returns the resource strings. */ public get resourceStrings(): IInputResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -149,6 +150,7 @@ export class IgxInputGroupComponent implements IgxInputGroupBase, AfterContentCh private _filled = false; private _theme: IgxTheme; private _resourceStrings: IInputResourceStrings | null = null; + private _customResourceStrings: IInputResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(InputResourceStringsEN); private _readOnly: undefined | boolean; @@ -254,6 +256,7 @@ export class IgxInputGroupComponent implements IgxInputGroupBase, AfterContentCh this._destroyRef.onDestroy(() => themeChange.unsubscribe()); onResourceChangeHandle(this._destroyRef, () => { this._defaultResourceStrings = getCurrentResourceStrings(InputResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/list/src/list/list.component.spec.ts b/projects/igniteui-angular/list/src/list/list.component.spec.ts index 5f8a39a347d..eeb5fc2f610 100644 --- a/projects/igniteui-angular/list/src/list/list.component.spec.ts +++ b/projects/igniteui-angular/list/src/list/list.component.spec.ts @@ -29,6 +29,7 @@ import { } from '../../../test-utils/list-components.spec'; import { wait } from '../../../test-utils/ui-interactions.spec'; import { GridFunctions } from '../../../test-utils/grid-functions.spec'; +import { ListResourceStringsEN, changei18n } from 'igniteui-angular/core'; describe('List', () => { @@ -817,6 +818,37 @@ describe('List', () => { expect(listLine.parent.nativeElement).toHaveClass('igx-list__item-lines'); }); + it('should return full resource strings when partial resourceStrings are set', () => { + const fix = TestBed.createComponent(EmptyListComponent); + fix.detectChanges(); + const list = fix.componentInstance.list; + + list.resourceStrings = { igx_list_no_items: 'No results found' }; + fix.detectChanges(); + + expect(list.resourceStrings.igx_list_no_items).toBe('No results found'); + expect(list.resourceStrings.igx_list_loading).toBe('Loading data from the server...'); + }); + + it('should update non-overridden resource strings when global i18n changes', () => { + const fix = TestBed.createComponent(EmptyListComponent); + fix.detectChanges(); + const list = fix.componentInstance.list; + + list.resourceStrings = { igx_list_no_items: 'Custom No Items' }; + fix.detectChanges(); + + try { + changei18n({ igx_list_loading: 'Fetching data...' }); + fix.detectChanges(); + + expect(list.resourceStrings.igx_list_no_items).toBe('Custom No Items'); + expect(list.resourceStrings.igx_list_loading).toBe('Fetching data...'); + } finally { + changei18n(ListResourceStringsEN); + } + }); + /* factorX - the coefficient used to calculate deltaX. Pan left by providing negative factorX; Pan right - positive factorX. */ diff --git a/projects/igniteui-angular/list/src/list/list.component.ts b/projects/igniteui-angular/list/src/list/list.component.ts index a3b86e2ec5b..7c98ebf28f6 100644 --- a/projects/igniteui-angular/list/src/list/list.component.ts +++ b/projects/igniteui-angular/list/src/list/list.component.ts @@ -456,6 +456,7 @@ export class IgxListComponent extends IgxListBaseDirective { protected defaultDataLoadingTemplate!: TemplateRef; private _resourceStrings: IListResourceStrings = null!; + private _customResourceStrings: IListResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(ListResourceStringsEN); /** @@ -464,20 +465,22 @@ export class IgxListComponent extends IgxListBaseDirective { */ @Input() public set resourceStrings(value: IListResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * Returns the resource strings. */ public get resourceStrings(): IListResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } constructor() { super(); onResourceChangeHandle(this.destroyRef, () => { this._defaultResourceStrings = getCurrentResourceStrings(ListResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/paginator/src/paginator/paginator.component.spec.ts b/projects/igniteui-angular/paginator/src/paginator/paginator.component.spec.ts index 5663d3d0d11..156217363c2 100644 --- a/projects/igniteui-angular/paginator/src/paginator/paginator.component.spec.ts +++ b/projects/igniteui-angular/paginator/src/paginator/paginator.component.spec.ts @@ -7,6 +7,7 @@ import { GridFunctions } from '../../../test-utils/grid-functions.spec'; import { ControlsFunction } from '../../../test-utils/controls-functions.spec'; import { first } from 'rxjs/operators'; import { IgxButtonDirective } from '../../../directives/src/directives/button/button.directive'; +import { PaginatorResourceStringsEN, changei18n } from 'igniteui-angular/core'; describe('IgxPaginator with default settings', () => { beforeEach(waitForAsync(() => { @@ -280,6 +281,26 @@ describe('IgxPaginator with default settings', () => { expect(paginator.resourceStrings.igx_paginator_next_page_button_text).toBe('Next page'); }); + it('should update non-overridden resource strings when global i18n changes', () => { + const fix = TestBed.createComponent(DefaultPaginatorComponent); + fix.detectChanges(); + const paginator = fix.componentInstance.paginator; + + paginator.resourceStrings = { igx_paginator_label: 'Custom per page' }; + fix.detectChanges(); + + try { + changei18n({ igx_paginator_pager_text: 'von' }); + fix.detectChanges(); + + expect(paginator.resourceStrings.igx_paginator_label).toBe('Custom per page'); + expect(paginator.resourceStrings.igx_paginator_pager_text).toBe('von'); + expect(paginator.resourceStrings.igx_paginator_first_page_button_text).toBe('Go to first page'); + } finally { + changei18n(PaginatorResourceStringsEN); + } + }); + }); describe('IgxPaginator with custom settings', () => { diff --git a/projects/igniteui-angular/paginator/src/paginator/paginator.component.ts b/projects/igniteui-angular/paginator/src/paginator/paginator.component.ts index d1e4f2ccb40..8b4760b1b34 100644 --- a/projects/igniteui-angular/paginator/src/paginator/paginator.component.ts +++ b/projects/igniteui-angular/paginator/src/paginator/paginator.component.ts @@ -147,6 +147,7 @@ export class IgxPaginatorComponent implements IgxPaginatorToken { protected _perPage = 15; private _resourceStrings: IPaginatorResourceStrings = null!; + private _customResourceStrings: IPaginatorResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(PaginatorResourceStringsEN, true); private _overlaySettings: OverlaySettings = {}; private defaultSelectValues = [5, 10, 15, 25, 50, 100, 500]; @@ -275,19 +276,21 @@ export class IgxPaginatorComponent implements IgxPaginatorToken { */ @Input() public set resourceStrings(value: IPaginatorResourceStrings) { - this._resourceStrings = Object.assign({}, this.resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * An accessor that returns the resource strings. */ public get resourceStrings(): IPaginatorResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } constructor() { onResourceChangeHandle(this.destroyRef, () => { this._defaultResourceStrings = getCurrentResourceStrings(PaginatorResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/query-builder/src/query-builder/query-builder-header.component.ts b/projects/igniteui-angular/query-builder/src/query-builder/query-builder-header.component.ts index 75fd0fee003..961b1840622 100644 --- a/projects/igniteui-angular/query-builder/src/query-builder/query-builder-header.component.ts +++ b/projects/igniteui-angular/query-builder/src/query-builder/query-builder-header.component.ts @@ -21,6 +21,7 @@ export class IgxQueryBuilderHeaderComponent { private _destroyRef = inject(DestroyRef); private _resourceStrings: IQueryBuilderResourceStrings = null!; + private _customResourceStrings: IQueryBuilderResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN); /** @@ -61,19 +62,21 @@ export class IgxQueryBuilderHeaderComponent { */ @Input() public set resourceStrings(value: IQueryBuilderResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * Returns the resource strings. */ public get resourceStrings(): IQueryBuilderResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } constructor() { onResourceChangeHandle(this._destroyRef, () => { this._defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } } diff --git a/projects/igniteui-angular/query-builder/src/query-builder/query-builder-tree.component.ts b/projects/igniteui-angular/query-builder/src/query-builder/query-builder-tree.component.ts index 661ef61dfd6..eb67ece8fac 100644 --- a/projects/igniteui-angular/query-builder/src/query-builder/query-builder-tree.component.ts +++ b/projects/igniteui-angular/query-builder/src/query-builder/query-builder-tree.component.ts @@ -239,7 +239,7 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { */ public set locale(value: string) { this._locale = value; - this._defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN, false, this._locale); + this.updateResources(this._locale); } /** @@ -248,14 +248,15 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { */ @Input() public set resourceStrings(value: IQueryBuilderResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; } /** * Returns the resource strings. */ public get resourceStrings(): IQueryBuilderResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -483,7 +484,8 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { private _locale?: string; private _defaultLocale!: string; private _entityNewValue!: EntityType; - private _resourceStrings = null; + private _resourceStrings: IQueryBuilderResourceStrings | null = null; + private _customResourceStrings: IQueryBuilderResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN); /** @@ -1705,10 +1707,16 @@ export class IgxQueryBuilderTreeComponent implements AfterViewInit, OnDestroy { private onResourceChange(args: CustomEvent) { this._defaultLocale = args.detail.newLocale; if (!this._locale) { - this._defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN, false); + // Avoid unnecessary fetch of resources, since they should be already retrieved when setting custom locale. + this.updateResources(); } } + private updateResources(locale?: string) { + this._defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN, false, locale); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; + } + /** rootGroup is recreated after clicking Apply, which sets new expressionTree and calls init()*/ protected trackExpressionItem = trackByIdentity; } diff --git a/projects/igniteui-angular/query-builder/src/query-builder/query-builder.component.spec.ts b/projects/igniteui-angular/query-builder/src/query-builder/query-builder.component.spec.ts index 0ddae9cf169..30d9dd35383 100644 --- a/projects/igniteui-angular/query-builder/src/query-builder/query-builder.component.spec.ts +++ b/projects/igniteui-angular/query-builder/src/query-builder/query-builder.component.spec.ts @@ -1,5 +1,5 @@ import { waitForAsync, TestBed, ComponentFixture, fakeAsync, tick, flush } from '@angular/core/testing'; -import { FilteringExpressionsTree, FilteringLogic, IExpressionTree, IgxDateFilteringOperand, IgxNumberFilteringOperand } from 'igniteui-angular/core'; +import { FilteringExpressionsTree, FilteringLogic, IExpressionTree, IgxDateFilteringOperand, IgxNumberFilteringOperand, QueryBuilderResourceStringsEN, changei18n } from 'igniteui-angular/core'; import { IgxChipComponent } from 'igniteui-angular/chips'; import { IgxComboComponent } from 'igniteui-angular/combo'; import { IgxIconComponent } from 'igniteui-angular/icon'; @@ -3232,6 +3232,33 @@ describe('IgxQueryBuilder', () => { })); }); + + describe('Resource Strings', () => { + it('should return full resource strings when partial resourceStrings are set', () => { + queryBuilder.resourceStrings = { igx_query_builder_date_placeholder: 'Pick date' }; + fix.detectChanges(); + + expect(queryBuilder.resourceStrings.igx_query_builder_date_placeholder).toBe('Pick date'); + expect(queryBuilder.resourceStrings.igx_query_builder_filter_operator_and).toBe('And'); + expect(queryBuilder.resourceStrings.igx_query_builder_add_condition).toBe('Add condition'); + }); + + it('should update non-overridden resource strings when global i18n changes', () => { + queryBuilder.resourceStrings = { igx_query_builder_date_placeholder: 'Custom date' }; + fix.detectChanges(); + + try { + changei18n({ igx_query_builder_filter_operator_and: 'Und' }); + fix.detectChanges(); + + expect(queryBuilder.resourceStrings.igx_query_builder_date_placeholder).toBe('Custom date'); + expect(queryBuilder.resourceStrings.igx_query_builder_filter_operator_and).toBe('Und'); + expect(queryBuilder.resourceStrings.igx_query_builder_add_condition).toBe('Add condition'); + } finally { + changei18n(QueryBuilderResourceStringsEN); + } + }); + }); }); @Component({ diff --git a/projects/igniteui-angular/query-builder/src/query-builder/query-builder.component.ts b/projects/igniteui-angular/query-builder/src/query-builder/query-builder.component.ts index be7fb41646c..c9fff3429e3 100644 --- a/projects/igniteui-angular/query-builder/src/query-builder/query-builder.component.ts +++ b/projects/igniteui-angular/query-builder/src/query-builder/query-builder.component.ts @@ -185,14 +185,15 @@ export class IgxQueryBuilderComponent implements OnDestroy { */ @Input() public set resourceStrings(value: IQueryBuilderResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * Returns the resource strings. */ public get resourceStrings(): IQueryBuilderResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -255,6 +256,7 @@ export class IgxQueryBuilderComponent implements OnDestroy { private destroy$ = new Subject(); private _resourceStrings: IQueryBuilderResourceStrings = null!; + private _customResourceStrings: IQueryBuilderResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN); private _expressionTree!: IExpressionTree; private _fields!: FieldType[]; @@ -266,6 +268,7 @@ export class IgxQueryBuilderComponent implements OnDestroy { this.registerSVGIcons(); onResourceChangeHandle(this.destroy$, () => { this._defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/time-picker/src/time-picker/time-picker.component.ts b/projects/igniteui-angular/time-picker/src/time-picker/time-picker.component.ts index 91a0c976d66..c0beee6fbf1 100644 --- a/projects/igniteui-angular/time-picker/src/time-picker/time-picker.component.ts +++ b/projects/igniteui-angular/time-picker/src/time-picker/time-picker.component.ts @@ -493,6 +493,7 @@ export class IgxTimePickerComponent extends PickerBaseDirective private _dateMaxValue!: Date; private _selectedDate!: Date; private _resourceStrings: ITimePickerResourceStrings = null!; + private _customResourceStrings: ITimePickerResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(TimePickerResourceStringsEN); private _okButtonLabel: string | null = null; private _cancelButtonLabel: string | null = null; @@ -570,14 +571,15 @@ export class IgxTimePickerComponent extends PickerBaseDirective */ @Input() public set resourceStrings(value: ITimePickerResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * An accessor that returns the resource strings. */ public get resourceStrings(): ITimePickerResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -1123,6 +1125,7 @@ export class IgxTimePickerComponent extends PickerBaseDirective protected override updateResources() { this._defaultResourceStrings = getCurrentResourceStrings(TimePickerResourceStringsEN, false, this._locale); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; } private get isTouchedOrDirty(): boolean { diff --git a/projects/igniteui-angular/tree/src/tree/tree-node/tree-node.component.ts b/projects/igniteui-angular/tree/src/tree/tree-node/tree-node.component.ts index 1627ece4645..a2473979953 100644 --- a/projects/igniteui-angular/tree/src/tree/tree-node/tree-node.component.ts +++ b/projects/igniteui-angular/tree/src/tree/tree-node/tree-node.component.ts @@ -224,14 +224,15 @@ export class IgxTreeNodeComponent extends ToggleAnimationPlayer implements Ig */ @Input() public set resourceStrings(value: ITreeResourceStrings) { - this._resourceStrings = Object.assign({}, this._resourceStrings, value); + this._resourceStrings = value; + this._customResourceStrings = Object.assign({}, this._defaultResourceStrings, this._resourceStrings); } /** * An accessor that returns the resource strings. */ public get resourceStrings(): ITreeResourceStrings { - return this._resourceStrings || this._defaultResourceStrings; + return this._resourceStrings ? this._customResourceStrings : this._defaultResourceStrings; } /** @@ -384,6 +385,7 @@ export class IgxTreeNodeComponent extends ToggleAnimationPlayer implements Ig public registeredChildren: IgxTreeNodeLinkDirective[] = []; private _resourceStrings: ITreeResourceStrings = null!; + private _customResourceStrings: ITreeResourceStrings = null!; private _defaultResourceStrings = getCurrentResourceStrings(TreeResourceStringsEN); private _tabIndex: number | null = null; private _disabled = false; @@ -392,6 +394,7 @@ export class IgxTreeNodeComponent extends ToggleAnimationPlayer implements Ig super(); onResourceChangeHandle(this.destroy$, () => { this._defaultResourceStrings = getCurrentResourceStrings(TreeResourceStringsEN, false); + this._customResourceStrings = this._resourceStrings ? Object.assign({}, this._defaultResourceStrings, this._resourceStrings) : null!; }, this); } diff --git a/projects/igniteui-angular/tree/src/tree/tree.spec.ts b/projects/igniteui-angular/tree/src/tree/tree.spec.ts index 651a42406a3..f520928cbce 100644 --- a/projects/igniteui-angular/tree/src/tree/tree.spec.ts +++ b/projects/igniteui-angular/tree/src/tree/tree.spec.ts @@ -4,7 +4,7 @@ import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { AnimationService, IgxAngularAnimationService } from 'igniteui-angular/core'; +import { AnimationService, IgxAngularAnimationService, TreeResourceStringsEN, changei18n } from 'igniteui-angular/core'; import { TreeTestFunctions } from './tree-functions.spec'; import { IgxTreeNavigationService } from './tree-navigation.service'; import { IgxTreeNodeComponent } from './tree-node/tree-node.component'; @@ -719,7 +719,50 @@ describe('IgxTree #treeView', () => { }); }); }); + + describe('IgxTreeNode Resource Strings', () => { + let fix: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule, IgxTreeSampleComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fix = TestBed.createComponent(IgxTreeSampleComponent); + fix.detectChanges(); + }); + + it('should return full resource strings when partial resourceStrings are set', () => { + const node = fix.componentInstance.tree.nodes.first; + + node.resourceStrings = { igx_expand: 'Open' }; + fix.detectChanges(); + + expect(node.resourceStrings.igx_expand).toBe('Open'); + expect(node.resourceStrings.igx_collapse).toBe('Collapse'); + }); + + it('should update non-overridden resource strings when global i18n changes', () => { + const node = fix.componentInstance.tree.nodes.first; + + node.resourceStrings = { igx_expand: 'Custom Expand' }; + fix.detectChanges(); + + try { + changei18n({ igx_collapse: 'Close' }); + fix.detectChanges(); + + expect(node.resourceStrings.igx_expand).toBe('Custom Expand'); + expect(node.resourceStrings.igx_collapse).toBe('Close'); + } finally { + changei18n(TreeResourceStringsEN); + } + }); + }); }); + @Component({ template: `