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 = `
-
-
-
-
-
Toggle to enable/disable the RPG Companion extension. Configure additional settings within the panel itself.
-
-
-
-
-
-
-
- `;
-
+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 @@