UNPKG

@formatjs/intl-listformat

Version:

Formats JS list in a i18n-safe way

218 lines (217 loc) 7.22 kB
import { CanonicalizeLocaleList, getInternalSlot, GetOption, GetOptionsObject, invariant, isLiteralPart, PartitionPattern, setInternalSlot, SupportedLocales } from "@formatjs/ecma402-abstract"; import { ResolveLocale } from "@formatjs/intl-localematcher"; function validateInstance(instance, method) { if (!(instance instanceof ListFormat)) { throw new TypeError(`Method Intl.ListFormat.prototype.${method} called on incompatible receiver ${String(instance)}`); } } /** * https://tc39.es/proposal-intl-list-format/#sec-createstringlistfromiterable * @param iterable list */ function stringListFromIterable(iterable) { if (typeof iterable !== "object") return []; const elements = []; const iterator = iterable[Symbol.iterator](); let result; while (true) { result = iterator.next(); if (result.done) break; if (typeof result.value !== "string") { const nextValue = result.value; throw new TypeError(`Iterable yielded ${nextValue} which is not a string`); } elements.push(result.value); } return elements; } function createPartsFromList(internalSlotMap, lf, list) { const size = list.length; if (size === 0) { return []; } if (size === 2) { const pattern = getInternalSlot(internalSlotMap, lf, "templatePair"); const first = { type: "element", value: list[0] }; const second = { type: "element", value: list[1] }; return deconstructPattern(pattern, { "0": first, "1": second }); } const last = { type: "element", value: list[size - 1] }; let parts = last; let i = size - 2; while (i >= 0) { let pattern; if (i === 0) { pattern = getInternalSlot(internalSlotMap, lf, "templateStart"); } else if (i < size - 2) { pattern = getInternalSlot(internalSlotMap, lf, "templateMiddle"); } else { pattern = getInternalSlot(internalSlotMap, lf, "templateEnd"); } const head = { type: "element", value: list[i] }; parts = deconstructPattern(pattern, { "0": head, "1": parts }); i--; } return parts; } function deconstructPattern(pattern, placeables) { const patternParts = PartitionPattern(pattern); const result = []; for (const patternPart of patternParts) { const { type: part } = patternPart; if (isLiteralPart(patternPart)) { result.push({ type: "literal", value: patternPart.value }); } else { invariant(part in placeables, `${part} is missing from placables`); const subst = placeables[part]; if (Array.isArray(subst)) { result.push(...subst); } else { result.push(subst); } } } return result; } export default class ListFormat { constructor(locales, options) { // test262/test/intl402/ListFormat/constructor/constructor/newtarget-undefined.js // Cannot use `new.target` bc of IE11 & TS transpiles it to something else const newTarget = this && this instanceof ListFormat ? this.constructor : void 0; if (!newTarget) { throw new TypeError("Intl.ListFormat must be called with 'new'"); } setInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "initializedListFormat", true); const requestedLocales = CanonicalizeLocaleList(locales); const opt = Object.create(null); const opts = GetOptionsObject(options); const matcher = GetOption(opts, "localeMatcher", "string", ["best fit", "lookup"], "best fit"); opt.localeMatcher = matcher; const { localeData } = ListFormat; const r = ResolveLocale(ListFormat.availableLocales, requestedLocales, opt, ListFormat.relevantExtensionKeys, localeData, ListFormat.getDefaultLocale); setInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "locale", r.locale); const type = GetOption(opts, "type", "string", [ "conjunction", "disjunction", "unit" ], "conjunction"); setInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "type", type); const style = GetOption(opts, "style", "string", [ "long", "short", "narrow" ], "long"); setInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "style", style); const { dataLocale } = r; const dataLocaleData = localeData[dataLocale]; invariant(!!dataLocaleData, `Missing locale data for ${dataLocale}`); const dataLocaleTypes = dataLocaleData[type]; const templates = dataLocaleTypes[style]; setInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "templatePair", templates.pair); setInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "templateStart", templates.start); setInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "templateMiddle", templates.middle); setInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "templateEnd", templates.end); } format(elements) { validateInstance(this, "format"); let result = ""; const parts = createPartsFromList(ListFormat.__INTERNAL_SLOT_MAP__, this, stringListFromIterable(elements)); if (!Array.isArray(parts)) { return parts.value; } for (const p of parts) { result += p.value; } return result; } formatToParts(elements) { validateInstance(this, "format"); const parts = createPartsFromList(ListFormat.__INTERNAL_SLOT_MAP__, this, stringListFromIterable(elements)); if (!Array.isArray(parts)) { return [parts]; } const result = []; for (const part of parts) { result.push({ ...part }); } return result; } resolvedOptions() { validateInstance(this, "resolvedOptions"); return { locale: getInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "locale"), type: getInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "type"), style: getInternalSlot(ListFormat.__INTERNAL_SLOT_MAP__, this, "style") }; } static supportedLocalesOf(locales, options) { // test262/test/intl402/ListFormat/constructor/supportedLocalesOf/result-type.js return SupportedLocales(ListFormat.availableLocales, CanonicalizeLocaleList(locales), options); } static __addLocaleData(...data) { for (const { data: d, locale } of data) { const minimizedLocale = new Intl.Locale(locale).minimize().toString(); ListFormat.localeData[locale] = ListFormat.localeData[minimizedLocale] = d; ListFormat.availableLocales.add(minimizedLocale); ListFormat.availableLocales.add(locale); if (!ListFormat.__defaultLocale) { ListFormat.__defaultLocale = minimizedLocale; } } } static localeData = {}; static availableLocales = new Set(); static __defaultLocale = ""; static getDefaultLocale() { return ListFormat.__defaultLocale; } static relevantExtensionKeys = []; static polyfilled = true; static __INTERNAL_SLOT_MAP__ = new WeakMap(); } try { // IE11 does not have Symbol if (typeof Symbol !== "undefined") { Object.defineProperty(ListFormat.prototype, Symbol.toStringTag, { value: "Intl.ListFormat", writable: false, enumerable: false, configurable: true }); } // https://github.com/tc39/test262/blob/master/test/intl402/ListFormat/constructor/length.js Object.defineProperty(ListFormat.prototype.constructor, "length", { value: 0, writable: false, enumerable: false, configurable: true }); // https://github.com/tc39/test262/blob/master/test/intl402/ListFormat/constructor/supportedLocalesOf/length.js Object.defineProperty(ListFormat.supportedLocalesOf, "length", { value: 1, writable: false, enumerable: false, configurable: true }); } catch {}