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 5f17dcfc137..ffa3a1c1b63 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 d56215f99d8..22dca0fa320 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 @@ -158,11 +158,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; } /** @@ -202,12 +203,14 @@ export class IgxActionStripComponent implements IgxActionStripToken, AfterViewIn private _destroyRef = inject(DestroyRef); 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 0dad3dffeab..6e8e47834bd 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 8de0f4b6ed6..8a6e918659a 100644 --- a/projects/igniteui-angular/banner/src/banner/banner.component.ts +++ b/projects/igniteui-angular/banner/src/banner/banner.component.ts @@ -155,11 +155,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; } /** @@ -240,14 +241,16 @@ export class IgxBannerComponent implements IToggleView { private _destroyRef = inject(DestroyRef); private _expanded: boolean = false; private _shouldFireEvent: boolean = false; - private _bannerEvent: BannerEventArgs; - private _animationSettings: ToggleAnimationSettings; + 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 98315a6fa0e..d99403a675a 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 74ddfc755c9..5fc39f17712 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 } 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; @@ -1046,6 +1047,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); + } + }); + }); }); class HelperTestFunctions { diff --git a/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts b/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts index ae88268ca89..34750ff517e 100644 --- a/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts +++ b/projects/igniteui-angular/carousel/src/carousel/carousel.component.ts @@ -379,11 +379,12 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On * @hidden * @internal */ - public stoppedByInteraction: boolean; - protected override currentItem: IgxSlideComponent; - protected override previousItem: IgxSlideComponent; - private _interval: number; + public stoppedByInteraction!: boolean; + protected override currentItem!: IgxSlideComponent; + 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; @@ -399,14 +400,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 */ @@ -549,6 +551,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 237decb0311..78b1051847d 100644 --- a/projects/igniteui-angular/chips/src/chips/chip.component.ts +++ b/projects/igniteui-angular/chips/src/chips/chip.component.ts @@ -345,14 +345,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; } /** @@ -609,11 +610,13 @@ export class IgxChipComponent implements OnInit, OnDestroy { protected _movedWhileRemoving = false; protected computedStyles; private _resourceStrings: IChipResourceStrings = 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 b7c54bf7951..ce37fbf1ba1 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'; @@ -397,6 +397,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 585b1a52e23..6e91bf1dddd 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); } /** @@ -975,6 +976,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; @@ -1003,6 +1005,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 4a236dc7de8..67a74646db6 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'; @@ -3722,6 +3723,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 ab123988cbb..5f3f90588af 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'; @@ -2270,6 +2270,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 5f1eea734e8..b28e0910e95 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 @@ -342,14 +342,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; } /** @@ -601,6 +602,7 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective } private _resourceStrings: IDateRangePickerResourceStrings = null; + private _customResourceStrings: IDateRangePickerResourceStrings = null; private _defaultResourceStrings = getCurrentResourceStrings(DateRangePickerResourceStringsEN); private _doneButtonText = null; private _cancelButtonText = null; @@ -1312,6 +1314,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 a0ee52c9459..5f4da7ca290 100644 --- a/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts +++ b/projects/igniteui-angular/grids/core/src/pivot-grid-dimensions.ts @@ -70,11 +70,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); } /** @@ -110,6 +111,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 9ddf22e2464..f146fff90eb 100644 --- a/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts +++ b/projects/igniteui-angular/grids/grid/src/grid-base.directive.ts @@ -1825,12 +1825,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; } /** @@ -1969,7 +1970,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++; @@ -3184,7 +3185,8 @@ export abstract class IgxGridBaseDirective implements GridType, }; protected _hGridSchema: EntityType[]; protected gridComputedStyles; - protected _resourceStrings = null; + protected _resourceStrings: IGridResourceStrings = null; + protected _customResourceStrings: IGridResourceStrings = getCurrentResourceStrings(GridResourceStringsEN); /** @hidden @internal */ public get paginator() { @@ -8261,7 +8263,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; @@ -8270,4 +8273,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 a542d364450..68e37eb652d 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, 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 a190d14c8f5..63be0ab288d 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 a383dd82702..ddea9af2f23 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 @@ -50,14 +50,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; } /** @@ -132,7 +133,8 @@ export class IgxInputGroupComponent implements IgxInputGroupBase, AfterContentCh private _type: IgxInputGroupType = null; private _filled = false; private _theme: IgxTheme; - private _resourceStrings: IInputResourceStrings = null; + private _resourceStrings: IInputResourceStrings | null = null; + private _customResourceStrings: IInputResourceStrings = null; private _defaultResourceStrings = getCurrentResourceStrings(InputResourceStringsEN); private _readOnly: undefined | boolean; @@ -238,6 +240,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 b9414781111..00591c55b6f 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', () => { @@ -761,6 +762,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 6a32ac34d52..a3bae0cc4b9 100644 --- a/projects/igniteui-angular/list/src/list/list.component.ts +++ b/projects/igniteui-angular/list/src/list/list.component.ts @@ -448,6 +448,7 @@ export class IgxListComponent extends IgxListBaseDirective { protected defaultDataLoadingTemplate: TemplateRef; private _resourceStrings: IListResourceStrings = null; + private _customResourceStrings: IListResourceStrings = null; private _defaultResourceStrings = getCurrentResourceStrings(ListResourceStringsEN); /** @@ -456,20 +457,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 0249c0fe796..6312a8af6d0 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 ea384e6f111..81b29d23e87 100644 --- a/projects/igniteui-angular/paginator/src/paginator/paginator.component.ts +++ b/projects/igniteui-angular/paginator/src/paginator/paginator.component.ts @@ -128,6 +128,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]; @@ -256,19 +257,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 70fb8580b42..9385f6c87f8 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 @@ -20,6 +20,7 @@ export class IgxQueryBuilderHeaderComponent { private _destroyRef = inject(DestroyRef); private _resourceStrings: IQueryBuilderResourceStrings = null; + private _customResourceStrings: IQueryBuilderResourceStrings = null; private _defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN); /** @@ -60,19 +61,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 00b5627f9bd..16f5717da8f 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; private _defaultLocale; private _entityNewValue: EntityType; - private _resourceStrings = null; + private _resourceStrings: IQueryBuilderResourceStrings = null; + private _customResourceStrings: IQueryBuilderResourceStrings = null; private _defaultResourceStrings = getCurrentResourceStrings(QueryBuilderResourceStringsEN); /** @@ -1702,10 +1704,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 5d394541e7c..29acba5e96a 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 dbb628996dc..c012b1b2fa9 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 @@ -171,14 +171,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; } /** @@ -241,6 +242,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[]; @@ -252,6 +254,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 28b2ad29172..e3af7f978a8 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 @@ -457,6 +457,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 = null; private _cancelButtonLabel = null; @@ -534,14 +535,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; } /** @@ -1087,6 +1089,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 880509c5d67..1ff8721a136 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 @@ -201,14 +201,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; } /** @@ -361,6 +362,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 = null; private _disabled = false; @@ -369,6 +371,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 290c2e85944..f8683888fd3 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: `