```
feat(i18n): 添加简体中文语言选项并扩展国际化支持 添加了简体中文(zh-cn)语言选项到设置页面的语言选择下拉菜单中。 同时新增了大量国际化字符串。 fix(parser): 提高解析器的鲁棒性 现在会遍历所有json对象检测统一格式,即使AI响应中包含多个JSON对象也能正确识别统一格式。 ```
This commit is contained in:
@@ -142,94 +142,94 @@ export class EncounterModal {
|
||||
<div class="rpg-encounter-overlay"></div>
|
||||
<div class="rpg-encounter-container" style="max-width: 600px;">
|
||||
<div class="rpg-encounter-header">
|
||||
<h2><i class="fa-solid fa-book-open"></i> Configure Combat Narrative</h2>
|
||||
<h2><i class="fa-solid fa-book-open"></i> ${i18n.getTranslation('encounter.configModal.title') || 'Configure Combat Narrative'}</h2>
|
||||
</div>
|
||||
<div class="rpg-encounter-content" style="padding: 24px;">
|
||||
<div class="rpg-narrative-config-section">
|
||||
<label class="label_text" style="margin-bottom: 16px; display: block; font-weight: 600;">
|
||||
<i class="fa-solid fa-swords"></i> Combat Narrative Style
|
||||
<i class="fa-solid fa-swords"></i> ${i18n.getTranslation('encounter.configModal.combatNarrativeStyle') || 'Combat Narrative Style'}
|
||||
</label>
|
||||
|
||||
<div class="rpg-setting-row" style="margin-bottom: 12px;">
|
||||
<label for="config-combat-tense" style="min-width: 100px;">Tense:</label>
|
||||
<label for="config-combat-tense" style="min-width: 100px;">${i18n.getTranslation('encounter.configModal.labels.tense') || 'Tense:'}</label>
|
||||
<select id="config-combat-tense" class="rpg-select" style="flex: 1;">
|
||||
<option value="present" ${combatDefaults.tense === 'present' ? 'selected' : ''}>Present</option>
|
||||
<option value="past" ${combatDefaults.tense === 'past' ? 'selected' : ''}>Past</option>
|
||||
<option value="present" ${combatDefaults.tense === 'present' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.present') || 'Present'}</option>
|
||||
<option value="past" ${combatDefaults.tense === 'past' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.past') || 'Past'}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row" style="margin-bottom: 12px;">
|
||||
<label for="config-combat-person" style="min-width: 100px;">Person:</label>
|
||||
<label for="config-combat-person" style="min-width: 100px;">${i18n.getTranslation('encounter.configModal.labels.person') || 'Person:'}</label>
|
||||
<select id="config-combat-person" class="rpg-select" style="flex: 1;">
|
||||
<option value="first" ${combatDefaults.person === 'first' ? 'selected' : ''}>First Person</option>
|
||||
<option value="second" ${combatDefaults.person === 'second' ? 'selected' : ''}>Second Person</option>
|
||||
<option value="third" ${combatDefaults.person === 'third' ? 'selected' : ''}>Third Person</option>
|
||||
<option value="first" ${combatDefaults.person === 'first' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.firstPerson') || 'First Person'}</option>
|
||||
<option value="second" ${combatDefaults.person === 'second' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.secondPerson') || 'Second Person'}</option>
|
||||
<option value="third" ${combatDefaults.person === 'third' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.thirdPerson') || 'Third Person'}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row" style="margin-bottom: 12px;">
|
||||
<label for="config-combat-narration" style="min-width: 100px;">Narration:</label>
|
||||
<label for="config-combat-narration" style="min-width: 100px;">${i18n.getTranslation('encounter.configModal.labels.narration') || 'Narration:'}</label>
|
||||
<select id="config-combat-narration" class="rpg-select" style="flex: 1;">
|
||||
<option value="omniscient" ${combatDefaults.narration === 'omniscient' ? 'selected' : ''}>Omniscient</option>
|
||||
<option value="limited" ${combatDefaults.narration === 'limited' ? 'selected' : ''}>Limited</option>
|
||||
<option value="omniscient" ${combatDefaults.narration === 'omniscient' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.omniscient') || 'Omniscient'}</option>
|
||||
<option value="limited" ${combatDefaults.narration === 'limited' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.limited') || 'Limited'}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row" style="margin-bottom: 12px;">
|
||||
<label for="config-combat-pov" style="min-width: 100px;">Point of View:</label>
|
||||
<input type="text" id="config-combat-pov" class="text_pole" placeholder="narrator" value="${combatDefaults.pov || ''}" style="flex: 1;" />
|
||||
<label for="config-combat-pov" style="min-width: 100px;">${i18n.getTranslation('encounter.configModal.labels.pointOfView') || 'Point of View:'}</label>
|
||||
<input type="text" id="config-combat-pov" class="text_pole" placeholder="${i18n.getTranslation('encounter.configModal.placeholders.narrator') || 'narrator'}" value="${combatDefaults.pov || ''}" style="flex: 1;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rpg-narrative-config-section" style="margin-top: 24px; padding-top: 24px; border-top: 1px solid var(--rpg-border, rgba(255,255,255,0.1));">
|
||||
<label class="label_text" style="margin-bottom: 16px; display: block; font-weight: 600;">
|
||||
<i class="fa-solid fa-scroll"></i> Combat Summary Style
|
||||
<i class="fa-solid fa-scroll"></i> ${i18n.getTranslation('encounter.configModal.combatSummaryStyle') || 'Combat Summary Style'}
|
||||
</label>
|
||||
|
||||
<div class="rpg-setting-row" style="margin-bottom: 12px;">
|
||||
<label for="config-summary-tense" style="min-width: 100px;">Tense:</label>
|
||||
<label for="config-summary-tense" style="min-width: 100px;">${i18n.getTranslation('encounter.configModal.labels.tense') || 'Tense:'}</label>
|
||||
<select id="config-summary-tense" class="rpg-select" style="flex: 1;">
|
||||
<option value="present" ${summaryDefaults.tense === 'present' ? 'selected' : ''}>Present</option>
|
||||
<option value="past" ${summaryDefaults.tense === 'past' ? 'selected' : ''}>Past</option>
|
||||
<option value="present" ${summaryDefaults.tense === 'present' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.present') || 'Present'}</option>
|
||||
<option value="past" ${summaryDefaults.tense === 'past' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.past') || 'Past'}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row" style="margin-bottom: 12px;">
|
||||
<label for="config-summary-person" style="min-width: 100px;">Person:</label>
|
||||
<label for="config-summary-person" style="min-width: 100px;">${i18n.getTranslation('encounter.configModal.labels.person') || 'Person:'}</label>
|
||||
<select id="config-summary-person" class="rpg-select" style="flex: 1;">
|
||||
<option value="first" ${summaryDefaults.person === 'first' ? 'selected' : ''}>First Person</option>
|
||||
<option value="second" ${summaryDefaults.person === 'second' ? 'selected' : ''}>Second Person</option>
|
||||
<option value="third" ${summaryDefaults.person === 'third' ? 'selected' : ''}>Third Person</option>
|
||||
<option value="first" ${summaryDefaults.person === 'first' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.firstPerson') || 'First Person'}</option>
|
||||
<option value="second" ${summaryDefaults.person === 'second' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.secondPerson') || 'Second Person'}</option>
|
||||
<option value="third" ${summaryDefaults.person === 'third' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.thirdPerson') || 'Third Person'}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row" style="margin-bottom: 12px;">
|
||||
<label for="config-summary-narration" style="min-width: 100px;">Narration:</label>
|
||||
<label for="config-summary-narration" style="min-width: 100px;">${i18n.getTranslation('encounter.configModal.labels.narration') || 'Narration:'}</label>
|
||||
<select id="config-summary-narration" class="rpg-select" style="flex: 1;">
|
||||
<option value="omniscient" ${summaryDefaults.narration === 'omniscient' ? 'selected' : ''}>Omniscient</option>
|
||||
<option value="limited" ${summaryDefaults.narration === 'limited' ? 'selected' : ''}>Limited</option>
|
||||
<option value="omniscient" ${summaryDefaults.narration === 'omniscient' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.omniscient') || 'Omniscient'}</option>
|
||||
<option value="limited" ${summaryDefaults.narration === 'limited' ? 'selected' : ''}>${i18n.getTranslation('encounter.configModal.options.limited') || 'Limited'}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row" style="margin-bottom: 12px;">
|
||||
<label for="config-summary-pov" style="min-width: 100px;">Point of View:</label>
|
||||
<input type="text" id="config-summary-pov" class="text_pole" placeholder="narrator" value="${summaryDefaults.pov || ''}" style="flex: 1;" />
|
||||
<label for="config-summary-pov" style="min-width: 100px;">${i18n.getTranslation('encounter.configModal.labels.pointOfView') || 'Point of View:'}</label>
|
||||
<input type="text" id="config-summary-pov" class="text_pole" placeholder="${i18n.getTranslation('encounter.configModal.placeholders.narrator') || 'narrator'}" value="${summaryDefaults.pov || ''}" style="flex: 1;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 24px; padding-top: 24px; border-top: 1px solid var(--rpg-border, rgba(255,255,255,0.1));">
|
||||
<label class="checkbox_label" style="display: flex; align-items: center; gap: 8px;">
|
||||
<input type="checkbox" id="config-remember" ${extensionSettings.encounterSettings?.narrativeConfigured ? 'checked' : ''} style="margin: 0;" />
|
||||
<span style="color: var(--rpg-text, #eaeaea);">Remember these settings for future encounters</span>
|
||||
<span style="color: var(--rpg-text, #eaeaea);">${i18n.getTranslation('encounter.configModal.rememberSettings') || 'Remember these settings for future encounters'}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 24px; display: flex; gap: 12px; justify-content: flex-end;">
|
||||
<button id="config-cancel" class="rpg-btn rpg-btn-secondary" style="padding: 12px 24px;">
|
||||
<i class="fa-solid fa-times"></i> Cancel
|
||||
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
|
||||
</button>
|
||||
<button id="config-proceed" class="rpg-btn rpg-btn-primary" style="padding: 12px 24px;">
|
||||
<i class="fa-solid fa-play"></i> Proceed
|
||||
<i class="fa-solid fa-play"></i> ${i18n.getTranslation('encounter.configModal.buttons.proceed') || 'Proceed'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -303,12 +303,12 @@ export class EncounterModal {
|
||||
<div class="rpg-encounter-overlay"></div>
|
||||
<div class="rpg-encounter-container">
|
||||
<div class="rpg-encounter-header">
|
||||
<h2><i class="fa-solid fa-swords"></i> Combat Encounter</h2>
|
||||
<h2><i class="fa-solid fa-swords"></i> ${i18n.getTranslation('encounter.ui.combatEncounterTitle') || 'Combat Encounter'}</h2>
|
||||
<div class="rpg-encounter-header-buttons">
|
||||
<button id="rpg-encounter-conclude" class="rpg-encounter-conclude-btn" title="Conclude encounter early">
|
||||
<i class="fa-solid fa-flag-checkered"></i> Conclude Encounter
|
||||
<button id="rpg-encounter-conclude" class="rpg-encounter-conclude-btn" title="${i18n.getTranslation('encounter.ui.concludeEncounterTitle') || 'Conclude encounter early'}">
|
||||
<i class="fa-solid fa-flag-checkered"></i> ${i18n.getTranslation('encounter.ui.concludeEncounterButton') || 'Conclude Encounter'}
|
||||
</button>
|
||||
<button id="rpg-encounter-close" class="rpg-encounter-close-btn" title="Close (ends combat)">
|
||||
<button id="rpg-encounter-close" class="rpg-encounter-close-btn" title="${i18n.getTranslation('encounter.ui.closeTitle') || 'Close (ends combat)'}">
|
||||
<i class="fa-solid fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -316,7 +316,7 @@ export class EncounterModal {
|
||||
<div class="rpg-encounter-content">
|
||||
<div id="rpg-encounter-loading" class="rpg-encounter-loading">
|
||||
<i class="fa-solid fa-spinner fa-spin"></i>
|
||||
<p>Initializing combat...</p>
|
||||
<p>${i18n.getTranslation('encounter.ui.initializingCombat') || 'Initializing combat...'}</p>
|
||||
</div>
|
||||
<div id="rpg-encounter-main" class="rpg-encounter-main" style="display: none;">
|
||||
<!-- Combat UI will be rendered here -->
|
||||
@@ -389,10 +389,10 @@ export class EncounterModal {
|
||||
|
||||
<!-- Combat Log -->
|
||||
<div class="rpg-encounter-log-section">
|
||||
<h3><i class="fa-solid fa-scroll"></i> Combat Log</h3>
|
||||
<h3><i class="fa-solid fa-scroll"></i> ${i18n.getTranslation('encounter.ui.combatLog') || 'Combat Log'}</h3>
|
||||
<div id="rpg-encounter-log" class="rpg-encounter-log">
|
||||
<div class="rpg-encounter-log-entry">
|
||||
<em>Combat begins!</em>
|
||||
<em>${i18n.getTranslation('encounter.ui.combatBegins') || 'Combat begins!'}</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -561,18 +561,18 @@ export class EncounterModal {
|
||||
targetOptions = `
|
||||
<div class="rpg-target-option" data-target="all-enemies">
|
||||
<div class="rpg-target-icon">💥</div>
|
||||
<div class="rpg-target-name">All Enemies</div>
|
||||
<div class="rpg-target-desc">Area of Effect</div>
|
||||
<div class="rpg-target-name">${i18n.getTranslation('encounter.ui.allEnemies') || 'All Enemies'}</div>
|
||||
<div class="rpg-target-desc">${i18n.getTranslation('encounter.ui.areaOfEffect') || 'Area of Effect'}</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (attackType === 'both') {
|
||||
targetOptions = `
|
||||
<div class="rpg-target-option" data-target="all-enemies">
|
||||
<div class="rpg-target-icon">💥</div>
|
||||
<div class="rpg-target-name">All Enemies</div>
|
||||
<div class="rpg-target-desc">Area of Effect</div>
|
||||
<div class="rpg-target-name">${i18n.getTranslation('encounter.ui.allEnemies') || 'All Enemies'}</div>
|
||||
<div class="rpg-target-desc">${i18n.getTranslation('encounter.ui.areaOfEffect') || 'Area of Effect'}</div>
|
||||
</div>
|
||||
<div class="rpg-target-divider">OR</div>
|
||||
<div class="rpg-target-divider">${i18n.getTranslation('encounter.ui.or') || 'OR'}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -618,11 +618,11 @@ export class EncounterModal {
|
||||
|
||||
targetModal.innerHTML = `
|
||||
<div class="rpg-target-selection-modal">
|
||||
<h3><i class="fa-solid fa-crosshairs"></i> Select Target</h3>
|
||||
<h3><i class="fa-solid fa-crosshairs"></i> ${i18n.getTranslation('encounter.ui.selectTarget') || 'Select Target'}</h3>
|
||||
<div class="rpg-target-list">
|
||||
${targetOptions}
|
||||
</div>
|
||||
<button class="rpg-target-cancel">Cancel</button>
|
||||
<button class="rpg-target-cancel">${i18n.getTranslation('global.cancel') || 'Cancel'}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -661,7 +661,7 @@ export class EncounterModal {
|
||||
renderPlayerControls(party, playerActions = null) {
|
||||
const player = party.find(m => m.isPlayer);
|
||||
if (!player || player.hp <= 0) {
|
||||
return '<div class="rpg-encounter-controls"><p class="rpg-encounter-defeated">You have been defeated...</p></div>';
|
||||
return '<div class="rpg-encounter-controls"><p class="rpg-encounter-defeated">' + (i18n.getTranslation('encounter.ui.youHaveBeenDefeated') || 'You have been defeated...') + '</p></div>';
|
||||
}
|
||||
|
||||
// Use playerActions if provided, otherwise fall back to player data
|
||||
@@ -674,7 +674,7 @@ export class EncounterModal {
|
||||
|
||||
<div class="rpg-encounter-action-buttons">
|
||||
<div class="rpg-encounter-button-group">
|
||||
<h4>Attacks</h4>
|
||||
<h4>${i18n.getTranslation('encounter.ui.attacks') || 'Attacks'}</h4>
|
||||
${attacks.map(attack => {
|
||||
// Support both old string format and new object format
|
||||
const attackName = typeof attack === 'string' ? attack : attack.name;
|
||||
@@ -695,7 +695,7 @@ export class EncounterModal {
|
||||
|
||||
${items && items.length > 0 ? `
|
||||
<div class="rpg-encounter-button-group">
|
||||
<h4>Items</h4>
|
||||
<h4>${i18n.getTranslation('encounter.ui.items') || 'Items'}</h4>
|
||||
${items.map(item => `
|
||||
<button class="rpg-encounter-action-btn rpg-encounter-item-btn" data-action="item" data-value="${item}">
|
||||
<i class="fa-solid fa-flask"></i> ${item}
|
||||
@@ -706,11 +706,11 @@ export class EncounterModal {
|
||||
</div>
|
||||
|
||||
<div class="rpg-encounter-custom-action">
|
||||
<h4>Custom Action</h4>
|
||||
<h4>${i18n.getTranslation('encounter.ui.customAction') || 'Custom Action'}</h4>
|
||||
<div class="rpg-encounter-input-group">
|
||||
<input type="text" id="rpg-encounter-custom-input" placeholder="Describe what you want to do..." />
|
||||
<input type="text" id="rpg-encounter-custom-input" placeholder="${i18n.getTranslation('encounter.ui.customActionPlaceholder') || 'Describe what you want to do...'}" />
|
||||
<button id="rpg-encounter-custom-submit" class="rpg-encounter-submit-btn">
|
||||
<i class="fa-solid fa-paper-plane"></i> Submit
|
||||
<i class="fa-solid fa-paper-plane"></i> ${i18n.getTranslation('encounter.ui.submit') || 'Submit'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -959,7 +959,7 @@ export class EncounterModal {
|
||||
|
||||
if (player && player.hp <= 0) {
|
||||
if (controlsContainer) {
|
||||
controlsContainer.innerHTML = '<p class="rpg-encounter-defeated">You have been defeated...</p>';
|
||||
controlsContainer.innerHTML = '<p class="rpg-encounter-defeated">' + (i18n.getTranslation('encounter.ui.youHaveBeenDefeated') || 'You have been defeated...') + '</p>';
|
||||
}
|
||||
} else if (currentEncounter.playerActions && controlsContainer) {
|
||||
// Check if actions have changed by comparing with previous state
|
||||
@@ -1212,10 +1212,10 @@ export class EncounterModal {
|
||||
<div class="rpg-encounter-over" style="text-align: center; padding: 40px 20px;">
|
||||
<i class="fa-solid ${icon}" style="font-size: 72px; color: ${color}; margin-bottom: 24px;"></i>
|
||||
<h2 style="font-size: 32px; margin-bottom: 16px; text-transform: uppercase;">${result}</h2>
|
||||
<p style="font-size: 18px; margin-bottom: 32px; opacity: 0.8;">Generating combat summary...</p>
|
||||
<p style="font-size: 18px; margin-bottom: 32px; opacity: 0.8;">${i18n.getTranslation('encounter.ui.generatingCombatSummary') || 'Generating combat summary...'}</p>
|
||||
<div class="rpg-encounter-loading" style="display: flex; justify-content: center; align-items: center; gap: 12px;">
|
||||
<i class="fa-solid fa-spinner fa-spin" style="font-size: 24px;"></i>
|
||||
<span>Please wait...</span>
|
||||
<span>${i18n.getTranslation('encounter.ui.pleaseWait') || 'Please wait...'}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1251,11 +1251,11 @@ export class EncounterModal {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
overScreen.querySelector('p').textContent = 'Error generating combat summary.';
|
||||
overScreen.querySelector('p').textContent = i18n.getTranslation('encounter.ui.errorGeneratingCombatSummary') || 'Error generating combat summary.';
|
||||
overScreen.querySelector('.rpg-encounter-loading').innerHTML = `
|
||||
<p style="color: #e94560;">Failed to create summary. You can close this window.</p>
|
||||
<p style="color: #e94560;">${i18n.getTranslation('encounter.ui.failedToCreateSummary') || 'Failed to create summary. You can close this window.'}</p>
|
||||
<button id="rpg-encounter-close-final" class="rpg-encounter-submit-btn" style="font-size: 18px; padding: 12px 24px; margin-top: 16px;">
|
||||
<i class="fa-solid fa-times"></i> Close Combat Window
|
||||
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('encounter.ui.closeCombatWindow') || 'Close Combat Window'}
|
||||
</button>
|
||||
`;
|
||||
|
||||
@@ -1321,11 +1321,11 @@ export class EncounterModal {
|
||||
loadingContent.innerHTML = `
|
||||
<div class="rpg-encounter-error-box">
|
||||
<i class="fa-solid fa-exclamation-triangle" style="color: #e94560; font-size: 48px; margin-bottom: 1em;"></i>
|
||||
<p style="color: #e94560; font-weight: bold; font-size: 1.2em; margin: 0 0 0.5em 0;">Wrong Format Detected</p>
|
||||
<p style="color: #e94560; font-weight: bold; font-size: 1.2em; margin: 0 0 0.5em 0;">${i18n.getTranslation('encounter.ui.wrongFormatDetected') || 'Wrong Format Detected'}</p>
|
||||
<p style="color: var(--rpg-text, #ccc); margin: 0 0 1.5em 0; max-width: 500px;">${message}</p>
|
||||
<div style="display: flex; gap: 1em;">
|
||||
<button id="rpg-error-regenerate" class="rpg-btn rpg-btn-primary">
|
||||
<i class="fa-solid fa-rotate-right"></i> Regenerate
|
||||
<i class="fa-solid fa-rotate-right"></i> ${i18n.getTranslation('encounter.ui.regenerate') || 'Regenerate'}
|
||||
</button>
|
||||
<button id="rpg-error-close" class="rpg-btn rpg-btn-secondary">
|
||||
<i class="fa-solid fa-times"></i> Close
|
||||
|
||||
Reference in New Issue
Block a user