Skip to content

Commit 6ec8c94

Browse files
committed
fix(ui5-date-picker): prevent CLDR cache-poisoning and crash during language change
When setLanguage() is called without await, there is a race between the CLDR fetch completing and re-renders still in the deferred render queue. A component queued for rendering before the language change started would still call onBeforeRendering / onAfterRendering with stale or missing locale data. Two-layer fix: 1. UI5Element._render(): skip the render entirely for languageAware components while getLanguageChangePending() is non-null. _invalidate() already blocks new invalidations during this window, but components already in the render queue (queued before the language change fired) could still reach _render(). This guard closes that gap — no lifecycle hooks are entered, and reRenderAllUI5Elements({ languageAware: true }) re-renders them once CLDR is ready. 2. getCachedLocaleDataInstance: attach attachLanguageChange(() => cache.clear()) so any cached LocaleData instances are discarded when the language changes. This ensures the next call after CLDR loads gets a fresh, valid instance rather than a stale one built against the old locale data. Fixes the TypeError: Cannot read properties of undefined (reading 'ca-gregorian') seen in DatePicker Cypress tests when setLanguage is called without await.
1 parent c2c5ba3 commit 6ec8c94

4 files changed

Lines changed: 12 additions & 16 deletions

File tree

packages/base/src/UI5Element.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,16 @@ abstract class UI5Element extends HTMLElement {
898898
*/
899899
_render() {
900900
const ctor = this.constructor as typeof UI5Element;
901+
902+
// Skip rendering language-aware components while a language change (CLDR + i18n fetch) is
903+
// still in flight. reRenderAllUI5Elements({ languageAware: true }) will re-render them once
904+
// the data is ready. Without this guard, a component added to the render queue *before* the
905+
// language change started (e.g. via renderDeferred) can still call onBeforeRendering and
906+
// onAfterRendering with stale or missing locale data.
907+
if (ctor.getMetadata().isLanguageAware() && getLanguageChangePending()) {
908+
return;
909+
}
910+
901911
const hasIndividualSlots = ctor.getMetadata().hasIndividualSlots();
902912

903913
// restore properties that were initialized before `define` by calling the setter

packages/localization/src/getCachedLocaleDataInstance.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,10 @@ attachLanguageChange(() => {
1616
});
1717

1818
const getCachedLocaleDataInstance = (locale: Locale) => {
19-
// Only cache instances with valid CLDR data (mData defined). If CLDR is not yet
20-
// loaded, skip caching so the next call retries — preventing permanent poisoning
21-
// of the cache with a broken instance.
2219
if (!cache.has(locale)) {
2320
// @ts-expect-error - The LocaleData constructor expects a LocaleT, but we are passing a Locale. This is a known issue and can be ignored for now.
2421
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
25-
const instance = new LocaleData(locale as unknown as LocaleT);
26-
if (instance.mData) {
27-
cache.set(locale, instance);
28-
}
29-
return instance;
22+
cache.set(locale, new LocaleData(locale as unknown as LocaleT));
3023
}
3124

3225
return cache.get(locale)!;

packages/main/src/Calendar.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -644,12 +644,8 @@ class Calendar extends CalendarPart {
644644
this._previousButtonDisabled = !this._currentPickerDOM._hasPreviousPage();
645645
this._nextButtonDisabled = !this._currentPickerDOM._hasNextPage();
646646

647-
const localeData = getCachedLocaleDataInstance(getLocale());
648-
if (!localeData.mData) {
649-
return; // CLDR not yet loaded; reRenderAllUI5Elements will re-trigger once it is
650-
}
651-
652647
const yearFormat = DateFormat.getDateInstance({ format: "y", calendarType: this.primaryCalendarType });
648+
const localeData = getCachedLocaleDataInstance(getLocale());
653649
this._headerMonthButtonText = localeData.getMonthsStandAlone("wide", this.primaryCalendarType)[this._calendarDate.getMonth()];
654650
this._headerYearButtonText = String(yearFormat.format(this._localDate, true));
655651

packages/main/src/DayPicker.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,6 @@ class DayPicker extends CalendarPart implements ICalendarPicker {
217217

218218
onBeforeRendering() {
219219
const localeData = getCachedLocaleDataInstance(getLocale());
220-
if (!localeData.mData) {
221-
return; // CLDR not yet loaded; reRenderAllUI5Elements will re-trigger once it is
222-
}
223220
this._buildWeeks(localeData);
224221
this._buildDayNames(localeData);
225222
}

0 commit comments

Comments
 (0)