From 1264b87a90e906ae412dcf221b1cba5860c301dd Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 25 May 2026 03:23:53 -0400 Subject: [PATCH 1/4] Add localization for loader and startup via core/__locales__ --- components/mjs/core/config.json | 6 ++++++ components/mjs/loader/loader.js | 1 + components/mjs/startup/init.js | 1 + package.json | 8 +++++--- ts/components/loader.ts | 20 +++++++++----------- ts/components/package.ts | 14 ++++++++++++-- ts/components/startup.ts | 18 ++++++------------ ts/core/__locales__/Component.ts | 28 ++++++++++++++++++++++++++++ ts/core/__locales__/de.json | 9 +++++++++ ts/core/__locales__/en.json | 9 +++++++++ 10 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 ts/core/__locales__/Component.ts create mode 100644 ts/core/__locales__/de.json create mode 100644 ts/core/__locales__/en.json diff --git a/components/mjs/core/config.json b/components/mjs/core/config.json index 87e95dc00..bdf45f334 100644 --- a/components/mjs/core/config.json +++ b/components/mjs/core/config.json @@ -18,6 +18,12 @@ "util/entities" ] }, + "copy": { + "to": "[bundle]/core", + "from": "[ts]/core", + "copy": ["__locales__"], + "excludes": ["__locales__/Component.ts"] + }, "webpack": { "name": "core" } diff --git a/components/mjs/loader/loader.js b/components/mjs/loader/loader.js index f83c2b205..51f00bf9b 100644 --- a/components/mjs/loader/loader.js +++ b/components/mjs/loader/loader.js @@ -1,3 +1,4 @@ +import '../core/locale.js'; import './lib/loader.js'; import '../core/core.js'; diff --git a/components/mjs/startup/init.js b/components/mjs/startup/init.js index 4da1e1770..e51fbf1a4 100644 --- a/components/mjs/startup/init.js +++ b/components/mjs/startup/init.js @@ -1,4 +1,5 @@ import './hasown.js'; // Can be removed with ES2024 implementation of Object.hasown +import '../core/locale.js'; import './lib/startup.js'; import '../core/core.js'; diff --git a/package.json b/package.json index 01e89b577..9a87c4847 100644 --- a/package.json +++ b/package.json @@ -80,11 +80,13 @@ "clean:lib": "clean() { pnpm -s log:single \"Cleaning $1 component libs\"; pnpm rimraf -g components/$1'/**/lib'; }; clean", "clean:mod": "clean() { pnpm -s log:comp \"Cleaning $1 module\"; pnpm -s clean:dir $1 && pnpm -s clean:lib $1; }; clean", "=============================================================================== copy": "", - "copy:assets": "pnpm -s log:comp 'Copying assets'; copy() { pnpm -s copy:locales $1 && pnpm -s copy:mj2 $1 && pnpm -s copy:mml3 $1 && pnpm -s copy:html $1; }; copy", + "copy:assets": "pnpm -s log:comp 'Copying assets'; copy() { for name in locales mj2 mml3 html; do pnpm -s copy:$name ${1:-mjs}; done; }; copy", + "copy:bundle": "copy() { components/bin/makeAll --copy --terse components/mjs/${1:-}; }; copy", "copy:html": "copy() { pnpm -s log:single 'Copying sre auxiliary files'; pnpm copyfiles -u 1 'ts/a11y/sre/*.html' 'ts/a11y/sre/require.*' $1; }; copy", - "copy:locales": "copy() { pnpm -s copy:locales:menu $1; pnpm -s copy:locales:tex $1; }; copy ", + "copy:locales": "copy() { for name in core tex menu; do pnpm -s copy:locales:$name ${1:-mjs}; done; }; copy ", + "copy:locales:core": "pnpm -s log:single 'Copying core locales'; copy() { pnpm copyfiles -u 1 'ts/core/__locales__/*.json' $1; }; copy", "copy:locales:menu": "pnpm -s log:single 'Copying menu locales'; copy() { pnpm copyfiles -u 1 'ts/ui/menu/__locales__/*.json' $1; }; copy", - "copy:locales:tex": "pnpm -s log:single 'Copying TeX extension locales'; copy() { pnpm copyfiles -u 1 'ts/input/tex/__locales__/*.json' $1 && pnpm copyfiles -u 3 'ts/input/tex/*/__locales__/*.json' $1/input/tex/extensions; }; copy", + "copy:locales:tex": "pnpm -s log:single 'Copying TeX locales'; copy() { pnpm copyfiles -u 1 'ts/input/tex/__locales__/*.json' $1 && pnpm copyfiles -u 3 'ts/input/tex/*/__locales__/*.json' $1/input/tex/extensions; }; copy", "copy:mj2": "copy() { pnpm -s log:single 'Copying legacy code AsciiMath'; pnpm copyfiles -u 1 'ts/input/asciimath/legacy/**/*' $1; }; copy", "copy:mml3": "copy() { pnpm -s log:single 'Copying MathML3 extension json'; pnpm copyfiles -u 1 ts/input/mathml/mml3/mml3.sef.json $1; }; copy", "copy:pkg": "copy() { pnpm -s log:single \"Copying package.json to $1\"; pnpm copyfiles -u 2 components/bin/package.json $1; }; copy", diff --git a/ts/components/loader.ts b/ts/components/loader.ts index d29382859..a7b84664a 100644 --- a/ts/components/loader.ts +++ b/ts/components/loader.ts @@ -43,6 +43,8 @@ import { mjxRoot } from '#root/root.js'; import { context } from '../util/context.js'; import { Locale } from '../util/Locale.js'; +import { COMPONENT } from '../core/__locales__/Component.js'; + /** * Function used to determine path to a given package. */ @@ -187,7 +189,7 @@ export const Loader = { /** * Load the named packages and return a promise that is resolved when they are all loaded * - * @param {string[]} names The packages to load + * @param {string[]} names The packages to load * @returns {Promise} A promise that resolves when all the named packages are ready */ load(...names: string[]): Promise { @@ -224,9 +226,7 @@ export const Loader = { extension.isLoaded && !Loader.versions.has(Package.resolvePath(name)) ) { - console.warn( - `No version information available for component ${name}` - ); + Locale.warn(COMPONENT, 'NoVersionFor', name); } return extension.result; }) as Promise @@ -346,15 +346,13 @@ export const Loader = { * * @param {string} name The name of the extension being checked * @param {string} version The version of the extension to check - * @param {string} _type The type of extension (future code may use this to check ranges of versions) - * @returns {boolean} True if there was a mismatch, false otherwise + * @param {string} _type The type of extension (future code may use this to check ranges of versions) + * @returns {boolean} True if there was a mismatch, false otherwise */ checkVersion(name: string, version: string, _type?: string): boolean { this.saveVersion(name); if (CONFIG.versionWarnings && version !== VERSION) { - console.warn( - `Component ${name} uses ${version} of MathJax; version in use is ${VERSION}` - ); + Locale.warn(COMPONENT, 'WrongVersion', name, version, VERSION); return true; } return false; @@ -363,7 +361,7 @@ export const Loader = { /** * Set the version of an extension (used for combined components so they can be loaded) * - * @param {string} name The name of the extension being checked + * @param {string} name The name of the extension being checked */ saveVersion(name: string) { Loader.versions.set(Package.resolvePath(name), VERSION); @@ -407,7 +405,7 @@ if (typeof MathJax.loader === 'undefined') { load: [], ready: Loader.defaultReady.bind(Loader), failed: (error: PackageError) => - console.log(`MathJax(${error.package || '?'}): ${error.message}`), + console.warn(`MathJax(${error.package || '?'}): ${error.message}`), require: null, json: null, pathFilters: [], diff --git a/ts/components/package.ts b/ts/components/package.ts index c853c554c..3a9eecae4 100644 --- a/ts/components/package.ts +++ b/ts/components/package.ts @@ -24,6 +24,8 @@ import { CONFIG, Loader } from './loader.js'; import { context } from '../util/context.js'; +import { Locale } from '../util/Locale.js'; +import { COMPONENT } from '../core/__locales__/Component.js'; /** * A map of package names to Package instances @@ -323,7 +325,14 @@ export class Package { .then((result) => (this.result = result)) .then(() => this.checkLoad()) .catch((err) => - this.failed('Can\'t load "' + url + '"\n' + err.message.trim()) + this.failed( + Locale.message( + COMPONENT, + 'CantLoad', + url, + ':\n' + err.message.trim() + ) + ) ); } else { this.result = result; @@ -344,7 +353,8 @@ export class Package { script.src = url; script.charset = 'UTF-8'; script.onload = (_event) => this.checkLoad(); - script.onerror = (_event) => this.failed('Can\'t load "' + url + '"'); + script.onerror = (_event) => + this.failed(Locale.message(COMPONENT, 'CantLoad', url, '')); // FIXME: Should there be a timeout failure as well? context.document.head.appendChild(script); } diff --git a/ts/components/startup.ts b/ts/components/startup.ts index 00e7be451..24ffd0821 100644 --- a/ts/components/startup.ts +++ b/ts/components/startup.ts @@ -43,6 +43,8 @@ import { DOMAdaptor } from '../core/DOMAdaptor.js'; import { PrioritizedList } from '../util/PrioritizedList.js'; import { OptionList, OPTIONS } from '../util/Options.js'; import { context } from '../util/context.js'; +import { Locale } from '../util/Locale.js'; +import { COMPONENT } from '../core/__locales__/Component.js'; import { TeX } from '../input/tex.js'; @@ -527,9 +529,7 @@ export abstract class Startup { jax[name] = new inputClass(MathJax.config[name]); jax.push(jax[name]); } else { - throw Error( - 'Input Jax "' + name + '" is not defined (has it been loaded?)' - ); + Locale.throw(COMPONENT, 'InputJaxNotDefined', name); } } return jax; @@ -543,9 +543,7 @@ export abstract class Startup { if (!name) return null; const outputClass = Startup.constructors[name]; if (!outputClass) { - throw Error( - 'Output Jax "' + name + '" is not defined (has it been loaded?)' - ); + Locale.throw(COMPONENT, 'OutputJaxNotDefined', name); } return new outputClass(MathJax.config[name]); } @@ -559,9 +557,7 @@ export abstract class Startup { if (!name || name === 'none') return null; const adaptor = Startup.constructors[name]; if (!adaptor) { - throw Error( - 'DOMAdaptor "' + name + '" is not defined (has it been loaded?)' - ); + Locale.throw(COMPONENT, 'AdaptorNotDefined', name); } return adaptor(MathJax.config[name]); } @@ -574,9 +570,7 @@ export abstract class Startup { if (!name || name === 'none' || !Startup.adaptor) return null; const handlerClass = Startup.constructors[name]; if (!handlerClass) { - throw Error( - 'Handler "' + name + '" is not defined (has it been loaded?)' - ); + Locale.throw(COMPONENT, 'HandlerNotDefined', name); } let handler = new handlerClass(Startup.adaptor, 5); for (const extend of Startup.extensions) { diff --git a/ts/core/__locales__/Component.ts b/ts/core/__locales__/Component.ts new file mode 100644 index 000000000..a32c85952 --- /dev/null +++ b/ts/core/__locales__/Component.ts @@ -0,0 +1,28 @@ +/************************************************************* + * + * Copyright (c) 2026 The MathJax Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file Locale component registration for core component + * + * @author v.sorge@mathjax.org (Volker Sorge) + */ + +import { Locale } from '../../util/Locale.js'; + +export const COMPONENT = 'core'; + +Locale.registerLocaleFiles(COMPONENT, '../ts/core'); diff --git a/ts/core/__locales__/de.json b/ts/core/__locales__/de.json new file mode 100644 index 000000000..0bdd11348 --- /dev/null +++ b/ts/core/__locales__/de.json @@ -0,0 +1,9 @@ +{ + "NoVersionFor": "Für die Komponente '%1' liegen keine Versionsinformationen vor", + "WrongVersion": "Die Komponente %1 verwendet Version %2 von MathJax; die verwendete Version ist %3", + "CantLoad": "'%1' kann nicht geladen werden%2", + "InputJaxNotDefined": "Input Jax '%1' ist nicht definiert (wurde es geladen?)", + "OutputJaxNotDefined": "Output Jax '%1' ist nicht definiert (wurde es geladen?)", + "AdaptorNotDefined": "DOMAdaptor '%1' ist nicht definiert (wurde es geladen?)", + "HandlerNotDefined": "Handler '%1' ist nicht definiert (wurde es geladen?)" +} diff --git a/ts/core/__locales__/en.json b/ts/core/__locales__/en.json new file mode 100644 index 000000000..54af9d217 --- /dev/null +++ b/ts/core/__locales__/en.json @@ -0,0 +1,9 @@ +{ + "NoVersionFor": "No version information available for component '%1'", + "WrongVersion": "Component %1 uses version %2 of MathJax; version in use is %3", + "CantLoad": "Can't load '%1'%2", + "InputJaxNotDefined": "Input Jax '%1' is not defined (has it been loaded?)", + "OutputJaxNotDefined": "Output Jax '%1' is not defined (has it been loaded?)", + "AdaptorNotDefined": "DOMAdaptor '%1' is not defined (has it been loaded?)", + "HandlerNotDefined": "Handler '%1' is not defined (has it been loaded?)" +} From 0ed9392d5d55daad4dba988167fea546549595aa Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 25 May 2026 14:09:49 -0400 Subject: [PATCH 2/4] sort locale files --- ts/core/__locales__/de.json | 8 ++++---- ts/core/__locales__/en.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ts/core/__locales__/de.json b/ts/core/__locales__/de.json index 0bdd11348..ed79631af 100644 --- a/ts/core/__locales__/de.json +++ b/ts/core/__locales__/de.json @@ -1,9 +1,9 @@ { - "NoVersionFor": "Für die Komponente '%1' liegen keine Versionsinformationen vor", - "WrongVersion": "Die Komponente %1 verwendet Version %2 von MathJax; die verwendete Version ist %3", + "AdaptorNotDefined": "DOMAdaptor '%1' ist nicht definiert (wurde es geladen?)", "CantLoad": "'%1' kann nicht geladen werden%2", + "HandlerNotDefined": "Handler '%1' ist nicht definiert (wurde es geladen?)" "InputJaxNotDefined": "Input Jax '%1' ist nicht definiert (wurde es geladen?)", + "NoVersionFor": "Für die Komponente '%1' liegen keine Versionsinformationen vor", "OutputJaxNotDefined": "Output Jax '%1' ist nicht definiert (wurde es geladen?)", - "AdaptorNotDefined": "DOMAdaptor '%1' ist nicht definiert (wurde es geladen?)", - "HandlerNotDefined": "Handler '%1' ist nicht definiert (wurde es geladen?)" + "WrongVersion": "Die Komponente %1 verwendet Version %2 von MathJax; die verwendete Version ist %3", } diff --git a/ts/core/__locales__/en.json b/ts/core/__locales__/en.json index 54af9d217..372526d9e 100644 --- a/ts/core/__locales__/en.json +++ b/ts/core/__locales__/en.json @@ -1,9 +1,9 @@ { - "NoVersionFor": "No version information available for component '%1'", - "WrongVersion": "Component %1 uses version %2 of MathJax; version in use is %3", + "AdaptorNotDefined": "DOMAdaptor '%1' is not defined (has it been loaded?)", "CantLoad": "Can't load '%1'%2", + "HandlerNotDefined": "Handler '%1' is not defined (has it been loaded?)" "InputJaxNotDefined": "Input Jax '%1' is not defined (has it been loaded?)", + "NoVersionFor": "No version information available for component '%1'", "OutputJaxNotDefined": "Output Jax '%1' is not defined (has it been loaded?)", - "AdaptorNotDefined": "DOMAdaptor '%1' is not defined (has it been loaded?)", - "HandlerNotDefined": "Handler '%1' is not defined (has it been loaded?)" + "WrongVersion": "Component %1 uses version %2 of MathJax; version in use is %3", } From c54df41e49a132f213d3c85b34ab5bedeb3334d9 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 25 May 2026 14:19:15 -0400 Subject: [PATCH 3/4] Add a localize() method for core --- ts/components/package.ts | 15 +++------------ ts/core/__locales__/Component.ts | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/ts/components/package.ts b/ts/components/package.ts index 3a9eecae4..0948fe599 100644 --- a/ts/components/package.ts +++ b/ts/components/package.ts @@ -24,8 +24,7 @@ import { CONFIG, Loader } from './loader.js'; import { context } from '../util/context.js'; -import { Locale } from '../util/Locale.js'; -import { COMPONENT } from '../core/__locales__/Component.js'; +import { localize } from '../core/__locales__/Component.js'; /** * A map of package names to Package instances @@ -325,14 +324,7 @@ export class Package { .then((result) => (this.result = result)) .then(() => this.checkLoad()) .catch((err) => - this.failed( - Locale.message( - COMPONENT, - 'CantLoad', - url, - ':\n' + err.message.trim() - ) - ) + this.failed(localize('CantLoad', url, ':\n' + err.message.trim())) ); } else { this.result = result; @@ -353,8 +345,7 @@ export class Package { script.src = url; script.charset = 'UTF-8'; script.onload = (_event) => this.checkLoad(); - script.onerror = (_event) => - this.failed(Locale.message(COMPONENT, 'CantLoad', url, '')); + script.onerror = (_event) => this.failed(localize('CantLoad', url, '')); // FIXME: Should there be a timeout failure as well? context.document.head.appendChild(script); } diff --git a/ts/core/__locales__/Component.ts b/ts/core/__locales__/Component.ts index a32c85952..030026917 100644 --- a/ts/core/__locales__/Component.ts +++ b/ts/core/__locales__/Component.ts @@ -18,11 +18,22 @@ /** * @file Locale component registration for core component * - * @author v.sorge@mathjax.org (Volker Sorge) + * @author dpvc@mathjax.org (Davide P. Cervone) */ -import { Locale } from '../../util/Locale.js'; +import { Locale, namedData } from '../../util/Locale.js'; export const COMPONENT = 'core'; Locale.registerLocaleFiles(COMPONENT, '../ts/core'); + +/** + * Get a localized message for this component + * + * @param {string} id The id of the message + * @param {(string|namedData)[]} args The replacement arguments for the message, if any + * @returns {string} The localized message + */ +export function localize(id: string, ...args: (string | namedData)[]): string { + return Locale.message(COMPONENT, id, ...args); +} From ce5c107f3486b87b62b76992568f86b0d47255bd Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Mon, 25 May 2026 14:23:22 -0400 Subject: [PATCH 4/4] Fix sorted locale files --- ts/core/__locales__/de.json | 4 ++-- ts/core/__locales__/en.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ts/core/__locales__/de.json b/ts/core/__locales__/de.json index ed79631af..067e6ea88 100644 --- a/ts/core/__locales__/de.json +++ b/ts/core/__locales__/de.json @@ -1,9 +1,9 @@ { "AdaptorNotDefined": "DOMAdaptor '%1' ist nicht definiert (wurde es geladen?)", "CantLoad": "'%1' kann nicht geladen werden%2", - "HandlerNotDefined": "Handler '%1' ist nicht definiert (wurde es geladen?)" + "HandlerNotDefined": "Handler '%1' ist nicht definiert (wurde es geladen?)", "InputJaxNotDefined": "Input Jax '%1' ist nicht definiert (wurde es geladen?)", "NoVersionFor": "Für die Komponente '%1' liegen keine Versionsinformationen vor", "OutputJaxNotDefined": "Output Jax '%1' ist nicht definiert (wurde es geladen?)", - "WrongVersion": "Die Komponente %1 verwendet Version %2 von MathJax; die verwendete Version ist %3", + "WrongVersion": "Die Komponente %1 verwendet Version %2 von MathJax; die verwendete Version ist %3" } diff --git a/ts/core/__locales__/en.json b/ts/core/__locales__/en.json index 372526d9e..2e186973a 100644 --- a/ts/core/__locales__/en.json +++ b/ts/core/__locales__/en.json @@ -1,9 +1,9 @@ { "AdaptorNotDefined": "DOMAdaptor '%1' is not defined (has it been loaded?)", "CantLoad": "Can't load '%1'%2", - "HandlerNotDefined": "Handler '%1' is not defined (has it been loaded?)" + "HandlerNotDefined": "Handler '%1' is not defined (has it been loaded?)", "InputJaxNotDefined": "Input Jax '%1' is not defined (has it been loaded?)", "NoVersionFor": "No version information available for component '%1'", "OutputJaxNotDefined": "Output Jax '%1' is not defined (has it been loaded?)", - "WrongVersion": "Component %1 uses version %2 of MathJax; version in use is %3", + "WrongVersion": "Component %1 uses version %2 of MathJax; version in use is %3" }