diff --git a/settings.html b/settings.html index 54c6201..3af9d0b 100644 --- a/settings.html +++ b/settings.html @@ -14,6 +14,7 @@ +
@@ -158,7 +164,7 @@ export function renderClothingView(clothingItems, viewMode = 'list') { let itemsHtml = ''; if (items.length === 0) { - itemsHtml = '
No clothing worn
'; + itemsHtml = '
' + (i18n.getTranslation('inventory.clothing.empty') || 'No clothing worn') + '
'; } else { if (viewMode === 'grid') { // Grid view: card-style items @@ -167,10 +173,10 @@ export function renderClothingView(clothingItems, viewMode = 'list') { return `
${lockIconHtml} - - ${escapeHtml(item)} + ${escapeHtml(item)}
`}).join(''); } else { @@ -180,8 +186,8 @@ export function renderClothingView(clothingItems, viewMode = 'list') { return `
${lockIconHtml} - ${escapeHtml(item)} -
@@ -194,30 +200,30 @@ export function renderClothingView(clothingItems, viewMode = 'list') { return `
-

Clothing Worn

+

${i18n.getTranslation('inventory.clothing.title') || 'Clothing & Armor'}

- -
-
@@ -242,30 +248,30 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li let html = `
-

Storage Locations

+

${i18n.getTranslation('inventory.stored.title') || 'Storage Locations'}

- -
-
@@ -274,7 +280,7 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li if (locations.length === 0) { html += `
- No storage locations yet. Click "Add Location" to create one. + ${i18n.getTranslation('inventory.stored.empty') || 'No storage locations yet. Click "Add Location" to create one.'}
`; } else { @@ -286,7 +292,7 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li let itemsHtml = ''; if (items.length === 0) { - itemsHtml = '
No items stored here
'; + itemsHtml = '
' + (i18n.getTranslation('inventory.stored.noItems') || 'No items stored here') + '
'; } else { if (viewMode === 'grid') { // Grid view: card-style items @@ -295,10 +301,10 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li return `
${lockIconHtml} - - ${escapeHtml(item)} + ${escapeHtml(item)}
`}).join(''); } else { @@ -308,8 +314,8 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li return `
${lockIconHtml} - ${escapeHtml(item)} -
@@ -327,20 +333,20 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li
${escapeHtml(location)}
-
@@ -348,19 +354,19 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li ${itemsHtml}
-
@@ -388,7 +394,7 @@ export function renderAssetsView(assets, viewMode = 'list') { let itemsHtml = ''; if (items.length === 0) { - itemsHtml = '
No assets owned
'; + itemsHtml = '
' + (i18n.getTranslation('inventory.assets.empty') || 'No assets owned') + '
'; } else { if (viewMode === 'grid') { // Grid view: card-style items @@ -397,10 +403,10 @@ export function renderAssetsView(assets, viewMode = 'list') { return `
${lockIconHtml} - - ${escapeHtml(item)} + ${escapeHtml(item)}
`}).join(''); } else { @@ -410,8 +416,8 @@ export function renderAssetsView(assets, viewMode = 'list') { return `
${lockIconHtml} - ${escapeHtml(item)} -
@@ -424,30 +430,30 @@ export function renderAssetsView(assets, viewMode = 'list') { return `
-

Vehicles, Property & Major Possessions

+

${i18n.getTranslation('inventory.assets.title') || 'Vehicles, Property & Major Possessions'}

- -
-
@@ -456,8 +462,7 @@ export function renderAssetsView(assets, viewMode = 'list') {
- Assets include vehicles (cars, motorcycles), property (homes, apartments), - and major equipment (workshop tools, special items). + ${i18n.getTranslation('inventory.assets.description') || 'Assets include vehicles (cars, motorcycles), property (homes, apartments), and major equipment (workshop tools, special items).'}
diff --git a/src/systems/rendering/quests.js b/src/systems/rendering/quests.js index 5f481a5..4667fb0 100644 --- a/src/systems/rendering/quests.js +++ b/src/systems/rendering/quests.js @@ -6,6 +6,7 @@ import { extensionSettings, $questsContainer, committedTrackerData, lastGeneratedData } from '../../core/state.js'; import { saveSettings, saveChatData } from '../../core/persistence.js'; import { isItemLocked, setItemLock } from '../generation/lockManager.js'; +import { i18n } from '../../core/i18n.js'; /** * Syncs the current extensionSettings.quests to committedTrackerData.userStats @@ -44,7 +45,7 @@ function getLockIconHtml(tracker, path) { const isLocked = isItemLocked(tracker, path); const lockIcon = isLocked ? '🔒' : '🔓'; - const lockTitle = isLocked ? 'Locked' : 'Unlocked'; + const lockTitle = isLocked ? i18n.getTranslation('global.locked') || 'Locked' : i18n.getTranslation('global.unlocked') || 'Unlocked'; const lockedClass = isLocked ? ' locked' : ''; return `${lockIcon}`; } @@ -66,13 +67,16 @@ function escapeHtml(text) { * @returns {string} HTML for sub-tab navigation */ export function renderQuestsSubTabs(activeTab = 'main') { + const mainText = i18n.getTranslation('quests.section.main') || 'Main Quest'; + const optionalText = i18n.getTranslation('quests.section.optional') || 'Optional Quests'; + return `
`; @@ -86,13 +90,18 @@ export function renderQuestsSubTabs(activeTab = 'main') { export function renderMainQuestView(mainQuest) { const questDisplay = (mainQuest && mainQuest !== 'None') ? mainQuest : ''; const hasQuest = questDisplay.length > 0; + const mainTitle = i18n.getTranslation('quests.main.title') || 'Main Quests'; + const mainHint = i18n.getTranslation('quests.main.hint') || 'The main quest represents your primary objective in the story.'; + const mainEmptyText = i18n.getTranslation('quests.main.empty') || 'No active main quests'; + const addQuestButtonText = i18n.getTranslation('quests.main.addQuestButton') || 'Add Quest'; + const addQuestPlaceholderText = i18n.getTranslation('quests.main.addQuestPlaceholder') || 'Enter main quest title...'; return `
-

Main Quests

- ${!hasQuest ? `` : ''}
@@ -101,10 +110,10 @@ export function renderMainQuestView(mainQuest) {
@@ -112,32 +121,32 @@ export function renderMainQuestView(mainQuest) { ${getLockIconHtml('userStats', 'quests.main')}
${escapeHtml(questDisplay)}
- -
` : ` -
No active main quests
+
${mainEmptyText}
`}
- The main quests represent your primary objective in the story. + ${mainHint}
`; @@ -150,18 +159,23 @@ export function renderMainQuestView(mainQuest) { */ export function renderOptionalQuestsView(optionalQuests) { const quests = optionalQuests.filter(q => q && q !== 'None'); + const optionalTitle = i18n.getTranslation('quests.optional.title') || 'Optional Quests'; + const optionalHint = i18n.getTranslation('quests.optional.hint') || 'Optional quests are side objectives that complement your main story.'; + const optionalEmptyText = i18n.getTranslation('quests.optional.empty') || 'No active optional quests'; + const addQuestButtonText = i18n.getTranslation('quests.optional.addQuestButton') || 'Add Quest'; + const addQuestPlaceholderText = i18n.getTranslation('quests.optional.addQuestPlaceholder') || 'Enter optional quest title...'; let questsHtml = ''; if (quests.length === 0) { - questsHtml = '
No active optional quests
'; + questsHtml = `
${optionalEmptyText}
`; } else { questsHtml = quests.map((quest, index) => { return `
${getLockIconHtml('userStats', `quests.optional[${index}]`)} -
${escapeHtml(quest)}
+
${escapeHtml(quest)}
-
@@ -172,20 +186,20 @@ export function renderOptionalQuestsView(optionalQuests) { return `
-

Optional Quests

-
@@ -194,7 +208,7 @@ export function renderOptionalQuestsView(optionalQuests) {
- Optional quests are side objectives that complement your main story. + ${optionalHint}
diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js index 6a90349..8e3b27d 100644 --- a/src/systems/rendering/thoughts.js +++ b/src/systems/rendering/thoughts.js @@ -31,7 +31,7 @@ function getLockIconHtml(tracker, path) { const isLocked = isItemLocked(tracker, path); const lockIcon = isLocked ? '🔒' : '🔓'; - const lockTitle = isLocked ? i18n.getTranslation('thoughts.locked') : i18n.getTranslation('thoughts.unlocked'); + const lockTitle = isLocked ? i18n.getTranslation('thoughts.locked') || 'Locked' : i18n.getTranslation('thoughts.unlocked') || 'Unlocked'; const lockedClass = isLocked ? ' locked' : ''; return `${lockIcon}`; } @@ -115,7 +115,7 @@ function extractFieldValue(fieldValue) { function toSnakeCase(name) { return name .toLowerCase() - .replace(/[^a-z0-9]+/g, '_') + .replace(/[^\p{L}\p{N}]+/gu, '_') .replace(/^_+|_+$/g, ''); } @@ -171,7 +171,7 @@ export function renderThoughts({ preserveScroll = false } = {}) { // Don't render if no data exists (e.g., after cache clear) const thoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts; if (!thoughtsData) { - $thoughtsContainer.html('
No character data generated yet
'); + $thoughtsContainer.html('
' + (i18n.getTranslation('thoughts.empty') || 'No character data generated yet') + '
'); return; } @@ -503,14 +503,14 @@ export function renderThoughts({ preserveScroll = false } = {}) { html += `
-
+
${char.name} - ${hasRelationshipEnabled ? `
${relationshipBadge}
` : ''} + ${hasRelationshipEnabled ? `
${relationshipBadge}
` : ''}
- ${char.emoji} - ${char.name} - + ${char.emoji} + ${char.name} +
@@ -533,12 +533,12 @@ export function renderThoughts({ preserveScroll = false } = {}) { html += `
${lockIconHtml} - ${fieldValue} + ${fieldValue}
`; } else { html += ` -
${fieldValue}
+
${fieldValue}
`; } } @@ -564,7 +564,7 @@ export function renderThoughts({ preserveScroll = false } = {}) { ); html += `
- ${stat.name}: ${statValue}% + ${stat.name}: ${statValue}%
`; } @@ -590,8 +590,8 @@ export function renderThoughts({ preserveScroll = false } = {}) { // Add "Add Character" button if data exists (inside rpg-thoughts-content) if (presentCharacters.length > 0) { html += ` - `; } @@ -1425,7 +1425,7 @@ function renderThoughtsSidebarOnly() { // Copy the rendering logic from renderThoughts but skip the updateChatThoughts call const thoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts; if (!thoughtsData) { - $thoughtsContainer.html('
No character data generated yet
'); + $thoughtsContainer.html('
' + (i18n.getTranslation('thoughts.empty') || 'No character data generated yet') + '
'); return; } diff --git a/src/systems/rendering/userStats.js b/src/systems/rendering/userStats.js index 7d98937..755dd55 100644 --- a/src/systems/rendering/userStats.js +++ b/src/systems/rendering/userStats.js @@ -34,7 +34,7 @@ function toFieldKey(name) { const baseName = name.replace(/\s*\(.*\)\s*$/, '').trim(); return baseName .toLowerCase() - .replace(/[^a-z0-9]+/g, '_') + .replace(/[^\p{L}\p{N}]+/gu, '_') .replace(/^_+|_+$/g, ''); } @@ -213,7 +213,7 @@ export function renderUserStats() { if (!lastGeneratedData.userStats && !committedTrackerData.userStats) { // Always render to the #rpg-user-stats container (mobile layout just moves it around in DOM) - $userStatsContainer.html('
No statuses generated yet
'); + $userStatsContainer.html('
' + (i18n.getTranslation('userStats.empty') || 'No statuses generated yet') + '
'); return; } @@ -274,7 +274,7 @@ export function renderUserStats() { // Check if stats bars section is locked const isStatsLocked = isItemLocked('userStats', 'stats'); const lockIcon = isStatsLocked ? '🔒' : '🔓'; - const lockTitle = isStatsLocked ? i18n.getTranslation('userStats.statsLocked') : i18n.getTranslation('userStats.statsUnlocked'); + const lockTitle = isStatsLocked ? (i18n.getTranslation('userStats.statsLocked') || 'Stats locked') : (i18n.getTranslation('userStats.statsUnlocked') || 'Stats unlocked'); const lockedClass = isStatsLocked ? ' locked' : ''; let html = '
'; @@ -287,8 +287,8 @@ export function renderUserStats() { ${userName} ${userName} ${showLevel ? `| - ${i18n.getTranslation('userStats.level')} - ${extensionSettings.level}` : ''} + ${i18n.getTranslation('userStats.level') || 'Level'} + ${extensionSettings.level}` : ''}
`; @@ -321,11 +321,11 @@ export function renderUserStats() { html += `
- ${stat.name}: + ${stat.name}:
- ${displayValue} + ${displayValue}
`; } @@ -335,7 +335,7 @@ export function renderUserStats() { if (config.statusSection.enabled) { const isMoodLocked = isItemLocked('userStats', 'status'); const moodLockIcon = isMoodLocked ? '🔒' : '🔓'; - const moodLockTitle = isMoodLocked ? i18n.getTranslation('userStats.moodLocked') : i18n.getTranslation('userStats.moodUnlocked'); + const moodLockTitle = isMoodLocked ? (i18n.getTranslation('userStats.moodLocked') || 'Mood locked') : (i18n.getTranslation('userStats.moodUnlocked') || 'Mood unlocked'); const moodLockedClass = isMoodLocked ? ' locked' : ''; html += '
'; if (showLockIcons) { @@ -343,7 +343,7 @@ export function renderUserStats() { } if (config.statusSection.showMoodEmoji) { - html += `
${stats.mood}
`; + html += `
${stats.mood}
`; } // Render custom status fields @@ -358,7 +358,7 @@ export function renderUserStats() { // Strip brackets if present (from JSON array format) fieldValue = fieldValue.replace(/^\[|\]$/g, '').trim(); } - html += `
${fieldValue}
`; + html += `
${fieldValue}
`; } } @@ -369,7 +369,7 @@ export function renderUserStats() { if (config.skillsSection.enabled) { const isSkillsLocked = isItemLocked('userStats', 'skills'); const skillsLockIcon = isSkillsLocked ? '🔒' : '🔓'; - const skillsLockTitle = isSkillsLocked ? i18n.getTranslation('userStats.skillsLocked') : i18n.getTranslation('userStats.skillsUnlocked'); + const skillsLockTitle = isSkillsLocked ? (i18n.getTranslation('userStats.skillsLocked') || 'Skills locked') : (i18n.getTranslation('userStats.skillsUnlocked') || 'Skills unlocked'); const skillsLockedClass = isSkillsLocked ? ' locked' : ''; let skillsValue = 'None'; // Handle JSON array format: [{name: "Art"}, {name: "Coding"}] @@ -386,7 +386,7 @@ export function renderUserStats() { } html += ` ${config.skillsSection.label}: -
${skillsValue}
+
${skillsValue}
`; } @@ -595,7 +595,7 @@ export function renderUserStats() { // Update icon const newIcon = !currentlyLocked ? '🔒' : '🔓'; - const newTitle = !currentlyLocked ? i18n.getTranslation('infoBox.locked') : i18n.getTranslation('infoBox.unlocked'); + const newTitle = !currentlyLocked ? (i18n.getTranslation('infoBox.locked') || 'Locked') : (i18n.getTranslation('infoBox.unlocked') || 'Unlocked'); $icon.text(newIcon); $icon.attr('title', newTitle); diff --git a/src/systems/ui/encounterUI.js b/src/systems/ui/encounterUI.js index 1d582b6..203c4cc 100644 --- a/src/systems/ui/encounterUI.js +++ b/src/systems/ui/encounterUI.js @@ -71,7 +71,7 @@ export class EncounterModal { } // Show loading state - this.showLoadingState('Initializing combat encounter...'); + this.showLoadingState(i18n.getTranslation('encounter.ui.initializingCombatEncounter') || 'Initializing combat encounter...'); // Open the modal this.modal.classList.add('is-open'); @@ -88,7 +88,7 @@ export class EncounterModal { }); if (!response) { - this.showErrorWithRegenerate('No response received from AI. The model may be unavailable.'); + this.showErrorWithRegenerate(i18n.getTranslation('encounter.ui.error.noResponse') || 'No response received from AI. The model may be unavailable.'); return; } @@ -96,7 +96,7 @@ export class EncounterModal { const combatData = parseEncounterJSON(response); if (!combatData || !combatData.party || !combatData.enemies) { - this.showErrorWithRegenerate('Invalid JSON format detected. The AI returned malformed data. Ensure the Max Response Length is set to at least 2048 tokens, otherwise the model might run out of tokens and produce unfinished structures.'); + this.showErrorWithRegenerate(i18n.getTranslation('encounter.ui.error.invalidJsonFormat') || 'Invalid JSON format detected. The AI returned malformed data. Ensure the Max Response Length is set to at least 2048 tokens, otherwise the model might run out of tokens and produce unfinished structures.'); return; } @@ -121,7 +121,7 @@ export class EncounterModal { } catch (error) { console.error('[RPG Companion] Error initializing encounter:', error); - this.showErrorWithRegenerate(`Failed to initialize combat: ${error.message}`); + this.showErrorWithRegenerate(`${i18n.getTranslation('encounter.ui.error.failedToInitialize') || 'Failed to initialize combat:'} ${error.message}`); } finally { this.isInitializing = false; } @@ -142,94 +142,94 @@ export class EncounterModal {
-

Configure Combat Narrative

+

${i18n.getTranslation('encounter.configModal.title') || 'Configure Combat Narrative'}

- +
- +
- +
- - + +
- +
- +
- +
- - + +
@@ -303,12 +303,12 @@ export class EncounterModal {
-

Combat Encounter

+

${i18n.getTranslation('encounter.ui.combatEncounterTitle') || 'Combat Encounter'}

- -
@@ -316,7 +316,7 @@ export class EncounterModal {
-

Initializing combat...

+

${i18n.getTranslation('encounter.ui.initializingCombat') || 'Initializing combat...'}