diff options
Diffstat (limited to 'src/data/things/language.js')
-rw-r--r-- | src/data/things/language.js | 66 |
1 files changed, 54 insertions, 12 deletions
diff --git a/src/data/things/language.js b/src/data/things/language.js index 93ed40b..dbe1ff3 100644 --- a/src/data/things/language.js +++ b/src/data/things/language.js @@ -2,6 +2,7 @@ import { Temporal, toTemporalInstant } from '@js-temporal/polyfill'; import {withAggregate} from '#aggregate'; import CacheableObject from '#cacheable-object'; +import {logWarn} from '#cli'; import * as html from '#html'; import {empty} from '#sugar'; import {isLanguageCode} from '#validators'; @@ -17,6 +18,8 @@ import { import {externalFunction, flag, name} from '#composite/wiki-properties'; +export const languageOptionRegex = /{(?<name>[A-Z0-9_]+)}/g; + export class Language extends Thing { static [Thing.getPropertyDescriptors] = () => ({ // Update & expose @@ -60,14 +63,46 @@ export class Language extends Thing { strings: { flags: {update: true, expose: true}, update: {validate: (t) => typeof t === 'object'}, + expose: { - dependencies: ['inheritedStrings'], - transform(strings, {inheritedStrings}) { - if (strings || inheritedStrings) { - return {...(inheritedStrings ?? {}), ...(strings ?? {})}; - } else { - return null; + dependencies: ['inheritedStrings', 'code'], + transform(strings, {inheritedStrings, code}) { + if (!strings && !inheritedStrings) return null; + if (!inheritedStrings) return strings; + + const validStrings = { + ...inheritedStrings, + ...strings, + }; + + const optionsFromTemplate = template => + Array.from(template.matchAll(languageOptionRegex)) + .map(({groups}) => groups.name); + + for (const [key, providedTemplate] of Object.entries(strings)) { + const inheritedTemplate = inheritedStrings[key]; + if (!inheritedTemplate) continue; + + const providedOptions = optionsFromTemplate(providedTemplate); + const inheritedOptions = optionsFromTemplate(inheritedTemplate); + + const missingOptionNames = + inheritedOptions.filter(name => !providedOptions.includes(name)); + + const misplacedOptionNames = + providedOptions.filter(name => !inheritedOptions.includes(name)); + + if (!empty(missingOptionNames) || !empty(misplacedOptionNames)) { + logWarn`Not using ${code ?? '(no code)'} string ${key}:`; + if (!empty(missingOptionNames)) + logWarn`- Missing options: ${missingOptionNames.join(', ')}`; + if (!empty(misplacedOptionNames)) + logWarn`- Unexpected options: ${misplacedOptionNames.join(', ')}`; + validStrings[key] = inheritedStrings[key]; + } } + + return validStrings; }, }, }, @@ -201,7 +236,7 @@ export class Language extends Thing { const output = this.#iterateOverTemplate({ template: this.strings[key], - match: /{(?<name>[A-Z0-9_]+)}/g, + match: languageOptionRegex, insert: ({name: optionName}, canceledForming) => { if (optionsMap.has(optionName)) { @@ -357,6 +392,7 @@ export class Language extends Thing { // contents, if needed. #wrapSanitized(content) { return html.tags(content, { + [html.blessAttributes]: true, [html.joinChildren]: '', [html.noEdgeWhitespace]: true, }); @@ -522,7 +558,7 @@ export class Language extends Thing { } formatExternalLink(url, { - style = 'normal', + style = 'platform', context = 'generic', } = {}) { if (!this.externalLinkSpec) { @@ -540,10 +576,16 @@ export class Language extends Thing { isExternalLinkStyle(style); - return getExternalLinkStringOfStyleFromDescriptors(url, style, this.externalLinkSpec, { - language: this, - context, - }); + const result = + getExternalLinkStringOfStyleFromDescriptors(url, style, this.externalLinkSpec, { + language: this, + context, + }); + + // It's possible for there to not actually be any string available for the + // given URL, style, and context, and we want this to be detectable via + // html.blank(). + return result ?? html.blank(); } formatIndex(value) { |