diff --git a/build-scripts/webpack.cjs b/build-scripts/webpack.cjs index fc0ce85cb6..58cc184ca8 100644 --- a/build-scripts/webpack.cjs +++ b/build-scripts/webpack.cjs @@ -132,6 +132,17 @@ const createWebpackConfig = ({ ), path.resolve(paths.polymer_dir, "src/util/empty.js") ), + // See `src/resources/intl-polyfill-legacy.ts` for explanation + !latestBuild && + new webpack.NormalModuleReplacementPlugin( + new RegExp( + path.resolve(paths.polymer_dir, "src/resources/intl-polyfill.ts") + ), + path.resolve( + paths.polymer_dir, + "src/resources/intl-polyfill-legacy.ts" + ) + ), !isProdBuild && new LogStartCompilePlugin(), ].filter(Boolean), resolve: { diff --git a/src/common/datetime/first_weekday.ts b/src/common/datetime/first_weekday.ts index 6316af6681..241049bae9 100644 --- a/src/common/datetime/first_weekday.ts +++ b/src/common/datetime/first_weekday.ts @@ -1,11 +1,7 @@ import { getWeekStartByLocale } from "weekstart"; import { FrontendLocaleData, FirstWeekday } from "../../data/translation"; -import { polyfillsLoaded } from "../translations/localize"; - -if (__BUILD__ === "latest" && polyfillsLoaded) { - await polyfillsLoaded; -} +import "../../resources/intl-polyfill"; export const weekdays = [ "sunday", diff --git a/src/common/datetime/format_date.ts b/src/common/datetime/format_date.ts index 35bc5eaa7a..36c022ab0c 100644 --- a/src/common/datetime/format_date.ts +++ b/src/common/datetime/format_date.ts @@ -1,10 +1,6 @@ import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; -import { polyfillsLoaded } from "../translations/localize"; - -if (__BUILD__ === "latest" && polyfillsLoaded) { - await polyfillsLoaded; -} +import "../../resources/intl-polyfill"; // Tuesday, August 10 export const formatDateWeekdayDay = ( diff --git a/src/common/datetime/format_date_time.ts b/src/common/datetime/format_date_time.ts index 9aef50e2c8..0b6e1dcf8c 100644 --- a/src/common/datetime/format_date_time.ts +++ b/src/common/datetime/format_date_time.ts @@ -1,12 +1,8 @@ import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; -import { polyfillsLoaded } from "../translations/localize"; +import "../../resources/intl-polyfill"; import { useAmPm } from "./use_am_pm"; -if (__BUILD__ === "latest" && polyfillsLoaded) { - await polyfillsLoaded; -} - // August 9, 2021, 8:23 AM export const formatDateTime = (dateObj: Date, locale: FrontendLocaleData) => formatDateTimeMem(locale).format(dateObj); diff --git a/src/common/datetime/format_time.ts b/src/common/datetime/format_time.ts index a072ed126c..ec4459fe2c 100644 --- a/src/common/datetime/format_time.ts +++ b/src/common/datetime/format_time.ts @@ -1,12 +1,8 @@ import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; -import { polyfillsLoaded } from "../translations/localize"; +import "../../resources/intl-polyfill"; import { useAmPm } from "./use_am_pm"; -if (__BUILD__ === "latest" && polyfillsLoaded) { - await polyfillsLoaded; -} - // 9:15 PM || 21:15 export const formatTime = (dateObj: Date, locale: FrontendLocaleData) => formatTimeMem(locale).format(dateObj); diff --git a/src/common/datetime/relative_time.ts b/src/common/datetime/relative_time.ts index 7b8e6f2e6b..5285096130 100644 --- a/src/common/datetime/relative_time.ts +++ b/src/common/datetime/relative_time.ts @@ -1,12 +1,8 @@ import memoizeOne from "memoize-one"; import { FrontendLocaleData } from "../../data/translation"; -import { polyfillsLoaded } from "../translations/localize"; +import "../../resources/intl-polyfill"; import { selectUnit } from "../util/select-unit"; -if (__BUILD__ === "latest" && polyfillsLoaded) { - await polyfillsLoaded; -} - const formatRelTimeMem = memoizeOne( (locale: FrontendLocaleData) => new Intl.RelativeTimeFormat(locale.language, { numeric: "auto" }) diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index 65c00f3b6c..8117af0ffe 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -1,11 +1,6 @@ -import { shouldPolyfill as shouldPolyfillLocale } from "@formatjs/intl-locale/lib/should-polyfill"; -import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-pluralrules/lib/should-polyfill"; -import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-relativetimeformat/lib/should-polyfill"; -import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill"; -import { shouldPolyfill as shouldPolyfillDisplayName } from "@formatjs/intl-displaynames/lib/should-polyfill"; import IntlMessageFormat from "intl-messageformat"; +import { polyfillLocaleData } from "../../resources/locale-data-polyfill"; import { Resources, TranslationDict } from "../../types"; -import { getLocalLanguage } from "../../util/common-translation"; // Exclude some patterns from key type checking for now // These are intended to be removed as errors are fixed @@ -64,40 +59,6 @@ export interface FormatsType { time: FormatType; } -const loadedPolyfillLocale = new Set(); - -const locale = getLocalLanguage(); - -const polyfills: Promise[] = []; -if (__BUILD__ === "latest") { - if (shouldPolyfillLocale()) { - await import("@formatjs/intl-locale/polyfill"); - } - if (shouldPolyfillPluralRules(locale)) { - polyfills.push(import("@formatjs/intl-pluralrules/polyfill")); - polyfills.push(import("@formatjs/intl-pluralrules/locale-data/en")); - } - if (shouldPolyfillRelativeTime(locale)) { - polyfills.push(import("@formatjs/intl-relativetimeformat/polyfill")); - } - if (shouldPolyfillDateTime(locale)) { - polyfills.push(import("@formatjs/intl-datetimeformat/polyfill")); - polyfills.push(import("@formatjs/intl-datetimeformat/add-all-tz")); - } - if (shouldPolyfillDisplayName(locale)) { - polyfills.push(import("@formatjs/intl-displaynames/polyfill")); - polyfills.push(import("@formatjs/intl-displaynames/locale-data/en")); - } -} - -export const polyfillsLoaded = - polyfills.length === 0 - ? undefined - : Promise.all(polyfills).then(() => - // Load the default language - loadPolyfillLocales(locale) - ); - /** * Adapted from Polymer app-localize-behavior. * @@ -125,11 +86,9 @@ export const computeLocalize = async ( resources: Resources, formats?: FormatsType ): Promise> => { - if (polyfillsLoaded) { - await polyfillsLoaded; - } - - await loadPolyfillLocales(language); + await import("../../resources/intl-polyfill").then(() => + polyfillLocaleData(language) + ); // Every time any of the parameters change, invalidate the strings cache. cache._localizationCache = {}; @@ -181,58 +140,3 @@ export const computeLocalize = async ( } }; }; - -export const loadPolyfillLocales = async (language: string) => { - if (loadedPolyfillLocale.has(language)) { - return; - } - loadedPolyfillLocale.add(language); - try { - if ( - Intl.NumberFormat && - // @ts-ignore - typeof Intl.NumberFormat.__addLocaleData === "function" - ) { - const result = await fetch( - `/static/locale-data/intl-numberformat/${language}.json` - ); - // @ts-ignore - Intl.NumberFormat.__addLocaleData(await result.json()); - } - if ( - Intl.RelativeTimeFormat && - // @ts-ignore - typeof Intl.RelativeTimeFormat.__addLocaleData === "function" - ) { - const result = await fetch( - `/static/locale-data/intl-relativetimeformat/${language}.json` - ); - // @ts-ignore - Intl.RelativeTimeFormat.__addLocaleData(await result.json()); - } - if ( - Intl.DateTimeFormat && - // @ts-ignore - typeof Intl.DateTimeFormat.__addLocaleData === "function" - ) { - const result = await fetch( - `/static/locale-data/intl-datetimeformat/${language}.json` - ); - // @ts-ignore - Intl.DateTimeFormat.__addLocaleData(await result.json()); - } - if ( - Intl.DisplayNames && - // @ts-ignore - typeof Intl.DisplayNames.__addLocaleData === "function" - ) { - const result = await fetch( - `/static/locale-data/intl-displaynames/${language}.json` - ); - // @ts-ignore - Intl.DisplayNames.__addLocaleData(await result.json()); - } - } catch (e) { - // Ignore - } -}; diff --git a/src/resources/compatibility.ts b/src/resources/compatibility.ts index eca81afe09..027b0309aa 100644 --- a/src/resources/compatibility.ts +++ b/src/resources/compatibility.ts @@ -3,21 +3,6 @@ import "core-js"; import "regenerator-runtime/runtime"; import "lit/polyfill-support"; -// For localize & formatting -import "@formatjs/intl-getcanonicallocales/polyfill"; -import "@formatjs/intl-locale/polyfill"; -import "@formatjs/intl-pluralrules/polyfill"; -import "@formatjs/intl-pluralrules/locale-data/en"; -import "@formatjs/intl-numberformat/polyfill"; -import "@formatjs/intl-numberformat/locale-data/en"; -import "@formatjs/intl-relativetimeformat/polyfill"; -import "@formatjs/intl-relativetimeformat/locale-data/en"; -import "@formatjs/intl-datetimeformat/polyfill"; -import "@formatjs/intl-datetimeformat/locale-data/en"; -import "@formatjs/intl-datetimeformat/add-all-tz"; -import "@formatjs/intl-displaynames/polyfill"; -import "@formatjs/intl-displaynames/locale-data/en"; - // To use comlink under ES5 import "proxy-polyfill"; import "unfetch/polyfill"; diff --git a/src/resources/intl-polyfill-legacy.ts b/src/resources/intl-polyfill-legacy.ts new file mode 100644 index 0000000000..c3072b0e90 --- /dev/null +++ b/src/resources/intl-polyfill-legacy.ts @@ -0,0 +1,17 @@ +// This module is a simpler version of `intl-polyfill` without top level await, and replaces it for legacy builds. +// Babel cannot transform TLA, and Webpack uses an async function to support it, +// so builds with browser targets without async support will be broken. + +import "@formatjs/intl-getcanonicallocales/polyfill"; +import "@formatjs/intl-locale/polyfill"; +import "@formatjs/intl-pluralrules/polyfill"; +import "@formatjs/intl-pluralrules/locale-data/en"; +import "@formatjs/intl-numberformat/polyfill"; +import "@formatjs/intl-numberformat/locale-data/en"; +import "@formatjs/intl-relativetimeformat/polyfill"; +import "@formatjs/intl-relativetimeformat/locale-data/en"; +import "@formatjs/intl-datetimeformat/polyfill"; +import "@formatjs/intl-datetimeformat/locale-data/en"; +import "@formatjs/intl-datetimeformat/add-all-tz"; +import "@formatjs/intl-displaynames/polyfill"; +import "@formatjs/intl-displaynames/locale-data/en"; diff --git a/src/resources/intl-polyfill.ts b/src/resources/intl-polyfill.ts new file mode 100644 index 0000000000..ec57044ecb --- /dev/null +++ b/src/resources/intl-polyfill.ts @@ -0,0 +1,46 @@ +import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill"; +import { shouldPolyfill as shouldPolyfillDisplayName } from "@formatjs/intl-displaynames/lib/should-polyfill"; +import { shouldPolyfill as shouldPolyfillLocale } from "@formatjs/intl-locale/lib/should-polyfill"; +import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-pluralrules/lib/should-polyfill"; +import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-relativetimeformat/lib/should-polyfill"; +import { getLocalLanguage } from "../util/common-translation"; +import { polyfillLocaleData } from "./locale-data-polyfill"; + +const polyfillIntl = async () => { + const locale = getLocalLanguage(); + const polyfills: Promise[] = []; + + if (shouldPolyfillLocale()) { + await import("@formatjs/intl-locale/polyfill-force"); + } + if (shouldPolyfillPluralRules(locale)) { + polyfills.push( + import("@formatjs/intl-pluralrules/polyfill-force").then( + () => import("@formatjs/intl-pluralrules/locale-data/en") + ) + ); + } + if (shouldPolyfillRelativeTime(locale)) { + polyfills.push(import("@formatjs/intl-relativetimeformat/polyfill-force")); + } + if (shouldPolyfillDateTime(locale)) { + polyfills.push( + import("@formatjs/intl-datetimeformat/polyfill-force").then( + () => import("@formatjs/intl-datetimeformat/add-all-tz") + ) + ); + } + if (shouldPolyfillDisplayName(locale)) { + polyfills.push( + import("@formatjs/intl-displaynames/polyfill-force").then( + () => import("@formatjs/intl-displaynames/locale-data/en") + ) + ); + } + await Promise.all(polyfills).then(() => + // Load the default language + polyfillLocaleData(locale) + ); +}; + +await polyfillIntl(); diff --git a/src/resources/locale-data-polyfill.ts b/src/resources/locale-data-polyfill.ts new file mode 100644 index 0000000000..f1f462887b --- /dev/null +++ b/src/resources/locale-data-polyfill.ts @@ -0,0 +1,59 @@ +// Loads the static locale data for a given language from FormatJS +// Parents need to load polyfills first; they are not imported here to avoid a circular reference + +const loadedPolyfillLocale = new Set(); + +export const polyfillLocaleData = async (language: string) => { + if (loadedPolyfillLocale.has(language)) { + return; + } + loadedPolyfillLocale.add(language); + try { + if ( + Intl.NumberFormat && + // @ts-ignore + typeof Intl.NumberFormat.__addLocaleData === "function" + ) { + const result = await fetch( + `/static/locale-data/intl-numberformat/${language}.json` + ); + // @ts-ignore + Intl.NumberFormat.__addLocaleData(await result.json()); + } + if ( + Intl.RelativeTimeFormat && + // @ts-ignore + typeof Intl.RelativeTimeFormat.__addLocaleData === "function" + ) { + const result = await fetch( + `/static/locale-data/intl-relativetimeformat/${language}.json` + ); + // @ts-ignore + Intl.RelativeTimeFormat.__addLocaleData(await result.json()); + } + if ( + Intl.DateTimeFormat && + // @ts-ignore + typeof Intl.DateTimeFormat.__addLocaleData === "function" + ) { + const result = await fetch( + `/static/locale-data/intl-datetimeformat/${language}.json` + ); + // @ts-ignore + Intl.DateTimeFormat.__addLocaleData(await result.json()); + } + if ( + Intl.DisplayNames && + // @ts-ignore + typeof Intl.DisplayNames.__addLocaleData === "function" + ) { + const result = await fetch( + `/static/locale-data/intl-displaynames/${language}.json` + ); + // @ts-ignore + Intl.DisplayNames.__addLocaleData(await result.json()); + } + } catch (e) { + // Ignore + } +}; diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index 447cebe7d1..7acfd4ce13 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -10,7 +10,6 @@ import { subscribeServices, } from "home-assistant-js-websocket"; import { fireEvent } from "../common/dom/fire_event"; -import { polyfillsLoaded } from "../common/translations/localize"; import { subscribeAreaRegistry } from "../data/area_registry"; import { broadcastConnectionStatus } from "../data/connection-status"; import { subscribeDeviceRegistry } from "../data/device_registry"; @@ -224,17 +223,12 @@ export const connectionMixin = >( }); subscribeConfig(conn, (config) => { if (this.hass?.config?.time_zone !== config.time_zone) { - if (__BUILD__ === "latest" && polyfillsLoaded) { - polyfillsLoaded.then(() => { - if ("__setDefaultTimeZone" in Intl.DateTimeFormat) { - // @ts-ignore - Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone); - } - }); - } else if ("__setDefaultTimeZone" in Intl.DateTimeFormat) { - // @ts-ignore - Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone); - } + import("../resources/intl-polyfill").then(() => { + if ("__setDefaultTimeZone" in Intl.DateTimeFormat) { + // @ts-ignore + Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone); + } + }); } this._updateHass({ config }); });