216 lines
6.1 KiB
JavaScript
216 lines
6.1 KiB
JavaScript
|
// Top-level application container
|
||
|
export default (Toolkit,_package)=>class App extends Toolkit.Component {
|
||
|
|
||
|
// Instance fields
|
||
|
#components; // Registered Toolkit.Components
|
||
|
#locale; // Current display text dictionary
|
||
|
#locales; // Registered display text dictionaries
|
||
|
#title; // Application title
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////// Initialization Methods //////////////////////////
|
||
|
|
||
|
static {
|
||
|
_package.App = {
|
||
|
localize: (a,c)=>a.#localize(c),
|
||
|
onCreate: (a,c)=>a.#onCreate(c),
|
||
|
onDelete: (a,c)=>a.#onDelete(c)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
constructor(overrides) {
|
||
|
super(null, _package.override(overrides, { className: "tk-app" }));
|
||
|
|
||
|
this.#components = new Set();
|
||
|
this.#locale = null;
|
||
|
this.#locales = new Map();
|
||
|
this.#title = null;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/////////////////////////////// Properties ////////////////////////////////
|
||
|
|
||
|
// Display text dictionary
|
||
|
get locale() { return this.#locale?.get("id"); }
|
||
|
set locale(value) {
|
||
|
|
||
|
// Unset the locale
|
||
|
if (value == null) {
|
||
|
if (this.#locale == null)
|
||
|
return;
|
||
|
this.#locale = null;
|
||
|
}
|
||
|
|
||
|
// Change to a different locale
|
||
|
else {
|
||
|
value = value == null ? null : String(value);
|
||
|
if (this.#locale?.get("id") == value)
|
||
|
return; // Same locale specified
|
||
|
if (!this.#locales.has(value))
|
||
|
throw new RangeError("No locale with ID " + value);
|
||
|
this.#locale = this.#locales.get(value);
|
||
|
}
|
||
|
|
||
|
// Update all display text
|
||
|
this.#localize();
|
||
|
}
|
||
|
|
||
|
// Display title
|
||
|
get title() { return this.#title; }
|
||
|
set title(value) {
|
||
|
if (value != null && !(value instanceof RegExp))
|
||
|
value = String(value);
|
||
|
this.#title = value;
|
||
|
this.#onLocalize();
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Public Methods //////////////////////////////
|
||
|
|
||
|
// Register translation text
|
||
|
addLocale(data) {
|
||
|
|
||
|
// Error checking
|
||
|
if (!(data instanceof Object))
|
||
|
throw new TypeError("Data must be an object.");
|
||
|
|
||
|
// Working variables
|
||
|
let locale = new Map();
|
||
|
let objects = [ [null,data] ];
|
||
|
|
||
|
// Process all objects
|
||
|
while (objects.length != 0) {
|
||
|
let object = objects.shift();
|
||
|
let prefix = object[0] ? object[0] + "." : "";
|
||
|
|
||
|
// Process all members of the object
|
||
|
for (let entry of Object.entries(object[1])) {
|
||
|
let key = prefix + entry[0];
|
||
|
let value = entry[1];
|
||
|
|
||
|
// Add the new object to he queue
|
||
|
if (value instanceof Object) {
|
||
|
objects.push([ key, entry[1] ]);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Error checking
|
||
|
if (typeof(value) != "string")
|
||
|
throw new TypeError("Non-string value encountered: "+key);
|
||
|
|
||
|
// Register the localization value
|
||
|
locale.set(key, new RegExp(value));
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Validate "id"
|
||
|
let id = locale.get("id");
|
||
|
if (id == null)
|
||
|
throw new Error("Locale does not contain \"id\" member.");
|
||
|
|
||
|
// Register the locale
|
||
|
this.#locales.set(id.source, locale);
|
||
|
return id;
|
||
|
}
|
||
|
|
||
|
// Resolve the value of some display text
|
||
|
translate(text, comp = null) {
|
||
|
|
||
|
// Error checking
|
||
|
if (comp != null) {
|
||
|
if (!(comp instanceof Toolkit.Component))
|
||
|
throw new TypeError("Component must be a Toolkit.Component.");
|
||
|
if (comp != this && comp.app != this)
|
||
|
throw new RangeError("Compoment must belong to this App.");
|
||
|
}
|
||
|
|
||
|
// Nothing to resolve
|
||
|
if (text == null)
|
||
|
return null;
|
||
|
|
||
|
// Input validation
|
||
|
if (!(text instanceof RegExp))
|
||
|
text = String(text);
|
||
|
|
||
|
// Working variables
|
||
|
let locale = this.#locale;
|
||
|
let ret = [ text ];
|
||
|
|
||
|
// Process all substitutions
|
||
|
for (let x = 0; x < ret.length; x++) {
|
||
|
let part = ret[x];
|
||
|
|
||
|
// Do not perform substitutions
|
||
|
if (!(part instanceof RegExp))
|
||
|
continue;
|
||
|
|
||
|
// Working variables
|
||
|
part = part.source;
|
||
|
|
||
|
// Locate the close of the innermost substitution
|
||
|
let close = part.indexOf("}}");
|
||
|
if (close == -1) {
|
||
|
if (part.indexOf("{{") != -1)
|
||
|
throw new Error("Found {{ without matching }}.");
|
||
|
ret[x] = part;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Locate the opening of the innermost substitution
|
||
|
let open = part.substring(0, close).lastIndexOf("{{");
|
||
|
if (open == -1)
|
||
|
throw new Error("Found }} without matching {{.");
|
||
|
|
||
|
// Working variables
|
||
|
let after = part.substring(close + 2);
|
||
|
let before = part.substring(0, open);
|
||
|
let key = part.substring(open + 2, close).trim();
|
||
|
let value = comp?.getSubstitution(key) ?? locale?.get(key) ??
|
||
|
"{{\u00d7" + key.toUpperCase() + "\u00d7}}";
|
||
|
let within = value instanceof RegExp ? value.source : value;
|
||
|
|
||
|
// Compose the replacement text
|
||
|
part = before + within + after;
|
||
|
if (value instanceof RegExp)
|
||
|
ret[x--] = new RegExp(part);
|
||
|
else ret[x] = part;
|
||
|
}
|
||
|
|
||
|
return ret.join("");
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Event Handlers //////////////////////////////
|
||
|
|
||
|
// Component created
|
||
|
#onCreate(comp) {
|
||
|
this.#components.add(comp);
|
||
|
}
|
||
|
|
||
|
// Component deleted
|
||
|
#onDelete(comp) {
|
||
|
this.#components.delete(comp);
|
||
|
}
|
||
|
|
||
|
// Configure display text
|
||
|
#onLocalize() {
|
||
|
document.title = this.translate(this.#title, this) ?? "";
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////// Package Methods /////////////////////////////
|
||
|
|
||
|
// Update the display text for one or all components
|
||
|
#localize(comp = null) {
|
||
|
for (comp of (comp == null ? this.#components : [comp]))
|
||
|
_package.Component.onLocalize(comp);
|
||
|
}
|
||
|
|
||
|
};
|