const {
/**
- * Returns the provided value converted into either a plain string
- * primitive or an object with `.["@value"]` and `.["@language"]`
- * properties.
+ * Returns the provided value converted into a `String` object with
+ * `.["@value"]` and `.["@language"]` properties.
+ *
+ * The same object will be returned for every call with an equivalent
+ * value.
*
* TODO: Ideally this would be extracted more fully into an R·D·F
* library.
*/
langString,
} = (() => {
+ /**
+ * Returns the language string object corresponding to the provided
+ * value and language.
+ */
+ const getLangString = (value, language = "") => {
+ const valueMap = languageMap[language] ??= Object.create(null);
+ const literal = valueMap[value]?.deref();
+ if (literal != null) {
+ // There is already an object corresponding to the provided value
+ // and language.
+ return literal;
+ } else {
+ // No object already exists corresponding to the provided value
+ // and language; create one.
+ const result = Object.preventExtensions(
+ Object.create(String.prototype, {
+ "@value": {
+ enumerable: true,
+ value,
+ },
+ "@language": {
+ enumerable: !!language,
+ value: language || null,
+ },
+ language: { enumerable: false, get: getLanguage },
+ toString: { enumerable: false, value: toString },
+ valueOf: { enumerable: false, value: valueOf },
+ }),
+ );
+ const ref = new WeakRef(result);
+ langStringRegistry.register(result, { ref, language, value });
+ valueMap[value] = ref;
+ return result;
+ }
+ };
+
/** Returns the `.["@language"]` of this object. */
const getLanguage = Object.defineProperty(
function () {
- return this["@language"];
+ return this["@language"] || null;
},
"name",
{ value: "get language" },
);
+ /**
+ * A `FinalizationRegistry` for language string objects.
+ *
+ * This simply cleans up the corresponding `WeakRef` in the language
+ * map.
+ */
+ const langStringRegistry = new FinalizationRegistry(
+ ({ ref, language, value }) => {
+ const valueMap = languageMap[language];
+ if (valueMap?.[value] === ref) {
+ delete valueMap[value];
+ } else {
+ /* do nothing */
+ }
+ },
+ );
+
+ /**
+ * An object whose own values are an object mapping values to
+ * language string objects for the language specified by the key.
+ */
+ const languageMap = Object.create(null);
+
/** Returns the `.["@value"]` of this object. */
const toString = function () {
return this["@value"];
};
- /** Returns the `.["@value"]` of this object. */
+ /**
+ * Returns this object if it has a `.["@language"]`; otherwise, its
+ * `.["@value"]`.
+ */
const valueOf = function () {
- return this["@value"];
+ return this["@language"] ? this : this["@value"];
};
return {
Object($) === $
? "@value" in $
? "@language" in $
- ? Object.preventExtensions(
- Object.create(String.prototype, {
- "@value": {
- enumerable: true,
- value: `${$["@value"]}`,
- },
- "@language": {
- enumerable: true,
- value: `${$["@language"]}`,
- },
- language: { enumerable: false, get: getLanguage },
- toString: { enumerable: false, value: toString },
- valueOf: { enumerable: false, value: valueOf },
- }),
+ ? getLangString(
+ `${$["@value"]}`,
+ `${$["@language"] ?? ""}`,
)
- : `${$["@value"]}`
+ : getLangString(`${$["@value"]}`)
: "language" in $
- ? Object.preventExtensions(
- Object.create(String.prototype, {
- "@value": { enumerable: true, value: `${$}` },
- "@language": {
- enumerable: true,
- value: `${$.language}`,
- },
- language: { enumerable: false, get: getLanguage },
- toString: { enumerable: false, value: toString },
- valueOf: { enumerable: false, value: valueOf },
- }),
- )
- : `${$}`
- : `${$ ?? ""}`,
+ ? getLangString(`${$}`, `${$.language ?? ""}`)
+ : getLangString(`${$}`)
+ : getLangString(`${$ ?? ""}`),
};
})();