Merge pull request #3573 from SillyTavern/extensions-i18n Add locale data loading for extensions

6905fcd0a4173af0f1666fc474cb893316d38547

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +65 -3Ignore whitespace
public/scripts/extensions.js+38 -2
@@ -7,7 +7,7 @@ import { renderTemplate, renderTemplateAsync } from './templates.js';
77import { delay, isSubsetOf, sanitizeSelector, setValueByPath } from './utils.js';
88import { getContext } from './st-context.js';
99import { isAdmin } from './user.js';
1010import { addLocaleData, getCurrentLocale, t } from './i18n.js';
1111import { debounce_timeout } from './constants.js';
1212import { accountStorage } from './util/AccountStorage.js';
1313
@@ -384,7 +384,7 @@ async function activateExtensions() {
384384 if (meetsModuleRequirements && !isDisabled) {
385385 try {
386386 console.debug('Activating extension', name);
387387 const promise = addExtensionLocale(name, manifest).finally(() => Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]));
388388 await promise
389389 .then(() => activeExtensions.add(name))
390390 .catch(err => console.log('Could not activate extension', name, err));
@@ -577,6 +577,42 @@ function addExtensionScript(name, manifest) {
577577}
578578
579579/**
580+ * Adds a localization data for an extension.
581+ * @param {string} name Extension name
582+ * @param {object} manifest Manifest object
583+ */
584+function addExtensionLocale(name, manifest) {
585+ // No i18n data in the manifest
586+ if (!manifest.i18n || typeof manifest.i18n !== 'object') {
587+ return Promise.resolve();
588+ }
589+
590+ const currentLocale = getCurrentLocale();
591+ const localeFile = manifest.i18n[currentLocale];
592+
593+ // Manifest doesn't provide a locale file for the current locale
594+ if (!localeFile) {
595+ return Promise.resolve();
596+ }
597+
598+ return fetch(`/scripts/extensions/${name}/${localeFile}`)
599+ .then(async response => {
600+ if (!response.ok) {
601+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
602+ }
603+
604+ const data = await response.json();
605+
606+ if (data && typeof data === 'object') {
607+ addLocaleData(currentLocale, data);
608+ }
609+ })
610+ .catch(err => {
611+ console.log('Could not load extension locale data for ' + name, err);
612+ });
613+}
614+
615+/**
580616 * Generates HTML string for displaying an extension in the UI.
581617 *
582618 * @param {string} name - The name of the extension.
public/scripts/i18n.js+24 -0
@@ -12,6 +12,30 @@ var localeData;
1212export const getCurrentLocale = () => localeFile;
1313
1414/**
15+ * Adds additional localization data to the current locale file.
16+ * @param {string} localeId Locale ID (e.g. 'fr-fr' or 'zh-cn')
17+ * @param {Record<string, string>} data Localization data to add
18+ */
19+export function addLocaleData(localeId, data) {
20+ if (!localeData) {
21+ console.warn('Localization data not loaded yet. Additional data will not be added.');
22+ return;
23+ }
24+
25+ if (localeId !== localeFile) {
26+ console.debug('Ignoring addLocaleData call for different locale', localeId);
27+ return;
28+ }
29+
30+ for (const [key, value] of Object.entries(data)) {
31+ // Overrides for default locale data are not allowed
32+ if (!Object.hasOwn(localeData, key)) {
33+ localeData[key] = value;
34+ }
35+ }
36+}
37+
38+/**
1539 * An observer that will check if any new i18n elements are added to the document
1640 * @type {MutationObserver}
1741 */
public/scripts/st-context.js+3 -1
@@ -54,7 +54,7 @@ import {
5454 writeExtensionField,
5555} from './extensions.js';
5656import { groups, openGroupChat, selected_group } from './group-chats.js';
5757import { addLocaleData, getCurrentLocale, t, translate } from './i18n.js';
5858import { hideLoader, showLoader } from './loader.js';
5959import { MacrosParser } from './macros.js';
6060import { getChatCompletionModel, oai_settings } from './openai.js';
@@ -165,6 +165,8 @@ export function getContext() {
165165 isMobile,
166166 t,
167167 translate,
168+ getCurrentLocale,
169+ addLocaleData,
168170 tags,
169171 tagMap: tag_map,
170172 menuType: menu_type,