diff --git a/locales/en.json b/locales/en.json index c1e07b90..bb3bf221 100644 --- a/locales/en.json +++ b/locales/en.json @@ -280,6 +280,27 @@ "export": "Export Recipe", "delete": "Delete Recipe" } + }, + "contextMenu": { + "copyRecipe": { + "missingId": "Cannot copy recipe: Missing recipe ID", + "failed": "Failed to copy recipe syntax" + }, + "sendRecipe": { + "missingId": "Cannot send recipe: Missing recipe ID", + "failed": "Failed to send recipe to workflow" + }, + "viewLoras": { + "missingId": "Cannot view LoRAs: Missing recipe ID", + "noLorasFound": "No LoRAs found in this recipe", + "loadError": "Error loading recipe LoRAs: {message}" + }, + "downloadMissing": { + "missingId": "Cannot download LoRAs: Missing recipe ID", + "noMissingLoras": "No missing LoRAs to download", + "getInfoFailed": "Failed to get information for missing LoRAs", + "prepareError": "Error preparing LoRAs for download: {message}" + } } }, "checkpoints": { @@ -563,6 +584,17 @@ } } }, + "modelTags": { + "messages": { + "updated": "Tags updated successfully", + "updateFailed": "Failed to update tags" + }, + "validation": { + "maxLength": "Tag should not exceed 30 characters", + "maxCount": "Maximum 30 tags allowed", + "duplicate": "This tag already exists" + } + }, "errors": { "general": "An error occurred", "networkError": "Network error. Please check your connection.", @@ -663,6 +695,11 @@ "finalizing": "Finalizing..." } }, + "showcase": { + "exampleImages": { + "deleteFailed": "Failed to delete example image: {error}" + } + }, "duplicates": { "found": "Found {count} duplicate groups", "showNotification": "Show Duplicates Notification", @@ -868,7 +905,7 @@ "cacheClearError": "Error clearing cache: {message}" }, "filters": { - "applied": "Filters applied - showing {count} {type}", + "applied": "{message}", "cleared": "Filters cleared" }, "downloads": { @@ -884,7 +921,9 @@ "resumed": "Download resumed", "resumeFailed": "Failed to resume download", "imagesCompleted": "Example images {action} completed", - "imagesFailed": "Example images {action} failed" + "imagesFailed": "Example images {action} failed", + "loadError": "Error loading downloads: {message}", + "downloadError": "Download error: {message}" }, "import": { "enterRecipeName": "Please enter a recipe name", @@ -892,7 +931,10 @@ "folderTreeFailed": "Failed to load folder tree", "folderTreeError": "Error loading folder tree", "imagesImported": "Example images imported successfully", - "importFailed": "Failed to import example images: {message}" + "importFailed": "Failed to import example images: {message}", + "recipeSaveFailed": "Failed to save recipe: {error}", + "processingError": "Processing error: {message}", + "folderBrowserError": "Folder browser error: {message}" }, "triggerWords": { "loadFailed": "Could not load trained words", @@ -939,11 +981,11 @@ "downloadInProgress": "Download already in progress", "enterLocationFirst": "Please enter a download location first", "downloadStarted": "Example images download started", - "downloadStartFailed": "Failed to start download", + "downloadStartFailed": "Failed to start download: {error}", "downloadPaused": "Download paused", - "pauseFailed": "Failed to pause download", + "pauseFailed": "Failed to pause download: {error}", "downloadResumed": "Download resumed", - "resumeFailed": "Failed to resume download", + "resumeFailed": "Failed to resume download: {error}", "deleted": "Example image deleted", "deleteFailed": "Failed to delete example image", "setPreviewFailed": "Failed to set preview image" @@ -961,8 +1003,14 @@ "previewUploadFailed": "Failed to upload preview image", "refreshComplete": "{action} complete", "refreshFailed": "Failed to {action} {type}s", + "metadataRefreshed": "Metadata refreshed successfully", + "metadataRefreshFailed": "Failed to refresh metadata: {message}", "metadataUpdateComplete": "Metadata update complete", "metadataFetchFailed": "Failed to fetch metadata: {message}", + "bulkMetadataCompleteAll": "Successfully refreshed all {count} {type}s", + "bulkMetadataCompletePartial": "Refreshed {success} of {total} {type}s", + "bulkMetadataCompleteNone": "Failed to refresh metadata for any {type}s", + "bulkMetadataFailureDetails": "Failed refreshes:\n{failures}", "bulkMetadataFailed": "Failed to refresh metadata: {message}", "moveNotSupported": "Moving {type}s is not supported", "alreadyInFolder": "{type} is already in the selected folder", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index e6ad6d89..7d24b2fe 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -280,6 +280,27 @@ "export": "导出配方", "delete": "删除配方" } + }, + "contextMenu": { + "copyRecipe": { + "missingId": "无法复制配方:缺少配方 ID", + "failed": "复制配方语法失败" + }, + "sendRecipe": { + "missingId": "无法发送配方:缺少配方 ID", + "failed": "发送配方到工作流失败" + }, + "viewLoras": { + "missingId": "无法查看 LoRA:缺少配方 ID", + "noLorasFound": "在此配方中未找到 LoRA", + "loadError": "加载配方 LoRA 错误:{message}" + }, + "downloadMissing": { + "missingId": "无法下载 LoRA:缺少配方 ID", + "noMissingLoras": "没有缺失的 LoRA 需要下载", + "getInfoFailed": "获取缺失 LoRA 信息失败", + "prepareError": "准备下载 LoRA 时出错:{message}" + } } }, "checkpoints": { @@ -563,6 +584,22 @@ } } }, + "showcase": { + "exampleImages": { + "deleteFailed": "删除示例图片失败:{error}" + } + }, + "modelTags": { + "messages": { + "updated": "标签更新成功", + "updateFailed": "更新标签失败" + }, + "validation": { + "maxLength": "标签长度不应超过 30 个字符", + "maxCount": "最多允许 30 个标签", + "duplicate": "此标签已存在" + } + }, "errors": { "general": "发生错误", "networkError": "网络错误,请检查您的连接。", @@ -899,8 +936,14 @@ "previewUploadFailed": "上传预览图失败", "refreshComplete": "{action} 完成", "refreshFailed": "{action} {type} 失败", + "metadataRefreshed": "元数据刷新成功", + "metadataRefreshFailed": "刷新元数据失败:{message}", "metadataUpdateComplete": "元数据更新完成", "metadataFetchFailed": "获取元数据失败:{message}", + "bulkMetadataCompleteAll": "成功刷新了所有 {count} 个 {type}", + "bulkMetadataCompletePartial": "已刷新 {success} / {total} 个 {type}", + "bulkMetadataCompleteNone": "刷新任何 {type} 的元数据都失败了", + "bulkMetadataFailureDetails": "刷新失败:\n{failures}", "bulkMetadataFailed": "刷新元数据失败:{message}", "moveNotSupported": "不支持移动 {type}", "alreadyInFolder": "{type} 已在所选文件夹中", diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index ec7da5b0..6e0593d5 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -1,5 +1,6 @@ import { state, getCurrentPageState } from '../state/index.js'; import { showToast } from '../utils/uiHelpers.js'; +import { translate } from '../utils/i18n.js'; import { getStorageItem, getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js'; import { getCompleteApiConfig, @@ -503,22 +504,22 @@ export class BaseModelApiClient { let completionMessage; if (successCount === totalItems) { - completionMessage = `Successfully refreshed all ${successCount} ${this.apiConfig.config.displayName}s`; - showToast(completionMessage, 'success'); + completionMessage = translate('toast.api.bulkMetadataCompleteAll', { count: successCount, type: this.apiConfig.config.displayName }, `Successfully refreshed all ${successCount} ${this.apiConfig.config.displayName}s`); + showToast('toast.api.bulkMetadataCompleteAll', { count: successCount, type: this.apiConfig.config.displayName }, 'success'); } else if (successCount > 0) { - completionMessage = `Refreshed ${successCount} of ${totalItems} ${this.apiConfig.config.displayName}s`; - showToast(completionMessage, 'warning'); + completionMessage = translate('toast.api.bulkMetadataCompletePartial', { success: successCount, total: totalItems, type: this.apiConfig.config.displayName }, `Refreshed ${successCount} of ${totalItems} ${this.apiConfig.config.displayName}s`); + showToast('toast.api.bulkMetadataCompletePartial', { success: successCount, total: totalItems, type: this.apiConfig.config.displayName }, 'warning'); if (failedItems.length > 0) { const failureMessage = failedItems.length <= 3 ? failedItems.map(item => `${item.fileName}: ${item.error}`).join('\n') : failedItems.slice(0, 3).map(item => `${item.fileName}: ${item.error}`).join('\n') + `\n(and ${failedItems.length - 3} more)`; - showToast(`Failed refreshes:\n${failureMessage}`, 'warning', 6000); + showToast('toast.api.bulkMetadataFailureDetails', { failures: failureMessage }, 'warning', 6000); } } else { - completionMessage = `Failed to refresh metadata for any ${this.apiConfig.config.displayName}s`; - showToast(completionMessage, 'error'); + completionMessage = translate('toast.api.bulkMetadataCompleteNone', { type: this.apiConfig.config.displayName }, `Failed to refresh metadata for any ${this.apiConfig.config.displayName}s`); + showToast('toast.api.bulkMetadataCompleteNone', { type: this.apiConfig.config.displayName }, 'error'); } await progressController.complete(completionMessage); diff --git a/static/js/components/ContextMenu/RecipeContextMenu.js b/static/js/components/ContextMenu/RecipeContextMenu.js index 953d380a..351263c7 100644 --- a/static/js/components/ContextMenu/RecipeContextMenu.js +++ b/static/js/components/ContextMenu/RecipeContextMenu.js @@ -99,7 +99,7 @@ export class RecipeContextMenu extends BaseContextMenu { copyRecipeSyntax() { const recipeId = this.currentCard.dataset.id; if (!recipeId) { - showToast('Cannot copy recipe: Missing recipe ID', 'error'); + showToast('recipes.contextMenu.copyRecipe.missingId', {}, 'error'); return; } @@ -114,7 +114,7 @@ export class RecipeContextMenu extends BaseContextMenu { }) .catch(err => { console.error('Failed to copy recipe syntax: ', err); - showToast('Failed to copy recipe syntax', 'error'); + showToast('recipes.contextMenu.copyRecipe.failed', {}, 'error'); }); } @@ -122,7 +122,7 @@ export class RecipeContextMenu extends BaseContextMenu { sendRecipeToWorkflow(replaceMode) { const recipeId = this.currentCard.dataset.id; if (!recipeId) { - showToast('Cannot send recipe: Missing recipe ID', 'error'); + showToast('recipes.contextMenu.sendRecipe.missingId', {}, 'error'); return; } @@ -137,14 +137,14 @@ export class RecipeContextMenu extends BaseContextMenu { }) .catch(err => { console.error('Failed to send recipe to workflow: ', err); - showToast('Failed to send recipe to workflow', 'error'); + showToast('recipes.contextMenu.sendRecipe.failed', {}, 'error'); }); } // View all LoRAs in the recipe viewRecipeLoRAs(recipeId) { if (!recipeId) { - showToast('Cannot view LoRAs: Missing recipe ID', 'error'); + showToast('recipes.contextMenu.viewLoras.missingId', {}, 'error'); return; } @@ -171,19 +171,19 @@ export class RecipeContextMenu extends BaseContextMenu { // Navigate to the LoRAs page window.location.href = '/loras'; } else { - showToast('No LoRAs found in this recipe', 'info'); + showToast('recipes.contextMenu.viewLoras.noLorasFound', {}, 'info'); } }) .catch(error => { console.error('Error loading recipe LoRAs:', error); - showToast('Error loading recipe LoRAs: ' + error.message, 'error'); + showToast('recipes.contextMenu.viewLoras.loadError', { message: error.message }, 'error'); }); } // Download missing LoRAs async downloadMissingLoRAs(recipeId) { if (!recipeId) { - showToast('Cannot download LoRAs: Missing recipe ID', 'error'); + showToast('recipes.contextMenu.downloadMissing.missingId', {}, 'error'); return; } @@ -196,7 +196,7 @@ export class RecipeContextMenu extends BaseContextMenu { const missingLoras = recipe.loras.filter(lora => !lora.inLibrary && !lora.isDeleted); if (missingLoras.length === 0) { - showToast('No missing LoRAs to download', 'info'); + showToast('recipes.contextMenu.downloadMissing.noMissingLoras', {}, 'info'); return; } @@ -234,7 +234,7 @@ export class RecipeContextMenu extends BaseContextMenu { const validLoras = lorasWithVersionInfo.filter(lora => lora !== null); if (validLoras.length === 0) { - showToast('Failed to get information for missing LoRAs', 'error'); + showToast('recipes.contextMenu.downloadMissing.getInfoFailed', {}, 'error'); return; } @@ -275,7 +275,7 @@ export class RecipeContextMenu extends BaseContextMenu { window.importManager.downloadMissingLoras(recipeData, recipeId); } catch (error) { console.error('Error downloading missing LoRAs:', error); - showToast('Error preparing LoRAs for download: ' + error.message, 'error'); + showToast('recipes.contextMenu.downloadMissing.prepareError', { message: error.message }, 'error'); } finally { if (state.loadingManager) { state.loadingManager.hide(); diff --git a/static/js/components/shared/ModelCard.js b/static/js/components/shared/ModelCard.js index 6525f4bd..6196297a 100644 --- a/static/js/components/shared/ModelCard.js +++ b/static/js/components/shared/ModelCard.js @@ -143,16 +143,13 @@ async function toggleFavorite(card) { }); if (newFavoriteState) { - const addedText = translate('modelCard.favorites.added', {}, 'Added to favorites'); - showToast(addedText, 'success'); + showToast('modelCard.favorites.added', {}, 'success'); } else { - const removedText = translate('modelCard.favorites.removed', {}, 'Removed from favorites'); - showToast(removedText, 'success'); + showToast('modelCard.favorites.removed', {}, 'success'); } } catch (error) { console.error('Failed to update favorite status:', error); - const errorText = translate('modelCard.favorites.updateFailed', {}, 'Failed to update favorite status'); - showToast(errorText, 'error'); + showToast('modelCard.favorites.updateFailed', {}, 'error'); } } @@ -164,8 +161,7 @@ function handleSendToWorkflow(card, replaceMode, modelType) { sendLoraToWorkflow(loraSyntax, replaceMode, 'lora'); } else { // Checkpoint send functionality - to be implemented - const text = translate('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'Send checkpoint to workflow - feature to be implemented'); - showToast(text, 'info'); + showToast('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'info'); } } @@ -201,8 +197,7 @@ async function handleExampleImagesAccess(card, modelType) { } } catch (error) { console.error('Error checking for example images:', error); - const text = translate('modelCard.exampleImages.checkError', {}, 'Error checking for example images'); - showToast(text, 'error'); + showToast('modelCard.exampleImages.checkError', {}, 'error'); } } @@ -284,8 +279,7 @@ function showExampleAccessModal(card, modelType) { // Get the model hash const modelHash = card.dataset.sha256; if (!modelHash) { - const text = translate('modelCard.exampleImages.missingHash', {}, 'Missing model hash information.'); - showToast(text, 'error'); + showToast('modelCard.exampleImages.missingHash', {}, 'error'); return; } diff --git a/static/js/components/shared/ModelDescription.js b/static/js/components/shared/ModelDescription.js index 5a7a1066..cf38baa4 100644 --- a/static/js/components/shared/ModelDescription.js +++ b/static/js/components/shared/ModelDescription.js @@ -154,8 +154,7 @@ export async function setupModelDescriptionEditing(filePath) { } if (!newValue) { this.innerHTML = originalValue; - const emptyErrorText = translate('modals.model.description.validation.cannotBeEmpty', {}, 'Description cannot be empty'); - showToast(emptyErrorText, 'error'); + showToast('modals.model.description.validation.cannotBeEmpty', {}, 'error'); exitEditMode(); return; } @@ -163,12 +162,10 @@ export async function setupModelDescriptionEditing(filePath) { // Save to backend const { getModelApiClient } = await import('../../api/modelApiFactory.js'); await getModelApiClient().saveModelMetadata(filePath, { modelDescription: newValue }); - const successText = translate('modals.model.description.messages.updated', {}, 'Model description updated'); - showToast(successText, 'success'); + showToast('modals.model.description.messages.updated', {}, 'success'); } catch (err) { this.innerHTML = originalValue; - const errorText = translate('modals.model.description.messages.updateFailed', {}, 'Failed to update model description'); - showToast(errorText, 'error'); + showToast('modals.model.description.messages.updateFailed', {}, 'error'); } finally { exitEditMode(); } diff --git a/static/js/components/shared/ModelModal.js b/static/js/components/shared/ModelModal.js index 79a7e486..1f1eb2c3 100644 --- a/static/js/components/shared/ModelModal.js +++ b/static/js/components/shared/ModelModal.js @@ -438,11 +438,9 @@ async function saveNotes(filePath) { try { await getModelApiClient().saveModelMetadata(filePath, { notes: content }); - const successMessage = translate('modals.model.notes.saved', {}, 'Notes saved successfully'); - showToast(successMessage, 'success'); + showToast('modals.model.notes.saved', {}, 'success'); } catch (error) { - const errorMessage = translate('modals.model.notes.saveFailed', {}, 'Failed to save notes'); - showToast(errorMessage, 'error'); + showToast('modals.model.notes.saveFailed', {}, 'error'); } } diff --git a/static/js/components/shared/ModelTags.js b/static/js/components/shared/ModelTags.js index 51c269be..87abe0ce 100644 --- a/static/js/components/shared/ModelTags.js +++ b/static/js/components/shared/ModelTags.js @@ -217,10 +217,10 @@ async function saveTags() { // Exit edit mode editBtn.click(); - showToast(translate('modelTags.messages.updated', {}, 'Tags updated successfully'), 'success'); + showToast('modelTags.messages.updated', {}, 'success'); } catch (error) { console.error('Error saving tags:', error); - showToast(translate('modelTags.messages.updateFailed', {}, 'Failed to update tags'), 'error'); + showToast('modelTags.messages.updateFailed', {}, 'error'); } } @@ -362,24 +362,21 @@ function addNewTag(tag) { // Validation: Check length if (tag.length > 30) { - const text = translate('modelTags.validation.maxLength', {}, 'Tag should not exceed 30 characters'); - showToast(text, 'error'); + showToast('modelTags.validation.maxLength', {}, 'error'); return; } // Validation: Check total number const currentTags = tagsContainer.querySelectorAll('.metadata-item'); if (currentTags.length >= 30) { - const text = translate('modelTags.validation.maxCount', {}, 'Maximum 30 tags allowed'); - showToast(text, 'error'); + showToast('modelTags.validation.maxCount', {}, 'error'); return; } // Validation: Check for duplicates const existingTags = Array.from(currentTags).map(tag => tag.dataset.tag); if (existingTags.includes(tag)) { - const text = translate('modelTags.validation.duplicate', {}, 'This tag already exists'); - showToast(text, 'error'); + showToast('modelTags.validation.duplicate', {}, 'error'); return; } diff --git a/static/js/components/shared/showcase/MediaUtils.js b/static/js/components/shared/showcase/MediaUtils.js index 685a85c4..b7d71632 100644 --- a/static/js/components/shared/showcase/MediaUtils.js +++ b/static/js/components/shared/showcase/MediaUtils.js @@ -445,7 +445,7 @@ export function initMediaControlHandlers(container) { state.virtualScroller.updateSingleItem(result.model_file_path, updateData); } else { // Show error message - showToast(result.error || 'Failed to delete example image', 'error'); + showToast('showcase.exampleImages.deleteFailed', { error: result.error }, 'error'); // Reset button state this.disabled = false; diff --git a/static/js/managers/DownloadManager.js b/static/js/managers/DownloadManager.js index 65971ccc..9639f8ec 100644 --- a/static/js/managers/DownloadManager.js +++ b/static/js/managers/DownloadManager.js @@ -343,7 +343,7 @@ export class DownloadManager { this.updateTargetPath(); } catch (error) { - showToast(error.message, 'error'); + showToast('downloads.loadError', { message: error.message }, 'error'); } } @@ -507,7 +507,7 @@ export class DownloadManager { await resetAndReload(true); } catch (error) { - showToast(error.message, 'error'); + showToast('downloads.downloadError', { message: error.message }, 'error'); } finally { this.loadingManager.hide(); } diff --git a/static/js/managers/ExampleImagesManager.js b/static/js/managers/ExampleImagesManager.js index f922af45..0c5bbe01 100644 --- a/static/js/managers/ExampleImagesManager.js +++ b/static/js/managers/ExampleImagesManager.js @@ -285,7 +285,7 @@ class ExampleImagesManager { // Close settings modal modalManager.closeModal('settingsModal'); } else { - showToast(data.error || 'Failed to start download', 'error'); + showToast('exampleImages.downloadStartFailed', { error: data.error }, 'error'); } } catch (error) { console.error('Failed to start download:', error); @@ -321,7 +321,7 @@ class ExampleImagesManager { this.updateDownloadButtonText(); showToast('toast.exampleImages.downloadPaused', {}, 'info'); } else { - showToast(data.error || 'Failed to pause download', 'error'); + showToast('exampleImages.pauseFailed', { error: data.error }, 'error'); } } catch (error) { console.error('Failed to pause download:', error); @@ -357,7 +357,7 @@ class ExampleImagesManager { this.updateDownloadButtonText(); showToast('toast.exampleImages.downloadResumed', {}, 'success'); } else { - showToast(data.error || 'Failed to resume download', 'error'); + showToast('exampleImages.resumeFailed', { error: data.error }, 'error'); } } catch (error) { console.error('Failed to resume download:', error); diff --git a/static/js/managers/FilterManager.js b/static/js/managers/FilterManager.js index 012f2e24..0794a25a 100644 --- a/static/js/managers/FilterManager.js +++ b/static/js/managers/FilterManager.js @@ -282,7 +282,7 @@ export class FilterManager { message = `Filtering by ${tagsCount} tag${tagsCount > 1 ? 's' : ''}`; } - showToast(message, 'success'); + showToast('filters.applied', { message }, 'success'); } } else { this.filterButton.classList.remove('active'); diff --git a/static/js/managers/import/DownloadManager.js b/static/js/managers/import/DownloadManager.js index 55424bf4..60111dc6 100644 --- a/static/js/managers/import/DownloadManager.js +++ b/static/js/managers/import/DownloadManager.js @@ -77,7 +77,7 @@ export class DownloadManager { if (!result.success) { // Handle save error console.error("Failed to save recipe:", result.error); - showToast(result.error, 'error'); + showToast('import.recipeSaveFailed', { error: result.error }, 'error'); // Close modal modalManager.closeModal('importModal'); return; @@ -107,7 +107,7 @@ export class DownloadManager { } catch (error) { console.error('Error:', error); - showToast(error.message, 'error'); + showToast('import.processingError', { message: error.message }, 'error'); } finally { this.importManager.loadingManager.hide(); } diff --git a/static/js/managers/import/FolderBrowser.js b/static/js/managers/import/FolderBrowser.js index 8eba907e..f8304229 100644 --- a/static/js/managers/import/FolderBrowser.js +++ b/static/js/managers/import/FolderBrowser.js @@ -136,7 +136,7 @@ export class FolderBrowser { this.initializeFolderBrowser(); } catch (error) { console.error('Error in API calls:', error); - showToast(error.message, 'error'); + showToast('import.folderBrowserError', { message: error.message }, 'error'); } }