From 7748315fc330ea40ae25a9ac4abefb835a8964a9 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 29 May 2024 08:01:21 -0400 Subject: [PATCH] Inject Intl polyfills where used (#20798) * Inject Intl polyfills where used * Replace Intl polyfill in localize method with loading intl-messageformat asynchronously * Remove spurious feature tests for Intl --- .../babel-plugins/custom-polyfill-plugin.js | 51 ++++++++++++++++++- build-scripts/bundle.cjs | 1 + src/common/datetime/first_weekday.ts | 2 - src/common/datetime/format_date.ts | 1 - src/common/datetime/format_date_time.ts | 1 - src/common/datetime/format_duration.ts | 1 - src/common/datetime/format_time.ts | 1 - src/common/datetime/localize_date.ts | 1 - src/common/datetime/relative_time.ts | 1 - src/common/language/format_language.ts | 13 +++-- src/common/number/format_number.ts | 6 +-- src/common/string/format-list.ts | 1 - src/common/translations/localize.ts | 9 ++-- src/components/ha-country-picker.ts | 13 ++--- src/components/ha-currency-picker.ts | 21 +++----- src/components/ha-language-picker.ts | 1 - src/data/automation_i18n.ts | 1 - .../{ => polyfills}/intl-polyfill.ts | 2 +- .../{ => polyfills}/locale-data-polyfill.ts | 0 19 files changed, 75 insertions(+), 52 deletions(-) rename src/resources/{ => polyfills}/intl-polyfill.ts (97%) rename src/resources/{ => polyfills}/locale-data-polyfill.ts (100%) diff --git a/build-scripts/babel-plugins/custom-polyfill-plugin.js b/build-scripts/babel-plugins/custom-polyfill-plugin.js index d2a02bf8c60a..3d71377b50aa 100644 --- a/build-scripts/babel-plugins/custom-polyfill-plugin.js +++ b/build-scripts/babel-plugins/custom-polyfill-plugin.js @@ -29,6 +29,31 @@ const PolyfillSupport = { safari: 10.1, samsung: 4.0, }, + "intl-getcanonicallocales": { + android: 54, + chrome: 54, + edge: 16, + firefox: 48, + ios: 10.3, + opera: 41, + opera_mobile: 41, + safari: 10.1, + samsung: 6.0, + }, + "intl-locale": { + android: 74, + chrome: 74, + edge: 79, + firefox: 75, + ios: 14.0, + opera: 62, + opera_mobile: 53, + safari: 14.0, + samsung: 11.0, + }, + "intl-other": { + // Not specified (i.e. always try polyfill) since compatibility depends on supported locales + }, proxy: { android: 49, chrome: 49, @@ -70,7 +95,31 @@ const polyfillMap = { module: "element-internals-polyfill", }, }, - static: {}, + static: { + Intl: { + getCanonicalLocales: { + key: "intl-getcanonicallocales", + module: join(POLYFILL_DIR, "intl-polyfill.ts"), + }, + Locale: { + key: "intl-locale", + module: join(POLYFILL_DIR, "intl-polyfill.ts"), + }, + ...Object.fromEntries( + [ + "DateTimeFormat", + "DisplayNames", + "ListFormat", + "NumberFormat", + "PluralRules", + "RelativeTimeFormat", + ].map((obj) => [ + obj, + { key: "intl-other", module: join(POLYFILL_DIR, "intl-polyfill.ts") }, + ]) + ), + }, + }, }; // Create plugin using the same factory as for CoreJS diff --git a/build-scripts/bundle.cjs b/build-scripts/bundle.cjs index cc2d671e5e36..430110936f9f 100644 --- a/build-scripts/bundle.cjs +++ b/build-scripts/bundle.cjs @@ -157,6 +157,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ exclude: [ path.join(paths.polymer_dir, "src/resources/polyfills"), ...[ + "@formatjs/intl-\\w+", "@lit-labs/virtualizer/polyfills", "@webcomponents/scoped-custom-element-registry", "element-internals-polyfill", diff --git a/src/common/datetime/first_weekday.ts b/src/common/datetime/first_weekday.ts index 241049bae9b2..3c34eb7c142c 100644 --- a/src/common/datetime/first_weekday.ts +++ b/src/common/datetime/first_weekday.ts @@ -1,8 +1,6 @@ import { getWeekStartByLocale } from "weekstart"; import { FrontendLocaleData, FirstWeekday } from "../../data/translation"; -import "../../resources/intl-polyfill"; - export const weekdays = [ "sunday", "monday", diff --git a/src/common/datetime/format_date.ts b/src/common/datetime/format_date.ts index 2369eb46b608..c48c9b049e1a 100644 --- a/src/common/datetime/format_date.ts +++ b/src/common/datetime/format_date.ts @@ -1,7 +1,6 @@ import { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; import { DateFormat, FrontendLocaleData } from "../../data/translation"; -import "../../resources/intl-polyfill"; import { resolveTimeZone } from "./resolve-time-zone"; // Tuesday, August 10 diff --git a/src/common/datetime/format_date_time.ts b/src/common/datetime/format_date_time.ts index 76a32e1accc1..a04408284ef5 100644 --- a/src/common/datetime/format_date_time.ts +++ b/src/common/datetime/format_date_time.ts @@ -1,7 +1,6 @@ import { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; -import "../../resources/intl-polyfill"; import { formatDateNumeric } from "./format_date"; import { formatTime } from "./format_time"; import { resolveTimeZone } from "./resolve-time-zone"; diff --git a/src/common/datetime/format_duration.ts b/src/common/datetime/format_duration.ts index d6c2d52943b9..bd258fe095b5 100644 --- a/src/common/datetime/format_duration.ts +++ b/src/common/datetime/format_duration.ts @@ -1,6 +1,5 @@ import { HaDurationData } from "../../components/ha-duration-input"; import { FrontendLocaleData } from "../../data/translation"; -import "../../resources/intl-polyfill"; const leftPad = (num: number) => (num < 10 ? `0${num}` : num); diff --git a/src/common/datetime/format_time.ts b/src/common/datetime/format_time.ts index ed3e6b603d29..669dcaf8a611 100644 --- a/src/common/datetime/format_time.ts +++ b/src/common/datetime/format_time.ts @@ -1,7 +1,6 @@ import { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; -import "../../resources/intl-polyfill"; import { resolveTimeZone } from "./resolve-time-zone"; import { useAmPm } from "./use_am_pm"; diff --git a/src/common/datetime/localize_date.ts b/src/common/datetime/localize_date.ts index 9fa851d7c293..428261801d58 100644 --- a/src/common/datetime/localize_date.ts +++ b/src/common/datetime/localize_date.ts @@ -1,5 +1,4 @@ import memoizeOne from "memoize-one"; -import "../../resources/intl-polyfill"; export const localizeWeekdays = memoizeOne( (language: string, short: boolean): string[] => { diff --git a/src/common/datetime/relative_time.ts b/src/common/datetime/relative_time.ts index 528509613088..8be9a65119ed 100644 --- a/src/common/datetime/relative_time.ts +++ b/src/common/datetime/relative_time.ts @@ -1,6 +1,5 @@ import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; -import "../../resources/intl-polyfill"; import { selectUnit } from "../util/select-unit"; const formatRelTimeMem = memoizeOne( diff --git a/src/common/language/format_language.ts b/src/common/language/format_language.ts index 76180912a95b..7230d0991f1e 100644 --- a/src/common/language/format_language.ts +++ b/src/common/language/format_language.ts @@ -12,11 +12,10 @@ export const formatLanguageCode = ( } }; -const formatLanguageCodeMem = memoizeOne((locale: FrontendLocaleData) => - Intl && "DisplayNames" in Intl - ? new Intl.DisplayNames(locale.language, { - type: "language", - fallback: "code", - }) - : undefined +const formatLanguageCodeMem = memoizeOne( + (locale: FrontendLocaleData) => + new Intl.DisplayNames(locale.language, { + type: "language", + fallback: "code", + }) ); diff --git a/src/common/number/format_number.ts b/src/common/number/format_number.ts index 822dfc5411e0..99803081637d 100644 --- a/src/common/number/format_number.ts +++ b/src/common/number/format_number.ts @@ -63,8 +63,7 @@ export const formatNumber = ( if ( localeOptions?.number_format !== NumberFormat.none && - !Number.isNaN(Number(num)) && - Intl + !Number.isNaN(Number(num)) ) { try { return new Intl.NumberFormat( @@ -85,8 +84,7 @@ export const formatNumber = ( if ( !Number.isNaN(Number(num)) && num !== "" && - localeOptions?.number_format === NumberFormat.none && - Intl + localeOptions?.number_format === NumberFormat.none ) { // If NumberFormat is none, use en-US format without grouping. return new Intl.NumberFormat( diff --git a/src/common/string/format-list.ts b/src/common/string/format-list.ts index 4eb53717c154..952ffca9349e 100644 --- a/src/common/string/format-list.ts +++ b/src/common/string/format-list.ts @@ -1,5 +1,4 @@ import memoizeOne from "memoize-one"; -import "../../resources/intl-polyfill"; import { FrontendLocaleData } from "../../data/translation"; export const formatListWithAnds = ( diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index f88e03691b05..92cf302ffae5 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -1,6 +1,6 @@ -import IntlMessageFormat from "intl-messageformat"; +import type { IntlMessageFormat } from "intl-messageformat"; import type { HTMLTemplateResult } from "lit"; -import { polyfillLocaleData } from "../../resources/locale-data-polyfill"; +import { polyfillLocaleData } from "../../resources/polyfills/locale-data-polyfill"; import { Resources, TranslationDict } from "../../types"; import { fireEvent } from "../dom/fire_event"; @@ -89,9 +89,8 @@ export const computeLocalize = async ( resources: Resources, formats?: FormatsType ): Promise> => { - await import("../../resources/intl-polyfill").then(() => - polyfillLocaleData(language) - ); + const { IntlMessageFormat } = await import("intl-messageformat"); + await polyfillLocaleData(language); // Every time any of the parameters change, invalidate the strings cache. cache._localizationCache = {}; diff --git a/src/components/ha-country-picker.ts b/src/components/ha-country-picker.ts index 55b7768c4058..ac6553c74dc7 100644 --- a/src/components/ha-country-picker.ts +++ b/src/components/ha-country-picker.ts @@ -4,7 +4,6 @@ import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; import { stopPropagation } from "../common/dom/stop_propagation"; import { caseInsensitiveStringCompare } from "../common/string/compare"; -import "../resources/intl-polyfill"; import "./ha-list-item"; import "./ha-select"; import type { HaSelect } from "./ha-select"; @@ -282,14 +281,10 @@ export class HaCountryPicker extends LitElement { private _getOptions = memoizeOne( (language?: string, countries?: string[]) => { let options: { label: string; value: string }[] = []; - const countryDisplayNames = - Intl && "DisplayNames" in Intl - ? new Intl.DisplayNames(language, { - type: "region", - fallback: "code", - }) - : undefined; - + const countryDisplayNames = new Intl.DisplayNames(language, { + type: "region", + fallback: "code", + }); if (countries) { options = countries.map((country) => ({ value: country, diff --git a/src/components/ha-currency-picker.ts b/src/components/ha-currency-picker.ts index 10a3f9001206..04b9cd8cba3f 100644 --- a/src/components/ha-currency-picker.ts +++ b/src/components/ha-currency-picker.ts @@ -4,7 +4,6 @@ import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; import { stopPropagation } from "../common/dom/stop_propagation"; import { caseInsensitiveStringCompare } from "../common/string/compare"; -import "../resources/intl-polyfill"; import "./ha-list-item"; import "./ha-select"; import type { HaSelect } from "./ha-select"; @@ -170,12 +169,9 @@ const CURRENCIES = [ ]; const curSymbol = (currency: string, locale?: string) => - Intl && "NumberFormat" in Intl - ? new Intl.NumberFormat(locale, { style: "currency", currency }) - .formatToParts(1) - .find((x) => x.type === "currency")?.value - : currency; - + new Intl.NumberFormat(locale, { style: "currency", currency }) + .formatToParts(1) + .find((x) => x.type === "currency")?.value; @customElement("ha-currency-picker") export class HaCurrencyPicker extends LitElement { @property() public language = "en"; @@ -189,13 +185,10 @@ export class HaCurrencyPicker extends LitElement { @property({ type: Boolean, reflect: true }) public disabled = false; private _getOptions = memoizeOne((language?: string) => { - const currencyDisplayNames = - Intl && "DisplayNames" in Intl - ? new Intl.DisplayNames(language, { - type: "currency", - fallback: "code", - }) - : undefined; + const currencyDisplayNames = new Intl.DisplayNames(language, { + type: "currency", + fallback: "code", + }); const options = CURRENCIES.map((currency) => ({ value: currency, label: `${ diff --git a/src/components/ha-language-picker.ts b/src/components/ha-language-picker.ts index c8a1e9d15b62..6a7ba8f1f98e 100644 --- a/src/components/ha-language-picker.ts +++ b/src/components/ha-language-picker.ts @@ -6,7 +6,6 @@ import { stopPropagation } from "../common/dom/stop_propagation"; import { formatLanguageCode } from "../common/language/format_language"; import { caseInsensitiveStringCompare } from "../common/string/compare"; import { FrontendLocaleData } from "../data/translation"; -import "../resources/intl-polyfill"; import { translationMetadata } from "../resources/translations-metadata"; import { HomeAssistant } from "../types"; import "./ha-list-item"; diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index 68fe6c24e25d..f1a3077acc76 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -8,7 +8,6 @@ import { import secondsToDuration from "../common/datetime/seconds_to_duration"; import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display"; import { computeStateName } from "../common/entity/compute_state_name"; -import "../resources/intl-polyfill"; import type { HomeAssistant } from "../types"; import { Condition, ForDict, Trigger } from "./automation"; import { diff --git a/src/resources/intl-polyfill.ts b/src/resources/polyfills/intl-polyfill.ts similarity index 97% rename from src/resources/intl-polyfill.ts rename to src/resources/polyfills/intl-polyfill.ts index 5dd0bf03ebe7..7832f5939833 100644 --- a/src/resources/intl-polyfill.ts +++ b/src/resources/polyfills/intl-polyfill.ts @@ -6,7 +6,7 @@ import { shouldPolyfill as shouldPolyfillLocale } from "@formatjs/intl-locale/sh import { shouldPolyfill as shouldPolyfillNumberFormat } from "@formatjs/intl-numberformat/should-polyfill"; import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-pluralrules/should-polyfill"; import { shouldPolyfill as shouldPolyfillRelativeTimeFormat } from "@formatjs/intl-relativetimeformat/should-polyfill"; -import { getLocalLanguage } from "../util/common-translation"; +import { getLocalLanguage } from "../../util/common-translation"; import { polyfillLocaleData, polyfillTimeZoneData, diff --git a/src/resources/locale-data-polyfill.ts b/src/resources/polyfills/locale-data-polyfill.ts similarity index 100% rename from src/resources/locale-data-polyfill.ts rename to src/resources/polyfills/locale-data-polyfill.ts