123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- /*!
- * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
- * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
- * v1.0.0-beta.97
- */
- import { BigDecimal, isValidNumber, sanitizeExponentialNumberString } from "./number";
- import { createObserver } from "./observers";
- import { closestElementCrossShadowBoundary, containsCrossShadowBoundary } from "./dom";
- export const defaultLocale = "en";
- export const locales = [
- "ar",
- "bg",
- "bs",
- "ca",
- "cs",
- "da",
- "de",
- "de-CH",
- "el",
- defaultLocale,
- "en-AU",
- "en-CA",
- "en-GB",
- "es",
- "es-MX",
- "et",
- "fi",
- "fr",
- "fr-CH",
- "he",
- "hi",
- "hr",
- "hu",
- "id",
- "it",
- "it-CH",
- "ja",
- "ko",
- "lt",
- "lv",
- "mk",
- "nb",
- "nl",
- "pl",
- "pt",
- "pt-PT",
- "ro",
- "ru",
- "sk",
- "sl",
- "sr",
- "sv",
- "th",
- "tr",
- "uk",
- "vi",
- "zh-CN",
- "zh-HK",
- "zh-TW"
- ];
- export const numberingSystems = [
- "arab",
- "arabext",
- "bali",
- "beng",
- "deva",
- "fullwide",
- "gujr",
- "guru",
- "hanidec",
- "khmr",
- "knda",
- "laoo",
- "latn",
- "limb",
- "mlym",
- "mong",
- "mymr",
- "orya",
- "tamldec",
- "telu",
- "thai",
- "tibt"
- ];
- const isNumberingSystemSupported = (numberingSystem) => numberingSystems.includes(numberingSystem);
- const browserNumberingSystem = new Intl.NumberFormat().resolvedOptions().numberingSystem;
- export const defaultNumberingSystem = browserNumberingSystem === "arab" || !isNumberingSystemSupported(browserNumberingSystem)
- ? "latn"
- : browserNumberingSystem;
- export const getSupportedNumberingSystem = (numberingSystem) => isNumberingSystemSupported(numberingSystem) ? numberingSystem : defaultNumberingSystem;
- export function getSupportedLocale(locale) {
- if (locales.indexOf(locale) > -1) {
- return locale;
- }
- if (!locale) {
- return defaultLocale;
- }
- locale = locale.toLowerCase();
- // we support both 'nb' and 'no' (BCP 47) for Norwegian
- if (locale === "nb") {
- return "no";
- }
- if (locale.includes("-")) {
- locale = locale.replace(/(\w+)-(\w+)/, (_match, language, region) => `${language}-${region.toUpperCase()}`);
- if (!locales.includes(locale)) {
- locale = locale.split("-")[0];
- }
- }
- return locales.includes(locale) ? locale : defaultLocale;
- }
- const connectedComponents = new Set();
- /**
- * This utility sets up internals for messages support.
- *
- * It needs to be called in `connectedCallback` before any logic that depends on locale
- *
- * @param component
- */
- export function connectLocalized(component) {
- updateEffectiveLocale(component);
- if (connectedComponents.size === 0) {
- mutationObserver.observe(document.documentElement, {
- attributes: true,
- attributeFilter: ["lang"],
- subtree: true
- });
- }
- connectedComponents.add(component);
- }
- /**
- * This is only exported for components that implemented the now deprecated `locale` prop.
- *
- * Do not use this utils for new components.
- *
- * @param component
- */
- export function updateEffectiveLocale(component) {
- component.effectiveLocale = getLocale(component);
- }
- /**
- * This utility tears down internals for messages support.
- *
- * It needs to be called in `disconnectedCallback`
- *
- * @param component
- */
- export function disconnectLocalized(component) {
- connectedComponents.delete(component);
- if (connectedComponents.size === 0) {
- mutationObserver.disconnect();
- }
- }
- const mutationObserver = createObserver("mutation", (records) => {
- records.forEach((record) => {
- const el = record.target;
- connectedComponents.forEach((component) => {
- const hasOverridingLocale = !!(component.locale && !component.el.lang);
- const inUnrelatedSubtree = !containsCrossShadowBoundary(el, component.el);
- if (hasOverridingLocale || inUnrelatedSubtree) {
- return;
- }
- const closestLangEl = closestElementCrossShadowBoundary(component.el, "[lang]");
- if (!closestLangEl) {
- component.effectiveLocale = defaultLocale;
- return;
- }
- const closestLang = closestLangEl.lang;
- component.effectiveLocale =
- // user set lang="" means unknown language, so we use default
- closestLangEl.hasAttribute("lang") && closestLang === "" ? defaultLocale : closestLang;
- });
- });
- });
- /**
- * This util helps resolve a component's locale.
- * It will also fall back on the deprecated `locale` if a component implemented this previously.
- *
- * @param component
- */
- function getLocale(component) {
- var _a;
- return (component.el.lang ||
- component.locale ||
- ((_a = closestElementCrossShadowBoundary(component.el, "[lang]")) === null || _a === void 0 ? void 0 : _a.lang) ||
- document.documentElement.lang ||
- defaultLocale);
- }
- /**
- * This util formats and parses numbers for localization
- */
- class NumberStringFormat {
- constructor() {
- this.delocalize = (numberString) =>
- // For performance, (de)localization is skipped if the formatter isn't initialized.
- // In order to localize/delocalize, e.g. when lang/numberingSystem props are not default values,
- // `numberFormatOptions` must be set in a component to create and cache the formatter.
- this._numberFormatOptions
- ? sanitizeExponentialNumberString(numberString, (nonExpoNumString) => nonExpoNumString
- .trim()
- .replace(new RegExp(`[${this._minusSign}]`, "g"), "-")
- .replace(new RegExp(`[${this._group}]`, "g"), "")
- .replace(new RegExp(`[${this._decimal}]`, "g"), ".")
- .replace(new RegExp(`[${this._digits.join("")}]`, "g"), this._getDigitIndex))
- : numberString;
- this.localize = (numberString) => this._numberFormatOptions
- ? sanitizeExponentialNumberString(numberString, (nonExpoNumString) => isValidNumber(nonExpoNumString.trim())
- ? new BigDecimal(nonExpoNumString.trim())
- .format(this._numberFormatter)
- .replace(new RegExp(`[${this._actualGroup}]`, "g"), this._group)
- : nonExpoNumString)
- : numberString;
- }
- get group() {
- return this._group;
- }
- get decimal() {
- return this._decimal;
- }
- get minusSign() {
- return this._minusSign;
- }
- get digits() {
- return this._digits;
- }
- get numberFormatter() {
- return this._numberFormatter;
- }
- get numberFormatOptions() {
- return this._numberFormatOptions;
- }
- /**
- * numberFormatOptions needs to be set before localize/delocalize is called to ensure the options are up to date
- */
- set numberFormatOptions(options) {
- options.locale = getSupportedLocale(options === null || options === void 0 ? void 0 : options.locale);
- options.numberingSystem = getSupportedNumberingSystem(options === null || options === void 0 ? void 0 : options.numberingSystem);
- if (
- // No need to create the formatter if `locale` and `numberingSystem`
- // are the default values and `numberFormatOptions` has not been set
- (!this._numberFormatOptions &&
- options.locale === defaultLocale &&
- options.numberingSystem === defaultNumberingSystem &&
- // don't skip initialization if any options besides locale/numberingSystem are set
- Object.keys(options).length === 2) ||
- // cache formatter by only recreating when options change
- JSON.stringify(this._numberFormatOptions) === JSON.stringify(options)) {
- return;
- }
- this._numberFormatOptions = options;
- this._numberFormatter = new Intl.NumberFormat(this._numberFormatOptions.locale, this._numberFormatOptions);
- this._digits = [
- ...new Intl.NumberFormat(this._numberFormatOptions.locale, {
- useGrouping: false,
- numberingSystem: this._numberFormatOptions.numberingSystem
- }).format(9876543210)
- ].reverse();
- const index = new Map(this._digits.map((d, i) => [d, i]));
- const parts = new Intl.NumberFormat(this._numberFormatOptions.locale).formatToParts(-12345678.9);
- this._actualGroup = parts.find((d) => d.type === "group").value;
- // change whitespace group characters that don't render correctly
- this._group = this._actualGroup.trim().length === 0 ? " " : this._actualGroup;
- this._decimal = parts.find((d) => d.type === "decimal").value;
- this._minusSign = parts.find((d) => d.type === "minusSign").value;
- this._getDigitIndex = (d) => index.get(d);
- }
- }
- export const numberStringFormatter = new NumberStringFormat();
|