Skip to content

refactor(*): New localization implementation. #16034

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { getCurrentResourceStrings } from '../core/i18n/resources';
import { IgxIconButtonDirective } from '../directives/button/icon-button.directive';
import { IgxActionStripToken } from './token';
import { trackByIdentity } from '../core/utils';
import { getI18nManager } from 'igniteui-i18n-core';

@Directive({
selector: '[igxActionStripMenuItem]',
Expand Down Expand Up @@ -197,7 +198,11 @@ export class IgxActionStripComponent implements IgxActionStripToken, AfterConten
protected el: ElementRef,
/** @hidden @internal **/
public cdr: ChangeDetectorRef,
) { }
) {
getI18nManager().onResourceChange(() => {
this._resourceStrings = getCurrentResourceStrings(ActionStripResourceStringsEN, false);
});
}

/**
* Menu Items list.
Expand Down
7 changes: 6 additions & 1 deletion projects/igniteui-angular/src/lib/banner/banner.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IgxExpansionPanelBodyComponent } from '../expansion-panel/expansion-pan
import { IgxExpansionPanelComponent } from '../expansion-panel/expansion-panel.component';
import { BannerResourceStringsEN, IBannerResourceStrings } from '../core/i18n/banner-resources';
import { getCurrentResourceStrings } from '../core/i18n/resources';
import { getI18nManager } from 'igniteui-i18n-core';

export interface BannerEventArgs extends IBaseEventArgs {
event?: Event;
Expand Down Expand Up @@ -237,7 +238,11 @@ export class IgxBannerComponent implements IToggleView {
private _animationSettings: ToggleAnimationSettings;
private _resourceStrings = getCurrentResourceStrings(BannerResourceStringsEN);

constructor(public elementRef: ElementRef<HTMLElement>) { }
constructor(public elementRef: ElementRef<HTMLElement>) {
getI18nManager().onResourceChange(() => {
this._resourceStrings = getCurrentResourceStrings(BannerResourceStringsEN, false);
});
}

/**
* Opens the banner
Expand Down
5 changes: 5 additions & 0 deletions projects/igniteui-angular/src/lib/calendar/calendar-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getCurrentResourceStrings } from '../core/i18n/resources';
import { KeyboardNavigationService } from './calendar.services';
import { getYearRange, isDateInRanges } from './common/helpers';
import { CalendarDay } from './common/model';
import { getI18nManager } from 'igniteui-i18n-core';

/** @hidden @internal */
@Directive({
Expand Down Expand Up @@ -659,6 +660,10 @@ export class IgxCalendarBaseDirective implements ControlValueAccessor {
this.locale = _localeId;
this.viewDate = this.viewDate ? this.viewDate : new Date();
this.initFormatters();

getI18nManager().onResourceChange(() => {
this._resourceStrings = getCurrentResourceStrings(CalendarResourceStringsEN, false);
});
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
booleanAttribute,
HostListener,
} from '@angular/core';
import { NgTemplateOutlet, DatePipe } from '@angular/common';
import { NgTemplateOutlet } from '@angular/common';
import { NG_VALUE_ACCESSOR } from '@angular/forms';

import {
Expand All @@ -34,6 +34,7 @@ import { areSameMonth, formatToParts, getClosestActiveDate, isDateInRanges } fro
import { CalendarDay } from './common/model';
import { IgxCalendarBaseDirective } from './calendar-base';
import { KeyboardNavigationService } from './calendar.services';
import { IgxDateFormatterPipe } from '../grids/common/pipes';

let NEXT_ID = 0;

Expand Down Expand Up @@ -71,7 +72,7 @@ let NEXT_ID = 0;
],
selector: 'igx-calendar',
templateUrl: 'calendar.component.html',
imports: [NgTemplateOutlet, IgxCalendarScrollPageDirective, IgxIconComponent, IgxDaysViewComponent, IgxMonthsViewComponent, IgxYearsViewComponent, DatePipe, IgxMonthViewSlotsCalendar, IgxGetViewDateCalendar]
imports: [NgTemplateOutlet, IgxCalendarScrollPageDirective, IgxIconComponent, IgxDaysViewComponent, IgxMonthsViewComponent, IgxYearsViewComponent, IgxDateFormatterPipe, IgxMonthViewSlotsCalendar, IgxGetViewDateCalendar]
})
export class IgxCalendarComponent extends IgxCalendarBaseDirective implements AfterViewInit, OnDestroy {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
OnDestroy,
OnInit,
} from "@angular/core";
import { NgTemplateOutlet, DatePipe } from "@angular/common";
import { NgTemplateOutlet } from "@angular/common";
import { NG_VALUE_ACCESSOR } from "@angular/forms";

import { IgxMonthsViewComponent } from "../months-view/months-view.component";
Expand All @@ -21,6 +21,7 @@ import { CalendarDay } from "../common/model";
import { IgxCalendarBaseDirective } from "../calendar-base";
import { KeyboardNavigationService } from "../calendar.services";
import { formatToParts } from "../common/helpers";
import { IgxDateFormatterPipe } from '../../grids/common/pipes';

let NEXT_ID = 0;
@Component({
Expand All @@ -39,7 +40,7 @@ let NEXT_ID = 0;
templateUrl: "month-picker.component.html",
imports: [
NgTemplateOutlet,
DatePipe,
IgxDateFormatterPipe,
IgxIconComponent,
IgxMonthsViewComponent,
IgxYearsViewComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { getCurrentResourceStrings } from '../core/i18n/resources';
import { HammerGesturesManager } from '../core/touch';
import { CarouselAnimationType, CarouselIndicatorsOrientation } from './enums';
import { IgxDirectionality } from '../services/direction/directionality';
import { getI18nManager } from 'igniteui-i18n-core';

let NEXT_ID = 0;

Expand Down Expand Up @@ -576,6 +577,9 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On
) {
super(animationService, cdr);
this.differ = this.iterableDiffers.find([]).create(null);
getI18nManager().onResourceChange(() => {
this._resourceStrings = getCurrentResourceStrings(CarouselResourceStringsEN, false);
});
}

/** @hidden */
Expand Down
7 changes: 6 additions & 1 deletion projects/igniteui-angular/src/lib/chips/chip.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { IgxIconComponent } from '../icon/icon.component';
import { NgClass, NgTemplateOutlet } from '@angular/common';
import { getCurrentResourceStrings } from '../core/i18n/resources';
import { Size } from '../grids/common/enums';
import { getI18nManager } from 'igniteui-i18n-core';

export const IgxChipTypeVariant = {
PRIMARY: 'primary',
Expand Down Expand Up @@ -610,7 +611,11 @@ export class IgxChipComponent implements OnInit, OnDestroy {
public cdr: ChangeDetectorRef,
private ref: ElementRef<HTMLElement>,
private renderer: Renderer2,
@Inject(DOCUMENT) public document: any) { }
@Inject(DOCUMENT) public document: any) {
getI18nManager().onResourceChange(() => {
this._resourceStrings = getCurrentResourceStrings(ChipResourceStringsEN, false);
});
}

/**
* @hidden
Expand Down
7 changes: 6 additions & 1 deletion projects/igniteui-angular/src/lib/combo/combo.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { IComboItemAdditionEvent, IComboSearchInputEventArgs } from './public_ap
import { ComboResourceStringsEN, IComboResourceStrings } from '../core/i18n/combo-resources';
import { getCurrentResourceStrings } from '../core/i18n/resources';
import { isEqual } from 'lodash-es';
import { getI18nManager } from 'igniteui-i18n-core';

export const IGX_COMBO_COMPONENT = /*@__PURE__*/new InjectionToken<IgxComboBase>('IgxComboComponentToken');

Expand Down Expand Up @@ -967,7 +968,11 @@ export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewCh
@Optional() @Inject(IGX_INPUT_GROUP_TYPE) protected _inputGroupType: IgxInputGroupType,
@Optional() protected _injector: Injector,
@Optional() @Inject(IgxIconService) protected _iconService?: IgxIconService,
) { }
) {
getI18nManager().onResourceChange(() => {
this._resourceStrings = getCurrentResourceStrings(ComboResourceStringsEN, false);
});
}

public ngAfterViewChecked() {
const targetElement = this.inputGroup.element.nativeElement.querySelector('.igx-input-group__bundle') as HTMLElement;
Expand Down
62 changes: 34 additions & 28 deletions projects/igniteui-angular/src/lib/core/i18n/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,54 @@ import { IActionStripResourceStrings } from './action-strip-resources';
import { IQueryBuilderResourceStrings } from './query-builder-resources';
import { IComboResourceStrings } from './combo-resources';
import { IBannerResourceStrings } from './banner-resources';
import {
registerI18n,
getCurrentResourceStrings as getCurrentResourceStringsCore,
IResourceStrings as IResourceStringsCore
} from 'igniteui-i18n-core';

export interface IResourceStrings extends IGridResourceStrings, ITimePickerResourceStrings, ICalendarResourceStrings,
ICarouselResourceStrings, IChipResourceStrings, IComboResourceStrings, IInputResourceStrings, IDatePickerResourceStrings,
IDateRangePickerResourceStrings, IListResourceStrings, IPaginatorResourceStrings, ITreeResourceStrings,
IActionStripResourceStrings, IQueryBuilderResourceStrings, IBannerResourceStrings { }

export class igxI18N {
private static _instance: igxI18N;

private _currentResourceStrings: IResourceStrings = { };

private constructor() { }

public static instance() {
return this._instance || (this._instance = new this());
function igxRegisterI18n(resourceStrings: IResourceStrings) {
// Remove `igx_` prefix for compatibility with older versions.
const genericResourceStrings: IResourceStringsCore = {};
for (const key of Object.keys(resourceStrings)) {
let stringKey = key;
if (stringKey.startsWith("igx_")) {
stringKey = stringKey.replace("igx_", "");
}
genericResourceStrings[stringKey] = resourceStrings[key];
}
registerI18n(genericResourceStrings);
}

/**
* Changes the resource strings for all components in the application
* ```
* @param resourceStrings to be applied
*/
public changei18n(resourceStrings: IResourceStrings) {
for (const key of Object.keys(resourceStrings)) {
this._currentResourceStrings[key] = resourceStrings[key];
}
/** Get current resource strings based on default. Result is truncated result, containing only relevant locale strings. */
export function getCurrentResourceStrings<T>(defaultEN: T, init = true) {
const igxResourceStringKeys = Object.keys(defaultEN);
if (init) {
igxRegisterI18n(defaultEN);
}

public getCurrentResourceStrings(en: IResourceStrings): IResourceStrings {
for (const key of Object.keys(en)) {
if (!this._currentResourceStrings[key]) {
this._currentResourceStrings[key] = en[key];
}
// Append back `igx_` prefix for compatibility with older versions.
const resourceStrings = getCurrentResourceStringsCore();
const normalizedResourceStrings: T = {} as T;
const resourceStringsKeys = Object.keys(resourceStrings);
for (const key of resourceStringsKeys) {
let stringKey = key;
if (!stringKey.startsWith("igx_")) {
stringKey = "igx_" + stringKey;
}
if (igxResourceStringKeys.includes(stringKey)) {
normalizedResourceStrings[stringKey] = resourceStrings[key];
}
return this._currentResourceStrings;
}
}

export function getCurrentResourceStrings(en: IResourceStrings) {
return igxI18N.instance().getCurrentResourceStrings(en);
return normalizedResourceStrings;
}

export function changei18n(resourceStrings: IResourceStrings) {
igxI18N.instance().changei18n(resourceStrings);
igxRegisterI18n(resourceStrings);
}
85 changes: 80 additions & 5 deletions projects/igniteui-angular/src/lib/core/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { CurrencyPipe, formatDate as _formatDate, isPlatformBrowser } from '@angular/common';
import { formatDate as _formatDate, isPlatformBrowser } from '@angular/common';
import { Inject, Injectable, InjectionToken, PLATFORM_ID, inject } from '@angular/core';
import { mergeWith } from 'lodash-es';
import { NEVER, Observable } from 'rxjs';
import { setImmediate } from './setImmediate';
import { isDevMode } from '@angular/core';
import type { IgxTheme } from '../services/theme/theme.token';
import { getI18nManager } from 'igniteui-i18n-core';

/** @hidden @internal */
export const ELEMENTS_TOKEN = /*@__PURE__*/new InjectionToken<boolean>('elements environment');
Expand Down Expand Up @@ -579,14 +580,88 @@ export const isConstructor = (ref: any) => typeof ref === 'function' && Boolean(
* Similar to Angular's formatDate. However it will not throw on `undefined | null | ''` instead
* coalescing to an empty string.
*/
export const formatDate = (value: string | number | Date, format: string, locale: string, timezone?: string): string => {
export function formatDate(value: Date | string | number | null | undefined, format: string, locale: string, timezone?: string): string {
if (value === null || value === undefined || value === '') {
return '';
}
return _formatDate(value, format, locale, timezone);
};
if (typeof value === "string") {
value = new Date(value);
}
let dateStyle = undefined, timeStyle = undefined;
if (format === 'short' || format === 'medium' || format === 'long' || format === 'full') {
dateStyle = format;
timeStyle = format;
} else if (format.includes('Date')) {
dateStyle = format.replace('Date', '');
} else if (format.includes('Time')) {
dateStyle = format.replace('Time', '');
} else {
// Match with Angular custom formatting?
}
const options: Intl.DateTimeFormatOptions = {
dateStyle,
timeStyle,
timeZone: timezone
};
return getI18nManager().formatDateTime(value, locale, options);
}

function parseDigitsInfo(value: string) {
let minIntegerDigits = undefined, minFractionDigits = undefined, maxFractionDigits = undefined;
if (value) {
const parts = value.split("-");
const innerParts = parts[0].split(".");
if (innerParts[0] !== "1") {
minIntegerDigits = parseInt(innerParts[0]);
}
if (innerParts.length == 2 && innerParts[1] !== "0") {
minFractionDigits = parseInt(innerParts[1]);
}
if (parts.length == 2 && parts[1] !== "3") {
maxFractionDigits = parseInt(parts[1]);
}
}
return { minIntegerDigits, minFractionDigits, maxFractionDigits };
}

function formatNumberGeneric(value: number | string | null | undefined, style?: 'decimal' | 'percent' | 'currency', locale?: string, digitsInfo?: string, currencyCode?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string) {
if (value === null || value === undefined || value === '') {
return '';
}
if (typeof value === "string") {
value = parseFloat(value);
}
const parsedDigitsInfo = parseDigitsInfo(digitsInfo);
let currencyDisplay: keyof Intl.NumberFormatOptionsCurrencyDisplayRegistry;
if (display !== 'code' && display !== 'symbol' && display !== 'symbol-narrow' && display !== 'narrowSymbol' && display !== "name") {
currencyDisplay = 'symbol';
} else if (display === 'symbol-narrow') {
currencyDisplay = 'narrowSymbol';
} else {
currencyCode = display || undefined;
}
const options: Intl.NumberFormatOptions = {
style: style,
currency: currencyCode,
currencyDisplay: currencyDisplay,
minimumIntegerDigits: parsedDigitsInfo.minIntegerDigits,
minimumFractionDigits: parsedDigitsInfo.minFractionDigits,
maximumFractionDigits: parsedDigitsInfo.maxFractionDigits
};
return getI18nManager().formatNumber(value, locale, options);
}

export function formatNumber(value: number | string | null | undefined, locale?: string, digitsInfo?: string): string {
return formatNumberGeneric(value, "decimal", locale, digitsInfo);
}

export const formatCurrency = new CurrencyPipe(undefined).transform;
export function formatPercent(value: number | string | null | undefined, locale?: string, digitsInfo?: string) {
return formatNumberGeneric(value, "percent", locale, digitsInfo);
}

export function formatCurrency(value: number | string | null | undefined, locale?: string, display?: 'code' | 'symbol' | 'symbol-narrow' | string, currencyCode?: string, digitsInfo?: string): string | null {
return formatNumberGeneric(value, "currency", locale, digitsInfo, currencyCode, display);
}

/** Converts pixel values to their rem counterparts for a base value */
export const rem = (value: number | string) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { FilteringLogic, type IFilteringExpression } from './filtering-expression.interface';
import { FilteringExpressionsTree, type IFilteringExpressionsTree } from './filtering-expressions-tree';
import { resolveNestedPath, parseDate, formatDate, formatCurrency, columnFieldPath } from '../core/utils';
import { resolveNestedPath, parseDate, formatDate, formatCurrency, columnFieldPath, formatNumber, formatPercent } from '../core/utils';
import type { ColumnType, EntityType, GridType } from '../grids/common/grid.interface';
import { GridColumnDataType } from './data-util';
import { SortingDirection } from './sorting-strategy';
import { formatNumber, formatPercent, getLocaleCurrencyCode } from '@angular/common';
import { getLocaleCurrencyCode } from '@angular/common';
import type { IFilteringState } from './filtering-state.interface';
import { isTree } from './expressions-tree-util';
import type { IgxHierarchicalGridComponent } from '../grids/hierarchical-grid/hierarchical-grid.component';
Expand Down Expand Up @@ -170,7 +170,7 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
case GridColumnDataType.Time:
return formatDate(value, format, locale, timezone);
case GridColumnDataType.Currency:
return formatCurrency(value, currencyCode || getLocaleCurrencyCode(locale), display, digitsInfo, locale);
return formatCurrency(value, locale, display, currencyCode || getLocaleCurrencyCode(locale), digitsInfo);
case GridColumnDataType.Number:
return formatNumber(value, locale, digitsInfo);
case GridColumnDataType.Percent:
Expand Down
Loading
Loading