From 5a1a3d07101da6ed7453070d233dcbb1c0562364 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 29 Dec 2023 16:16:56 -0400 Subject: data: language: preserve live values through list formatters --- src/data/things/language.js | 72 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/data/things/language.js b/src/data/things/language.js index cc2e5bc3..f91c981d 100644 --- a/src/data/things/language.js +++ b/src/data/things/language.js @@ -523,35 +523,83 @@ export class Language extends Thing { return this.formatString('count.words.withUnit.' + this.getUnitForm(value), {words}); } + #formatListHelper(array, processFn) { + // Operate on "insertion markers" instead of the actual contents of the + // array, because the process function (likely an Intl operation) is taken + // to only operate on strings. We'll insert the contents of the array back + // at these points afterwards. + const insertionMarkers = + Array.from( + {length: array.length}, + (_item, index) => `<::insertion_${index}>`); + + const template = processFn(insertionMarkers); + + // Basically the same insertion logic as in formatString. Like there, we + // can't assume that insertion markers were kept in the same order as they + // were provided, so we'll refer to the marked index. But we don't need to + // worry about some of the indices *not* corresponding to a provided source + // item, like we do in formatString, so that cuts out a lot of the + // validation logic. + + const outputParts = []; + + const insertionMarkerRegexp = /<::insertion_(?[0-9]+)>/g; + let lastIndex = 0; + + for (const match of template.matchAll(insertionMarkerRegexp)) { + const markerIndex = match.groups.index; + const markerValue = array[markerIndex]; + + const languageText = template.slice(lastIndex, match.index); + const markerPart = this.#sanitizeStringArg(markerValue, { + // TODO: Won't need to specify preserveType. + preserveType: true, + }); + + outputParts.push(languageText); + outputParts.push(markerPart); + + lastIndex = match.index + match[0].length; + } + + if (lastIndex < template.length) { + const lastLanguageText = template.slice(lastIndex); + outputParts.push(lastLanguageText); + } + + return this.#wrapSanitized(outputParts); + } + // Conjunction list: A, B, and C formatConjunctionList(array) { this.assertIntlAvailable('intl_listConjunction'); - return this.#wrapSanitized( - this.intl_listConjunction.format( - array.map(item => this.#sanitizeStringArg(item)))); + return this.#formatListHelper( + array, + array => this.intl_listConjunction.format(array)); } // Disjunction lists: A, B, or C formatDisjunctionList(array) { this.assertIntlAvailable('intl_listDisjunction'); - return this.#wrapSanitized( - this.intl_listDisjunction.format( - array.map(item => this.#sanitizeStringArg(item)))); + return this.#formatListHelper( + array, + array => this.intl_listDisjunction.format(array)); } // Unit lists: A, B, C formatUnitList(array) { this.assertIntlAvailable('intl_listUnit'); - return this.#wrapSanitized( - this.intl_listUnit.format( - array.map(item => this.#sanitizeStringArg(item)))); + return this.#formatListHelper( + array, + array => this.intl_listUnit.format(array)); } // Lists without separator: A B C formatListWithoutSeparator(array) { - return this.#wrapSanitized( - array.map(item => this.#sanitizeStringArg(item)) - .join(' ')); + return this.#formatListHelper( + array, + array => array.join(' ')); } // File sizes: 42.5 kB, 127.2 MB, 4.13 GB, 998.82 TB -- cgit 1.3.0-6-gf8a5