feat(localization): enhance toast messages for better user feedback and localization support

This commit is contained in:
Will Miao
2025-08-31 11:51:28 +08:00
parent a258a18fa4
commit be8edafed0
9 changed files with 268 additions and 50 deletions

View File

@@ -76,8 +76,8 @@ export class AppCore {
}
// Show toast messages
showToast(message, type = 'info') {
showToast(message, type);
showToast(key, params = {}, type = 'info') {
showToast(key, params, type);
}
// Initialize common UI features based on page type

View File

@@ -283,12 +283,12 @@ export class BulkManager {
async copyAllModelsSyntax() {
if (state.currentPageType !== MODEL_TYPES.LORA) {
showToast('Copy syntax is only available for LoRAs', 'warning');
showToast('toast.loras.copyOnlyForLoras', {}, 'warning');
return;
}
if (state.selectedModels.size === 0) {
showToast('No LoRAs selected', 'warning');
showToast('toast.loras.noLorasSelected', {}, 'warning');
return;
}
@@ -310,11 +310,11 @@ export class BulkManager {
if (missingLoras.length > 0) {
console.warn('Missing metadata for some selected loras:', missingLoras);
showToast(`Missing data for ${missingLoras.length} LoRAs`, 'warning');
showToast('toast.loras.missingDataForLoras', { count: missingLoras.length }, 'warning');
}
if (loraSyntaxes.length === 0) {
showToast('No valid LoRAs to copy', 'error');
showToast('toast.loras.noValidLorasToCopy', {}, 'error');
return;
}
@@ -323,12 +323,12 @@ export class BulkManager {
async sendAllModelsToWorkflow() {
if (state.currentPageType !== MODEL_TYPES.LORA) {
showToast('Send to workflow is only available for LoRAs', 'warning');
showToast('toast.loras.sendOnlyForLoras', {}, 'warning');
return;
}
if (state.selectedModels.size === 0) {
showToast('No LoRAs selected', 'warning');
showToast('toast.loras.noLorasSelected', {}, 'warning');
return;
}

View File

@@ -1,4 +1,4 @@
import { updatePanelPositions } from "../utils/uiHelpers.js";
import { updatePanelPositions, showToast } from "../utils/uiHelpers.js";
import { getCurrentPageState } from "../state/index.js";
import { getModelApiClient } from "../api/modelApiFactory.js";
import { setStorageItem, getStorageItem } from "../utils/storageHelpers.js";
@@ -97,10 +97,7 @@ export class SearchManager {
// Check if clicking would deselect the last active option
const activeOptions = document.querySelectorAll('.search-option-tag.active');
if (activeOptions.length === 1 && activeOptions[0] === tag) {
// Don't allow deselecting the last option
if (typeof showToast === 'function') {
showToast('At least one search option must be selected', 'info');
}
showToast('toast.search.atLeastOneOption', {}, 'info');
return;
}

View File

@@ -85,7 +85,7 @@ class StatisticsManager {
console.log('Statistics data loaded:', this.data);
} catch (error) {
console.error('Error loading statistics data:', error);
showToast('Failed to load statistics data', 'error');
showToast('toast.general.statisticsLoadFailed', {}, 'error');
}
}

View File

@@ -210,7 +210,7 @@ export class VirtualScroller {
this.scheduleRender();
} catch (err) {
console.error('Failed to initialize virtual scroller:', err);
showToast('Failed to load items', 'error');
showToast('toast.virtual.loadFailed', {}, 'error');
}
}
@@ -293,7 +293,7 @@ export class VirtualScroller {
return items;
} catch (err) {
console.error('Failed to load more items:', err);
showToast('Failed to load more items', 'error');
showToast('toast.virtual.loadMoreFailed', {}, 'error');
} finally {
this.isLoading = false;
pageState.isLoading = false;
@@ -571,7 +571,7 @@ export class VirtualScroller {
}
} catch (err) {
console.error('Failed to fetch data window:', err);
showToast('Failed to load items at this position', 'error');
showToast('toast.virtual.loadPositionFailed', {}, 'error');
} finally {
this.fetchingWindow = false;
}

View File

@@ -129,7 +129,7 @@ async function initializeVirtualScroll(pageType) {
} catch (error) {
console.error(`Error initializing virtual scroller for ${pageType}:`, error);
showToast(`Failed to initialize ${pageType} page. Please reload.`, 'error');
showToast('toast.general.pageInitFailed', { pageType }, 'error');
// Fallback: show a message in the grid
grid.innerHTML = `

View File

@@ -34,18 +34,18 @@ export async function copyToClipboard(text, successMessage = null) {
}
if (defaultSuccessMessage) {
showToast(defaultSuccessMessage, 'success');
showToast('uiHelpers.clipboard.copied', {}, 'success');
}
return true;
} catch (err) {
console.error('Copy failed:', err);
const errorMessage = translate('uiHelpers.clipboard.copyFailed', {}, 'Copy failed');
showToast(errorMessage, 'error');
showToast('uiHelpers.clipboard.copyFailed', {}, 'error');
return false;
}
}
export function showToast(message, type = 'info') {
export function showToast(key, params = {}, type = 'info') {
const message = translate(key, params);
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
@@ -376,11 +376,11 @@ export async function sendLoraToWorkflow(loraSyntax, replaceMode = false, syntax
// Handle specific error cases
if (registryData.error === 'Standalone Mode Active') {
// Standalone mode - show warning with specific message
showToast(registryData.message || 'Cannot interact with ComfyUI in standalone mode', 'warning');
showToast('toast.general.cannotInteractStandalone', {}, 'warning');
return false;
} else {
// Other errors - show error toast
showToast(registryData.message || registryData.error || 'Failed to get workflow information', 'error');
showToast('toast.general.failedWorkflowInfo', {}, 'error');
return false;
}
}
@@ -388,8 +388,7 @@ export async function sendLoraToWorkflow(loraSyntax, replaceMode = false, syntax
// Success case - check node count
if (registryData.data.node_count === 0) {
// No nodes found - show warning
const message = translate('uiHelpers.workflow.noSupportedNodes', {}, 'No supported target nodes found in workflow');
showToast(message, 'warning');
showToast('uiHelpers.workflow.noSupportedNodes', {}, 'warning');
return false;
} else if (registryData.data.node_count > 1) {
// Multiple nodes - show selector
@@ -402,8 +401,7 @@ export async function sendLoraToWorkflow(loraSyntax, replaceMode = false, syntax
}
} catch (error) {
console.error('Failed to get registry:', error);
const message = translate('uiHelpers.workflow.communicationFailed', {}, 'Failed to communicate with ComfyUI');
showToast(message, 'error');
showToast('uiHelpers.workflow.communicationFailed', {}, 'error');
return false;
}
}
@@ -435,31 +433,30 @@ async function sendToSpecificNode(nodeIds, loraSyntax, replaceMode, syntaxType)
if (result.success) {
// Use different toast messages based on syntax type
if (syntaxType === 'recipe') {
const message = replaceMode ?
translate('uiHelpers.workflow.recipeReplaced', {}, 'Recipe replaced in workflow') :
translate('uiHelpers.workflow.recipeAdded', {}, 'Recipe added to workflow');
showToast(message, 'success');
const messageKey = replaceMode ?
'uiHelpers.workflow.recipeReplaced' :
'uiHelpers.workflow.recipeAdded';
showToast(messageKey, {}, 'success');
} else {
const message = replaceMode ?
translate('uiHelpers.workflow.loraReplaced', {}, 'LoRA replaced in workflow') :
translate('uiHelpers.workflow.loraAdded', {}, 'LoRA added to workflow');
showToast(message, 'success');
const messageKey = replaceMode ?
'uiHelpers.workflow.loraReplaced' :
'uiHelpers.workflow.loraAdded';
showToast(messageKey, {}, 'success');
}
return true;
} else {
const errorMessage = result.error ||
(syntaxType === 'recipe' ?
translate('uiHelpers.workflow.recipeFailedToSend', {}, 'Failed to send recipe to workflow') :
translate('uiHelpers.workflow.loraFailedToSend', {}, 'Failed to send LoRA to workflow'));
showToast(errorMessage, 'error');
const messageKey = syntaxType === 'recipe' ?
'uiHelpers.workflow.recipeFailedToSend' :
'toast.workflow.failedToSend';
showToast(messageKey, {}, 'error');
return false;
}
} catch (error) {
console.error('Failed to send to workflow:', error);
const message = syntaxType === 'recipe' ?
translate('uiHelpers.workflow.recipeFailedToSend', {}, 'Failed to send recipe to workflow') :
translate('uiHelpers.workflow.loraFailedToSend', {}, 'Failed to send LoRA to workflow');
showToast(message, 'error');
const messageKey = syntaxType === 'recipe' ?
'uiHelpers.workflow.recipeFailedToSend' :
'toast.workflow.failedToSend';
showToast(messageKey, {}, 'error');
return false;
}
}
@@ -680,17 +677,15 @@ export async function openExampleImagesFolder(modelHash) {
if (result.success) {
const message = translate('uiHelpers.exampleImages.openingFolder', {}, 'Opening example images folder');
showToast(message, 'success');
showToast('uiHelpers.exampleImages.opened', {}, 'success');
return true;
} else {
const message = result.error || translate('uiHelpers.exampleImages.failedToOpen', {}, 'Failed to open example images folder');
showToast(message, 'error');
showToast('uiHelpers.exampleImages.failedToOpen', {}, 'error');
return false;
}
} catch (error) {
console.error('Failed to open example images folder:', error);
const message = translate('uiHelpers.exampleImages.failedToOpen', {}, 'Failed to open example images folder');
showToast(message, 'error');
showToast('uiHelpers.exampleImages.failedToOpen', {}, 'error');
return false;
}
}