feat(i18n): 添加简体中文语言选项并扩展国际化支持

添加了简体中文(zh-cn)语言选项到设置页面的语言选择下拉菜单中。

同时新增了大量国际化字符串。

fix(parser): 提高解析器的鲁棒性

现在会遍历所有json对象检测统一格式,即使AI响应中包含多个JSON对象也能正确识别统一格式。
```
This commit is contained in:
dd178
2026-03-22 14:07:11 +08:00
parent 502646bb92
commit 55aa2a1e6a
21 changed files with 1042 additions and 332 deletions
+1
View File
@@ -14,6 +14,7 @@
<label for="rpg-companion-language-select" data-i18n-key="settings.language.label">Language</label>
<select id="rpg-companion-language-select" class="text_pole">
<option value="en" data-i18n-key="settings.language.option.en">English</option>
<option value="zh-cn" data-i18n-key="settings.language.option.zh-cn">简体中文</option>
<option value="zh-tw" data-i18n-key="settings.language.option.zh-tw">繁體中文</option>
<option value="ru" data-i18n-key="settings.language.option.ru">Русский</option>
<option value="fr" data-i18n-key="settings.language.option.fr">Français</option>
+184 -1
View File
@@ -1,8 +1,10 @@
{
"settings.language.label": "Language",
"settings.language.option.en": "English",
"settings.language.option.zh-cn": "简体中文",
"settings.language.option.zh-tw": "繁體中文",
"settings.language.option.ru": "Русский",
"settings.language.option.fr": "Français",
"settings.extensionEnabled": "Enable RPG Companion",
"settings.note": "Toggle to enable/disable the RPG Companion extension. Configure additional settings within the panel itself.",
"template.settingsTitle": "RPG Companion Settings",
@@ -73,6 +75,12 @@
"template.settingsModal.display.showStartEncounterNote": "Display button to initiate interactive combat encounters.",
"template.settingsModal.display.showDiceDisplay": "Show Dice Roll Display",
"template.settingsModal.display.showDiceDisplayNote": "Display the \"Last Roll\" indicator in the panel.",
"template.settingsModal.display.showCYOAToggle": "Show CYOA",
"template.settingsModal.display.showCYOAToggleNote": "Display a toggle button to enable/disable \"Choose Your Own Adventure\" formatting instruction that makes the model produce five possible actions/dialogues for you to choose from at the end of the output.",
"template.settingsModal.display.weatherPosition.background": "Show in Background",
"template.settingsModal.display.weatherPosition.backgroundNote": "Display weather effects behind the chat (standard behavior).",
"template.settingsModal.display.weatherPosition.foreground": "Show in Foreground",
"template.settingsModal.display.weatherPosition.foregroundNote": "Display weather effects in front of the chat (experimental).",
"template.mainPanel.autoAvatars": "Auto Avatars",
"template.settingsModal.advancedTitle": "Advanced",
"template.settingsModal.advanced.encounterHistoryDepth": "Chat History Depth For Encounters:",
@@ -168,6 +176,7 @@
"template.mainPanel.coloredDialogues": "Colored Dialogues",
"template.mainPanel.deceptionSystem": "Deception System",
"template.mainPanel.omniscienceFilter": "Omniscience Filter",
"template.mainPanel.cyoa": "CYOA",
"template.mainPanel.spotifyMusic": "Spotify Music",
"template.mainPanel.snowflakesEffect": "Snowflakes Effect",
"template.mainPanel.dynamicWeatherEffects": "Dynamic Weather",
@@ -186,6 +195,12 @@
"global.inventory": "Inventory",
"global.quests": "Quests",
"global.info": "Info",
"global.removeItem": "Remove item",
"global.clickToEdit": "Click to edit",
"global.collapseExpandPanel": "Collapse/Expand Panel",
"global.refreshRpgInfo": "Refresh RPG Info",
"global.showHideApiKey": "Show/Hide API Key",
"global.closeDialog": "Close dialog",
"infobox.noData.title": "No data yet",
"infobox.noData.instruction": "Generate a new response in the roleplay or switch to \"Separate Generation\" in Settings to access and click the \"Refresh RPG Info\" button",
"infobox.recentEvents.title": "Recent Events",
@@ -218,6 +233,13 @@
"inventory.assets.addAssetButton": "Add Asset",
"inventory.assets.addAssetPlaceholder": "Enter asset name...",
"inventory.assets.description": "Assets include vehicles (cars, motorcycles), property (homes, apartments), and major equipment (workshop tools, special items).",
"inventory.onPerson.addItemTitle": "Add new item",
"inventory.clothing.addItemTitle": "Add new clothing item",
"inventory.stored.addLocationTitle": "Add new storage location",
"inventory.stored.addItemToLocationTitle": "Add item to this location",
"inventory.stored.removeLocationTitle": "Remove this storage location",
"inventory.assets.addItemTitle": "Add new asset",
"inventory.assets.removeAssetTitle": "Remove asset",
"quests.section.main": "Main Quest",
"quests.section.optional": "Optional Quests",
"quests.main.title": "Main Quests",
@@ -230,6 +252,8 @@
"quests.optional.addQuestPlaceholder": "Enter optional quest title...",
"quests.optional.empty": "No active optional quests",
"quests.optional.hint": "Optional quests are side objectives that complement your main story.",
"quests.editQuestTitle": "Edit quest",
"quests.removeQuestTitle": "Complete/Remove quest",
"checkpoint.setChapterStart": "Set Chapter Start",
"checkpoint.clearChapterStart": "Clear Chapter Start",
"checkpoint.indicator": "Chapter Start",
@@ -245,6 +269,7 @@
"thoughts.clickToEdit": "Click to edit",
"thoughts.clickToUpload": "Click to upload avatar",
"thoughts.removeCharacter": "Remove character",
"thoughts.empty": "No character data generated yet",
"userStats.level": "LVL",
"userStats.clickToEditLevel": "Click to edit level",
"userStats.statsLocked": "Locked - AI cannot change stats",
@@ -257,6 +282,7 @@
"userStats.skillsLocked": "Locked - AI cannot change skills",
"userStats.skillsUnlocked": "Unlocked - AI can change skills",
"userStats.clickToEditSkills": "Click to edit skills",
"userStats.empty": "No statuses generated yet",
"infoBox.clickToEdit": "Click to edit",
"infoBox.locked": "Locked - AI cannot change this",
"infoBox.unlocked": "Unlocked - AI can change this",
@@ -272,5 +298,162 @@
"stats.con": "CON",
"stats.int": "INT",
"stats.wis": "WIS",
"stats.cha": "CHA"
"stats.cha": "CHA",
"stats.displayMode": "Display Mode:",
"stats.displayMode.percentage": "Percentage",
"stats.displayMode.number": "Number",
"dice.title": "Roll Dice",
"dice.numberOfDice": "Number of Dice:",
"dice.diceType": "Dice Type:",
"dice.rolling": "Rolling...",
"dice.result": "Result:",
"dice.saveRoll": "Save Roll",
"preset.createNewPresetTitle": "Create New Preset",
"preset.deleteCurrentPresetTitle": "Delete Current Preset",
"preset.setDefaultPresetTitle": "Set as Default Preset",
"preset.defaultPresetDescription": "This is the default preset",
"preset.label": "Preset:",
"preset.useThisPresetFor": "Use this preset for: ",
"stats.showLevel": "Show Level",
"dateFormat.weekdayMonthYear": "Weekday, Month, Year",
"dateFormat.dayNumericalMonthYear": "Day (Numerical), Month, Year",
"historyPersistence.tabTitle": "History Persistence",
"historyPersistence.settingsTitle": "History Persistence Settings",
"historyPersistence.enable": "Enable History Persistence",
"template.trackerEditorModal.tabs.historyPersistence": "History Persistence",
"historyPersistence.hint": "Inject selected tracker data into historical messages to help the AI maintain continuity for time-sensitive events, weather changes, and location tracking.",
"historyPersistence.sendAllEnabledStats": "Send All Enabled Stats on Refresh",
"historyPersistence.sendAllEnabledStatsHint": "When enabled, Refresh RPG Info will include all enabled stats from the preset in history context, ignoring the individual selections below.",
"historyPersistence.numberOfMessages": "Number of messages to include (0 = all available):",
"historyPersistence.injectionPosition": "Injection Position:",
"historyPersistence.injectionPosition.userMessageEnd": "End of the User's Message",
"historyPersistence.injectionPosition.assistantMessageEnd": "End of the Assistant's Message",
"historyPersistence.customContextPreamble": "Custom Context Preamble:",
"historyPersistence.customContextPreamblePlaceholder": "Context for that moment:",
"historyPersistence.userStatsSection": "User Stats",
"historyPersistence.userStatsHint": "Select which stats should be included in historical messages.",
"historyPersistence.statusSection": "Status (Mood/Conditions)",
"historyPersistence.inventory": "Inventory",
"historyPersistence.quests": "Quests",
"historyPersistence.infoBoxSection": "Info Box",
"historyPersistence.infoBoxHint": "Select which info box fields should be included in historical messages. These are recommended for time tracking.",
"historyPersistence.presentCharactersSection": "Present Characters",
"historyPersistence.presentCharactersHint": "Select which character fields should be included in historical messages.",
"historyPersistence.widget.date": "Date",
"historyPersistence.widget.weather": "Weather",
"historyPersistence.widget.temperature": "Temperature",
"historyPersistence.widget.time": "Time",
"historyPersistence.widget.location": "Location",
"historyPersistence.widget.recentEvents": "Recent Events",
"historyPersistence.thoughts": "Thoughts",
"historyPersistence.skills": "Skills",
"template.promptsEditor.button": "Customize Prompts",
"template.promptsEditor.buttonNote": "Edit all AI prompts used for generation, plot progression, and combat encounters.",
"template.promptsEditor.title": "Customize Prompts",
"template.promptsEditor.description": "Customize the AI prompts used throughout the extension. Leave fields empty to use defaults.",
"template.promptsEditor.restoreDefault": "Restore Default",
"template.promptsEditor.htmlPrompt.title": "HTML Prompt",
"template.promptsEditor.htmlPrompt.note": "Injected when \"Enable Immersive HTML\" is enabled. Affects all generation modes.",
"template.promptsEditor.dialogueColoringPrompt.title": "Dialogue Coloring Prompt",
"template.promptsEditor.dialogueColoringPrompt.note": "Injected when \"Enable Colored Dialogues\" is enabled. Affects all generation modes.",
"template.promptsEditor.deceptionPrompt.title": "Deception System Prompt",
"template.promptsEditor.deceptionPrompt.note": "Injected when \"Enable Deception System\" is enabled. Instructs AI to mark lies and deceptions with hidden tags.",
"template.promptsEditor.omnisciencePrompt.title": "Omniscience Filter Prompt",
"template.promptsEditor.omnisciencePrompt.note": "Injected when \"Enable Omniscience Filter\" is enabled. Instructs AI to separate information the player character cannot perceive into hidden filter tags.",
"template.promptsEditor.cyoaPrompt.title": "CYOA Prompt",
"template.promptsEditor.cyoaPrompt.note": "Injected when \"Enable CYOA\" is enabled. Instructs AI to end responses with numbered action choices. Uses very high priority (depth 102) to ensure it's the last instruction.",
"template.promptsEditor.spotifyPrompt.title": "Spotify Music Prompt",
"template.promptsEditor.spotifyPrompt.note": "Injected when \"Enable Spotify Music\" is enabled. Asks AI to suggest appropriate music for the scene.",
"template.promptsEditor.narratorPrompt.title": "Narrator Mode Prompt",
"template.promptsEditor.narratorPrompt.note": "Injected when \"Narrator Mode\" is enabled. Instructs AI to infer characters from context.",
"template.promptsEditor.contextPrompt.title": "Context Instructions Prompt",
"template.promptsEditor.contextPrompt.note": "Injected in Separate/External mode after the context summary. Tells the AI how to use the context.",
"template.promptsEditor.randomPlotPrompt.title": "Random Plot Progression Prompt",
"template.promptsEditor.randomPlotPrompt.note": "Injected when the \"Randomized Plot\" button is clicked. Introduces random elements to the story.",
"template.promptsEditor.naturalPlotPrompt.title": "Natural Plot Progression Prompt",
"template.promptsEditor.naturalPlotPrompt.note": "Injected when the \"Natural Plot\" button is clicked. Progresses the story naturally.",
"template.promptsEditor.avatarPrompt.title": "Avatar Generation Instruction",
"template.promptsEditor.avatarPrompt.note": "Instructions for LLM when generating avatar image prompts. Used by Auto-generate Missing Avatars feature.",
"template.promptsEditor.trackerPrompt.title": "Tracker Instructions",
"template.promptsEditor.trackerPrompt.note": "Instruction portion only (format specification is hardcoded). {userName} will be replaced with the user's name.",
"template.promptsEditor.trackerContinuationPrompt.title": "Tracker Continuation Instruction",
"template.promptsEditor.trackerContinuationPrompt.note": "Instructions added after tracker format specifications, telling the AI how to continue the narrative.",
"template.promptsEditor.combatPrompt.title": "Combat Narrative Style Instruction",
"template.promptsEditor.combatPrompt.note": "Writing style instructions for combat encounters. Includes prose quality guidelines and anti-repetition rules. {userName} will be replaced with the user's name.",
"template.settingsModal.mobileFabTitle": "Mobile Button Widgets",
"template.settingsModal.mobileFabNote": "Show compact info widgets around the floating button on mobile. Widgets are positioned automatically.",
"template.settingsModal.mobileFab.enabled": "Enable Floating Mobile Widgets",
"template.settingsModal.mobileFab.enabledNote": "Master toggle to show info widgets around the mobile floating button.",
"template.settingsModal.mobileFab.weatherIcon": "Weather Icon",
"template.settingsModal.mobileFab.weatherDesc": "Weather Description",
"template.settingsModal.mobileFab.clock": "Time/Clock",
"template.settingsModal.mobileFab.date": "Date",
"template.settingsModal.mobileFab.location": "Location",
"template.settingsModal.mobileFab.stats": "Stats (Health, Energy, etc.)",
"template.settingsModal.mobileFab.attributes": "RPG Attributes (STR, DEX, etc.)",
"template.settingsModal.desktopStripTitle": "Desktop Collapsed Strip Widgets",
"template.settingsModal.desktopStripNote": "Show compact info widgets in the collapsed panel strip on desktop. Displays stats vertically without needing to expand the panel.",
"template.settingsModal.desktopStrip.enabled": "Enable Strip Widgets",
"template.settingsModal.desktopStrip.enabledNote": "Shows widgets in the collapsed panel strip for quick access to stats.",
"template.settingsModal.desktopStrip.weatherIcon": "Weather Icon",
"template.settingsModal.desktopStrip.clock": "Time/Clock",
"template.settingsModal.desktopStrip.date": "Date",
"template.settingsModal.desktopStrip.location": "Location",
"template.settingsModal.desktopStrip.stats": "Stats (Health, Energy, etc.)",
"template.settingsModal.desktopStrip.attributes": "RPG Attributes (STR, DEX, etc.)",
"plotProgression.buttons.randomizedPlot": "Randomized Plot",
"plotProgression.buttons.naturalPlot": "Natural Plot",
"plotProgression.buttons.enterEncounter": "Enter Encounter",
"plotProgression.tooltips.randomizedPlot": "Generate a random plot twist or event",
"plotProgression.tooltips.naturalPlot": "Continue the story naturally without twists",
"plotProgression.tooltips.enterEncounter": "Enter combat encounter",
"encounter.configModal.title": "Configure Combat Narrative",
"encounter.configModal.combatNarrativeStyle": "Combat Narrative Style",
"encounter.configModal.combatSummaryStyle": "Combat Summary Style",
"encounter.configModal.labels.tense": "Tense:",
"encounter.configModal.labels.person": "Person:",
"encounter.configModal.labels.narration": "Narration:",
"encounter.configModal.labels.pointOfView": "Point of View:",
"encounter.configModal.options.present": "Present",
"encounter.configModal.options.past": "Past",
"encounter.configModal.options.firstPerson": "First Person",
"encounter.configModal.options.secondPerson": "Second Person",
"encounter.configModal.options.thirdPerson": "Third Person",
"encounter.configModal.options.omniscient": "Omniscient",
"encounter.configModal.options.limited": "Limited",
"encounter.configModal.placeholders.narrator": "narrator",
"encounter.configModal.rememberSettings": "Remember these settings for future encounters",
"encounter.configModal.buttons.proceed": "Proceed",
"encounter.ui.concludeEncounterTitle": "Conclude encounter early",
"encounter.ui.closeTitle": "Close (ends combat)",
"encounter.ui.initializingCombat": "Initializing combat...",
"encounter.ui.combatBegins": "Combat begins!",
"encounter.ui.allEnemies": "All Enemies",
"encounter.ui.areaOfEffect": "Area of Effect",
"encounter.ui.youHaveBeenDefeated": "You have been defeated...",
"encounter.ui.attacks": "Attacks",
"encounter.ui.items": "Items",
"encounter.ui.customAction": "Custom Action",
"encounter.ui.customActionPlaceholder": "Describe what you want to do...",
"encounter.ui.generatingCombatSummary": "Generating combat summary...",
"encounter.ui.pleaseWait": "Please wait...",
"encounter.ui.failedToCreateSummary": "Failed to create summary. You can close this window.",
"encounter.ui.wrongFormatDetected": "Wrong Format Detected",
"encounter.ui.concludeEncounterButton": "Conclude Encounter",
"encounter.ui.combatEncounterTitle": "Combat Encounter",
"encounter.ui.errorGeneratingCombatSummary": "Error generating combat summary.",
"encounter.ui.closeCombatWindow": "Close Combat Window",
"encounter.ui.combatLog": "Combat Log",
"encounter.ui.selectTarget": "Select Target",
"encounter.ui.submit": "Submit",
"encounter.ui.regenerate": "Regenerate",
"encounter.ui.or": "OR",
"global.locked": "Locked",
"global.unlocked": "Unlocked",
"global.confirm": "Confirm",
"inventory.addItemPlaceholder": "Enter item name...",
"inventory.stored.removeLocationConfirm": "Remove \"{location}\"? This will delete all items stored there.",
"userStats.clickToEdit": "Click to edit",
"quests.main.addQuestTitle": "Add main quests",
"quests.optional.addQuestTitle": "Add optional quest"
}
+1
View File
@@ -1,6 +1,7 @@
{
"settings.language.label": "Langue",
"settings.language.option.en": "English",
"settings.language.option.zh-cn": "简体中文",
"settings.language.option.zh-tw": "繁體中文",
"settings.language.option.ru": "Русский",
"settings.language.option.fr": "Français",
+1
View File
@@ -1,6 +1,7 @@
{
"settings.language.label": "Язык",
"settings.language.option.en": "English",
"settings.language.option.zh-cn": "简体中文",
"settings.language.option.zh-tw": "繁體中文",
"settings.language.option.ru": "Русский",
"settings.extensionEnabled": "Включить RPG Companion",
+459
View File
@@ -0,0 +1,459 @@
{
"settings.language.label": "语言",
"settings.language.option.en": "English",
"settings.language.option.zh-cn": "简体中文",
"settings.language.option.zh-tw": "繁體中文",
"settings.language.option.ru": "Русский",
"settings.language.option.fr": "Français",
"settings.extensionEnabled": "启用 RPG Companion",
"settings.note": "切换以启用/禁用 RPG Companion 扩展。其他设置可在面板内配置。",
"template.settingsTitle": "RPG Companion 设置",
"template.settingsModal.themeTitle": "主题",
"template.settingsModal.themeLabel": "视觉主题:",
"template.settingsModal.themeOptions.default": "默认",
"template.settingsModal.themeOptions.sciFi": "科幻 (合成波)",
"template.settingsModal.themeOptions.fantasy": "奇幻 (古朴羊皮纸)",
"template.settingsModal.themeOptions.cyberpunk": "赛博朋克 (霓虹网格)",
"template.settingsModal.themeOptions.custom": "自定义",
"template.settingsModal.themeOptions.custom.background": "背景:",
"template.settingsModal.themeOptions.custom.accent": "强调色:",
"template.settingsModal.themeOptions.custom.text": "文字:",
"template.settingsModal.themeOptions.custom.highlight": "高亮:",
"template.settingsModal.theme.statBarLow": "状态条颜色 (低)",
"template.settingsModal.theme.statBarLowNote": "数值为 0% 时的颜色。",
"template.settingsModal.theme.statBarHigh": "状态条颜色 (高)",
"template.settingsModal.theme.statBarHighNote": "数值为 100% 时的颜色。",
"template.settingsModal.displayTitle": "显示选项",
"template.settingsModal.displayNote": "您可以在 SillyTavern 的扩展标签页中启用/禁用整个 RPG Companion 扩展。",
"template.settingsModal.display.panelPosition": "面板位置:",
"template.settingsModal.display.panelPositionOptions.right": "右侧边栏",
"template.settingsModal.display.panelPositionOptions.left": "左侧边栏",
"template.settingsModal.display.toggleAutoUpdate": "消息后自动更新",
"template.settingsModal.display.toggleAutoUpdateNote": "每条消息后自动刷新 RPG 信息。",
"template.settingsModal.display.showUserStats": "显示用户数值",
"template.settingsModal.display.showUserStatsNote": "启用用户数值,跟踪您角色的数值、心情、属性、技能等。",
"template.settingsModal.display.showInfoBox": "显示信息框",
"template.settingsModal.display.showInfoBoxNote": "显示位置、时间、天气和最近事件。",
"template.settingsModal.display.showPresentCharacters": "显示在场角色",
"template.settingsModal.display.showPresentCharactersNote": "显示角色肖像及其当前想法和状态。",
"template.settingsModal.display.narratorMode": "旁白模式",
"template.settingsModal.display.narratorModeNote": "使用角色卡作为旁白。根据上下文推断角色,而非使用固定的角色引用。",
"template.settingsModal.display.showInventory": "显示物品栏",
"template.settingsModal.display.showInventoryNote": "跟踪携带的物品、穿戴的衣物、存储的物品和资产。",
"template.settingsModal.display.showQuests": "显示任务",
"template.settingsModal.display.showQuestsNote": "管理带有目标的主要和可选任务。",
"template.settingsModal.display.showLockIcons": "显示锁定/解锁跟踪器",
"template.settingsModal.display.showLockIconsNote": "在跟踪器项目上显示锁定/解锁图标,以防止 AI 修改它们。",
"template.settingsModal.display.showThoughtsInChat": "显示想法",
"template.settingsModal.display.showThoughtsInChatNote": "将角色想法显示为其消息旁边的气泡。",
"template.settingsModal.display.alwaysShowThoughtBubble": "始终显示想法气泡",
"template.settingsModal.display.alwaysShowThoughtBubbleNote": "自动展开想法气泡,无需先点击图标",
"template.settingsModal.display.enableAnimations": "启用动画",
"template.settingsModal.display.enableAnimationsNote": "数值、内容更新和掷骰的平滑过渡。",
"template.settingsModal.display.showImmersiveHtmlToggle": "显示沉浸式 HTML",
"template.settingsModal.display.showImmersiveHtmlToggleNote": "显示一个切换按钮以启用/禁用消息中的 HTML 格式。",
"template.settingsModal.display.showDialogueColoringToggle": "显示彩色对话",
"template.settingsModal.display.showDialogueColoringToggleNote": "显示一个切换按钮以启用/禁用彩色对话格式。",
"template.settingsModal.display.showDeceptionToggle": "显示欺骗系统",
"template.settingsModal.display.showDeceptionToggleNote": "显示一个切换按钮以启用/禁用用于标记谎言和欺骗的欺骗系统。",
"template.settingsModal.display.showOmniscienceToggle": "显示全知过滤器",
"template.settingsModal.display.showOmniscienceToggleNote": "显示一个切换按钮以启用/禁用用于过滤隐藏事件的全知过滤器。",
"template.settingsModal.display.showSpotifyMusicToggle": "显示 Spotify 音乐",
"template.settingsModal.display.showSpotifyMusicToggleNote": "显示 Spotify 音乐播放器,带有 AI 推荐的适合场景的曲目。",
"template.settingsModal.display.showSnowflakesToggle": "显示雪花效果",
"template.settingsModal.display.showDynamicWeatherToggle": "显示动态天气效果",
"template.settingsModal.display.showDynamicWeatherToggleNote": "显示一个切换按钮以启用/禁用动画天气效果。",
"template.settingsModal.display.showNarratorMode": "显示旁白模式",
"template.settingsModal.display.showNarratorModeNote": "显示一个切换按钮以启用/禁用旁白模式(根据上下文推断角色)。",
"template.settingsModal.display.showAutoAvatars": "显示自动生成头像",
"template.settingsModal.display.showAutoAvatarsNote": "显示一个切换按钮以自动为没有图片的角色生成头像。",
"template.settingsModal.display.showRandomizedPlot": "显示随机化剧情推进",
"template.settingsModal.display.showRandomizedPlotNote": "显示用于 AI 生成的随机剧情推进提示的按钮。",
"template.settingsModal.display.showNaturalPlot": "显示自然剧情推进",
"template.settingsModal.display.showNaturalPlotNote": "显示用于上下文感知的叙事延续提示的按钮。",
"template.settingsModal.display.showStartEncounter": "显示开始遭遇",
"template.settingsModal.display.showStartEncounterNote": "显示按钮以启动交互式战斗遭遇。",
"template.settingsModal.display.showDiceDisplay": "显示掷骰显示",
"template.settingsModal.display.showDiceDisplayNote": "在面板中显示“上次掷骰”指示器。",
"template.settingsModal.display.showCYOAToggle": "显示选择冒险",
"template.settingsModal.display.showCYOAToggleNote": "显示一个切换按钮,用于启用/禁用“选择你自己的冒险”格式指令,该指令使模型在输出结束时生成五个可能的行动/对话供你选择。",
"template.settingsModal.display.weatherPosition.background": "在背景中显示",
"template.settingsModal.display.weatherPosition.backgroundNote": "在聊天背景中显示天气效果(标准行为)。",
"template.settingsModal.display.weatherPosition.foreground": "在前景中显示",
"template.settingsModal.display.weatherPosition.foregroundNote": "在聊天前景中显示天气效果(实验性)。",
"template.mainPanel.autoAvatars": "自动头像",
"template.settingsModal.advancedTitle": "高级",
"template.settingsModal.advanced.encounterHistoryDepth": "遭遇战的聊天历史深度:",
"template.settingsModal.advanced.encounterHistoryDepthNote": "包含在战斗初始化中的最近消息数量。",
"template.settingsModal.advanced.autoSaveCombatLogs": "自动保存战斗日志",
"template.settingsModal.advanced.autoSaveCombatLogsNote": "将详细战斗日志保存到文件以供将来参考和分析。",
"template.settingsModal.advanced.clearCacheNote": "清除当前活动聊天中已提交和显示的跟踪器数据。",
"template.settingsModal.advanced.generationMode": "生成模式:",
"template.settingsModal.advanced.generationModeOptions.together": "集成生成",
"template.settingsModal.advanced.generationModeOptions.separate": "单独生成",
"template.settingsModal.advanced.generationModeNote": "集成:将 RPG 跟踪添加到主要角色扮演中。单独:单独生成 RPG 数据(手动或自动)。外部:直接连接到 OpenAI 兼容端点。",
"template.settingsModal.advanced.generationModeOptions.external": "外部 API",
"template.settingsModal.advanced.externalApi.title": "外部 API 设置",
"template.settingsModal.advanced.externalApi.baseUrl": "API 基础 URL",
"template.settingsModal.advanced.externalApi.baseUrlNote": "OpenAI 兼容端点(例如 OpenAI、OpenRouter、本地 LLM 服务器)。",
"template.settingsModal.advanced.externalApi.apiKey": "API 密钥",
"template.settingsModal.advanced.externalApi.apiKeyNote": "您的外部服务 API 密钥。",
"template.settingsModal.advanced.externalApi.model": "模型",
"template.settingsModal.advanced.externalApi.modelNote": "模型标识符(例如 gpt-4o-mini、claude-3-haiku、mistral-7b)。",
"template.settingsModal.advanced.externalApi.maxTokens": "最大令牌数",
"template.settingsModal.advanced.externalApi.temperature": "温度",
"template.settingsModal.advanced.externalApi.testConnection": "测试连接",
"template.settingsModal.advanced.contextMessages": "上下文消息:",
"template.settingsModal.advanced.contextMessagesNote": "包含的最近消息数量。",
"template.settingsModal.advanced.useSeparatePreset": "使用连接到 RPG Companion Trackers 预设的模型",
"template.settingsModal.advanced.useSeparatePresetNote": "启用后,跟踪器生成将使用“RPG Companion Trackers”预设中的模型,而不是您的主 API 模型。预设将在生成期间自动切换并在之后恢复。在该预设中选择所需模型,并确保“将预设绑定到 API 连接”切换已打开(位于导入/导出预设按钮旁边)。",
"template.settingsModal.advanced.skipInjections": "在引导生成期间跳过注入:",
"template.settingsModal.advanced.skipInjectionsOptions.none": "从不跳过",
"template.settingsModal.advanced.skipInjectionsOptions.impersonation": "仅在模拟请求时",
"template.settingsModal.advanced.skipInjectionsOptions.guided": "始终针对引导或静默提示",
"template.settingsModal.advanced.skipInjectionsNote": "设置后,当检测到引导生成(通过 `instruct` 或 `quiet_prompt`)时,扩展将不会根据所选模式注入跟踪器提示、示例或 HTML 指令。在使用 GuidedGenerations 或类似扩展时很有用。",
"template.settingsModal.advanced.customHtmlPromptTitle": "自定义 HTML 提示:",
"template.settingsModal.advanced.restoreDefaultHtmlPrompt": "恢复默认",
"template.settingsModal.advanced.customHtmlPromptNote": "自定义启用“启用沉浸式 HTML”时注入的 HTML 提示。默认提示显示在上面 - 您可以直接编辑或完全替换。点击“恢复默认”以重置。这会影响所有生成模式(同时、单独和剧情推进)。",
"template.settingsModal.advanced.clearCache": "清除扩展缓存",
"template.settingsModal.advanced.resetFabPositions": "重置按钮位置",
"template.settingsModal.advanced.resetFabPositionsNote": "将所有浮动操作按钮(切换、刷新、调试)重置为默认的左上角位置。如果按钮在屏幕外,这很有用。",
"template.trackerEditorModal.title": "编辑跟踪器",
"template.trackerEditorModal.tabs.userStats": "用户数值",
"template.trackerEditorModal.tabs.infoBox": "信息框",
"template.trackerEditorModal.tabs.presentCharacters": "在场角色",
"template.trackerEditorModal.buttons.reset": "重置",
"template.trackerEditorModal.buttons.cancel": "取消",
"template.trackerEditorModal.buttons.save": "保存并应用",
"template.trackerEditorModal.buttons.export": "导出",
"template.trackerEditorModal.buttons.import": "导入",
"template.trackerEditorModal.messages.exportSuccess": "跟踪器预设导出成功!",
"template.trackerEditorModal.messages.exportError": "跟踪器预设导出失败。请检查控制台以获取详细信息。",
"template.trackerEditorModal.messages.importSuccess": "跟踪器预设导入成功!",
"template.trackerEditorModal.messages.importError": "跟踪器预设导入失败",
"template.trackerEditorModal.messages.importConfirm": "这将替换您当前的跟踪器配置。继续吗?",
"template.trackerEditorModal.userStatsTab.customStatsTitle": "自定义数值",
"template.trackerEditorModal.userStatsTab.addCustomStatButton": "添加自定义数值",
"template.trackerEditorModal.userStatsTab.rpgAttributesTitle": "RPG 属性",
"template.trackerEditorModal.userStatsTab.enableRpgAttributes": "启用 RPG 属性部分",
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributes": "始终在提示中包含属性",
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributesNote": "如果禁用,属性仅在掷骰活动时发送。",
"template.trackerEditorModal.userStatsTab.addAttributeButton": "添加属性",
"template.trackerEditorModal.userStatsTab.statusSectionTitle": "状态部分",
"template.trackerEditorModal.userStatsTab.enableStatusSection": "启用状态部分",
"template.trackerEditorModal.userStatsTab.showMoodEmoji": "显示心情表情",
"template.trackerEditorModal.userStatsTab.statusFieldsLabel": "状态字段(逗号分隔):",
"template.trackerEditorModal.userStatsTab.skillsSectionTitle": "技能部分",
"template.trackerEditorModal.userStatsTab.enableSkillsSection": "启用技能部分",
"template.trackerEditorModal.userStatsTab.skillsLabelLabel": "技能标签:",
"template.trackerEditorModal.userStatsTab.skillsListLabel": "技能列表(逗号分隔):",
"template.trackerEditorModal.infoBoxTab.widgetsTitle": "小部件",
"template.trackerEditorModal.infoBoxTab.dateWidget": "日期",
"template.trackerEditorModal.infoBoxTab.weatherWidget": "天气",
"template.trackerEditorModal.infoBoxTab.temperatureWidget": "温度",
"template.trackerEditorModal.infoBoxTab.timeWidget": "时间",
"template.trackerEditorModal.infoBoxTab.locationWidget": "位置",
"template.trackerEditorModal.infoBoxTab.recentEventsWidget": "最近事件",
"template.trackerEditorModal.presentCharactersTab.relationshipStatusTitle": "关系状态字段",
"template.trackerEditorModal.presentCharactersTab.enableRelationshipStatus": "启用关系状态字段",
"template.trackerEditorModal.presentCharactersTab.relationshipStatusHint": "定义关系类型,并在角色肖像上显示相应的表情符号。",
"template.trackerEditorModal.presentCharactersTab.newRelationshipButton": "新关系",
"template.trackerEditorModal.presentCharactersTab.appearanceDemeanorTitle": "外观/举止字段",
"template.trackerEditorModal.presentCharactersTab.appearanceDemeanorHint": "显示在角色名字下方的字段。",
"template.trackerEditorModal.presentCharactersTab.addCustomFieldButton": "添加自定义字段",
"template.trackerEditorModal.presentCharactersTab.thoughtsConfigTitle": "想法配置",
"template.trackerEditorModal.presentCharactersTab.enableCharacterThoughts": "启用角色想法",
"template.trackerEditorModal.presentCharactersTab.thoughtsLabelLabel": "想法标签:",
"template.trackerEditorModal.presentCharactersTab.aiInstructionLabel": "AI 指令:",
"template.trackerEditorModal.presentCharactersTab.characterStatsTitle": "角色数值",
"template.trackerEditorModal.presentCharactersTab.trackCharacterStats": "跟踪角色数值",
"template.trackerEditorModal.presentCharactersTab.characterStatsHint": "为每个角色创建要跟踪的数值(显示为彩色数字)。",
"template.trackerEditorModal.presentCharactersTab.addCharacterStatButton": "添加角色数值",
"template.mainPanel.title": "RPG Companion",
"template.mainPanel.lastRoll": "上次掷骰:",
"template.mainPanel.clearLastRoll": "清除上次掷骰",
"template.mainPanel.immersiveHtml": "沉浸式 HTML",
"template.mainPanel.coloredDialogues": "彩色对话",
"template.mainPanel.deceptionSystem": "欺骗系统",
"template.mainPanel.omniscienceFilter": "全知过滤器",
"template.mainPanel.cyoa": "选择冒险",
"template.mainPanel.spotifyMusic": "Spotify 音乐",
"template.mainPanel.snowflakesEffect": "雪花效果",
"template.mainPanel.dynamicWeatherEffects": "动态天气",
"template.mainPanel.narratorMode": "旁白模式",
"template.mainPanel.refreshRpgInfo": "刷新 RPG 信息",
"template.mainPanel.updating": "更新中...",
"template.mainPanel.editTrackersButton": "编辑跟踪器",
"template.mainPanel.settingsButton": "设置",
"global.none": "无",
"global.add": "添加",
"global.cancel": "取消",
"global.listView": "列表视图",
"global.gridView": "网格视图",
"global.save": "保存",
"global.status": "状态",
"global.inventory": "物品栏",
"global.quests": "任务",
"global.info": "信息",
"global.removeItem": "移除物品",
"global.clickToEdit": "点击编辑",
"global.collapseExpandPanel": "折叠/展开面板",
"global.refreshRpgInfo": "刷新RPG信息",
"global.showHideApiKey": "显示/隐藏API密钥",
"global.closeDialog": "关闭对话框",
"infobox.noData.title": "尚无数据",
"infobox.noData.instruction": "在角色扮演中生成新响应,或在设置中切换到“单独生成”以访问并点击“刷新 RPG 信息”按钮",
"infobox.recentEvents.title": "最近事件",
"infobox.recentEvents.addEventPlaceholder": "添加事件...",
"inventory.section.onPerson": "随身携带",
"inventory.section.clothing": "衣物",
"inventory.section.stored": "存储",
"inventory.section.assets": "资产",
"inventory.onPerson.empty": "未携带任何物品",
"inventory.onPerson.title": "当前携带的物品",
"inventory.onPerson.addItemButton": "添加物品",
"inventory.onPerson.addItemPlaceholder": "输入物品名称...",
"inventory.clothing.empty": "未穿戴任何衣物",
"inventory.clothing.title": "衣物和护甲",
"inventory.clothing.addItemButton": "添加衣物",
"inventory.clothing.addItemPlaceholder": "输入衣物物品...",
"inventory.stored.title": "存储位置",
"inventory.stored.addLocationButton": "添加位置",
"inventory.stored.addLocationPlaceholder": "输入位置名称...",
"inventory.stored.saveButton": "保存",
"inventory.stored.empty": "尚无存储位置。点击“添加位置”以创建一个。",
"inventory.stored.noItems": "此处未存储任何物品",
"inventory.stored.addItemToLocationPlaceholder": "输入物品名称...",
"inventory.stored.addItemButton": "添加物品",
"inventory.stored.confirmRemoveLocationMessage": "删除“${location}”?这将删除存储在该处的所有物品。",
"inventory.stored.confirmRemoveLocationConfirmButton": "确认",
"inventory.assets.empty": "未拥有任何资产",
"inventory.assets.title": "车辆、财产和主要所有物",
"inventory.assets.addAssetModalTitle": "添加资产",
"inventory.assets.addAssetButton": "添加资产",
"inventory.assets.addAssetPlaceholder": "输入资产名称...",
"inventory.assets.description": "资产包括车辆(汽车、摩托车)、财产(房屋、公寓)和主要设备(车间工具、特殊物品)。",
"inventory.onPerson.addItemTitle": "添加新物品",
"inventory.clothing.addItemTitle": "添加新衣物",
"inventory.stored.addLocationTitle": "添加新存储位置",
"inventory.stored.addItemToLocationTitle": "添加物品到此位置",
"inventory.stored.removeLocationTitle": "移除此存储位置",
"inventory.assets.addItemTitle": "添加新资产",
"inventory.assets.removeAssetTitle": "移除资产",
"quests.section.main": "主要任务",
"quests.section.optional": "可选任务",
"quests.main.title": "主要任务",
"quests.main.addQuestButton": "添加任务",
"quests.main.addQuestPlaceholder": "输入主要任务标题...",
"quests.main.empty": "无活跃的主要任务",
"quests.main.hint": "主要任务代表您在故事中的主要目标。",
"quests.optional.title": "可选任务",
"quests.optional.addQuestButton": "添加任务",
"quests.optional.addQuestPlaceholder": "输入可选任务标题...",
"quests.optional.empty": "无活跃的可选任务",
"quests.optional.hint": "可选任务是补充您主要故事的次要目标。",
"quests.editQuestTitle": "编辑任务",
"quests.removeQuestTitle": "完成/移除任务",
"checkpoint.setChapterStart": "设置章节开始",
"checkpoint.clearChapterStart": "清除章节开始",
"checkpoint.indicator": "章节开始",
"checkpoint.tooltip": "此点之前的消息从上下文中排除",
"musicPlayer.title": "场景音乐",
"musicPlayer.noMusic": "AI 将在适合场景时推荐音乐",
"errors.parsingError": "RPG Companion Trackers 解析错误!模型返回了错误的格式。如果问题持续存在,请考虑更换生成模型。",
"settings.recommendedModels.title": "推荐模型",
"settings.recommendedModels.description": "为使扩展正常工作,**不建议使用任何低于 20B 的模型,尤其是旧模型。** 它与 SOTA 模型(如 Deepseek、Claude、GPT 或 Gemini)配合最佳。",
"thoughts.addCharacter": "添加角色",
"thoughts.locked": "已锁定",
"thoughts.unlocked": "已解锁",
"thoughts.clickToEdit": "点击编辑",
"thoughts.clickToUpload": "点击上传头像",
"thoughts.removeCharacter": "移除角色",
"thoughts.empty": "尚无角色数据生成",
"userStats.level": "LVL",
"userStats.clickToEditLevel": "点击编辑等级",
"userStats.statsLocked": "已锁定 - AI 无法更改数值",
"userStats.statsUnlocked": "已解锁 - AI 可以更改数值",
"userStats.clickToEditStatName": "点击编辑数值名称",
"userStats.clickToEditStatValue": "点击编辑",
"userStats.moodLocked": "已锁定 - AI 无法更改心情",
"userStats.moodUnlocked": "已解锁 - AI 可以更改心情",
"userStats.clickToEditEmoji": "点击编辑表情",
"userStats.skillsLocked": "已锁定 - AI 无法更改技能",
"userStats.skillsUnlocked": "已解锁 - AI 可以更改技能",
"userStats.clickToEditSkills": "点击编辑技能",
"userStats.empty": "尚无数值生成",
"infoBox.clickToEdit": "点击编辑",
"infoBox.locked": "已锁定 - AI 无法更改此项",
"infoBox.unlocked": "已解锁 - AI 可以更改此项",
"infoBox.weatherFallback": "天气",
"infoBox.locationFallback": "位置",
"stats.health": "Health",
"stats.satiety": "Satiety",
"stats.energy": "Energy",
"stats.hygiene": "Hygiene",
"stats.arousal": "Arousal",
"stats.str": "STR",
"stats.dex": "DEX",
"stats.con": "CON",
"stats.int": "INT",
"stats.wis": "WIS",
"stats.cha": "CHA",
"stats.displayMode": "显示模式:",
"stats.displayMode.percentage": "百分比",
"stats.displayMode.number": "数值",
"dice.title": "掷骰子",
"dice.numberOfDice": "骰子数量:",
"dice.diceType": "骰子类型:",
"dice.rolling": "掷骰中...",
"dice.result": "结果:",
"dice.saveRoll": "保存掷骰",
"preset.createNewPresetTitle": "创建新预设",
"preset.deleteCurrentPresetTitle": "删除当前预设",
"preset.setDefaultPresetTitle": "设为默认预设",
"preset.defaultPresetDescription": "这是默认预设",
"preset.label": "预设:",
"preset.useThisPresetFor": "将此预设用于:",
"stats.showLevel": "显示等级",
"dateFormat.weekdayMonthYear": "星期,月份,年份",
"dateFormat.dayNumericalMonthYear": "日(数字),月份,年份",
"historyPersistence.tabTitle": "历史持久性",
"historyPersistence.settingsTitle": "历史持久性设置",
"historyPersistence.enable": "启用历史持久性",
"template.trackerEditorModal.tabs.historyPersistence": "历史持久性",
"historyPersistence.hint": "将选定的跟踪器数据注入历史消息中,帮助AI保持时间敏感事件、天气变化和位置跟踪的连续性。",
"historyPersistence.sendAllEnabledStats": "刷新时发送所有启用的数值",
"historyPersistence.sendAllEnabledStatsHint": "启用后,刷新RPG信息将在历史上下文中包含预设中的所有启用数值,忽略下面的单个选择。",
"historyPersistence.numberOfMessages": "包含的消息数量(0 = 所有可用):",
"historyPersistence.injectionPosition": "注入位置:",
"historyPersistence.injectionPosition.userMessageEnd": "用户消息末尾",
"historyPersistence.injectionPosition.assistantMessageEnd": "助手消息末尾",
"historyPersistence.customContextPreamble": "自定义上下文前言:",
"historyPersistence.customContextPreamblePlaceholder": "该时刻的上下文:",
"historyPersistence.userStatsSection": "用户数值",
"historyPersistence.userStatsHint": "选择哪些数值应包含在历史消息中。",
"historyPersistence.statusSection": "状态(心情/状况)",
"historyPersistence.inventory": "物品栏",
"historyPersistence.quests": "任务",
"historyPersistence.infoBoxSection": "信息框",
"historyPersistence.infoBoxHint": "选择哪些信息框字段应包含在历史消息中。这些字段推荐用于时间跟踪。",
"historyPersistence.presentCharactersSection": "当前角色",
"historyPersistence.presentCharactersHint": "选择哪些角色字段应包含在历史消息中。",
"historyPersistence.widget.date": "日期",
"historyPersistence.widget.weather": "天气",
"historyPersistence.widget.temperature": "温度",
"historyPersistence.widget.time": "时间",
"historyPersistence.widget.location": "位置",
"historyPersistence.widget.recentEvents": "近期事件",
"historyPersistence.thoughts": "想法",
"historyPersistence.skills": "技能",
"template.promptsEditor.button": "自定义提示",
"template.promptsEditor.buttonNote": "编辑用于生成、剧情推进和战斗遭遇的所有AI提示。",
"template.promptsEditor.title": "自定义提示",
"template.promptsEditor.description": "自定义整个扩展中使用的AI提示。留空字段以使用默认值。",
"template.promptsEditor.restoreDefault": "恢复默认",
"template.promptsEditor.htmlPrompt.title": "HTML提示",
"template.promptsEditor.htmlPrompt.note": "当“启用沉浸式HTML”启用时注入。影响所有生成模式。",
"template.promptsEditor.dialogueColoringPrompt.title": "对话着色提示",
"template.promptsEditor.dialogueColoringPrompt.note": "当“启用彩色对话”启用时注入。影响所有生成模式。",
"template.promptsEditor.deceptionPrompt.title": "欺骗系统提示",
"template.promptsEditor.deceptionPrompt.note": "当“启用欺骗系统”启用时注入。指示AI用隐藏标签标记谎言和欺骗行为。",
"template.promptsEditor.omnisciencePrompt.title": "全知过滤器提示",
"template.promptsEditor.omnisciencePrompt.note": "当“启用全知过滤器”启用时注入。指示AI将玩家角色无法感知的信息分离到隐藏的过滤器标签中。",
"template.promptsEditor.cyoaPrompt.title": "选择冒险提示",
"template.promptsEditor.cyoaPrompt.note": "当“启用选择冒险”启用时注入。指示AI在回复结尾提供带编号的动作选项。使用非常高的优先级(深度102)确保它是最后一条指令。",
"template.promptsEditor.spotifyPrompt.title": "Spotify音乐提示",
"template.promptsEditor.spotifyPrompt.note": "当“启用Spotify音乐”启用时注入。要求AI为场景推荐合适的音乐。",
"template.promptsEditor.narratorPrompt.title": "旁白模式提示",
"template.promptsEditor.narratorPrompt.note": "当“旁白模式”启用时注入。指示AI从上下文中推断角色信息。",
"template.promptsEditor.contextPrompt.title": "上下文指令提示",
"template.promptsEditor.contextPrompt.note": "在Separate/External模式中,上下文摘要后注入。告诉AI如何使用上下文。",
"template.promptsEditor.randomPlotPrompt.title": "随机剧情推进提示",
"template.promptsEditor.randomPlotPrompt.note": "当点击“随机剧情”按钮时注入。为故事引入随机元素。",
"template.promptsEditor.naturalPlotPrompt.title": "自然剧情推进提示",
"template.promptsEditor.naturalPlotPrompt.note": "当点击“自然剧情”按钮时注入。自然地推进故事发展。",
"template.promptsEditor.avatarPrompt.title": "头像生成指令",
"template.promptsEditor.avatarPrompt.note": "生成头像图像提示时给LLM的指令。用于“自动生成缺失头像”功能。",
"template.promptsEditor.trackerPrompt.title": "跟踪器指令",
"template.promptsEditor.trackerPrompt.note": "仅指令部分(格式规范已硬编码)。{userName}将被替换为用户名称。",
"template.promptsEditor.trackerContinuationPrompt.title": "跟踪器延续指令",
"template.promptsEditor.trackerContinuationPrompt.note": "在跟踪器格式规范后添加的指令,告诉AI如何继续叙事。",
"template.promptsEditor.combatPrompt.title": "战斗叙事风格指令",
"template.promptsEditor.combatPrompt.note": "战斗遭遇的写作风格指令。包括散文质量指南和防重复规则。{userName}将被替换为用户名称。",
"template.settingsModal.mobileFabTitle": "移动按钮小部件",
"template.settingsModal.mobileFabNote": "在移动设备上显示围绕浮动按钮的紧凑信息小部件。小部件自动定位。",
"template.settingsModal.mobileFab.enabled": "启用浮动移动小部件",
"template.settingsModal.mobileFab.enabledNote": "主开关,用于在移动浮动按钮周围显示信息小部件。",
"template.settingsModal.mobileFab.weatherIcon": "天气图标",
"template.settingsModal.mobileFab.weatherDesc": "天气描述",
"template.settingsModal.mobileFab.clock": "时间/时钟",
"template.settingsModal.mobileFab.date": "日期",
"template.settingsModal.mobileFab.location": "位置",
"template.settingsModal.mobileFab.stats": "数值(生命值、能量等)",
"template.settingsModal.mobileFab.attributes": "RPG属性(力量、敏捷等)",
"template.settingsModal.desktopStripTitle": "桌面折叠面板条小部件",
"template.settingsModal.desktopStripNote": "在桌面上的折叠面板条中显示紧凑信息小部件。垂直显示数值,无需展开面板。",
"template.settingsModal.desktopStrip.enabled": "启用面板条小部件",
"template.settingsModal.desktopStrip.enabledNote": "在折叠面板条中显示小部件,以便快速访问数值。",
"template.settingsModal.desktopStrip.weatherIcon": "天气图标",
"template.settingsModal.desktopStrip.clock": "时间/时钟",
"template.settingsModal.desktopStrip.date": "日期",
"template.settingsModal.desktopStrip.location": "位置",
"template.settingsModal.desktopStrip.stats": "数值(生命值、能量等)",
"template.settingsModal.desktopStrip.attributes": "RPG属性(力量、敏捷等)",
"plotProgression.buttons.randomizedPlot": "随机化剧情",
"plotProgression.buttons.naturalPlot": "自然剧情",
"plotProgression.buttons.enterEncounter": "进入遭遇战",
"plotProgression.tooltips.randomizedPlot": "生成随机剧情转折或事件",
"plotProgression.tooltips.naturalPlot": "无转折地自然延续故事",
"plotProgression.tooltips.enterEncounter": "进入战斗遭遇",
"encounter.configModal.title": "配置战斗叙事",
"encounter.configModal.combatNarrativeStyle": "战斗叙事风格",
"encounter.configModal.combatSummaryStyle": "战斗总结风格",
"encounter.configModal.labels.tense": "时态:",
"encounter.configModal.labels.person": "人称:",
"encounter.configModal.labels.narration": "叙述:",
"encounter.configModal.labels.pointOfView": "视角:",
"encounter.configModal.options.present": "现在时",
"encounter.configModal.options.past": "过去时",
"encounter.configModal.options.firstPerson": "第一人称",
"encounter.configModal.options.secondPerson": "第二人称",
"encounter.configModal.options.thirdPerson": "第三人称",
"encounter.configModal.options.omniscient": "全知视角",
"encounter.configModal.options.limited": "有限视角",
"encounter.configModal.placeholders.narrator": "叙述者",
"encounter.configModal.rememberSettings": "记住这些设置以供未来遭遇使用",
"encounter.configModal.buttons.proceed": "继续",
"encounter.ui.concludeEncounterTitle": "提前结束遭遇",
"encounter.ui.closeTitle": "关闭(结束战斗)",
"encounter.ui.initializingCombat": "正在初始化战斗...",
"encounter.ui.combatBegins": "战斗开始!",
"encounter.ui.allEnemies": "所有敌人",
"encounter.ui.areaOfEffect": "范围效果",
"encounter.ui.youHaveBeenDefeated": "你已被击败...",
"encounter.ui.attacks": "攻击",
"encounter.ui.items": "物品",
"encounter.ui.customAction": "自定义动作",
"encounter.ui.customActionPlaceholder": "描述你想要做什么...",
"encounter.ui.generatingCombatSummary": "正在生成战斗总结...",
"encounter.ui.pleaseWait": "请稍候...",
"encounter.ui.failedToCreateSummary": "无法创建总结。你可以关闭此窗口。",
"encounter.ui.wrongFormatDetected": "检测到错误格式",
"encounter.ui.concludeEncounterButton": "结束遭遇",
"encounter.ui.combatEncounterTitle": "战斗遭遇",
"encounter.ui.errorGeneratingCombatSummary": "生成战斗总结时出错。",
"encounter.ui.closeCombatWindow": "关闭战斗窗口",
"encounter.ui.combatLog": "战斗日志",
"encounter.ui.selectTarget": "选择目标",
"encounter.ui.submit": "提交",
"encounter.ui.regenerate": "重新生成",
"encounter.ui.or": "或",
"global.locked": "已锁定",
"global.unlocked": "已解锁",
"global.confirm": "确认",
"inventory.addItemPlaceholder": "输入物品名称...",
"inventory.stored.removeLocationConfirm": "删除\"{location}\"?这将删除该位置存储的所有物品。",
"userStats.clickToEdit": "点击编辑",
"quests.main.addQuestTitle": "添加主线任务",
"quests.optional.addQuestTitle": "添加可选任务"
}
+2
View File
@@ -1,8 +1,10 @@
{
"settings.language.label": "語言",
"settings.language.option.en": "English",
"settings.language.option.zh-cn": "简体中文",
"settings.language.option.zh-tw": "繁體中文",
"settings.language.option.ru": "Русский",
"settings.language.option.fr": "Français",
"settings.extensionEnabled": "啟用 RPG Companion",
"settings.note": "切換開關以啟用/停用 RPG Companion。其他設定可在面板內配置。",
"template.settingsTitle": "RPG Companion 設定",
+7 -6
View File
@@ -7,6 +7,7 @@ import { togglePlotButtons } from '../ui/layout.js';
import { extensionSettings, setIsPlotProgression } from '../../core/state.js';
import { DEFAULT_HTML_PROMPT, DEFAULT_DIALOGUE_COLORING_PROMPT, DEFAULT_DECEPTION_PROMPT, DEFAULT_CYOA_PROMPT } from '../generation/promptBuilder.js';
import { Generate } from '../../../../../../../script.js';
import { i18n } from '../../core/i18n.js';
/**
* Sets up the plot progression buttons inside the send form area.
@@ -34,8 +35,8 @@ export function setupPlotButtons(handlePlotClick, handleEncounterClick) {
font-size: 13px;
cursor: pointer;
margin: 0 2px;
" tabindex="0" role="button" title="Generate a random plot twist or event">
<i class="fa-solid fa-dice"></i>&nbsp;<span class="rpg-btn-text">Randomized Plot</span>
" tabindex="0" role="button" title="${i18n.getTranslation('plotProgression.tooltips.randomizedPlot') || 'Generate a random plot twist or event'}">
<i class="fa-solid fa-dice"></i>&nbsp;<span class="rpg-btn-text">${i18n.getTranslation('plotProgression.buttons.randomizedPlot') || 'Randomized Plot'}</span>
</button>
<button id="rpg-plot-natural" class="menu_button interactable" style="
background-color: #4a90e2;
@@ -46,8 +47,8 @@ export function setupPlotButtons(handlePlotClick, handleEncounterClick) {
font-size: 13px;
cursor: pointer;
margin: 0 2px;
" tabindex="0" role="button" title="Continue the story naturally without twists">
<i class="fa-solid fa-forward"></i>&nbsp;<span class="rpg-btn-text">Natural Plot</span>
" tabindex="0" role="button" title="${i18n.getTranslation('plotProgression.tooltips.naturalPlot') || 'Continue the story naturally without twists'}">
<i class="fa-solid fa-forward"></i>&nbsp;<span class="rpg-btn-text">${i18n.getTranslation('plotProgression.buttons.naturalPlot') || 'Natural Plot'}</span>
</button>
<button id="rpg-encounter-button" class="menu_button interactable" style="
background-color: #cc3333;
@@ -58,8 +59,8 @@ export function setupPlotButtons(handlePlotClick, handleEncounterClick) {
font-size: 13px;
cursor: pointer;
margin: 0 2px;
" tabindex="0" role="button" title="Enter combat encounter">
<i class="fa-solid fa-fire"></i>&nbsp;<span class="rpg-btn-text">Enter Encounter</span>
" tabindex="0" role="button" title="${i18n.getTranslation('plotProgression.tooltips.enterEncounter') || 'Enter combat encounter'}">
<i class="fa-solid fa-fire"></i>&nbsp;<span class="rpg-btn-text">${i18n.getTranslation('plotProgression.buttons.enterEncounter') || 'Enter Encounter'}</span>
</button>
</span>
`;
+1 -1
View File
@@ -268,7 +268,7 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
// Check if parsing completely failed (no tracker data found)
if (parsedData.parsingFailed) {
toastr.error(i18n.getTranslation('errors.parsingError'), '', { timeOut: 5000 });
toastr.error(i18n.getTranslation('errors.parsingError') || 'RPG Companion Trackers parsing error! The model returned incorrect format. Consider switching generation model if this persists.', '', { timeOut: 5000 });
}
// Remove locks from parsed data (JSON format only, text format is unaffected)
+27 -6
View File
@@ -229,8 +229,10 @@ export function parseResponse(responseText) {
debugLog(`[RPG Parser] ✓ Found ${extractedObjects.length} raw JSON objects (v3 format)`);
// First, try to parse as unified JSON structure (new v3.1 format)
if (extractedObjects.length === 1) {
const parsed = repairJSON(extractedObjects[0]);
// Look through all extracted objects for unified structure
let foundUnified = false;
for (let idx = 0; idx < extractedObjects.length; idx++) {
const parsed = repairJSON(extractedObjects[idx]);
if (parsed && (parsed.userStats || parsed.infoBox || parsed.characters)) {
// console.log('[RPG Parser] ✓ Detected unified JSON structure (v3.1 format)');
@@ -247,13 +249,17 @@ export function parseResponse(responseText) {
// console.log('[RPG Parser] ✓ Extracted characters from unified structure');
}
if (result.userStats || result.infoBox || result.characterThoughts) {
foundUnified = true;
break; // Found unified structure, stop searching
}
}
if (foundUnified) {
// console.log('[RPG Parser] ✓ Returning unified JSON parse results');
debugLog('[RPG Parser] Returning unified JSON parse results');
return result;
}
}
}
// If no unified structure found, proceed to multi-object classification
// Fall back to parsing multiple separate JSON objects (legacy v3.0 format)
for (let idx = 0; idx < extractedObjects.length; idx++) {
@@ -277,6 +283,21 @@ export function parseResponse(responseText) {
}
}
// Check for unified structure format (even if previous detection missed it)
// This handles the prompt-requested format: {"userStats": {...}, "infoBox": {...}, "characters": [...]}
if (parsed.userStats || parsed.infoBox || parsed.characters) {
if (parsed.userStats) {
result.userStats = JSON.stringify(parsed.userStats);
}
if (parsed.infoBox) {
result.infoBox = JSON.stringify(parsed.infoBox);
}
if (parsed.characters) {
result.characterThoughts = JSON.stringify(parsed.characters);
}
continue; // Skip further classification
}
// Detect tracker type by checking for top-level fields
if (unwrapped.stats || unwrapped.status || unwrapped.skills || unwrapped.inventory || unwrapped.quests) {
result.userStats = jsonContent;
+15 -15
View File
@@ -363,9 +363,9 @@ export function renderInfoBox() {
row1Widgets.push(`
<div class="rpg-dashboard-widget rpg-calendar-widget">
${dateLockIconHtml}
<div class="rpg-calendar-top rpg-editable" contenteditable="true" data-field="month" data-full-value="${data.month || ''}" title="${i18n.getTranslation('infoBox.clickToEdit')}">${monthDisplay}</div>
<div class="rpg-calendar-day" title="${i18n.getTranslation('infoBox.clickToEdit')}"><span class="rpg-calendar-day-text rpg-editable" contenteditable="true" data-field="weekday" data-full-value="${data.weekday || ''}">${weekdayDisplay}</span></div>
<div class="rpg-calendar-year rpg-editable" contenteditable="true" data-field="year" data-full-value="${data.year || ''}" title="${i18n.getTranslation('infoBox.clickToEdit')}">${yearDisplay}</div>
<div class="rpg-calendar-top rpg-editable" contenteditable="true" data-field="month" data-full-value="${data.month || ''}" title="${i18n.getTranslation('infoBox.clickToEdit') || 'Click to edit'}">${monthDisplay}</div>
<div class="rpg-calendar-day" title="${i18n.getTranslation('infoBox.clickToEdit') || 'Click to edit'}"><span class="rpg-calendar-day-text rpg-editable" contenteditable="true" data-field="weekday" data-full-value="${data.weekday || ''}">${weekdayDisplay}</span></div>
<div class="rpg-calendar-year rpg-editable" contenteditable="true" data-field="year" data-full-value="${data.year || ''}" title="${i18n.getTranslation('infoBox.clickToEdit') || 'Click to edit'}">${yearDisplay}</div>
</div>
`);
}
@@ -373,14 +373,14 @@ export function renderInfoBox() {
// Weather widget - show if enabled
if (config?.widgets?.weather?.enabled) {
const weatherEmoji = data.weatherEmoji || '🌤️';
const weatherForecast = data.weatherForecast || i18n.getTranslation('infoBox.weatherFallback');
const weatherForecast = data.weatherForecast || i18n.getTranslation('infoBox.weatherFallback') || 'Weather unknown';
const weatherLockIconHtml = getLockIconHtml('infoBox', 'weather');
row1Widgets.push(`
<div class="rpg-dashboard-widget rpg-weather-widget">
${weatherLockIconHtml}
<div class="rpg-weather-icon rpg-editable" contenteditable="true" data-field="weatherEmoji" title="${i18n.getTranslation('userStats.clickToEditEmoji')}">${weatherEmoji}</div>
<div class="rpg-weather-forecast rpg-editable" contenteditable="true" data-field="weatherForecast" title="${i18n.getTranslation('infoBox.clickToEdit')}">${weatherForecast}</div>
<div class="rpg-weather-icon rpg-editable" contenteditable="true" data-field="weatherEmoji" title="${i18n.getTranslation('userStats.clickToEditEmoji') || 'Click to edit emoji'}">${weatherEmoji}</div>
<div class="rpg-weather-forecast rpg-editable" contenteditable="true" data-field="weatherForecast" title="${i18n.getTranslation('infoBox.clickToEdit') || 'Click to edit'}">${weatherForecast}</div>
</div>
`);
}
@@ -429,7 +429,7 @@ export function renderInfoBox() {
<div class="rpg-thermometer-fill" style="height: ${tempPercent}%; background: ${tempColor}"></div>
</div>
</div>
<div class="rpg-temp-value rpg-editable" contenteditable="true" data-field="temperature" title="${i18n.getTranslation('infoBox.clickToEdit')}">${tempDisplay}</div>
<div class="rpg-temp-value rpg-editable" contenteditable="true" data-field="temperature" title="${i18n.getTranslation('infoBox.clickToEdit') || 'Click to edit'}">${tempDisplay}</div>
</div>
`);
}
@@ -464,9 +464,9 @@ export function renderInfoBox() {
</div>
</div>
<div class="rpg-time-range">
<div class="rpg-time-value rpg-editable" contenteditable="true" data-field="timeStart" title="${i18n.getTranslation('infoBox.clickToEdit')}">${timeStartDisplay}</div>
<div class="rpg-time-value rpg-editable" contenteditable="true" data-field="timeStart" title="${i18n.getTranslation('infoBox.clickToEdit') || 'Click to edit'}">${timeStartDisplay}</div>
<span class="rpg-time-separator">→</span>
<div class="rpg-time-value rpg-editable" contenteditable="true" data-field="timeEnd" title="${i18n.getTranslation('infoBox.clickToEdit')}">${timeEndDisplay}</div>
<div class="rpg-time-value rpg-editable" contenteditable="true" data-field="timeEnd" title="${i18n.getTranslation('infoBox.clickToEdit') || 'Click to edit'}">${timeEndDisplay}</div>
</div>
</div>
`);
@@ -481,7 +481,7 @@ export function renderInfoBox() {
// Row 2: Location widget (full width) - show if enabled
if (config?.widgets?.location?.enabled) {
const locationDisplay = data.location || i18n.getTranslation('infoBox.locationFallback');
const locationDisplay = data.location || i18n.getTranslation('infoBox.locationFallback') || 'Unknown location';
const locationLockIconHtml = getLockIconHtml('infoBox', 'location');
html += `
@@ -491,7 +491,7 @@ export function renderInfoBox() {
<div class="rpg-map-bg">
<div class="rpg-map-marker">📍</div>
</div>
<div class="rpg-location-text rpg-editable" contenteditable="true" data-field="location" title="${i18n.getTranslation('infoBox.clickToEdit')}">${locationDisplay}</div>
<div class="rpg-location-text rpg-editable" contenteditable="true" data-field="location" title="${i18n.getTranslation('infoBox.clickToEdit') || 'Click to edit'}">${locationDisplay}</div>
</div>
</div>
`;
@@ -541,7 +541,7 @@ export function renderInfoBox() {
<div class="rpg-notebook-ring"></div>
<div class="rpg-notebook-ring"></div>
</div>
<div class="rpg-notebook-title" data-i18n-key="infobox.recentEvents.title">${i18n.getTranslation('infobox.recentEvents.title')}</div>
<div class="rpg-notebook-title" data-i18n-key="infobox.recentEvents.title">${i18n.getTranslation('infobox.recentEvents.title') || 'Recent Events'}</div>
<div class="rpg-notebook-lines">
`;
@@ -550,7 +550,7 @@ export function renderInfoBox() {
html += `
<div class="rpg-notebook-line">
<span class="rpg-bullet">•</span>
<span class="rpg-event-text rpg-editable" contenteditable="true" data-field="event${i + 1}" title="${i18n.getTranslation('infoBox.clickToEdit')}">${validEvents[i]}</span>
<span class="rpg-event-text rpg-editable" contenteditable="true" data-field="event${i + 1}" title="${i18n.getTranslation('infoBox.clickToEdit') || 'Click to edit'}">${validEvents[i]}</span>
</div>
`;
}
@@ -560,7 +560,7 @@ export function renderInfoBox() {
html += `
<div class="rpg-notebook-line rpg-event-add">
<span class="rpg-bullet">+</span>
<span class="rpg-event-text rpg-editable rpg-event-placeholder" contenteditable="true" data-field="event${i + 1}" title="Click to add event" data-i18n-key="infobox.recentEvents.addEventPlaceholder">${i18n.getTranslation('infobox.recentEvents.addEventPlaceholder')}</span>
<span class="rpg-event-text rpg-editable rpg-event-placeholder" contenteditable="true" data-field="event${i + 1}" title="Click to add event" data-i18n-key="infobox.recentEvents.addEventPlaceholder">${i18n.getTranslation('infobox.recentEvents.addEventPlaceholder') || 'Click to add event'}</span>
</div>
`;
}
@@ -652,7 +652,7 @@ export function renderInfoBox() {
// Update icon
$lockIcon.text(newLockState ? '🔒' : '🔓');
$lockIcon.attr('title', newLockState ? i18n.getTranslation('infoBox.locked') : i18n.getTranslation('infoBox.unlocked'));
$lockIcon.attr('title', newLockState ? (i18n.getTranslation('infoBox.locked') || 'Locked') : (i18n.getTranslation('infoBox.unlocked') || 'Unlocked'));
$lockIcon.toggleClass('locked', newLockState);
// Save settings to persist lock state
+74 -69
View File
@@ -9,6 +9,7 @@ import { getInventoryRenderOptions, restoreFormStates } from '../interaction/inv
import { updateInventoryItem } from '../interaction/inventoryEdit.js';
import { parseItems } from '../../utils/itemParser.js';
import { isItemLocked, setItemLock } from '../generation/lockManager.js';
import { i18n } from '../../core/i18n.js';
// Type imports
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
@@ -25,7 +26,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 `<span class="rpg-section-lock-icon${lockedClass}" data-tracker="${tracker}" data-path="${path}" title="${lockTitle}">${lockIcon}</span>`;
}
@@ -47,19 +48,24 @@ export function getLocationId(locationName) {
* @returns {string} HTML for sub-tab navigation
*/
export function renderInventorySubTabs(activeTab = 'onPerson') {
const onPersonText = i18n.getTranslation('inventory.section.onPerson') || 'On Person';
const clothingText = i18n.getTranslation('inventory.section.clothing') || 'Clothing';
const storedText = i18n.getTranslation('inventory.section.stored') || 'Stored';
const assetsText = i18n.getTranslation('inventory.section.assets') || 'Assets';
return `
<div class="rpg-inventory-subtabs">
<button class="rpg-inventory-subtab ${activeTab === 'onPerson' ? 'active' : ''}" data-tab="onPerson">
On Person
${onPersonText}
</button>
<button class="rpg-inventory-subtab ${activeTab === 'clothing' ? 'active' : ''}" data-tab="clothing">
Clothing
${clothingText}
</button>
<button class="rpg-inventory-subtab ${activeTab === 'stored' ? 'active' : ''}" data-tab="stored">
Stored
${storedText}
</button>
<button class="rpg-inventory-subtab ${activeTab === 'assets' ? 'active' : ''}" data-tab="assets">
Assets
${assetsText}
</button>
</div>
`;
@@ -76,7 +82,7 @@ export function renderOnPersonView(onPersonItems, viewMode = 'list') {
let itemsHtml = '';
if (items.length === 0) {
itemsHtml = '<div class="rpg-inventory-empty">No items carried</div>';
itemsHtml = '<div class="rpg-inventory-empty">' + (i18n.getTranslation('inventory.onPerson.empty') || 'No items carried') + '</div>';
} else {
if (viewMode === 'grid') {
// Grid view: card-style items
@@ -85,10 +91,10 @@ export function renderOnPersonView(onPersonItems, viewMode = 'list') {
return `
<div class="rpg-item-card" data-field="onPerson" data-index="${index}">
${lockIconHtml}
<button class="rpg-item-remove" data-action="remove-item" data-field="onPerson" data-index="${index}" title="Remove item">
<button class="rpg-item-remove" data-action="remove-item" data-field="onPerson" data-index="${index}" title="${i18n.getTranslation('global.removeItem') || 'Remove item'}">
<i class="fa-solid fa-times"></i>
</button>
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" title="${i18n.getTranslation('global.clickToEdit') || 'Click to edit'}">${escapeHtml(item)}</span>
</div>
`}).join('');
} else {
@@ -98,8 +104,8 @@ export function renderOnPersonView(onPersonItems, viewMode = 'list') {
return `
<div class="rpg-item-row" data-field="onPerson" data-index="${index}">
${lockIconHtml}
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
<button class="rpg-item-remove" data-action="remove-item" data-field="onPerson" data-index="${index}" title="Remove item">
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" title="${i18n.getTranslation('global.clickToEdit') || 'Click to edit'}">${escapeHtml(item)}</span>
<button class="rpg-item-remove" data-action="remove-item" data-field="onPerson" data-index="${index}" title="${i18n.getTranslation('global.removeItem') || 'Remove item'}">
<i class="fa-solid fa-times"></i>
</button>
</div>
@@ -112,30 +118,30 @@ export function renderOnPersonView(onPersonItems, viewMode = 'list') {
return `
<div class="rpg-inventory-section" data-section="onPerson">
<div class="rpg-inventory-header">
<h4>Items Currently Carried</h4>
<h4>${i18n.getTranslation('inventory.onPerson.title') || 'Items Currently Carried'}</h4>
<div class="rpg-inventory-header-actions">
<div class="rpg-view-toggle">
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="onPerson" data-view="list" title="List view">
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="onPerson" data-view="list" title="${i18n.getTranslation('global.listView') || 'List view'}">
<i class="fa-solid fa-list"></i>
</button>
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="onPerson" data-view="grid" title="Grid view">
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="onPerson" data-view="grid" title="${i18n.getTranslation('global.gridView') || 'Grid view'}">
<i class="fa-solid fa-th"></i>
</button>
</div>
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="onPerson" title="Add new item">
<i class="fa-solid fa-plus"></i> Add Item
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="onPerson" title="${i18n.getTranslation('inventory.onPerson.addItemTitle') || 'Add new item'}">
<i class="fa-solid fa-plus"></i> ${i18n.getTranslation('inventory.onPerson.addItemButton') || 'Add Item'}
</button>
</div>
</div>
<div class="rpg-inventory-content">
<div class="rpg-inline-form" id="rpg-add-item-form-onPerson" style="display: none;">
<input type="text" class="rpg-inline-input" id="rpg-new-item-onPerson" placeholder="Enter item name..." />
<input type="text" class="rpg-inline-input" id="rpg-new-item-onPerson" placeholder="${i18n.getTranslation('inventory.onPerson.addItemPlaceholder') || 'Enter item name...'}" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-item" data-field="onPerson">
<i class="fa-solid fa-times"></i> Cancel
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-item" data-field="onPerson">
<i class="fa-solid fa-check"></i> Add
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.add') || 'Add'}
</button>
</div>
</div>
@@ -158,7 +164,7 @@ export function renderClothingView(clothingItems, viewMode = 'list') {
let itemsHtml = '';
if (items.length === 0) {
itemsHtml = '<div class="rpg-inventory-empty">No clothing worn</div>';
itemsHtml = '<div class="rpg-inventory-empty">' + (i18n.getTranslation('inventory.clothing.empty') || 'No clothing worn') + '</div>';
} else {
if (viewMode === 'grid') {
// Grid view: card-style items
@@ -167,10 +173,10 @@ export function renderClothingView(clothingItems, viewMode = 'list') {
return `
<div class="rpg-item-card" data-field="clothing" data-index="${index}">
${lockIconHtml}
<button class="rpg-item-remove" data-action="remove-item" data-field="clothing" data-index="${index}" title="Remove item">
<button class="rpg-item-remove" data-action="remove-item" data-field="clothing" data-index="${index}" title="${i18n.getTranslation('global.removeItem') || 'Remove item'}">
<i class="fa-solid fa-times"></i>
</button>
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="clothing" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="clothing" data-index="${index}" title="${i18n.getTranslation('global.clickToEdit') || 'Click to edit'}">${escapeHtml(item)}</span>
</div>
`}).join('');
} else {
@@ -180,8 +186,8 @@ export function renderClothingView(clothingItems, viewMode = 'list') {
return `
<div class="rpg-item-row" data-field="clothing" data-index="${index}">
${lockIconHtml}
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="clothing" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
<button class="rpg-item-remove" data-action="remove-item" data-field="clothing" data-index="${index}" title="Remove item">
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="clothing" data-index="${index}" title="${i18n.getTranslation('global.clickToEdit') || 'Click to edit'}">${escapeHtml(item)}</span>
<button class="rpg-item-remove" data-action="remove-item" data-field="clothing" data-index="${index}" title="${i18n.getTranslation('global.removeItem') || 'Remove item'}">
<i class="fa-solid fa-times"></i>
</button>
</div>
@@ -194,30 +200,30 @@ export function renderClothingView(clothingItems, viewMode = 'list') {
return `
<div class="rpg-inventory-section" data-section="clothing">
<div class="rpg-inventory-header">
<h4>Clothing Worn</h4>
<h4>${i18n.getTranslation('inventory.clothing.title') || 'Clothing & Armor'}</h4>
<div class="rpg-inventory-header-actions">
<div class="rpg-view-toggle">
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="clothing" data-view="list" title="List view">
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="clothing" data-view="list" title="${i18n.getTranslation('global.listView') || 'List view'}">
<i class="fa-solid fa-list"></i>
</button>
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="clothing" data-view="grid" title="Grid view">
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="clothing" data-view="grid" title="${i18n.getTranslation('global.gridView') || 'Grid view'}">
<i class="fa-solid fa-th"></i>
</button>
</div>
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="clothing" title="Add new clothing item">
<i class="fa-solid fa-plus"></i> Add Clothing
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="clothing" title="${i18n.getTranslation('inventory.clothing.addItemTitle') || 'Add new clothing item'}">
<i class="fa-solid fa-plus"></i> ${i18n.getTranslation('inventory.clothing.addItemButton') || 'Add Clothing'}
</button>
</div>
</div>
<div class="rpg-inventory-content">
<div class="rpg-inline-form" id="rpg-add-item-form-clothing" style="display: none;">
<input type="text" class="rpg-inline-input" id="rpg-new-item-clothing" placeholder="Enter clothing item..." />
<input type="text" class="rpg-inline-input" id="rpg-new-item-clothing" placeholder="${i18n.getTranslation('inventory.clothing.addItemPlaceholder') || 'Enter clothing item...'}" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-item" data-field="clothing">
<i class="fa-solid fa-times"></i> Cancel
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-item" data-field="clothing">
<i class="fa-solid fa-check"></i> Add
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.add') || 'Add'}
</button>
</div>
</div>
@@ -242,30 +248,30 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li
let html = `
<div class="rpg-inventory-section" data-section="stored">
<div class="rpg-inventory-header">
<h4>Storage Locations</h4>
<h4>${i18n.getTranslation('inventory.stored.title') || 'Storage Locations'}</h4>
<div class="rpg-inventory-header-actions">
<div class="rpg-view-toggle">
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="stored" data-view="list" title="List view">
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="stored" data-view="list" title="${i18n.getTranslation('global.listView') || 'List view'}">
<i class="fa-solid fa-list"></i>
</button>
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="stored" data-view="grid" title="Grid view">
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="stored" data-view="grid" title="${i18n.getTranslation('global.gridView') || 'Grid view'}">
<i class="fa-solid fa-th"></i>
</button>
</div>
<button class="rpg-inventory-add-btn" data-action="add-location" title="Add new storage location">
<i class="fa-solid fa-plus"></i> Add Location
<button class="rpg-inventory-add-btn" data-action="add-location" title="${i18n.getTranslation('inventory.stored.addLocationTitle') || 'Add new storage location'}">
<i class="fa-solid fa-plus"></i> ${i18n.getTranslation('inventory.stored.addLocationButton') || 'Add Location'}
</button>
</div>
</div>
<div class="rpg-inventory-content">
<div class="rpg-inline-form" id="rpg-add-location-form" style="display: none;">
<input type="text" class="rpg-inline-input" id="rpg-new-location-name" placeholder="Enter location name..." />
<input type="text" class="rpg-inline-input" id="rpg-new-location-name" placeholder="${i18n.getTranslation('inventory.stored.addLocationPlaceholder') || 'Enter location name...'}" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-location">
<i class="fa-solid fa-times"></i> Cancel
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-location">
<i class="fa-solid fa-check"></i> Save
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.save') || 'Save'}
</button>
</div>
</div>
@@ -274,7 +280,7 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li
if (locations.length === 0) {
html += `
<div class="rpg-inventory-empty">
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.'}
</div>
`;
} else {
@@ -286,7 +292,7 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li
let itemsHtml = '';
if (items.length === 0) {
itemsHtml = '<div class="rpg-inventory-empty">No items stored here</div>';
itemsHtml = '<div class="rpg-inventory-empty">' + (i18n.getTranslation('inventory.stored.noItems') || 'No items stored here') + '</div>';
} else {
if (viewMode === 'grid') {
// Grid view: card-style items
@@ -295,10 +301,10 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li
return `
<div class="rpg-item-card" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}">
${lockIconHtml}
<button class="rpg-item-remove" data-action="remove-item" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Remove item">
<button class="rpg-item-remove" data-action="remove-item" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="${i18n.getTranslation('global.removeItem') || 'Remove item'}">
<i class="fa-solid fa-times"></i>
</button>
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="${i18n.getTranslation('global.clickToEdit') || 'Click to edit'}">${escapeHtml(item)}</span>
</div>
`}).join('');
} else {
@@ -308,8 +314,8 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li
return `
<div class="rpg-item-row" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}">
${lockIconHtml}
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
<button class="rpg-item-remove" data-action="remove-item" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Remove item">
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="${i18n.getTranslation('global.clickToEdit') || 'Click to edit'}">${escapeHtml(item)}</span>
<button class="rpg-item-remove" data-action="remove-item" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="${i18n.getTranslation('global.removeItem') || 'Remove item'}">
<i class="fa-solid fa-times"></i>
</button>
</div>
@@ -327,20 +333,20 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li
</button>
<h5 class="rpg-storage-name">${escapeHtml(location)}</h5>
<div class="rpg-storage-actions">
<button class="rpg-inventory-remove-btn" data-action="remove-location" data-location="${escapeHtml(location)}" title="Remove this storage location">
<button class="rpg-inventory-remove-btn" data-action="remove-location" data-location="${escapeHtml(location)}" title="${i18n.getTranslation('inventory.stored.removeLocationTitle') || 'Remove this storage location'}">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
<div class="rpg-storage-content" ${isCollapsed ? 'style="display:none;"' : ''}>
<div class="rpg-inline-form" id="rpg-add-item-form-stored-${locationId}" style="display: none;">
<input type="text" class="rpg-inline-input rpg-location-item-input" data-location="${escapeHtml(location)}" placeholder="Enter item name..." />
<input type="text" class="rpg-inline-input rpg-location-item-input" data-location="${escapeHtml(location)}" placeholder="${i18n.getTranslation('inventory.addItemPlaceholder') || 'Enter item name...'}" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-item" data-field="stored" data-location="${escapeHtml(location)}">
<i class="fa-solid fa-times"></i> Cancel
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-item" data-field="stored" data-location="${escapeHtml(location)}">
<i class="fa-solid fa-check"></i> Add
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.add') || 'Add'}
</button>
</div>
</div>
@@ -348,19 +354,19 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li
${itemsHtml}
</div>
<div class="rpg-storage-add-item-container">
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="stored" data-location="${escapeHtml(location)}" title="Add item to this location">
<i class="fa-solid fa-plus"></i> Add Item
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="stored" data-location="${escapeHtml(location)}" title="${i18n.getTranslation('inventory.stored.addItemToLocationTitle') || 'Add item to this location'}">
<i class="fa-solid fa-plus"></i> ${i18n.getTranslation('inventory.stored.addItemButton') || 'Add Item'}
</button>
</div>
</div>
<div class="rpg-inline-confirmation" id="rpg-remove-confirm-${locationId}" style="display: none;">
<p>Remove "${escapeHtml(location)}"? This will delete all items stored there.</p>
<p>${(i18n.getTranslation('inventory.stored.removeLocationConfirm') || 'Remove "{location}"? This will delete all items stored there.').replace('{location}', escapeHtml(location))}</p>
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-remove-location" data-location="${escapeHtml(location)}">
<i class="fa-solid fa-times"></i> Cancel
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
</button>
<button class="rpg-inline-btn rpg-inline-confirm" data-action="confirm-remove-location" data-location="${escapeHtml(location)}">
<i class="fa-solid fa-check"></i> Confirm
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.confirm') || 'Confirm'}
</button>
</div>
</div>
@@ -388,7 +394,7 @@ export function renderAssetsView(assets, viewMode = 'list') {
let itemsHtml = '';
if (items.length === 0) {
itemsHtml = '<div class="rpg-inventory-empty">No assets owned</div>';
itemsHtml = '<div class="rpg-inventory-empty">' + (i18n.getTranslation('inventory.assets.empty') || 'No assets owned') + '</div>';
} else {
if (viewMode === 'grid') {
// Grid view: card-style items
@@ -397,10 +403,10 @@ export function renderAssetsView(assets, viewMode = 'list') {
return `
<div class="rpg-item-card" data-field="assets" data-index="${index}">
${lockIconHtml}
<button class="rpg-item-remove" data-action="remove-item" data-field="assets" data-index="${index}" title="Remove asset">
<button class="rpg-item-remove" data-action="remove-item" data-field="assets" data-index="${index}" title="${i18n.getTranslation('inventory.assets.removeAssetTitle') || 'Remove asset'}">
<i class="fa-solid fa-times"></i>
</button>
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" title="${i18n.getTranslation('global.clickToEdit') || 'Click to edit'}">${escapeHtml(item)}</span>
</div>
`}).join('');
} else {
@@ -410,8 +416,8 @@ export function renderAssetsView(assets, viewMode = 'list') {
return `
<div class="rpg-item-row" data-field="assets" data-index="${index}">
${lockIconHtml}
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
<button class="rpg-item-remove" data-action="remove-item" data-field="assets" data-index="${index}" title="Remove asset">
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" title="${i18n.getTranslation('global.clickToEdit') || 'Click to edit'}">${escapeHtml(item)}</span>
<button class="rpg-item-remove" data-action="remove-item" data-field="assets" data-index="${index}" title="${i18n.getTranslation('inventory.assets.removeAssetTitle') || 'Remove asset'}">
<i class="fa-solid fa-times"></i>
</button>
</div>
@@ -424,30 +430,30 @@ export function renderAssetsView(assets, viewMode = 'list') {
return `
<div class="rpg-inventory-section" data-section="assets">
<div class="rpg-inventory-header">
<h4>Vehicles, Property & Major Possessions</h4>
<h4>${i18n.getTranslation('inventory.assets.title') || 'Vehicles, Property & Major Possessions'}</h4>
<div class="rpg-inventory-header-actions">
<div class="rpg-view-toggle">
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="assets" data-view="list" title="List view">
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="assets" data-view="list" title="${i18n.getTranslation('global.listView') || 'List view'}">
<i class="fa-solid fa-list"></i>
</button>
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="assets" data-view="grid" title="Grid view">
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="assets" data-view="grid" title="${i18n.getTranslation('global.gridView') || 'Grid view'}">
<i class="fa-solid fa-th"></i>
</button>
</div>
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="assets" title="Add new asset">
<i class="fa-solid fa-plus"></i> Add Asset
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="assets" title="${i18n.getTranslation('inventory.assets.addItemTitle') || 'Add new asset'}">
<i class="fa-solid fa-plus"></i> ${i18n.getTranslation('inventory.assets.addAssetButton') || 'Add Asset'}
</button>
</div>
</div>
<div class="rpg-inventory-content">
<div class="rpg-inline-form" id="rpg-add-item-form-assets" style="display: none;">
<input type="text" class="rpg-inline-input" id="rpg-new-item-assets" placeholder="Enter asset name..." />
<input type="text class="rpg-inline-input" id="rpg-new-item-assets" placeholder="${i18n.getTranslation('inventory.assets.addAssetPlaceholder') || 'Enter asset name...'}" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-item" data-field="assets">
<i class="fa-solid fa-times"></i> Cancel
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-item" data-field="assets">
<i class="fa-solid fa-check"></i> Add
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.add') || 'Add'}
</button>
</div>
</div>
@@ -456,8 +462,7 @@ export function renderAssetsView(assets, viewMode = 'list') {
</div>
<div class="rpg-inventory-hint">
<i class="fa-solid fa-info-circle"></i>
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).'}
</div>
</div>
</div>
+39 -25
View File
@@ -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 `<span class="rpg-section-lock-icon${lockedClass}" data-tracker="${tracker}" data-path="${path}" title="${lockTitle}">${lockIcon}</span>`;
}
@@ -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 `
<div class="rpg-quests-subtabs">
<button class="rpg-quests-subtab ${activeTab === 'main' ? 'active' : ''}" data-tab="main">
Main Quest
${mainText}
</button>
<button class="rpg-quests-subtab ${activeTab === 'optional' ? 'active' : ''}" data-tab="optional">
Optional Quests
${optionalText}
</button>
</div>
`;
@@ -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 `
<div class="rpg-quest-section">
<div class="rpg-quest-header">
<h3 class="rpg-quest-section-title">Main Quests</h3>
${!hasQuest ? `<button class="rpg-add-quest-btn" data-action="add-quest" data-field="main" title="Add main quests">
<i class="fa-solid fa-plus"></i> Add Quest
<h3 class="rpg-quest-section-title">${mainTitle}</h3>
${!hasQuest ? `<button class="rpg-add-quest-btn" data-action="add-quest" data-field="main" title="${i18n.getTranslation('quests.main.addQuestTitle') || 'Add main quests'}">
<i class="fa-solid fa-plus"></i> ${addQuestButtonText}
</button>` : ''}
</div>
<div class="rpg-quest-content">
@@ -101,10 +110,10 @@ export function renderMainQuestView(mainQuest) {
<input type="text" class="rpg-inline-input" id="rpg-edit-quest-main" value="${escapeHtml(questDisplay)}" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-edit-quest" data-field="main">
<i class="fa-solid fa-times"></i> Cancel
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-edit-quest" data-field="main">
<i class="fa-solid fa-check"></i> Save
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.save') || 'Save'}
</button>
</div>
</div>
@@ -112,32 +121,32 @@ export function renderMainQuestView(mainQuest) {
${getLockIconHtml('userStats', 'quests.main')}
<div class="rpg-quest-title">${escapeHtml(questDisplay)}</div>
<div class="rpg-quest-actions">
<button class="rpg-quest-edit" data-action="edit-quest" data-field="main" title="Edit quest">
<button class="rpg-quest-edit" data-action="edit-quest" data-field="main" title="${i18n.getTranslation('quests.editQuestTitle') || 'Edit quest'}">
<i class="fa-solid fa-edit"></i>
</button>
<button class="rpg-quest-remove" data-action="remove-quest" data-field="main" title="Complete/Remove quest">
<button class="rpg-quest-remove" data-action="remove-quest" data-field="main" title="${i18n.getTranslation('quests.removeQuestTitle') || 'Complete/Remove quest'}">
<i class="fa-solid fa-check"></i>
</button>
</div>
</div>
` : `
<div class="rpg-inline-form" id="rpg-add-quest-form-main" style="display: none;">
<input type="text" class="rpg-inline-input" id="rpg-new-quest-main" placeholder="Enter main quests title..." />
<input type="text" class="rpg-inline-input" id="rpg-new-quest-main" placeholder="${addQuestPlaceholderText}" />
<div class="rpg-inline-actions">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-quest" data-field="main">
<i class="fa-solid fa-times"></i> Cancel
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-quest" data-field="main">
<i class="fa-solid fa-check"></i> Add
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.add') || 'Add'}
</button>
</div>
</div>
<div class="rpg-quest-empty">No active main quests</div>
<div class="rpg-quest-empty">${mainEmptyText}</div>
`}
</div>
<div class="rpg-quest-hint">
<i class="fa-solid fa-lightbulb"></i>
The main quests represent your primary objective in the story.
${mainHint}
</div>
</div>
`;
@@ -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 = '<div class="rpg-quest-empty">No active optional quests</div>';
questsHtml = `<div class="rpg-quest-empty">${optionalEmptyText}</div>`;
} else {
questsHtml = quests.map((quest, index) => {
return `
<div class="rpg-quest-item" data-field="optional" data-index="${index}">
${getLockIconHtml('userStats', `quests.optional[${index}]`)}
<div class="rpg-quest-title rpg-editable" contenteditable="true" data-field="optional" data-index="${index}" title="Click to edit">${escapeHtml(quest)}</div>
<div class="rpg-quest-title rpg-editable" contenteditable="true" data-field="optional" data-index="${index}" title="${i18n.getTranslation('global.clickToEdit') || 'Click to edit'}">${escapeHtml(quest)}</div>
<div class="rpg-quest-actions">
<button class="rpg-quest-remove" data-action="remove-quest" data-field="optional" data-index="${index}" title="Complete/Remove quest">
<button class="rpg-quest-remove" data-action="remove-quest" data-field="optional" data-index="${index}" title="${i18n.getTranslation('quests.removeQuestTitle') || 'Complete/Remove quest'}">
<i class="fa-solid fa-check"></i>
</button>
</div>
@@ -172,20 +186,20 @@ export function renderOptionalQuestsView(optionalQuests) {
return `
<div class="rpg-quest-section">
<div class="rpg-quest-header">
<h3 class="rpg-quest-section-title">Optional Quests</h3>
<button class="rpg-add-quest-btn" data-action="add-quest" data-field="optional" title="Add optional quest">
<i class="fa-solid fa-plus"></i> Add Quest
<h3 class="rpg-quest-section-title">${optionalTitle}</h3>
<button class="rpg-add-quest-btn" data-action="add-quest" data-field="optional" title="${i18n.getTranslation('quests.optional.addQuestTitle') || 'Add optional quest'}">
<i class="fa-solid fa-plus"></i> ${addQuestButtonText}
</button>
</div>
<div class="rpg-quest-content">
<div class="rpg-inline-form" id="rpg-add-quest-form-optional" style="display: none;">
<input type="text" class="rpg-inline-input" id="rpg-new-quest-optional" placeholder="Enter optional quest title..." />
<input type="text" class="rpg-inline-input" id="rpg-new-quest-optional" placeholder="${addQuestPlaceholderText}" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-quest" data-field="optional">
<i class="fa-solid fa-times"></i> Cancel
<i class="fa-solid fa-times"></i> ${i18n.getTranslation('global.cancel') || 'Cancel'}
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-quest" data-field="optional">
<i class="fa-solid fa-check"></i> Add
<i class="fa-solid fa-check"></i> ${i18n.getTranslation('global.add') || 'Add'}
</button>
</div>
</div>
@@ -194,7 +208,7 @@ export function renderOptionalQuestsView(optionalQuests) {
</div>
<div class="rpg-quest-hint">
<i class="fa-solid fa-info-circle"></i>
Optional quests are side objectives that complement your main story.
${optionalHint}
</div>
</div>
</div>
+13 -13
View File
@@ -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 `<span class="rpg-section-lock-icon${lockedClass}" data-tracker="${tracker}" data-path="${path}" title="${lockTitle}">${lockIcon}</span>`;
}
@@ -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('<div class="rpg-inventory-empty">No character data generated yet</div>');
$thoughtsContainer.html('<div class="rpg-inventory-empty">' + (i18n.getTranslation('thoughts.empty') || 'No character data generated yet') + '</div>');
return;
}
@@ -503,14 +503,14 @@ export function renderThoughts({ preserveScroll = false } = {}) {
html += `
<div class="rpg-character-card" data-character-name="${char.name}">
<div class="rpg-character-header-row">
<div class="rpg-character-avatar rpg-avatar-upload" data-character="${char.name}" title="${i18n.getTranslation('thoughts.clickToUpload')}">
<div class="rpg-character-avatar rpg-avatar-upload" data-character="${char.name}" title="${i18n.getTranslation('thoughts.clickToUpload') || 'Click to upload avatar'}">
<img src="${characterPortrait}" alt="${char.name}" onerror="this.style.opacity='0.5';this.onerror=null;" />
${hasRelationshipEnabled ? `<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${relationshipFieldName}" title="${i18n.getTranslation('thoughts.clickToEdit')} (emoji: ⚔️ ⚖️ ⭐ ❤️)">${relationshipBadge}</div>` : ''}
${hasRelationshipEnabled ? `<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${relationshipFieldName}" title="${i18n.getTranslation('thoughts.clickToEdit') || 'Click to edit'} (emoji: ⚔️ ⚖️ ⭐ ❤️)">${relationshipBadge}</div>` : ''}
</div>
<div class="rpg-character-header">
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${char.name}" data-field="emoji" title="${i18n.getTranslation('thoughts.clickToEdit')}">${char.emoji}</span>
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="name" title="${i18n.getTranslation('thoughts.clickToEdit')}">${char.name}</span>
<button class="rpg-character-remove" data-character="${char.name}" title="${i18n.getTranslation('thoughts.removeCharacter')}">×</button>
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${char.name}" data-field="emoji" title="${i18n.getTranslation('thoughts.clickToEdit') || 'Click to edit'}">${char.emoji}</span>
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="name" title="${i18n.getTranslation('thoughts.clickToEdit') || 'Click to edit'}">${char.name}</span>
<button class="rpg-character-remove" data-character="${char.name}" title="${i18n.getTranslation('thoughts.removeCharacter') || 'Remove character'}">×</button>
</div>
</div>
<div class="rpg-character-content">
@@ -533,12 +533,12 @@ export function renderThoughts({ preserveScroll = false } = {}) {
html += `
<div class="rpg-character-field rpg-character-${fieldId}" style="position: relative;">
${lockIconHtml}
<span class="rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="${i18n.getTranslation('thoughts.clickToEdit')}" ${placeholder}>${fieldValue}</span>
<span class="rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="${i18n.getTranslation('thoughts.clickToEdit') || 'Click to edit'}" ${placeholder}>${fieldValue}</span>
</div>
`;
} else {
html += `
<div class="rpg-character-field rpg-character-${fieldId} rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="${i18n.getTranslation('thoughts.clickToEdit')}" ${placeholder}>${fieldValue}</div>
<div class="rpg-character-field rpg-character-${fieldId} rpg-editable${emptyClass}" contenteditable="true" data-character="${char.name}" data-field="${field.name}" title="${i18n.getTranslation('thoughts.clickToEdit') || 'Click to edit'}" ${placeholder}>${fieldValue}</div>
`;
}
}
@@ -564,7 +564,7 @@ export function renderThoughts({ preserveScroll = false } = {}) {
);
html += `
<div class="rpg-character-stat">
<span class="rpg-stat-name">${stat.name}: </span><span class="rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${stat.name}" style="color: ${statColor}" title="${i18n.getTranslation('thoughts.clickToEdit')}">${statValue}%</span>
<span class="rpg-stat-name">${stat.name}: </span><span class="rpg-editable" contenteditable="true" data-character="${char.name}" data-field="${stat.name}" style="color: ${statColor}" title="${i18n.getTranslation('thoughts.clickToEdit') || 'Click to edit'}">${statValue}%</span>
</div>
`;
}
@@ -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 += `
<button class="rpg-add-character-btn" title="${i18n.getTranslation('thoughts.addCharacter')}">
<i class="fa-solid fa-plus"></i> ${i18n.getTranslation('thoughts.addCharacter')}
<button class="rpg-add-character-btn" title="${i18n.getTranslation('thoughts.addCharacter') || 'Add character'}">
<i class="fa-solid fa-plus"></i> ${i18n.getTranslation('thoughts.addCharacter') || 'Add character'}
</button>
`;
}
@@ -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('<div class="rpg-inventory-empty">No character data generated yet</div>');
$thoughtsContainer.html('<div class="rpg-inventory-empty">' + (i18n.getTranslation('thoughts.empty') || 'No character data generated yet') + '</div>');
return;
}
+12 -12
View File
@@ -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('<div class="rpg-inventory-empty">No statuses generated yet</div>');
$userStatsContainer.html('<div class="rpg-inventory-empty">' + (i18n.getTranslation('userStats.empty') || 'No statuses generated yet') + '</div>');
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 = '<div class="rpg-stats-content">';
@@ -287,8 +287,8 @@ export function renderUserStats() {
<img src="${userPortrait}" alt="${userName}" class="rpg-user-portrait" onerror="this.style.opacity='0.5';this.onerror=null;" />
<span class="rpg-user-name">${userName}</span>
${showLevel ? `<span style="opacity: 0.5;">|</span>
<span class="rpg-level-label">${i18n.getTranslation('userStats.level')}</span>
<span class="rpg-level-value rpg-editable" contenteditable="true" data-field="level" title="${i18n.getTranslation('userStats.clickToEditLevel')}">${extensionSettings.level}</span>` : ''}
<span class="rpg-level-label">${i18n.getTranslation('userStats.level') || 'Level'}</span>
<span class="rpg-level-value rpg-editable" contenteditable="true" data-field="level" title="${i18n.getTranslation('userStats.clickToEditLevel') || 'Click to edit level'}">${extensionSettings.level}</span>` : ''}
</div>
`;
@@ -321,11 +321,11 @@ export function renderUserStats() {
html += `
<div class="rpg-stat-row">
<span class="rpg-stat-label rpg-editable-stat-name" contenteditable="true" data-field="${stat.id}" title="${i18n.getTranslation('userStats.clickToEditStatName')}">${stat.name}:</span>
<span class="rpg-stat-label rpg-editable-stat-name" contenteditable="true" data-field="${stat.id}" title="${i18n.getTranslation('userStats.clickToEditStatName') || 'Click to edit stat name'}">${stat.name}:</span>
<div class="rpg-stat-bar" style="background: ${gradient}">
<div class="rpg-stat-fill" style="width: ${100 - percentage}%"></div>
</div>
<span class="rpg-stat-value rpg-editable-stat" contenteditable="true" data-field="${stat.id}" data-max="${maxValue}" data-mode="${displayMode}" title="${i18n.getTranslation('userStats.clickToEditStatValue')}">${displayValue}</span>
<span class="rpg-stat-value rpg-editable-stat" contenteditable="true" data-field="${stat.id}" data-max="${maxValue}" data-mode="${displayMode}" title="${i18n.getTranslation('userStats.clickToEditStatValue') || 'Click to edit stat value'}">${displayValue}</span>
</div>
`;
}
@@ -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 += '<div class="rpg-mood">';
if (showLockIcons) {
@@ -343,7 +343,7 @@ export function renderUserStats() {
}
if (config.statusSection.showMoodEmoji) {
html += `<div class="rpg-mood-emoji rpg-editable" contenteditable="true" data-field="mood" title="${i18n.getTranslation('userStats.clickToEditEmoji')}">${stats.mood}</div>`;
html += `<div class="rpg-mood-emoji rpg-editable" contenteditable="true" data-field="mood" title="${i18n.getTranslation('userStats.clickToEditEmoji') || 'Click to edit emoji'}">${stats.mood}</div>`;
}
// 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 += `<div class="rpg-mood-conditions rpg-editable" contenteditable="true" data-field="${fieldKey}" title="Click to edit ${fieldName}">${fieldValue}</div>`;
html += `<div class="rpg-mood-conditions rpg-editable" contenteditable="true" data-field="${fieldKey}" title="${i18n.getTranslation('userStats.clickToEdit') || 'Click to edit'} ${fieldName}">${fieldValue}</div>`;
}
}
@@ -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 += `
<span class="rpg-skills-label">${config.skillsSection.label}:</span>
<div class="rpg-skills-value rpg-editable" contenteditable="true" data-field="skills" title="${i18n.getTranslation('userStats.clickToEditSkills')}">${skillsValue}</div>
<div class="rpg-skills-value rpg-editable" contenteditable="true" data-field="skills" title="${i18n.getTranslation('userStats.clickToEditSkills') || 'Click to edit skills'}">${skillsValue}</div>
</div>
`;
}
@@ -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);
+58 -58
View File
@@ -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
+1 -1
View File
@@ -144,7 +144,7 @@ export function updateCollapseToggleIcon() {
*/
export function setupCollapseToggle() {
const $collapseToggle = $('#rpg-collapse-toggle');
$collapseToggle.attr('title', i18n.getTranslation('template.mainPanel.collapseExpand'));
$collapseToggle.attr('title', i18n.getTranslation('template.mainPanel.collapseExpand') || 'Collapse/Expand panel');
const $panel = $('#rpg-companion-panel');
const $icon = $collapseToggle.find('i');
+20 -7
View File
@@ -38,10 +38,23 @@ export function updateMobileTabLabels() {
}
if (translationKey) {
const translation = i18n.getTranslation(translationKey);
if (translation) {
$tab.find('span').text(translation);
let fallback = '';
switch (tabName) {
case 'stats':
fallback = 'Status';
break;
case 'info':
fallback = 'Info';
break;
case 'inventory':
fallback = 'Inventory';
break;
case 'quests':
fallback = 'Quests';
break;
}
const translation = i18n.getTranslation(translationKey) || fallback;
$tab.find('span').text(translation);
}
});
}
@@ -609,19 +622,19 @@ export function setupMobileTabs() {
// Tab 1: Stats (User Stats only)
if (hasStats) {
tabs.push('<button class="rpg-mobile-tab active" data-tab="stats"><i class="fa-solid fa-chart-bar"></i><span>' + i18n.getTranslation('global.status') + '</span></button>');
tabs.push('<button class="rpg-mobile-tab active" data-tab="stats"><i class="fa-solid fa-chart-bar"></i><span>' + (i18n.getTranslation('global.status') || 'Status') + '</span></button>');
}
// Tab 2: Info (Info Box + Character Thoughts)
if (hasInfo) {
tabs.push('<button class="rpg-mobile-tab ' + (tabs.length === 0 ? 'active' : '') + '" data-tab="info"><i class="fa-solid fa-book"></i><span>' + i18n.getTranslation('global.info') + '</span></button>');
tabs.push('<button class="rpg-mobile-tab ' + (tabs.length === 0 ? 'active' : '') + '" data-tab="info"><i class="fa-solid fa-book"></i><span>' + (i18n.getTranslation('global.info') || 'Info') + '</span></button>');
}
// Tab 3: Inventory
if (hasInventory) {
tabs.push('<button class="rpg-mobile-tab ' + (tabs.length === 0 ? 'active' : '') + '" data-tab="inventory"><i class="fa-solid fa-box"></i><span>' + i18n.getTranslation('global.inventory') + '</span></button>');
tabs.push('<button class="rpg-mobile-tab ' + (tabs.length === 0 ? 'active' : '') + '" data-tab="inventory"><i class="fa-solid fa-box"></i><span>' + (i18n.getTranslation('global.inventory') || 'Inventory') + '</span></button>');
}
// Tab 4: Quests
if (hasQuests) {
tabs.push('<button class="rpg-mobile-tab ' + (tabs.length === 0 ? 'active' : '') + '" data-tab="quests"><i class="fa-solid fa-scroll"></i><span>' + i18n.getTranslation('global.quests') + '</span></button>');
tabs.push('<button class="rpg-mobile-tab ' + (tabs.length === 0 ? 'active' : '') + '" data-tab="quests"><i class="fa-solid fa-scroll"></i><span>' + (i18n.getTranslation('global.quests') || 'Quests') + '</span></button>');
}
const $tabNav = $('<div class="rpg-mobile-tabs">' + tabs.join('') + '</div>');
+1 -1
View File
@@ -323,7 +323,7 @@ export function setupDiceRoller() {
e.stopPropagation(); // Prevent opening the dice popup
clearDiceRollCore();
});
$('#rpg-clear-dice').attr('title', i18n.getTranslation('template.mainPanel.clearLastRoll'));
$('#rpg-clear-dice').attr('title', i18n.getTranslation('template.mainPanel.clearLastRoll') || 'Clear last roll');
return diceModal;
}
+41 -41
View File
@@ -225,7 +225,7 @@ function updatePresetUI() {
// Update the default button appearance
const $defaultBtn = $('#rpg-preset-default');
if (isDefaultPreset(activePresetId)) {
$defaultBtn.addClass('rpg-btn-active').attr('title', 'This is the default preset');
$defaultBtn.addClass('rpg-btn-active').attr('title', i18n.getTranslation('preset.defaultPresetDescription') || 'This is the default preset');
} else {
$defaultBtn.removeClass('rpg-btn-active').attr('title', 'Set as Default Preset');
}
@@ -334,18 +334,18 @@ function resetToDefaults() {
extensionSettings.trackerConfig = {
userStats: {
customStats: [
{ id: 'health', name: i18n.getTranslation('stats.health'), enabled: true, persistInHistory: false },
{ id: 'satiety', name: i18n.getTranslation('stats.satiety'), enabled: true, persistInHistory: false },
{ id: 'energy', name: i18n.getTranslation('stats.energy'), enabled: true, persistInHistory: false },
{ id: 'hygiene', name: i18n.getTranslation('stats.hygiene'), enabled: true, persistInHistory: false },
{ id: 'arousal', name: i18n.getTranslation('stats.arousal'), enabled: true, persistInHistory: false }
{ id: 'health', name: i18n.getTranslation('stats.health') || 'Health', enabled: true, persistInHistory: false },
{ id: 'satiety', name: i18n.getTranslation('stats.satiety') || 'Satiety', enabled: true, persistInHistory: false },
{ id: 'energy', name: i18n.getTranslation('stats.energy') || 'Energy', enabled: true, persistInHistory: false },
{ id: 'hygiene', name: i18n.getTranslation('stats.hygiene') || 'Hygiene', enabled: true, persistInHistory: false },
{ id: 'arousal', name: i18n.getTranslation('stats.arousal') || 'Arousal', enabled: true, persistInHistory: false }
],
showRPGAttributes: true,
rpgAttributes: [
{ id: 'str', name: i18n.getTranslation('stats.str'), enabled: true, persistInHistory: false },
{ id: 'dex', name: i18n.getTranslation('stats.dex'), enabled: true, persistInHistory: false },
{ id: 'con', name: i18n.getTranslation('stats.con'), enabled: true, persistInHistory: false },
{ id: 'int', name: i18n.getTranslation('stats.int'), enabled: true, persistInHistory: false },
{ id: 'str', name: i18n.getTranslation('stats.str') || 'STR', enabled: true, persistInHistory: false },
{ id: 'dex', name: i18n.getTranslation('stats.dex') || 'DEX', enabled: true, persistInHistory: false },
{ id: 'con', name: i18n.getTranslation('stats.con') || 'CON', enabled: true, persistInHistory: false },
{ id: 'int', name: i18n.getTranslation('stats.int') || 'INT', enabled: true, persistInHistory: false },
{ id: 'wis', name: i18n.getTranslation('stats.wis'), enabled: true, persistInHistory: false },
{ id: 'cha', name: i18n.getTranslation('stats.cha'), enabled: true, persistInHistory: false }
],
@@ -635,7 +635,7 @@ function showImportModeDialog(migratedConfig, suggestedName, historyPersistence
</button>
<button id="rpg-import-as-new" class="rpg-btn-primary">
<i class="fa-solid fa-plus"></i>
Create New Preset
${i18n.getTranslation('preset.createNewPresetTitle') || 'Create New Preset'}
</button>
</div>
<button id="rpg-import-cancel" class="rpg-btn-cancel">Cancel</button>
@@ -728,15 +728,15 @@ function renderUserStatsTab() {
let html = '<div class="rpg-editor-section">';
// Custom Stats section
html += `<h4><i class="fa-solid fa-heart-pulse"></i> ${i18n.getTranslation('template.trackerEditorModal.userStatsTab.customStatsTitle')}</h4>`;
html += `<h4><i class="fa-solid fa-heart-pulse"></i> ${i18n.getTranslation('template.trackerEditorModal.userStatsTab.customStatsTitle') || 'Custom Stats'}</h4>`;
// Stats display mode toggle
const statsDisplayMode = config.statsDisplayMode || 'percentage';
html += '<div class="rpg-editor-toggle-row">';
html += '<label>Display Mode:</label>';
html += '<label>' + (i18n.getTranslation('stats.displayMode') || 'Display Mode:') + '</label>';
html += '<div class="rpg-radio-group">';
html += `<label><input type="radio" name="stats-display-mode" value="percentage" ${statsDisplayMode === 'percentage' ? 'checked' : ''}> Percentage</label>`;
html += `<label><input type="radio" name="stats-display-mode" value="number" ${statsDisplayMode === 'number' ? 'checked' : ''}> Number</label>`;
html += `<label><input type="radio" name="stats-display-mode" value="percentage" ${statsDisplayMode === 'percentage' ? 'checked' : ''}> ${i18n.getTranslation('stats.displayMode.percentage') || 'Percentage'}</label>`;
html += `<label><input type="radio" name="stats-display-mode" value="number" ${statsDisplayMode === 'number' ? 'checked' : ''}> ${i18n.getTranslation('stats.displayMode.number') || 'Number'}</label>`;
html += '</div>';
html += '</div>';
@@ -772,7 +772,7 @@ function renderUserStatsTab() {
const showLevel = config.showLevel !== undefined ? config.showLevel : true;
html += '<div class="rpg-editor-toggle-row">';
html += `<input type="checkbox" id="rpg-show-level" ${showLevel ? 'checked' : ''}>`;
html += `<label for="rpg-show-level">Show Level</label>`;
html += `<label for="rpg-show-level">${i18n.getTranslation('stats.showLevel') || 'Show Level'}</label>`;
html += '</div>';
// Always send attributes toggle
@@ -1006,8 +1006,8 @@ function renderInfoBoxTab() {
html += `<input type="checkbox" id="rpg-widget-date" ${config.widgets.date.enabled ? 'checked' : ''}>`;
html += `<label for="rpg-widget-date">${i18n.getTranslation('template.trackerEditorModal.infoBoxTab.dateWidget')}</label>`;
html += '<select id="rpg-date-format" class="rpg-select-mini">';
html += `<option value="Weekday, Month, Year" ${config.widgets.date.format === 'Weekday, Month, Year' ? 'selected' : ''}>Weekday, Month, Year</option>`;
html += `<option value="Day (Numerical), Month, Year" ${config.widgets.date.format === 'Day (Numerical), Month, Year' ? 'selected' : ''}>Day (Numerical), Month, Year</option>`;
html += `<option value="Weekday, Month, Year" ${config.widgets.date.format === 'Weekday, Month, Year' ? 'selected' : ''}>${i18n.getTranslation('dateFormat.weekdayMonthYear') || 'Weekday, Month, Year'}</option>`;
html += `<option value="Day (Numerical), Month, Year" ${config.widgets.date.format === 'Day (Numerical), Month, Year' ? 'selected' : ''}>${i18n.getTranslation('dateFormat.dayNumericalMonthYear') || 'Day (Numerical), Month, Year'}</option>`;
html += '</select>';
html += '</div>';
@@ -1467,48 +1467,48 @@ function renderHistoryPersistenceTab() {
let html = '<div class="rpg-editor-section">';
// Main toggle and settings
html += `<h4><i class="fa-solid fa-clock-rotate-left"></i> History Persistence Settings</h4>`;
html += `<p class="rpg-editor-hint">Inject selected tracker data into historical messages to help the AI maintain continuity for time-sensitive events, weather changes, and location tracking.</p>`;
html += `<h4><i class="fa-solid fa-clock-rotate-left"></i> ${i18n.getTranslation('historyPersistence.settingsTitle') || 'History Persistence Settings'}</h4>`;
html += `<p class="rpg-editor-hint">${i18n.getTranslation('historyPersistence.hint') || 'Inject selected tracker data into historical messages to help the AI maintain continuity for time-sensitive events, weather changes, and location tracking.'}</p>`;
// Enable toggle
html += '<div class="rpg-editor-toggle-row">';
html += `<input type="checkbox" id="rpg-history-persistence-enabled" ${historyPersistence.enabled ? 'checked' : ''}>`;
html += `<label for="rpg-history-persistence-enabled">Enable History Persistence</label>`;
html += `<label for="rpg-history-persistence-enabled">${i18n.getTranslation('historyPersistence.enable') || 'Enable History Persistence'}</label>`;
html += '</div>';
// External API Only toggle - only show for separate/external modes
if (generationMode === 'separate' || generationMode === 'external') {
html += '<div class="rpg-editor-toggle-row" style="margin-top: 8px;">';
html += `<input type="checkbox" id="rpg-history-send-all-enabled" ${historyPersistence.sendAllEnabledOnRefresh ? 'checked' : ''}>`;
html += `<label for="rpg-history-send-all-enabled">Send All Enabled Stats on Refresh</label>`;
html += `<label for="rpg-history-send-all-enabled">${i18n.getTranslation('historyPersistence.sendAllEnabledStats') || 'Send All Enabled Stats on Refresh'}</label>`;
html += '</div>';
html += `<p class="rpg-editor-hint" style="margin-top: 4px; margin-left: 24px;">When enabled, Refresh RPG Info will include all enabled stats from the preset in history context, ignoring the individual selections below.</p>`;
html += `<p class="rpg-editor-hint" style="margin-top: 4px; margin-left: 24px;">${i18n.getTranslation('historyPersistence.sendAllEnabledStatsHint') || 'When enabled, Refresh RPG Info will include all enabled stats from the preset in history context, ignoring the individual selections below.'}</p>`;
}
// Message count
html += '<div class="rpg-editor-input-row" style="margin-top: 12px;">';
html += `<label for="rpg-history-message-count">Number of messages to include (0 = all available):</label>`;
html += `<label for="rpg-history-message-count">${i18n.getTranslation('historyPersistence.numberOfMessages') || 'Number of messages to include (0 = all available):'}</label>`;
html += `<input type="number" id="rpg-history-message-count" min="0" max="50" value="${historyPersistence.messageCount}" class="rpg-input" style="width: 80px; margin-left: 8px;">`;
html += '</div>';
// Injection position
html += '<div class="rpg-editor-input-row" style="margin-top: 12px;">';
html += `<label for="rpg-history-injection-position">Injection Position:</label>`;
html += `<label for="rpg-history-injection-position">${i18n.getTranslation('historyPersistence.injectionPosition') || 'Injection Position:'}</label>`;
html += `<select id="rpg-history-injection-position" class="rpg-select" style="margin-left: 8px;">`;
html += `<option value="user_message_end" ${historyPersistence.injectionPosition === 'user_message_end' ? 'selected' : ''}>End of the User's Message</option>`;
html += `<option value="assistant_message_end" ${historyPersistence.injectionPosition === 'assistant_message_end' ? 'selected' : ''}>End of the Assistant's Message</option>`;
html += `<option value="user_message_end" ${historyPersistence.injectionPosition === 'user_message_end' ? 'selected' : ''}>${i18n.getTranslation('historyPersistence.injectionPosition.userMessageEnd') || 'End of the User\'s Message'}</option>`;
html += `<option value="assistant_message_end" ${historyPersistence.injectionPosition === 'assistant_message_end' ? 'selected' : ''}>${i18n.getTranslation('historyPersistence.injectionPosition.assistantMessageEnd') || 'End of the Assistant\'s Message'}</option>`;
html += `</select>`;
html += '</div>';
// Custom preamble
html += '<div class="rpg-editor-input-row" style="margin-top: 12px;">';
html += `<label for="rpg-history-context-preamble">Custom Context Preamble:</label>`;
html += `<input type="text" id="rpg-history-context-preamble" value="${historyPersistence.contextPreamble || ''}" class="rpg-text-input" placeholder="Context for that moment:" style="width: 100%; margin-top: 4px;">`;
html += `<label for="rpg-history-context-preamble">${i18n.getTranslation('historyPersistence.customContextPreamble') || 'Custom Context Preamble:'}</label>`;
html += `<input type="text" id="rpg-history-context-preamble" value="${historyPersistence.contextPreamble || ''}" class="rpg-text-input" placeholder="${i18n.getTranslation('historyPersistence.customContextPreamblePlaceholder') || 'Context for that moment:'}" style="width: 100%; margin-top: 4px;">`;
html += '</div>';
// User Stats section - which stats to persist
html += `<h4 style="margin-top: 20px;"><i class="fa-solid fa-heart-pulse"></i> User Stats</h4>`;
html += `<p class="rpg-editor-hint">Select which stats should be included in historical messages.</p>`;
html += `<h4 style="margin-top: 20px;"><i class="fa-solid fa-heart-pulse"></i> ${i18n.getTranslation('historyPersistence.userStatsSection') || 'User Stats'}</h4>`;
html += `<p class="rpg-editor-hint">${i18n.getTranslation('historyPersistence.userStatsHint') || 'Select which stats should be included in historical messages.'}</p>`;
// Custom stats
html += '<div class="rpg-history-persist-list">';
@@ -1528,7 +1528,7 @@ function renderHistoryPersistenceTab() {
html += `
<div class="rpg-editor-toggle-row">
<input type="checkbox" id="rpg-history-status" ${userStatsConfig.statusSection.persistInHistory ? 'checked' : ''}>
<label for="rpg-history-status">Status (Mood/Conditions)</label>
<label for="rpg-history-status">${i18n.getTranslation('historyPersistence.statusSection') || 'Status (Mood/Conditions)'}</label>
</div>
`;
}
@@ -1538,7 +1538,7 @@ function renderHistoryPersistenceTab() {
html += `
<div class="rpg-editor-toggle-row">
<input type="checkbox" id="rpg-history-skills" ${userStatsConfig.skillsSection.persistInHistory ? 'checked' : ''}>
<label for="rpg-history-skills">${userStatsConfig.skillsSection.label || 'Skills'}</label>
<label for="rpg-history-skills">${userStatsConfig.skillsSection.label || i18n.getTranslation('historyPersistence.skills') || 'Skills'}</label>
</div>
`;
}
@@ -1547,7 +1547,7 @@ function renderHistoryPersistenceTab() {
html += `
<div class="rpg-editor-toggle-row">
<input type="checkbox" id="rpg-history-inventory" ${userStatsConfig.inventoryPersistInHistory ? 'checked' : ''}>
<label for="rpg-history-inventory">Inventory</label>
<label for="rpg-history-inventory">${i18n.getTranslation('historyPersistence.inventory') || 'Inventory'}</label>
</div>
`;
@@ -1555,14 +1555,14 @@ function renderHistoryPersistenceTab() {
html += `
<div class="rpg-editor-toggle-row">
<input type="checkbox" id="rpg-history-quests" ${userStatsConfig.questsPersistInHistory ? 'checked' : ''}>
<label for="rpg-history-quests">Quests</label>
<label for="rpg-history-quests">${i18n.getTranslation('historyPersistence.quests') || 'Quests'}</label>
</div>
`;
html += '</div>';
// Info Box section - which widgets to persist
html += `<h4 style="margin-top: 20px;"><i class="fa-solid fa-info-circle"></i> Info Box</h4>`;
html += `<p class="rpg-editor-hint">Select which info box fields should be included in historical messages. These are recommended for time tracking.</p>`;
html += `<h4 style="margin-top: 20px;"><i class="fa-solid fa-info-circle"></i> ${i18n.getTranslation('historyPersistence.infoBoxSection') || 'Info Box'}</h4>`;
html += `<p class="rpg-editor-hint">${i18n.getTranslation('historyPersistence.infoBoxHint') || 'Select which info box fields should be included in historical messages. These are recommended for time tracking.'}</p>`;
html += '<div class="rpg-history-persist-list">';
const widgetLabels = {
@@ -1579,7 +1579,7 @@ function renderHistoryPersistenceTab() {
html += `
<div class="rpg-editor-toggle-row">
<input type="checkbox" id="rpg-history-widget-${widgetId}" class="rpg-history-widget-toggle" data-widget="${widgetId}" ${widget.persistInHistory ? 'checked' : ''}>
<label for="rpg-history-widget-${widgetId}">${widgetLabels[widgetId] || widgetId}</label>
<label for="rpg-history-widget-${widgetId}">${i18n.getTranslation('historyPersistence.widget.' + widgetId) || widgetLabels[widgetId] || widgetId}</label>
</div>
`;
}
@@ -1587,8 +1587,8 @@ function renderHistoryPersistenceTab() {
html += '</div>';
// Present Characters section
html += `<h4 style="margin-top: 20px;"><i class="fa-solid fa-users"></i> Present Characters</h4>`;
html += `<p class="rpg-editor-hint">Select which character fields should be included in historical messages.</p>`;
html += `<h4 style="margin-top: 20px;"><i class="fa-solid fa-users"></i> ${i18n.getTranslation('historyPersistence.presentCharactersSection') || 'Present Characters'}</h4>`;
html += `<p class="rpg-editor-hint">${i18n.getTranslation('historyPersistence.presentCharactersHint') || 'Select which character fields should be included in historical messages.'}</p>`;
html += '<div class="rpg-history-persist-list">';
@@ -1609,7 +1609,7 @@ function renderHistoryPersistenceTab() {
html += `
<div class="rpg-editor-toggle-row">
<input type="checkbox" id="rpg-history-thoughts" ${presentCharsConfig.thoughts.persistInHistory ? 'checked' : ''}>
<label for="rpg-history-thoughts">${presentCharsConfig.thoughts.name || 'Thoughts'}</label>
<label for="rpg-history-thoughts">${presentCharsConfig.thoughts.name || i18n.getTranslation('historyPersistence.thoughts') || 'Thoughts'}</label>
</div>
`;
}
+3
View File
@@ -4716,6 +4716,9 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
.rpg-restore-prompt-btn {
font-size: 0.9em;
white-space: nowrap;
min-width: 120px;
padding: 0.5em 1em;
}
/* Editor buttons */
+81 -75
View File
@@ -1,6 +1,6 @@
<div id="rpg-companion-panel" class="rpg-panel">
<!-- Collapse/Expand Toggle Button -->
<button class="rpg-collapse-toggle" id="rpg-collapse-toggle" title="Collapse/Expand Panel">
<button class="rpg-collapse-toggle" id="rpg-collapse-toggle" title="Collapse/Expand Panel" data-i18n-title="global.collapseExpandPanel">
<i class="fa-solid fa-chevron-right"></i>
</button>
@@ -39,7 +39,7 @@
<div class="rpg-strip-attributes-grid"></div>
</div>
<!-- Refresh Button (bottom) -->
<button id="rpg-strip-refresh" class="rpg-strip-refresh-btn" title="Refresh RPG Info">
<button id="rpg-strip-refresh" class="rpg-strip-refresh-btn" title="Refresh RPG Info" data-i18n-title="global.refreshRpgInfo">
<i class="fa-solid fa-sync"></i>
</button>
</div>
@@ -114,7 +114,7 @@
<div class="rpg-features-row" id="rpg-features-row">
<!-- HTML Prompt Toggle -->
<div class="rpg-toggle-container rpg-feature-col" id="rpg-html-toggle-wrapper">
<label class="rpg-toggle-label" title="Immersive HTML">
<label class="rpg-toggle-label" title="Immersive HTML" data-i18n-title="template.mainPanel.immersiveHtml">
<input type="checkbox" id="rpg-toggle-html-prompt">
<i class="fa-solid fa-code"></i>
<span class="rpg-toggle-text" data-i18n-key="template.mainPanel.immersiveHtml">Immersive HTML</span>
@@ -123,7 +123,7 @@
<!-- Dialogue Coloring Toggle -->
<div class="rpg-toggle-container rpg-feature-col" id="rpg-dialogue-coloring-toggle-wrapper">
<label class="rpg-toggle-label" title="Colored Dialogues">
<label class="rpg-toggle-label" title="Colored Dialogues" data-i18n-title="template.mainPanel.coloredDialogues">
<input type="checkbox" id="rpg-toggle-dialogue-coloring">
<i class="fa-solid fa-palette"></i>
<span class="rpg-toggle-text" data-i18n-key="template.mainPanel.coloredDialogues">Colored Dialogues</span>
@@ -132,7 +132,7 @@
<!-- Deception System Toggle -->
<div class="rpg-toggle-container rpg-feature-col" id="rpg-deception-toggle-wrapper">
<label class="rpg-toggle-label" title="Deception System">
<label class="rpg-toggle-label" title="Deception System" data-i18n-title="template.mainPanel.deceptionSystem">
<input type="checkbox" id="rpg-toggle-deception">
<i class="fa-solid fa-masks-theater"></i>
<span class="rpg-toggle-text" data-i18n-key="template.mainPanel.deceptionSystem">Deception System</span>
@@ -141,7 +141,7 @@
<!-- Omniscience Filter Toggle -->
<div class="rpg-toggle-container rpg-feature-col" id="rpg-omniscience-toggle-wrapper">
<label class="rpg-toggle-label" title="Omniscience Filter">
<label class="rpg-toggle-label" title="Omniscience Filter" data-i18n-title="template.mainPanel.omniscienceFilter">
<input type="checkbox" id="rpg-toggle-omniscience">
<i class="fa-solid fa-eye-slash"></i>
<span class="rpg-toggle-text" data-i18n-key="template.mainPanel.omniscienceFilter">Omniscience Filter</span>
@@ -150,7 +150,7 @@
<!-- CYOA Toggle -->
<div class="rpg-toggle-container rpg-feature-col" id="rpg-cyoa-toggle-wrapper">
<label class="rpg-toggle-label" title="CYOA">
<label class="rpg-toggle-label" title="CYOA" data-i18n-title="template.mainPanel.cyoa">
<input type="checkbox" id="rpg-toggle-cyoa">
<i class="fa-solid fa-list-ol"></i>
<span class="rpg-toggle-text" data-i18n-key="template.mainPanel.cyoa">CYOA</span>
@@ -159,7 +159,7 @@
<!-- Spotify Music Toggle -->
<div class="rpg-toggle-container rpg-feature-col" id="rpg-spotify-toggle-wrapper">
<label class="rpg-toggle-label" title="Spotify Music">
<label class="rpg-toggle-label" title="Spotify Music" data-i18n-title="template.mainPanel.spotifyMusic">
<input type="checkbox" id="rpg-toggle-spotify-music">
<i class="fa-brands fa-spotify"></i>
<span class="rpg-toggle-text" data-i18n-key="template.mainPanel.spotifyMusic">Spotify Music</span>
@@ -168,7 +168,7 @@
<!-- Dynamic Weather Toggle -->
<div class="rpg-toggle-container rpg-feature-col" id="rpg-dynamic-weather-toggle-wrapper">
<label class="rpg-toggle-label" title="Dynamic Weather Effects">
<label class="rpg-toggle-label" title="Dynamic Weather Effects" data-i18n-title="template.mainPanel.dynamicWeatherEffects">
<input type="checkbox" id="rpg-toggle-dynamic-weather">
<i class="fa-solid fa-cloud-sun-rain"></i>
<span class="rpg-toggle-text" data-i18n-key="template.mainPanel.dynamicWeatherEffects">Dynamic Weather</span>
@@ -176,7 +176,7 @@
</div>
<!-- Narrator Mode Toggle -->
<div class="rpg-toggle-container rpg-feature-col" id="rpg-narrator-toggle-wrapper">
<label class="rpg-toggle-label" title="Narrator Mode">
<label class="rpg-toggle-label" title="Narrator Mode" data-i18n-title="template.mainPanel.narratorMode">
<input type="checkbox" id="rpg-toggle-narrator">
<i class="fa-solid fa-book-open"></i>
<span class="rpg-toggle-text" data-i18n-key="template.mainPanel.narratorMode">Narrator Mode</span>
@@ -184,7 +184,7 @@
</div>
<!-- Auto-generate Avatars Toggle -->
<div class="rpg-toggle-container rpg-feature-col" id="rpg-auto-avatars-toggle-wrapper">
<label class="rpg-toggle-label" title="Auto-generate Avatars">
<label class="rpg-toggle-label" title="Auto-generate Avatars" data-i18n-title="template.mainPanel.autoAvatars">
<input type="checkbox" id="rpg-toggle-auto-avatars-panel">
<i class="fa-solid fa-user-plus"></i>
<span class="rpg-toggle-text" data-i18n-key="template.mainPanel.autoAvatars">Auto Avatars</span>
@@ -461,17 +461,19 @@
<div id="rpg-weather-suboptions" style="margin-left: 24px; margin-top: 8px;">
<label class="checkbox_label">
<input type="radio" name="rpg-weather-position" id="rpg-toggle-weather-background" />
<span>Show in Background</span>
<span data-i18n-key="template.settingsModal.display.weatherPosition.background">Show in Background</span>
</label>
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;">
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
data-i18n-key="template.settingsModal.display.weatherPosition.backgroundNote">
Display weather effects behind the chat (standard behavior).
</small>
<label class="checkbox_label">
<input type="radio" name="rpg-weather-position" id="rpg-toggle-weather-foreground" />
<span>Show in Foreground</span>
<span data-i18n-key="template.settingsModal.display.weatherPosition.foreground">Show in Foreground</span>
</label>
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;">
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
data-i18n-key="template.settingsModal.display.weatherPosition.foregroundNote">
Display weather effects in front of the chat (experimental).
</small>
</div>
@@ -684,7 +686,7 @@
<input type="password" id="rpg-external-api-key" class="rpg-input" placeholder="sk-..."
style="flex: 1;" />
<button id="rpg-toggle-api-key-visibility" class="menu_button" type="button"
title="Show/Hide API Key" style="padding: 4px 8px;">
title="Show/Hide API Key" data-i18n-title="global.showHideApiKey" style="padding: 4px 8px;">
<i class="fa-solid fa-eye"></i>
</button>
</div>
@@ -789,9 +791,10 @@
<!-- Customize Prompts Button -->
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--rpg-border);">
<button id="rpg-open-prompts-editor" class="rpg-btn-customize-prompts">
<i class="fa-solid fa-file-lines" aria-hidden="true"></i> <span>Customize Prompts</span>
<i class="fa-solid fa-file-lines" aria-hidden="true"></i> <span data-i18n-key="template.promptsEditor.button">Customize Prompts</span>
</button>
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;"
data-i18n-key="template.promptsEditor.buttonNote">
Edit all AI prompts used for generation, plot progression, and combat encounters.
</small>
</div>
@@ -832,9 +835,9 @@
<header class="rpg-dice-popup-header">
<h3 id="rpg-dice-title">
<i class="fa-solid fa-dice-d20" aria-hidden="true"></i>
<span>Roll Dice</span>
<span data-i18n-key="dice.title">Roll Dice</span>
</h3>
<button id="rpg-dice-popup-close" class="rpg-btn-icon" type="button" aria-label="Close dialog">
<button id="rpg-dice-popup-close" class="rpg-btn-icon" type="button" aria-label="Close dialog" data-i18n-aria-label="global.closeDialog">
<i class="fa-solid fa-times" aria-hidden="true"></i>
</button>
</header>
@@ -843,12 +846,12 @@
<div class="rpg-dice-selector-container">
<div class="rpg-dice-selector">
<div class="rpg-dice-input-group">
<label for="rpg-dice-count">Number of Dice:</label>
<label for="rpg-dice-count" data-i18n-key="dice.numberOfDice">Number of Dice:</label>
<input type="number" id="rpg-dice-count" name="dice-count" min="1" max="20" value="1"
class="rpg-input" />
</div>
<div class="rpg-dice-input-group">
<label for="rpg-dice-sides">Dice Type:</label>
<label for="rpg-dice-sides" data-i18n-key="dice.diceType">Dice Type:</label>
<select id="rpg-dice-sides" name="dice-sides" class="rpg-select">
<option value="4">d4</option>
<option value="6">d6</option>
@@ -862,7 +865,7 @@
</div>
<button id="rpg-dice-roll-btn" class="rpg-btn-primary" type="button">
<i class="fa-solid fa-dice" aria-hidden="true"></i>
<span>Roll Dice</span>
<span data-i18n-key="dice.title">Roll Dice</span>
</button>
</div>
@@ -870,17 +873,17 @@
<div class="rpg-dice-rolling">
<i class="fa-solid fa-dice-d20 fa-spin" aria-hidden="true"></i>
</div>
<div class="rpg-dice-rolling-text">Rolling...</div>
<div class="rpg-dice-rolling-text" data-i18n-key="dice.rolling">Rolling...</div>
</div>
<div id="rpg-dice-result" class="rpg-dice-result" hidden aria-live="polite">
<div class="rpg-dice-result-label">Result:</div>
<div class="rpg-dice-result-label" data-i18n-key="dice.result">Result:</div>
<output id="rpg-dice-result-value" class="rpg-dice-result-value"
for="rpg-dice-count rpg-dice-sides">0</output>
<div id="rpg-dice-result-details" class="rpg-dice-result-details" role="status"></div>
<button id="rpg-dice-save-btn" class="rpg-btn-primary rpg-dice-save-btn" type="button">
<i class="fa-solid fa-check" aria-hidden="true"></i>
<span>Save Roll</span>
<span data-i18n-key="dice.saveRoll">Save Roll</span>
</button>
</div>
</div>
@@ -903,24 +906,24 @@
<!-- Preset Management Section -->
<div class="rpg-preset-management">
<div class="rpg-preset-row">
<label for="rpg-preset-select">Preset:</label>
<label for="rpg-preset-select" data-i18n-key="preset.label">Preset:</label>
<select id="rpg-preset-select" class="rpg-select">
<!-- Options populated by JavaScript -->
</select>
<button id="rpg-preset-new" class="rpg-btn-icon" type="button" title="Create New Preset">
<button id="rpg-preset-new" class="rpg-btn-icon" type="button" title="Create New Preset" data-i18n-title="preset.createNewPresetTitle">
<i class="fa-solid fa-plus"></i>
</button>
<button id="rpg-preset-default" class="rpg-btn-icon" type="button" title="Set as Default Preset">
<button id="rpg-preset-default" class="rpg-btn-icon" type="button" title="Set as Default Preset" data-i18n-title="preset.setDefaultPresetTitle">
<i class="fa-solid fa-star"></i>
</button>
<button id="rpg-preset-delete" class="rpg-btn-icon" type="button" title="Delete Current Preset">
<button id="rpg-preset-delete" class="rpg-btn-icon" type="button" title="Delete Current Preset" data-i18n-title="preset.deleteCurrentPresetTitle">
<i class="fa-solid fa-trash"></i>
</button>
</div>
<div class="rpg-preset-association-row">
<label class="checkbox_label">
<input type="checkbox" id="rpg-preset-associate">
<span>Use this preset for: <strong id="rpg-preset-entity-name">Character</strong></span>
<span data-i18n-key="preset.useThisPresetFor">Use this preset for: </span><strong id="rpg-preset-entity-name">Character</strong>
</label>
</div>
</div>
@@ -985,210 +988,213 @@
<header class="rpg-settings-popup-header">
<h3 id="rpg-prompts-editor-title">
<i class="fa-solid fa-file-lines" aria-hidden="true"></i>
<span>Customize Prompts</span>
<span data-i18n-key="template.promptsEditor.title">Customize Prompts</span>
</h3>
<button id="rpg-close-prompts-editor" class="rpg-popup-close" type="button"
aria-label="Close prompts editor">&times;</button>
</header>
<div class="rpg-settings-popup-body">
<small class="notes" style="display: block; margin-bottom: 16px;">
<small class="notes" style="display: block; margin-bottom: 16px;"
data-i18n-key="template.promptsEditor.description">
Customize the AI prompts used throughout the extension. Leave fields empty to use defaults.
</small>
<!-- HTML Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-html" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-code"></i> HTML Prompt
<i class="fa-solid fa-code"></i> <span data-i18n-key="template.promptsEditor.htmlPrompt.title">HTML Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;"
data-i18n-key="template.promptsEditor.htmlPrompt.note">
Injected when "Enable Immersive HTML" is enabled. Affects all generation modes.
</small>
<textarea id="rpg-prompt-html" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="html" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Dialogue Coloring Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-dialogue-coloring" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-palette"></i> Dialogue Coloring Prompt
<i class="fa-solid fa-palette"></i> <span data-i18n-key="template.promptsEditor.dialogueColoringPrompt.title">Dialogue Coloring Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;"
data-i18n-key="template.promptsEditor.dialogueColoringPrompt.note">
Injected when "Enable Dialogue Coloring" is enabled. Affects all generation modes.
</small>
<textarea id="rpg-prompt-dialogue-coloring" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="dialogue-coloring" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Deception System Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-deception" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-masks-theater"></i> Deception System Prompt
<i class="fa-solid fa-masks-theater"></i> <span data-i18n-key="template.promptsEditor.deceptionPrompt.title">Deception System Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.deceptionPrompt.note">
Injected when "Enable Deception System" is enabled. Instructs AI to mark lies and deceptions with hidden tags.
</small>
<textarea id="rpg-prompt-deception" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="deception" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Omniscience Filter Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-omniscience" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-eye-slash"></i> Omniscience Filter Prompt
<i class="fa-solid fa-eye-slash"></i> <span data-i18n-key="template.promptsEditor.omnisciencePrompt.title">Omniscience Filter Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.omnisciencePrompt.note">
Injected when "Enable Omniscience Filter" is enabled. Instructs AI to separate information the player character cannot perceive into hidden filter tags.
</small>
<textarea id="rpg-prompt-omniscience" class="rpg-prompt-textarea" rows="6"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="omniscience" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- CYOA Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-cyoa" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-list-ol"></i> CYOA Prompt
<i class="fa-solid fa-list-ol"></i> <span data-i18n-key="template.promptsEditor.cyoaPrompt.title">CYOA Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.cyoaPrompt.note">
Injected when "Enable CYOA" is enabled. Instructs AI to end responses with numbered action choices. Uses very high priority (depth 102) to ensure it's the last instruction.
</small>
<textarea id="rpg-prompt-cyoa" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="cyoa" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Spotify Music Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-spotify" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-brands fa-spotify"></i> Spotify Music Prompt
<i class="fa-brands fa-spotify"></i> <span data-i18n-key="template.promptsEditor.spotifyPrompt.title">Spotify Music Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.spotifyPrompt.note">
Injected when "Enable Spotify Music" is enabled. Asks AI to suggest appropriate music for the scene.
</small>
<textarea id="rpg-prompt-spotify" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="spotify" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Narrator Mode Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-narrator" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-book-open"></i> Narrator Mode Prompt
<i class="fa-solid fa-book-open"></i> <span data-i18n-key="template.promptsEditor.narratorPrompt.title">Narrator Mode Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.narratorPrompt.note">
Injected when "Narrator Mode" is enabled. Instructs AI to infer characters from context.
</small>
<textarea id="rpg-prompt-narrator" class="rpg-prompt-textarea" rows="3"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="narrator" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Context Instructions Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-context-instructions" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-comment-dots"></i> Context Instructions Prompt
<i class="fa-solid fa-comment-dots"></i> <span data-i18n-key="template.promptsEditor.contextPrompt.title">Context Instructions Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.contextPrompt.note">
Injected in Separate/External mode after the context summary. Tells the AI how to use the context.
</small>
<textarea id="rpg-prompt-context-instructions" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="contextInstructions" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Random Plot Progression Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-plot-random" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-dice"></i> Random Plot Progression Prompt
<i class="fa-solid fa-dice"></i> <span data-i18n-key="template.promptsEditor.randomPlotPrompt.title">Random Plot Progression Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.randomPlotPrompt.note">
Injected when the "Randomized Plot" button is clicked. Introduces random elements to the story.
</small>
<textarea id="rpg-prompt-plot-random" class="rpg-prompt-textarea" rows="6"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="plotRandom" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Natural Plot Progression Prompt -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-plot-natural" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-forward"></i> Natural Plot Progression Prompt
<i class="fa-solid fa-forward"></i> <span data-i18n-key="template.promptsEditor.naturalPlotPrompt.title">Natural Plot Progression Prompt</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.naturalPlotPrompt.note">
Injected when the "Natural Plot" button is clicked. Progresses the story naturally.
</small>
<textarea id="rpg-prompt-plot-natural" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="plotNatural" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Avatar Generation Instruction -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-avatar" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-user-circle"></i> Avatar Generation Instruction
<i class="fa-solid fa-user-circle"></i> <span data-i18n-key="template.promptsEditor.avatarPrompt.title">Avatar Generation Instruction</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.avatarPrompt.note">
Instructions for LLM when generating avatar image prompts. Used by Auto-generate Missing Avatars feature.
</small>
<textarea id="rpg-prompt-avatar" class="rpg-prompt-textarea" rows="3"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="avatar" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Tracker Instructions -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-tracker-instructions" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-list-check"></i> Tracker Instructions
<i class="fa-solid fa-list-check"></i> <span data-i18n-key="template.promptsEditor.trackerPrompt.title">Tracker Instructions</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.trackerPrompt.note">
Instruction portion only (format specification is hardcoded). {userName} will be replaced with the user's name.
</small>
<textarea id="rpg-prompt-tracker-instructions" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="trackerInstructions" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Tracker Continuation Instruction -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-tracker-continuation" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-arrow-right"></i> Tracker Continuation Instruction
<i class="fa-solid fa-arrow-right"></i> <span data-i18n-key="template.promptsEditor.trackerContinuationPrompt.title">Tracker Continuation Instruction</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.trackerContinuationPrompt.note">
Instructions added after tracker format specifications, telling the AI how to continue the narrative.
</small>
<textarea id="rpg-prompt-tracker-continuation" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="trackerContinuation" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
<!-- Combat Narrative Style Instruction -->
<div class="rpg-prompt-editor-section">
<label for="rpg-prompt-combat-narrative" style="display: block; margin-bottom: 8px; font-weight: 600;">
<i class="fa-solid fa-fire"></i> Combat Narrative Style Instruction
<i class="fa-solid fa-fire"></i> <span data-i18n-key="template.promptsEditor.combatPrompt.title">Combat Narrative Style Instruction</span>
</label>
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;">
<small style="display: block; margin-bottom: 8px; color: #888; font-size: 11px;" data-i18n-key="template.promptsEditor.combatPrompt.note">
Writing style instructions for combat encounters. Includes prose quality guidelines and anti-repetition rules. {userName} will be replaced with the user's name.
</small>
<textarea id="rpg-prompt-combat-narrative" class="rpg-prompt-textarea" rows="6"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="combatNarrative" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;Restore Default
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>
</div>