diff --git a/index.js b/index.js index 6d8b462..fae72aa 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ import { power_user } from '../../../power-user.js'; // Core modules import { extensionName, extensionFolderPath } from './src/core/config.js'; +import { i18n } from './src/core/i18n.js'; import { extensionSettings, lastGeneratedData, @@ -155,32 +156,9 @@ import { /** * Adds the extension settings to the Extensions tab. */ -function addExtensionSettings() { - const settingsHtml = ` -
-
- RPG Companion -
-
-
- - Toggle to enable/disable the RPG Companion extension. Configure additional settings within the panel itself. - -
- - Discord - - - Support Creator - -
-
-
- `; - +async function addExtensionSettings() { + // Load the HTML template for the settings + const settingsHtml = await renderExtensionTemplateAsync(extensionName, 'settings'); $('#extensions_settings2').append(settingsHtml); // Set up the enable/disable toggle @@ -210,12 +188,27 @@ function addExtensionSettings() { // Update Memory Recollection button visibility updateMemoryRecollectionButton(); }); + + // Set up language selector + const langSelect = $('#rpg-companion-language-select'); + if (langSelect.length) { + langSelect.val(i18n.currentLanguage); + langSelect.on('change', async function() { + const selectedLanguage = $(this).val(); + await i18n.setLanguage(selectedLanguage); + // We need to re-apply translations to the settings panel specifically + i18n.applyTranslations(document.getElementById('extensions_settings2')); + }); + } } /** * Initializes the UI for the extension. */ async function initUI() { + // Initialize i18n + await i18n.init(); + // Only initialize UI if extension is enabled if (!extensionSettings.enabled) { console.log('[RPG Companion] Extension disabled - skipping UI initialization'); @@ -249,6 +242,9 @@ async function initUI() { setInventoryContainer($('#rpg-inventory')); setQuestsContainer($('#rpg-quests')); + // Re-apply translations to the entire body to catch all new elements from the template + i18n.applyTranslations(document.body); + // Set up event listeners (enable/disable is handled in Extensions tab) $('#rpg-toggle-auto-update').on('change', function() { extensionSettings.autoUpdate = $(this).prop('checked'); @@ -597,9 +593,12 @@ jQuery(async () => { console.error('[RPG Companion] Settings load failed, continuing with defaults:', error); } + // Initialize i18n early for the settings panel + await i18n.init(); + // Add extension settings to Extensions tab try { - addExtensionSettings(); + await addExtensionSettings(); } catch (error) { console.error('[RPG Companion] Failed to add extension settings tab:', error); // Don't throw - extension can still work without settings tab diff --git a/settings.html b/settings.html index a6d40ea..cb15e22 100644 --- a/settings.html +++ b/settings.html @@ -9,6 +9,15 @@ Enable RPG Companion + +
+ + +
+ Toggle to enable/disable the RPG Companion extension. Configure additional settings within the panel itself.
diff --git a/src/core/i18n.js b/src/core/i18n.js new file mode 100644 index 0000000..cb6a57d --- /dev/null +++ b/src/core/i18n.js @@ -0,0 +1,66 @@ +//- No-op in case this is running outside of SillyTavern +const { extension_settings } = typeof self.SillyTavern !== 'undefined' ? self.SillyTavern.getContext() : { extension_settings: {} }; + +class Internationalization { + constructor() { + this.currentLanguage = 'en'; + this.translations = {}; + } + + async init() { + const savedLanguage = localStorage.getItem('rpgCompanionLanguage') || 'en'; + this.currentLanguage = savedLanguage; + + await this.loadTranslations(this.currentLanguage); + this.applyTranslations(document.body); + + const langSelect = document.getElementById('rpg-companion-language-select'); + if (langSelect) { + langSelect.value = this.currentLanguage; + } + } + + async loadTranslations(lang) { + const fetchUrl = `/scripts/extensions/third-party/rpg-companion-sillytavern/src/i18n/${lang}.json`; + try { + const response = await fetch(fetchUrl); + if (!response.ok) { + console.error(`[RPG-Companion-i18n] Failed to load translation file for ${lang}. Status: ${response.status}`); + if (lang !== 'en') { + return this.loadTranslations('en'); + } + return; + } + this.translations = await response.json(); + } catch (error) { + console.error('[RPG-Companion-i18n] CRITICAL error loading translation file:', error); + } + } + + applyTranslations(rootElement) { + if (!rootElement) { + return; + } + const elements = rootElement.querySelectorAll('[data-i18n-key]'); + elements.forEach(element => { + const key = element.dataset.i18nKey; + const translation = this.getTranslation(key); + if (translation) { + element.textContent = translation; + } + }); + } + + getTranslation(key) { + return this.translations[key] || null; + } + + async setLanguage(lang) { + this.currentLanguage = lang; + localStorage.setItem('rpgCompanionLanguage', lang); + await this.loadTranslations(lang); + this.applyTranslations(document.body); + } +} + +export const i18n = new Internationalization(); diff --git a/src/i18n/en.json b/src/i18n/en.json new file mode 100644 index 0000000..d315746 --- /dev/null +++ b/src/i18n/en.json @@ -0,0 +1,6 @@ +{ + "settings.language.label": "Language", + "settings.language.option.en": "English", + "settings.language.option.zh-tw": "繁體中文", + "template.settingsTitle": "RPG Companion Settings" +} diff --git a/src/i18n/zh-tw.json b/src/i18n/zh-tw.json new file mode 100644 index 0000000..82cbe19 --- /dev/null +++ b/src/i18n/zh-tw.json @@ -0,0 +1,6 @@ +{ + "settings.language.label": "語言", + "settings.language.option.en": "English", + "settings.language.option.zh-tw": "繁體中文", + "template.settingsTitle": "RPG Companion 設定" +} diff --git a/template.html b/template.html index ff8c8fa..50c9d3a 100644 --- a/template.html +++ b/template.html @@ -92,7 +92,7 @@

- RPG Companion Settings + RPG Companion Settings