From 8c63173b0c359977b2b9d22b6ebd520d7eecb7b7 Mon Sep 17 00:00:00 2001 From: mickr777 <115216705+mickr777@users.noreply.github.com> Date: Wed, 13 Sep 2023 17:31:34 +1000 Subject: [PATCH] Translation update (#4503) * Update Translations * Fix Prettier Issue * Fix Error in invokebutton.tsx * More Translations * few Fixes * More Translations * More Translations and lint Fixes * Update constants.ts Revert "Update constants.ts" --------- Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com> --- invokeai/frontend/web/public/locales/en.json | 1441 ++++++++++------- .../src/common/hooks/useIsReadyToInvoke.ts | 32 +- .../components/ChangeBoardModal.tsx | 16 +- .../controlNet/components/ControlNet.tsx | 26 +- .../components/ControlNetImagePreview.tsx | 8 +- .../ParamControlNetShouldAutoConfig.tsx | 6 +- .../imports/ControlNetCanvasImageImports.tsx | 10 +- .../parameters/ParamControlNetBeginEnd.tsx | 6 +- .../parameters/ParamControlNetControlMode.tsx | 18 +- .../parameters/ParamControlNetModel.tsx | 8 +- .../ParamControlNetProcessorSelect.tsx | 4 +- .../parameters/ParamControlNetResizeMode.tsx | 16 +- .../parameters/ParamControlNetWeight.tsx | 4 +- .../components/processors/CannyProcessor.tsx | 6 +- .../processors/ContentShuffleProcessor.tsx | 12 +- .../components/processors/HedProcessor.tsx | 8 +- .../processors/LineartAnimeProcessor.tsx | 6 +- .../processors/LineartProcessor.tsx | 8 +- .../processors/MediapipeFaceProcessor.tsx | 6 +- .../processors/MidasDepthProcessor.tsx | 6 +- .../processors/MlsdImageProcessor.tsx | 10 +- .../processors/NormalBaeProcessor.tsx | 6 +- .../processors/OpenposeProcessor.tsx | 8 +- .../components/processors/PidiProcessor.tsx | 10 +- .../features/controlNet/store/constants.ts | 108 +- .../components/ImageUsageMessage.tsx | 23 +- .../ParamDynamicPromptsCollapse.tsx | 4 +- .../ParamDynamicPromptsCombinatorial.tsx | 4 +- .../components/ParamDynamicPromptsEnabled.tsx | 4 +- .../ParamDynamicPromptsMaxPrompts.tsx | 4 +- .../components/AddEmbeddingButton.tsx | 6 +- .../components/ParamEmbeddingPopover.tsx | 10 +- .../components/Boards/BoardAutoAddSelect.tsx | 8 +- .../components/Boards/BoardContextMenu.tsx | 5 +- .../Boards/BoardsList/AddBoardButton.tsx | 12 +- .../Boards/BoardsList/BoardsSearch.tsx | 6 +- .../components/Boards/DeleteBoardModal.tsx | 4 +- .../CurrentImage/CurrentImagePreview.tsx | 5 +- .../components/ImageGrid/GalleryImage.tsx | 4 +- .../components/ImageGrid/GalleryImageGrid.tsx | 6 +- .../ImageMetadataViewer/DataViewer.tsx | 11 +- .../ImageMetadataActions.tsx | 44 +- .../ImageMetadataViewer.tsx | 20 +- .../features/nodes/components/NodeEditor.tsx | 4 +- .../flow/AddNodePopover/AddNodePopover.tsx | 81 +- .../nodes/Invocation/InvocationNodeNotes.tsx | 27 +- .../InvocationNodeStatusIndicator.tsx | 15 +- .../flow/nodes/Invocation/NotesTextarea.tsx | 4 +- .../Invocation/fields/EditableFieldTitle.tsx | 12 +- .../Invocation/fields/FieldContextMenu.tsx | 6 +- .../Invocation/fields/FieldTooltipContent.tsx | 8 +- .../Invocation/fields/LinearViewField.tsx | 7 +- .../fields/inputs/LoRAModelInputField.tsx | 8 +- .../fields/inputs/MainModelInputField.tsx | 7 +- .../fields/inputs/RefinerModelInputField.tsx | 8 +- .../fields/inputs/SDXLMainModelInputField.tsx | 8 +- .../flow/nodes/common/NodeTitle.tsx | 12 +- .../BottomLeftPanel/NodeOpacitySlider.tsx | 4 +- .../flow/panels/TopLeftPanel/TopLeftPanel.tsx | 7 +- .../TopRightPanel/WorkflowEditorSettings.tsx | 29 +- .../inspector/InspectorDetailsTab.tsx | 6 +- .../inspector/InspectorOutputsTab.tsx | 12 +- .../inspector/InspectorTemplateTab.tsx | 8 +- .../sidePanel/workflow/WorkflowGeneralTab.tsx | 25 +- .../sidePanel/workflow/WorkflowJSONTab.tsx | 4 +- .../sidePanel/workflow/WorkflowLinearTab.tsx | 4 +- .../nodes/hooks/useLoadWorkflowFromFile.tsx | 10 +- .../util/makeIsConnectionValidSelector.ts | 24 +- .../Advanced/ParamAdvancedCollapse.tsx | 5 +- .../Parameters/Noise/ParamCpuNoise.tsx | 4 +- .../Parameters/Noise/ParamNoiseToggle.tsx | 4 +- .../ProcessButtons/CancelButton.tsx | 2 +- .../ProcessButtons/InvokeButton.tsx | 13 +- .../sdxl/components/ParamSDXLConcatButton.tsx | 6 +- .../ParamSDXLImg2ImgDenoisingStrength.tsx | 2 +- .../ParamSDXLNegativeStyleConditioning.tsx | 4 +- .../ParamSDXLPositiveStyleConditioning.tsx | 4 +- .../components/ParamSDXLRefinerCollapse.tsx | 4 +- .../SDXLRefiner/ParamSDXLRefinerCFGScale.tsx | 4 +- .../ParamSDXLRefinerModelSelect.tsx | 12 +- ...ParamSDXLRefinerNegativeAestheticScore.tsx | 4 +- ...ParamSDXLRefinerPositiveAestheticScore.tsx | 4 +- .../SDXLRefiner/ParamSDXLRefinerScheduler.tsx | 2 +- .../SDXLRefiner/ParamSDXLRefinerStart.tsx | 4 +- .../SDXLRefiner/ParamSDXLRefinerSteps.tsx | 4 +- .../SDXLRefiner/ParamUseSDXLRefiner.tsx | 4 +- .../AddModelsPanel/AdvancedAddCheckpoint.tsx | 20 +- .../AddModelsPanel/AdvancedAddDiffusers.tsx | 18 +- .../AddModelsPanel/AdvancedAddModels.tsx | 5 +- .../AddModelsPanel/FoundModelsList.tsx | 10 +- .../AddModelsPanel/ScanAdvancedAddModels.tsx | 7 +- .../AddModelsPanel/SimpleAddModels.tsx | 8 +- 92 files changed, 1502 insertions(+), 961 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 125554fc40..c1983c6a53 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1,40 +1,66 @@ { "accessibility": { - "modelSelect": "Model Select", - "invokeProgressBar": "Invoke progress bar", - "reset": "Reset", - "uploadImage": "Upload Image", - "previousImage": "Previous Image", - "nextImage": "Next Image", - "useThisParameter": "Use this parameter", "copyMetadataJson": "Copy metadata JSON", "exitViewer": "Exit Viewer", - "zoomIn": "Zoom In", - "zoomOut": "Zoom Out", - "rotateCounterClockwise": "Rotate Counter-Clockwise", - "rotateClockwise": "Rotate Clockwise", "flipHorizontally": "Flip Horizontally", "flipVertically": "Flip Vertically", + "invokeProgressBar": "Invoke progress bar", + "menu": "Menu", + "modelSelect": "Model Select", "modifyConfig": "Modify Config", - "toggleAutoscroll": "Toggle autoscroll", - "toggleLogViewer": "Toggle Log Viewer", + "nextImage": "Next Image", + "previousImage": "Previous Image", + "reset": "Reset", + "rotateClockwise": "Rotate Clockwise", + "rotateCounterClockwise": "Rotate Counter-Clockwise", "showGallery": "Show Gallery", "showOptionsPanel": "Show Side Panel", - "menu": "Menu" + "toggleAutoscroll": "Toggle autoscroll", + "toggleLogViewer": "Toggle Log Viewer", + "uploadImage": "Upload Image", + "useThisParameter": "Use this parameter", + "zoomIn": "Zoom In", + "zoomOut": "Zoom Out" + }, + "boards": { + "addBoard": "Add Board", + "autoAddBoard": "Auto-Add Board", + "bottomMessage": "Deleting this board and its images will reset any features currently using them.", + "cancel": "Cancel", + "changeBoard": "Change Board", + "clearSearch": "Clear Search", + "loading": "Loading...", + "menuItemAutoAdd": "Auto-add to this Board", + "move": "Move", + "myBoard": "My Board", + "noMatching": "No matching Boards", + "searchBoard": "Search Boards...", + "selectBoard": "Select a Board", + "topMessage": "This board contains images used in the following features:", + "uncategorized": "Uncategorized" }, "common": { + "accept": "Accept", + "advanced": "Advanced", + "areYouSure": "Are you sure?", + "back": "Back", + "batch": "Batch Manager", + "cancel": "Cancel", + "close": "Close", "communityLabel": "Community", - "hotkeysLabel": "Hotkeys", + "controlNet": "Controlnet", "darkMode": "Dark Mode", - "lightMode": "Light Mode", - "languagePickerLabel": "Language", - "reportBugLabel": "Report Bug", - "githubLabel": "Github", "discordLabel": "Discord", - "settingsLabel": "Settings", + "dontAskMeAgain": "Don't ask me again", + "generate": "Generate", + "githubLabel": "Github", + "hotkeysLabel": "Hotkeys", + "imagePrompt": "Image Prompt", + "img2img": "Image To Image", "langArabic": "العربية", - "langEnglish": "English", + "langBrPortuguese": "Português do Brasil", "langDutch": "Nederlands", + "langEnglish": "English", "langFrench": "Français", "langGerman": "Deutsch", "langHebrew": "עברית", @@ -43,377 +69,426 @@ "langKorean": "한국어", "langPolish": "Polski", "langPortuguese": "Português", - "langBrPortuguese": "Português do Brasil", "langRussian": "Русский", "langSimplifiedChinese": "简体中文", - "langUkranian": "Украї́нська", "langSpanish": "Español", - "txt2img": "Text To Image", - "img2img": "Image To Image", - "unifiedCanvas": "Unified Canvas", + "languagePickerLabel": "Language", + "langUkranian": "Украї́нська", + "lightMode": "Light Mode", "linear": "Linear", - "nodes": "Workflow Editor", - "batch": "Batch Manager", + "load": "Load", + "loading": "Loading", + "loadingInvokeAI": "Loading Invoke AI", "modelManager": "Model Manager", - "postprocessing": "Post Processing", + "nodeEditor": "Node Editor", + "nodes": "Workflow Editor", "nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.", - "postProcessing": "Post Processing", + "openInNewTab": "Open in New Tab", "postProcessDesc1": "Invoke AI offers a wide variety of post processing features. Image Upscaling and Face Restoration are already available in the WebUI. You can access them from the Advanced Options menu of the Text To Image and Image To Image tabs. You can also process images directly, using the image action buttons above the current image display or in the viewer.", "postProcessDesc2": "A dedicated UI will be released soon to facilitate more advanced post processing workflows.", "postProcessDesc3": "The Invoke AI Command Line Interface offers various other features including Embiggen.", - "training": "Training", - "trainingDesc1": "A dedicated workflow for training your own embeddings and checkpoints using Textual Inversion and Dreambooth from the web interface.", - "trainingDesc2": "InvokeAI already supports training custom embeddourings using Textual Inversion using the main script.", - "upload": "Upload", - "close": "Close", - "cancel": "Cancel", - "accept": "Accept", - "load": "Load", - "back": "Back", + "postprocessing": "Post Processing", + "postProcessing": "Post Processing", + "random": "Random", + "reportBugLabel": "Report Bug", + "settingsLabel": "Settings", "statusConnected": "Connected", + "statusConvertingModel": "Converting Model", "statusDisconnected": "Disconnected", "statusError": "Error", - "statusPreparing": "Preparing", - "statusProcessingCanceled": "Processing Canceled", - "statusProcessingComplete": "Processing Complete", "statusGenerating": "Generating", - "statusGeneratingTextToImage": "Generating Text To Image", "statusGeneratingImageToImage": "Generating Image To Image", "statusGeneratingInpainting": "Generating Inpainting", "statusGeneratingOutpainting": "Generating Outpainting", + "statusGeneratingTextToImage": "Generating Text To Image", "statusGenerationComplete": "Generation Complete", "statusIterationComplete": "Iteration Complete", - "statusSavingImage": "Saving Image", + "statusLoadingModel": "Loading Model", + "statusMergedModels": "Models Merged", + "statusMergingModels": "Merging Models", + "statusModelChanged": "Model Changed", + "statusModelConverted": "Model Converted", + "statusPreparing": "Preparing", + "statusProcessingCanceled": "Processing Canceled", + "statusProcessingComplete": "Processing Complete", "statusRestoringFaces": "Restoring Faces", - "statusRestoringFacesGFPGAN": "Restoring Faces (GFPGAN)", "statusRestoringFacesCodeFormer": "Restoring Faces (CodeFormer)", + "statusRestoringFacesGFPGAN": "Restoring Faces (GFPGAN)", + "statusSavingImage": "Saving Image", "statusUpscaling": "Upscaling", "statusUpscalingESRGAN": "Upscaling (ESRGAN)", - "statusLoadingModel": "Loading Model", - "statusModelChanged": "Model Changed", - "statusConvertingModel": "Converting Model", - "statusModelConverted": "Model Converted", - "statusMergingModels": "Merging Models", - "statusMergedModels": "Models Merged", - "loading": "Loading", - "loadingInvokeAI": "Loading Invoke AI", - "random": "Random", - "generate": "Generate", - "openInNewTab": "Open in New Tab", - "dontAskMeAgain": "Don't ask me again", - "areYouSure": "Are you sure?", - "imagePrompt": "Image Prompt" + "training": "Training", + "trainingDesc1": "A dedicated workflow for training your own embeddings and checkpoints using Textual Inversion and Dreambooth from the web interface.", + "trainingDesc2": "InvokeAI already supports training custom embeddourings using Textual Inversion using the main script.", + "txt2img": "Text To Image", + "unifiedCanvas": "Unified Canvas", + "upload": "Upload" + }, + "controlnet": { + "amult": "a_mult", + "autoConfigure": "Auto configure processor", + "balanced": "Balanced", + "beginEndStepPercent": "Begin / End Step Percentage", + "bgth": "bg_th", + "canny": "Canny", + "cannyDescription": "Canny edge detection", + "coarse": "Coarse", + "contentShuffle": "Content Shuffle", + "contentShuffleDescription": "Shuffles the content in an image", + "control": "Control", + "controlMode": "Control Mode", + "crop": "Crop", + "delete": "Delete", + "depthMidas": "Depth (Midas)", + "depthMidasDescription": "Depth map generation using Midas", + "depthZoe": "Depth (Zoe)", + "depthZoeDescription": "Depth map generation using Zoe", + "detectResolution": "Detect Resolution", + "duplicate": "Duplicate", + "enableControlnet": "Enable ControlNet", + "f": "F", + "fill": "Fill", + "h": "H", + "handAndFace": "Hand and Face", + "hed": "HED", + "hedDescription": "Holistically-Nested Edge Detection", + "hideAdvanced": "Hide Advanced", + "highThreshold": "High Threshold", + "imageResolution": "Image Resolution", + "importImageFromCanvas": "Import Image From Canvas", + "importMaskFromCanvas": "Import Mask From Canvas", + "incompatibleBaseModel": "Incompatible base model:", + "lineart": "Lineart", + "lineartAnime": "Lineart Anime", + "lineartAnimeDescription": "Anime-style lineart processing", + "lineartDescription": "Converts image to lineart", + "lowThreshold": "Low Threshold", + "maxFaces": "Max Faces", + "mediapipeFace": "Mediapipe Face", + "mediapipeFaceDescription": "Face detection using Mediapipe", + "megaControl": "Mega Control", + "minConfidence": "Min Confidence", + "mlsd": "M-LSD", + "mlsdDescription": "Minimalist Line Segment Detector", + "none": "None", + "noneDescription": "No processing applied", + "normalBae": "Normal BAE", + "normalBaeDescription": "Normal BAE processing", + "openPose": "Openpose", + "openPoseDescription": "Human pose estimation using Openpose", + "pidi": "PIDI", + "pidiDescription": "PIDI image processing", + "processor": "Processor", + "prompt": "Prompt", + "resetControlImage": "Reset Control Image", + "resize": "Resize", + "resizeMode": "Resize Mode", + "safe": "Safe", + "saveControlImage": "Save Control Image", + "scribble": "scribble", + "selectModel": "Select a model", + "setControlImageDimensions": "Set Control Image Dimensions To W/H", + "showAdvanced": "Show Advanced", + "toggleControlNet": "Toggle this ControlNet", + "w": "W", + "weight": "Weight" + }, + "embedding": { + "addEmbedding": "Add Embedding", + "incompatibleModel": "Incompatible base model:", + "noMatchingEmbedding": "No matching Embeddings" }, "gallery": { - "generations": "Generations", - "showGenerations": "Show Generations", - "uploads": "Uploads", - "showUploads": "Show Uploads", - "galleryImageSize": "Image Size", - "galleryImageResetSize": "Reset Size", - "gallerySettings": "Gallery Settings", - "maintainAspectRatio": "Maintain Aspect Ratio", - "autoSwitchNewImages": "Auto-Switch to New Images", - "singleColumnLayout": "Single Column Layout", "allImagesLoaded": "All Images Loaded", - "loadMore": "Load More", - "noImagesInGallery": "No Images to Display", + "assets": "Assets", + "autoAssignBoardOnClick": "Auto-Assign Board on Click", + "autoSwitchNewImages": "Auto-Switch to New Images", + "copy": "Copy", + "currentlyInUse": "This image is currently in use in the following features:", "deleteImage": "Delete Image", "deleteImageBin": "Deleted images will be sent to your operating system's Bin.", "deleteImagePermanent": "Deleted images cannot be restored.", + "download": "Download", + "featuresWillReset": "If you delete this image, those features will immediately be reset.", + "galleryImageResetSize": "Reset Size", + "galleryImageSize": "Image Size", + "gallerySettings": "Gallery Settings", + "generations": "Generations", "images": "Images", - "assets": "Assets", - "autoAssignBoardOnClick": "Auto-Assign Board on Click" + "loading": "Loading", + "loadMore": "Load More", + "maintainAspectRatio": "Maintain Aspect Ratio", + "noImagesInGallery": "No Images to Display", + "setCurrentImage": "Set as Current Image", + "showGenerations": "Show Generations", + "showUploads": "Show Uploads", + "singleColumnLayout": "Single Column Layout", + "unableToLoad": "Unable to load Gallery", + "uploads": "Uploads" }, "hotkeys": { - "keyboardShortcuts": "Keyboard Shortcuts", - "appHotkeys": "App Hotkeys", - "generalHotkeys": "General Hotkeys", - "galleryHotkeys": "Gallery Hotkeys", - "unifiedCanvasHotkeys": "Unified Canvas Hotkeys", - "nodesHotkeys": "Nodes Hotkeys", - "invoke": { - "title": "Invoke", - "desc": "Generate an image" - }, - "cancel": { - "title": "Cancel", - "desc": "Cancel image generation" - }, - "focusPrompt": { - "title": "Focus Prompt", - "desc": "Focus the prompt input area" - }, - "toggleOptions": { - "title": "Toggle Options", - "desc": "Open and close the options panel" - }, - "pinOptions": { - "title": "Pin Options", - "desc": "Pin the options panel" - }, - "toggleViewer": { - "title": "Toggle Viewer", - "desc": "Open and close Image Viewer" - }, - "toggleGallery": { - "title": "Toggle Gallery", - "desc": "Open and close the gallery drawer" - }, - "maximizeWorkSpace": { - "title": "Maximize Workspace", - "desc": "Close panels and maximize work area" - }, - "changeTabs": { - "title": "Change Tabs", - "desc": "Switch to another workspace" - }, - "consoleToggle": { - "title": "Console Toggle", - "desc": "Open and close console" - }, - "setPrompt": { - "title": "Set Prompt", - "desc": "Use the prompt of the current image" - }, - "setSeed": { - "title": "Set Seed", - "desc": "Use the seed of the current image" - }, - "setParameters": { - "title": "Set Parameters", - "desc": "Use all parameters of the current image" - }, - "restoreFaces": { - "title": "Restore Faces", - "desc": "Restore the current image" - }, - "upscale": { - "title": "Upscale", - "desc": "Upscale the current image" - }, - "showInfo": { - "title": "Show Info", - "desc": "Show metadata info of the current image" - }, - "sendToImageToImage": { - "title": "Send To Image To Image", - "desc": "Send current image to Image to Image" - }, - "deleteImage": { - "title": "Delete Image", - "desc": "Delete the current image" - }, - "closePanels": { - "title": "Close Panels", - "desc": "Closes open panels" - }, - "previousImage": { - "title": "Previous Image", - "desc": "Display the previous image in gallery" - }, - "nextImage": { - "title": "Next Image", - "desc": "Display the next image in gallery" - }, - "toggleGalleryPin": { - "title": "Toggle Gallery Pin", - "desc": "Pins and unpins the gallery to the UI" - }, - "increaseGalleryThumbSize": { - "title": "Increase Gallery Image Size", - "desc": "Increases gallery thumbnails size" - }, - "decreaseGalleryThumbSize": { - "title": "Decrease Gallery Image Size", - "desc": "Decreases gallery thumbnails size" - }, - "selectBrush": { - "title": "Select Brush", - "desc": "Selects the canvas brush" - }, - "selectEraser": { - "title": "Select Eraser", - "desc": "Selects the canvas eraser" - }, - "decreaseBrushSize": { - "title": "Decrease Brush Size", - "desc": "Decreases the size of the canvas brush/eraser" - }, - "increaseBrushSize": { - "title": "Increase Brush Size", - "desc": "Increases the size of the canvas brush/eraser" - }, - "decreaseBrushOpacity": { - "title": "Decrease Brush Opacity", - "desc": "Decreases the opacity of the canvas brush" - }, - "increaseBrushOpacity": { - "title": "Increase Brush Opacity", - "desc": "Increases the opacity of the canvas brush" - }, - "moveTool": { - "title": "Move Tool", - "desc": "Allows canvas navigation" - }, - "fillBoundingBox": { - "title": "Fill Bounding Box", - "desc": "Fills the bounding box with brush color" - }, - "eraseBoundingBox": { - "title": "Erase Bounding Box", - "desc": "Erases the bounding box area" - }, - "colorPicker": { - "title": "Select Color Picker", - "desc": "Selects the canvas color picker" - }, - "toggleSnap": { - "title": "Toggle Snap", - "desc": "Toggles Snap to Grid" - }, - "quickToggleMove": { - "title": "Quick Toggle Move", - "desc": "Temporarily toggles Move mode" - }, - "toggleLayer": { - "title": "Toggle Layer", - "desc": "Toggles mask/base layer selection" - }, - "clearMask": { - "title": "Clear Mask", - "desc": "Clear the entire mask" - }, - "hideMask": { - "title": "Hide Mask", - "desc": "Hide and unhide mask" - }, - "showHideBoundingBox": { - "title": "Show/Hide Bounding Box", - "desc": "Toggle visibility of bounding box" - }, - "mergeVisible": { - "title": "Merge Visible", - "desc": "Merge all visible layers of canvas" - }, - "saveToGallery": { - "title": "Save To Gallery", - "desc": "Save current canvas to gallery" - }, - "copyToClipboard": { - "title": "Copy to Clipboard", - "desc": "Copy current canvas to clipboard" - }, - "downloadImage": { - "title": "Download Image", - "desc": "Download current canvas" - }, - "undoStroke": { - "title": "Undo Stroke", - "desc": "Undo a brush stroke" - }, - "redoStroke": { - "title": "Redo Stroke", - "desc": "Redo a brush stroke" - }, - "resetView": { - "title": "Reset View", - "desc": "Reset Canvas View" - }, - "previousStagingImage": { - "title": "Previous Staging Image", - "desc": "Previous Staging Area Image" - }, - "nextStagingImage": { - "title": "Next Staging Image", - "desc": "Next Staging Area Image" - }, "acceptStagingImage": { - "title": "Accept Staging Image", - "desc": "Accept Current Staging Area Image" + "desc": "Accept Current Staging Area Image", + "title": "Accept Staging Image" }, "addNodes": { - "title": "Add Nodes", - "desc": "Opens the add node menu" + "desc": "Opens the add node menu", + "title": "Add Nodes" + }, + "appHotkeys": "App Hotkeys", + "cancel": { + "desc": "Cancel image generation", + "title": "Cancel" + }, + "changeTabs": { + "desc": "Switch to another workspace", + "title": "Change Tabs" + }, + "clearMask": { + "desc": "Clear the entire mask", + "title": "Clear Mask" + }, + "closePanels": { + "desc": "Closes open panels", + "title": "Close Panels" + }, + "colorPicker": { + "desc": "Selects the canvas color picker", + "title": "Select Color Picker" + }, + "consoleToggle": { + "desc": "Open and close console", + "title": "Console Toggle" + }, + "copyToClipboard": { + "desc": "Copy current canvas to clipboard", + "title": "Copy to Clipboard" + }, + "decreaseBrushOpacity": { + "desc": "Decreases the opacity of the canvas brush", + "title": "Decrease Brush Opacity" + }, + "decreaseBrushSize": { + "desc": "Decreases the size of the canvas brush/eraser", + "title": "Decrease Brush Size" + }, + "decreaseGalleryThumbSize": { + "desc": "Decreases gallery thumbnails size", + "title": "Decrease Gallery Image Size" + }, + "deleteImage": { + "desc": "Delete the current image", + "title": "Delete Image" + }, + "downloadImage": { + "desc": "Download current canvas", + "title": "Download Image" + }, + "eraseBoundingBox": { + "desc": "Erases the bounding box area", + "title": "Erase Bounding Box" + }, + "fillBoundingBox": { + "desc": "Fills the bounding box with brush color", + "title": "Fill Bounding Box" + }, + "focusPrompt": { + "desc": "Focus the prompt input area", + "title": "Focus Prompt" + }, + "galleryHotkeys": "Gallery Hotkeys", + "generalHotkeys": "General Hotkeys", + "hideMask": { + "desc": "Hide and unhide mask", + "title": "Hide Mask" + }, + "increaseBrushOpacity": { + "desc": "Increases the opacity of the canvas brush", + "title": "Increase Brush Opacity" + }, + "increaseBrushSize": { + "desc": "Increases the size of the canvas brush/eraser", + "title": "Increase Brush Size" + }, + "increaseGalleryThumbSize": { + "desc": "Increases gallery thumbnails size", + "title": "Increase Gallery Image Size" + }, + "invoke": { + "desc": "Generate an image", + "title": "Invoke" + }, + "keyboardShortcuts": "Keyboard Shortcuts", + "maximizeWorkSpace": { + "desc": "Close panels and maximize work area", + "title": "Maximize Workspace" + }, + "mergeVisible": { + "desc": "Merge all visible layers of canvas", + "title": "Merge Visible" + }, + "moveTool": { + "desc": "Allows canvas navigation", + "title": "Move Tool" + }, + "nextImage": { + "desc": "Display the next image in gallery", + "title": "Next Image" + }, + "nextStagingImage": { + "desc": "Next Staging Area Image", + "title": "Next Staging Image" + }, + "nodesHotkeys": "Nodes Hotkeys", + "pinOptions": { + "desc": "Pin the options panel", + "title": "Pin Options" + }, + "previousImage": { + "desc": "Display the previous image in gallery", + "title": "Previous Image" + }, + "previousStagingImage": { + "desc": "Previous Staging Area Image", + "title": "Previous Staging Image" + }, + "quickToggleMove": { + "desc": "Temporarily toggles Move mode", + "title": "Quick Toggle Move" + }, + "redoStroke": { + "desc": "Redo a brush stroke", + "title": "Redo Stroke" + }, + "resetView": { + "desc": "Reset Canvas View", + "title": "Reset View" + }, + "restoreFaces": { + "desc": "Restore the current image", + "title": "Restore Faces" + }, + "saveToGallery": { + "desc": "Save current canvas to gallery", + "title": "Save To Gallery" + }, + "selectBrush": { + "desc": "Selects the canvas brush", + "title": "Select Brush" + }, + "selectEraser": { + "desc": "Selects the canvas eraser", + "title": "Select Eraser" + }, + "sendToImageToImage": { + "desc": "Send current image to Image to Image", + "title": "Send To Image To Image" + }, + "setParameters": { + "desc": "Use all parameters of the current image", + "title": "Set Parameters" + }, + "setPrompt": { + "desc": "Use the prompt of the current image", + "title": "Set Prompt" + }, + "setSeed": { + "desc": "Use the seed of the current image", + "title": "Set Seed" + }, + "showHideBoundingBox": { + "desc": "Toggle visibility of bounding box", + "title": "Show/Hide Bounding Box" + }, + "showInfo": { + "desc": "Show metadata info of the current image", + "title": "Show Info" + }, + "toggleGallery": { + "desc": "Open and close the gallery drawer", + "title": "Toggle Gallery" + }, + "toggleGalleryPin": { + "desc": "Pins and unpins the gallery to the UI", + "title": "Toggle Gallery Pin" + }, + "toggleLayer": { + "desc": "Toggles mask/base layer selection", + "title": "Toggle Layer" + }, + "toggleOptions": { + "desc": "Open and close the options panel", + "title": "Toggle Options" + }, + "toggleSnap": { + "desc": "Toggles Snap to Grid", + "title": "Toggle Snap" + }, + "toggleViewer": { + "desc": "Open and close Image Viewer", + "title": "Toggle Viewer" + }, + "undoStroke": { + "desc": "Undo a brush stroke", + "title": "Undo Stroke" + }, + "unifiedCanvasHotkeys": "Unified Canvas Hotkeys", + "upscale": { + "desc": "Upscale the current image", + "title": "Upscale" } }, - "modelManager": { - "modelManager": "Model Manager", + "metadata": { + "cfgScale": "CFG scale", + "createdBy": "Created By", + "fit": "Image to image fit", + "generationMode": "Generation Mode", + "height": "Height", + "hiresFix": "High Resolution Optimization", + "imageDetails": "Image Details", + "initImage": "Initial image", + "metadata": "Metadata", "model": "Model", - "vae": "VAE", - "allModels": "All Models", - "checkpointModels": "Checkpoints", - "diffusersModels": "Diffusers", - "loraModels": "LoRAs", - "safetensorModels": "SafeTensors", - "onnxModels": "Onnx", - "oliveModels": "Olives", - "modelAdded": "Model Added", - "modelUpdated": "Model Updated", - "modelUpdateFailed": "Model Update Failed", - "modelEntryDeleted": "Model Entry Deleted", - "cannotUseSpaces": "Cannot Use Spaces", + "negativePrompt": "Negative Prompt", + "noImageDetails": "No image details found", + "noMetaData": "No metadata found", + "perlin": "Perlin Noise", + "positivePrompt": "Positive Prompt", + "scheduler": "Scheduler", + "seamless": "Seamless", + "seed": "Seed", + "steps": "Steps", + "strength": "Image to image strength", + "Threshold": "Noise Threshold", + "variations": "Seed-weight pairs", + "width": "Width", + "workflow": "Workflow" + }, + "modelManager": { + "active": "active", + "addCheckpointModel": "Add Checkpoint / Safetensor Model", + "addDifference": "Add Difference", + "addDiffuserModel": "Add Diffusers", + "addManually": "Add Manually", + "addModel": "Add Model", "addNew": "Add New", "addNewModel": "Add New Model", - "addCheckpointModel": "Add Checkpoint / Safetensor Model", - "addDiffuserModel": "Add Diffusers", - "scanForModels": "Scan For Models", - "addManually": "Add Manually", - "manual": "Manual", + "addSelected": "Add Selected", + "advanced": "Advanced", + "allModels": "All Models", + "alpha": "Alpha", + "availableModels": "Available Models", "baseModel": "Base Model", - "name": "Name", - "nameValidationMsg": "Enter a name for your model", - "description": "Description", - "descriptionValidationMsg": "Add a description for your model", + "cached": "cached", + "cannotUseSpaces": "Cannot Use Spaces", + "checkpointFolder": "Checkpoint Folder", + "checkpointModels": "Checkpoints", + "clearCheckpointFolder": "Clear Checkpoint Folder", + "closeAdvanced": "Close Advanced", "config": "Config", "configValidationMsg": "Path to the config file of your model.", - "modelLocation": "Model Location", - "modelLocationValidationMsg": "Path to where your model is located locally.", - "repo_id": "Repo ID", - "repoIDValidationMsg": "Online repository of your model", - "vaeLocation": "VAE Location", - "vaeLocationValidationMsg": "Path to where your VAE is located.", - "variant": "Variant", - "vaeRepoID": "VAE Repo ID", - "vaeRepoIDValidationMsg": "Online repository of your VAE", - "width": "Width", - "widthValidationMsg": "Default width of your model.", - "height": "Height", - "heightValidationMsg": "Default height of your model.", - "addModel": "Add Model", - "updateModel": "Update Model", - "availableModels": "Available Models", - "search": "Search", - "load": "Load", - "active": "active", - "notLoaded": "not loaded", - "cached": "cached", - "checkpointFolder": "Checkpoint Folder", - "clearCheckpointFolder": "Clear Checkpoint Folder", - "findModels": "Find Models", - "scanAgain": "Scan Again", - "modelsFound": "Models Found", - "selectFolder": "Select Folder", - "selected": "Selected", - "selectAll": "Select All", - "deselectAll": "Deselect All", - "showExisting": "Show Existing", - "addSelected": "Add Selected", - "modelExists": "Model Exists", - "selectAndAdd": "Select and Add Models Listed Below", - "noModelsFound": "No Models Found", - "delete": "Delete", - "deleteModel": "Delete Model", - "deleteConfig": "Delete Config", - "deleteMsg1": "Are you sure you want to delete this model from InvokeAI?", - "modelDeleted": "Model Deleted", - "modelDeleteFailed": "Failed to delete model", - "deleteMsg2": "This WILL delete the model from disk if it is in the InvokeAI root folder. If you are using a custom location, then the model WILL NOT be deleted from disk.", - "formMessageDiffusersModelLocation": "Diffusers Model Location", - "formMessageDiffusersModelLocationDesc": "Please enter at least one.", - "formMessageDiffusersVAELocation": "VAE Location", - "formMessageDiffusersVAELocationDesc": "If not provided, InvokeAI will look for the VAE file inside the model location given above.", "convert": "Convert", + "convertingModelBegin": "Converting Model. Please wait.", "convertToDiffusers": "Convert To Diffusers", "convertToDiffusersHelpText1": "This model will be converted to the 🧨 Diffusers format.", "convertToDiffusersHelpText2": "This process will replace your Model Manager entry with the Diffusers version of the same model.", @@ -422,318 +497,492 @@ "convertToDiffusersHelpText5": "Please make sure you have enough disk space. Models generally vary between 2GB-7GB in size.", "convertToDiffusersHelpText6": "Do you wish to convert this model?", "convertToDiffusersSaveLocation": "Save Location", - "noCustomLocationProvided": "No Custom Location Provided", - "convertingModelBegin": "Converting Model. Please wait.", - "v1": "v1", - "v2_base": "v2 (512px)", - "v2_768": "v2 (768px)", - "inpainting": "v1 Inpainting", - "customConfig": "Custom Config", - "pathToCustomConfig": "Path To Custom Config", - "statusConverting": "Converting", - "modelConverted": "Model Converted", - "modelConversionFailed": "Model Conversion Failed", - "sameFolder": "Same folder", - "invokeRoot": "InvokeAI folder", "custom": "Custom", + "customConfig": "Custom Config", + "customConfigFileLocation": "Custom Config File Location", "customSaveLocation": "Custom Save Location", - "merge": "Merge", - "modelsMerged": "Models Merged", - "modelsMergeFailed": "Model Merge Failed", - "mergeModels": "Merge Models", - "modelOne": "Model 1", - "modelTwo": "Model 2", - "modelThree": "Model 3", - "mergedModelName": "Merged Model Name", - "alpha": "Alpha", - "interpolationType": "Interpolation Type", - "mergedModelSaveLocation": "Save Location", - "mergedModelCustomSaveLocation": "Custom Path", - "invokeAIFolder": "Invoke AI Folder", + "delete": "Delete", + "deleteConfig": "Delete Config", + "deleteModel": "Delete Model", + "deleteMsg1": "Are you sure you want to delete this model from InvokeAI?", + "deleteMsg2": "This WILL delete the model from disk if it is in the InvokeAI root folder. If you are using a custom location, then the model WILL NOT be deleted from disk.", + "description": "Description", + "descriptionValidationMsg": "Add a description for your model", + "deselectAll": "Deselect All", + "diffusersModels": "Diffusers", + "findModels": "Find Models", + "formMessageDiffusersModelLocation": "Diffusers Model Location", + "formMessageDiffusersModelLocationDesc": "Please enter at least one.", + "formMessageDiffusersVAELocation": "VAE Location", + "formMessageDiffusersVAELocationDesc": "If not provided, InvokeAI will look for the VAE file inside the model location given above.", + "height": "Height", + "heightValidationMsg": "Default height of your model.", "ignoreMismatch": "Ignore Mismatches Between Selected Models", + "importModels": "Import Models", + "inpainting": "v1 Inpainting", + "interpolationType": "Interpolation Type", + "inverseSigmoid": "Inverse Sigmoid", + "invokeAIFolder": "Invoke AI Folder", + "invokeRoot": "InvokeAI folder", + "load": "Load", + "loraModels": "LoRAs", + "manual": "Manual", + "merge": "Merge", + "mergedModelCustomSaveLocation": "Custom Path", + "mergedModelName": "Merged Model Name", + "mergedModelSaveLocation": "Save Location", + "mergeModels": "Merge Models", + "model": "Model", + "modelAdded": "Model Added", + "modelConversionFailed": "Model Conversion Failed", + "modelConverted": "Model Converted", + "modelDeleted": "Model Deleted", + "modelDeleteFailed": "Failed to delete model", + "modelEntryDeleted": "Model Entry Deleted", + "modelExists": "Model Exists", + "modelLocation": "Model Location", + "modelLocationValidationMsg": "Provide the path to a local folder where your Diffusers Model is stored", + "modelManager": "Model Manager", + "modelMergeAlphaHelp": "Alpha controls blend strength for the models. Lower alpha values lead to lower influence of the second model.", "modelMergeHeaderHelp1": "You can merge up to three different models to create a blend that suits your needs.", "modelMergeHeaderHelp2": "Only Diffusers are available for merging. If you want to merge a checkpoint model, please convert it to Diffusers first.", - "modelMergeAlphaHelp": "Alpha controls blend strength for the models. Lower alpha values lead to lower influence of the second model.", "modelMergeInterpAddDifferenceHelp": "In this mode, Model 3 is first subtracted from Model 2. The resulting version is blended with Model 1 with the alpha rate set above.", - "inverseSigmoid": "Inverse Sigmoid", - "sigmoid": "Sigmoid", - "weightedSum": "Weighted Sum", + "modelOne": "Model 1", + "modelsFound": "Models Found", + "modelsMerged": "Models Merged", + "modelsMergeFailed": "Model Merge Failed", + "modelsSynced": "Models Synced", + "modelSyncFailed": "Model Sync Failed", + "modelThree": "Model 3", + "modelTwo": "Model 2", + "modelType": "Model Type", + "modelUpdated": "Model Updated", + "modelUpdateFailed": "Model Update Failed", + "name": "Name", + "nameValidationMsg": "Enter a name for your model", + "noCustomLocationProvided": "No Custom Location Provided", + "noModels": "No Models Found", + "noModelsFound": "No Models Found", "none": "none", - "addDifference": "Add Difference", + "notLoaded": "not loaded", + "oliveModels": "Olives", + "onnxModels": "Onnx", + "pathToCustomConfig": "Path To Custom Config", "pickModelType": "Pick Model Type", + "predictionType": "Prediction Type (for Stable Diffusion 2.x Models only)", + "quickAdd": "Quick Add", + "repo_id": "Repo ID", + "repoIDValidationMsg": "Online repository of your model", + "safetensorModels": "SafeTensors", + "sameFolder": "Same folder", + "scanAgain": "Scan Again", + "scanForModels": "Scan For Models", + "search": "Search", + "selectAll": "Select All", + "selectAndAdd": "Select and Add Models Listed Below", + "selected": "Selected", + "selectFolder": "Select Folder", "selectModel": "Select Model", - "importModels": "Import Models", "settings": "Settings", + "showExisting": "Show Existing", + "sigmoid": "Sigmoid", + "simpleModelDesc": "Provide a path to a local Diffusers model, local checkpoint / safetensors model a HuggingFace Repo ID, or a checkpoint/diffusers model URL.", + "statusConverting": "Converting", "syncModels": "Sync Models", "syncModelsDesc": "If your models are out of sync with the backend, you can refresh them up using this option. This is generally handy in cases where you manually update your models.yaml file or add models to the InvokeAI root folder after the application has booted.", - "modelsSynced": "Models Synced", - "modelSyncFailed": "Model Sync Failed" + "updateModel": "Update Model", + "useCustomConfig": "Use Custom Config", + "v1": "v1", + "v2_768": "v2 (768px)", + "v2_base": "v2 (512px)", + "vae": "VAE", + "vaeLocation": "VAE Location", + "vaeLocationValidationMsg": "Path to where your VAE is located.", + "vaeRepoID": "VAE Repo ID", + "vaeRepoIDValidationMsg": "Online repository of your VAE", + "variant": "Variant", + "weightedSum": "Weighted Sum", + "width": "Width", + "widthValidationMsg": "Default width of your model." + }, + "models": { + "loading": "loading", + "noLoRAsAvailable": "No LoRAs available", + "noMatchingLoRAs": "No matching LoRAs", + "noMatchingModels": "No matching Models", + "noModelsAvailable": "No Modelss available", + "selectLoRA": "Select a LoRA", + "selectModel": "Select a Model" + }, + "nodes": { + "addNode": "Add Node", + "addNodeToolTip": "Add Node (Shift+A, Space)", + "animatedEdges": "Animated Edges", + "animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes", + "cannotConnectInputToInput": "Cannot connect input to input", + "cannotConnectOutputToOutput": "Cannot connect output to output", + "cannotConnectToSelf": "Cannot connect to self", + "colorCodeEdges": "Color-Code Edges", + "colorCodeEdgesHelp": "Color-code edges according to their connected fields", + "connectionWouldCreateCycle": "Connection would create a cycle", + "currentImage": "Current Image", + "currentImageDescription": "Displays the current image in the Node Editor", + "downloadWorkflow": "Download Workflow JSON", + "fieldTypesMustMatch": "Field types must match", + "fitViewportNodes": "Fit View", + "fullyContainNodes": "Fully Contain Nodes to Select", + "fullyContainNodesHelp": "Nodes must be fully inside the selection box to be selected", + "hideGraphNodes": "Hide Graph Overlay", + "hideLegendNodes": "Hide Field Type Legend", + "hideMinimapnodes": "Hide MiniMap", + "inputMayOnlyHaveOneConnection": "Input may only have one connection", + "loadingNodes": "Loading Nodes...", + "loadWorkflow": "Load Workflow", + "noConnectionData": "No connection data", + "noConnectionInProgress": "No connection in progress", + "nodeOutputs": "Node Outputs", + "nodeSearch": "Search for nodes", + "nodeTemplate": "Node Template", + "noFieldsLinearview": "No fields added to Linear View", + "noFieldType": "No field type", + "noMatchingNodes": "No matching nodes", + "noNodeSelected": "No node selected", + "noOpacity": "Node Opacity", + "noOutputRecorded": "No outputs recorded", + "notes": "Notes", + "notesDescription": "Add notes about your workflow", + "pickOne": "Pick One", + "problemSettingTitle": "Problem Setting Title", + "reloadNodeTemplates": "Reload Node Templates", + "removeLinearView": "Remove from Linear View", + "resetWorkflow": "Reset Workflow", + "resetWorkflowDesc": "Are you sure you want to reset this workflow?", + "resetWorkflowDesc2": "Resetting the workflow will clear all nodes, edges and workflow details.", + "showGraphNodes": "Show Graph Overlay", + "showLegendNodes": "Show Field Type Legend", + "showMinimapnodes": "Show MiniMap", + "snapToGrid": "Snap to Grid", + "snapToGridHelp": "Snap nodes to grid when moved", + "unableToLoadWorkflow": "Unable to Validate Workflow", + "unableToValidateWorkflow": "Unable to Validate Workflow", + "unknownField": "Unknown Field", + "unkownInvocation": "Unknown Invocation type", + "validateConnections": "Validate Connections and Graph", + "validateConnectionsHelp": "Prevent invalid connections from being made, and invalid graphs from being invoked", + "workflow": "Workflow", + "workflowAuthor": "Author", + "workflowContact": "Contact", + "workflowDescription": "Short Description", + "workflowName": "Name", + "workflowNotes": "Notes", + "workflowSettings": "Workflow Editor Settings", + "workflowTags": "Tags", + "workflowValidation": "Workflow Validation Error", + "workflowVersion": "Version", + "zoomInNodes": "Zoom In", + "zoomOutNodes": "Zoom Out", + "executionStateError": "Error", + "executionStateCompleted": "Completed", + "executionStateInProgress": "In Progress", + "versionUnknown": " Version Unknown", + "unknownNode": "Unknown Node", + "version": "Version", + "updateApp": "Update App", + "unknownTemplate": "Unknown Template" }, "parameters": { - "general": "General", - "images": "Images", - "steps": "Steps", - "cfgScale": "CFG Scale", - "width": "Width", - "height": "Height", - "scheduler": "Scheduler", - "seed": "Seed", - "boundingBoxWidth": "Bounding Box Width", + "aspectRatio": "Ratio", + "boundingBoxHeader": "Bounding Box", "boundingBoxHeight": "Bounding Box Height", - "imageToImage": "Image to Image", - "randomizeSeed": "Randomize Seed", - "shuffle": "Shuffle Seed", - "noiseThreshold": "Noise Threshold", - "perlinNoise": "Perlin Noise", - "noiseSettings": "Noise", - "variations": "Variations", - "variationAmount": "Variation Amount", - "seedWeights": "Seed Weights", - "faceRestoration": "Face Restoration", - "restoreFaces": "Restore Faces", - "type": "Type", - "strength": "Strength", - "upscaling": "Upscaling", - "upscale": "Upscale", - "upscaleImage": "Upscale Image", + "boundingBoxWidth": "Bounding Box Width", + "cancel": { + "cancel": "Cancel", + "immediate": "Cancel immediately", + "isScheduled": "Canceling", + "schedule": "Cancel after current iteration", + "setType": "Set cancel type" + }, + "cfgScale": "CFG Scale", + "clipSkip": "CLIP Skip", + "closeViewer": "Close Viewer", + "codeformerFidelity": "Fidelity", + "coherenceMode": "Mode", + "coherencePassHeader": "Coherence Pass", + "coherenceSteps": "Steps", + "coherenceStrength": "Strength", + "compositingSettingsHeader": "Compositing Settings", + "controlNetControlMode": "Control Mode", + "copyImage": "Copy Image", + "copyImageToLink": "Copy Image To Link", "denoisingStrength": "Denoising Strength", - "scale": "Scale", - "otherOptions": "Other Options", - "seamlessTiling": "Seamless Tiling", - "seamlessXAxis": "X Axis", - "seamlessYAxis": "Y Axis", + "downloadImage": "Download Image", + "enableNoiseSettings": "Enable Noise Settings", + "faceRestoration": "Face Restoration", + "general": "General", + "height": "Height", + "hidePreview": "Hide Preview", "hiresOptim": "High Res Optimization", "hiresStrength": "High Res Strength", + "hSymmetryStep": "H Symmetry Step", "imageFit": "Fit Initial Image To Output Size", - "codeformerFidelity": "Fidelity", - "compositingSettingsHeader": "Compositing Settings", + "images": "Images", + "imageToImage": "Image to Image", + "img2imgStrength": "Image To Image Strength", + "infillMethod": "Infill Method", + "infillScalingHeader": "Infill and Scaling", + "info": "Info", + "initialImage": "Initial Image", + "invoke": { + "addingImagesTo": "Adding images to", + "invoke": "Invoke", + "missingFieldTemplate": "Missing field template", + "missingInputForField": "{{nodeLabel}} -> {{fieldLabel}} missing input", + "missingNodeTemplate": "Missing node template", + "noControlImageForControlNet": "ControlNet {{index}} has no control image", + "noInitialImageSelected": "No initial image selected", + "noModelForControlNet": "ControlNet {{index}} has no model selected.", + "noModelSelected": "No model selected", + "noNodesInGraph": "No nodes in graph", + "readyToInvoke": "Ready to Invoke", + "systemBusy": "System busy", + "systemDisconnected": "System disconnected", + "unableToInvoke": "Unable to Invoke" + }, "maskAdjustmentsHeader": "Mask Adjustments", "maskBlur": "Blur", "maskBlurMethod": "Blur Method", - "coherencePassHeader": "Coherence Pass", - "coherenceMode": "Mode", - "coherenceSteps": "Steps", - "coherenceStrength": "Strength", - "seamLowThreshold": "Low", - "seamHighThreshold": "High", - "scaleBeforeProcessing": "Scale Before Processing", - "scaledWidth": "Scaled W", - "scaledHeight": "Scaled H", - "infillMethod": "Infill Method", - "tileSize": "Tile Size", - "patchmatchDownScaleSize": "Downscale", - "boundingBoxHeader": "Bounding Box", - "seamCorrectionHeader": "Seam Correction", - "infillScalingHeader": "Infill and Scaling", - "img2imgStrength": "Image To Image Strength", - "toggleLoopback": "Toggle Loopback", - "symmetry": "Symmetry", - "hSymmetryStep": "H Symmetry Step", - "vSymmetryStep": "V Symmetry Step", - "invoke": "Invoke", - "cancel": { - "immediate": "Cancel immediately", - "schedule": "Cancel after current iteration", - "isScheduled": "Canceling", - "setType": "Set cancel type" - }, - "positivePromptPlaceholder": "Positive Prompt", "negativePromptPlaceholder": "Negative Prompt", + "noiseSettings": "Noise", + "noiseThreshold": "Noise Threshold", + "openInViewer": "Open In Viewer", + "otherOptions": "Other Options", + "patchmatchDownScaleSize": "Downscale", + "perlinNoise": "Perlin Noise", + "positivePromptPlaceholder": "Positive Prompt", + "randomizeSeed": "Randomize Seed", + "restoreFaces": "Restore Faces", + "scale": "Scale", + "scaleBeforeProcessing": "Scale Before Processing", + "scaledHeight": "Scaled H", + "scaledWidth": "Scaled W", + "scheduler": "Scheduler", + "seamCorrectionHeader": "Seam Correction", + "seamHighThreshold": "High", + "seamlessTiling": "Seamless Tiling", + "seamlessXAxis": "X Axis", + "seamlessYAxis": "Y Axis", + "seamLowThreshold": "Low", + "seed": "Seed", + "seedWeights": "Seed Weights", "sendTo": "Send to", "sendToImg2Img": "Send to Image to Image", "sendToUnifiedCanvas": "Send To Unified Canvas", - "copyImage": "Copy Image", - "copyImageToLink": "Copy Image To Link", - "downloadImage": "Download Image", - "openInViewer": "Open In Viewer", - "closeViewer": "Close Viewer", + "showOptionsPanel": "Show Options Panel", + "showPreview": "Show Preview", + "shuffle": "Shuffle Seed", + "steps": "Steps", + "strength": "Strength", + "symmetry": "Symmetry", + "tileSize": "Tile Size", + "toggleLoopback": "Toggle Loopback", + "type": "Type", + "upscale": "Upscale", + "upscaleImage": "Upscale Image", + "upscaling": "Upscaling", + "useAll": "Use All", + "useCpuNoise": "Use CPU Noise", + "useInitImg": "Use Initial Image", "usePrompt": "Use Prompt", "useSeed": "Use Seed", - "useAll": "Use All", - "useInitImg": "Use Initial Image", - "info": "Info", - "initialImage": "Initial Image", - "showOptionsPanel": "Show Options Panel", - "hidePreview": "Hide Preview", - "showPreview": "Show Preview", - "controlNetControlMode": "Control Mode", - "clipSkip": "CLIP Skip", - "aspectRatio": "Ratio" + "variationAmount": "Variation Amount", + "variations": "Variations", + "vSymmetryStep": "V Symmetry Step", + "width": "Width" + }, + "prompt": { + "combinatorial": "Combinatorial Generation", + "dynamicPrompts": "Dynamic Prompts", + "enableDynamicPrompts": "Enable Dynamic Prompts", + "maxPrompts": "Max Prompts" + }, + "sdxl": { + "cfgScale": "CFG Scale", + "concatPromptStyle": "Concatenate Prompt & Style", + "denoisingStrength": "Denoising Strength", + "loading": "Loading...", + "negAestheticScore": "Negative Aesthetic Score", + "negStylePrompt": "Negative Style Prompt", + "noModelsAvailable": "No models available", + "posAestheticScore": "Positive Aesthetic Score", + "posStylePrompt": "Positive Style Prompt", + "refiner": "Refiner", + "refinermodel": "Refiner Model", + "refinerStart": "Refiner Start", + "scheduler": "Scheduler", + "selectAModel": "Select a model", + "steps": "Steps", + "useRefiner": "Use Refiner" }, "settings": { - "models": "Models", - "displayInProgress": "Display Progress Images", - "saveSteps": "Save images every n steps", - "confirmOnDelete": "Confirm On Delete", - "displayHelpIcons": "Display Help Icons", "alternateCanvasLayout": "Alternate Canvas Layout", - "enableNodesEditor": "Enable Nodes Editor", - "enableImageDebugging": "Enable Image Debugging", - "useSlidersForAll": "Use Sliders For All Options", - "showProgressInViewer": "Show Progress Images in Viewer", "antialiasProgressImages": "Antialias Progress Images", "autoChangeDimensions": "Update W/H To Model Defaults On Change", + "beta": "Beta", + "confirmOnDelete": "Confirm On Delete", + "consoleLogLevel": "Log Level", + "developer": "Developer", + "displayHelpIcons": "Display Help Icons", + "displayInProgress": "Display Progress Images", + "enableImageDebugging": "Enable Image Debugging", + "enableNodesEditor": "Enable Nodes Editor", + "experimental": "Experimental", + "favoriteSchedulers": "Favorite Schedulers", + "favoriteSchedulersPlaceholder": "No schedulers favorited", + "general": "General", + "generation": "Generation", + "models": "Models", + "resetComplete": "Web UI has been reset.", "resetWebUI": "Reset Web UI", "resetWebUIDesc1": "Resetting the web UI only resets the browser's local cache of your images and remembered settings. It does not delete any images from disk.", "resetWebUIDesc2": "If images aren't showing up in the gallery or something else isn't working, please try resetting before submitting an issue on GitHub.", - "resetComplete": "Web UI has been reset.", - "consoleLogLevel": "Log Level", + "saveSteps": "Save images every n steps", "shouldLogToConsole": "Console Logging", - "developer": "Developer", - "general": "General", - "generation": "Generation", - "ui": "User Interface", - "favoriteSchedulers": "Favorite Schedulers", - "favoriteSchedulersPlaceholder": "No schedulers favorited", "showAdvancedOptions": "Show Advanced Options", - "experimental": "Experimental", - "beta": "Beta" + "showProgressInViewer": "Show Progress Images in Viewer", + "ui": "User Interface", + "useSlidersForAll": "Use Sliders For All Options" }, "toast": { - "serverError": "Server Error", - "disconnected": "Disconnected from Server", - "connected": "Connected to Server", "canceled": "Processing Canceled", - "tempFoldersEmptied": "Temp Folder Emptied", - "uploadFailed": "Upload failed", - "uploadFailedUnableToLoadDesc": "Unable to load file", - "uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image", + "canvasMerged": "Canvas Merged", + "connected": "Connected to Server", + "disconnected": "Disconnected from Server", "downloadImageStarted": "Image Download Started", + "faceRestoreFailed": "Face Restoration Failed", "imageCopied": "Image Copied", - "problemCopyingImage": "Unable to Copy Image", "imageLinkCopied": "Image Link Copied", - "problemCopyingImageLink": "Unable to Copy Image Link", "imageNotLoaded": "No Image Loaded", "imageNotLoadedDesc": "Could not find image", "imageSavedToGallery": "Image Saved to Gallery", - "canvasMerged": "Canvas Merged", - "sentToImageToImage": "Sent To Image To Image", - "sentToUnifiedCanvas": "Sent to Unified Canvas", - "parameterSet": "Parameter set", - "parameterNotSet": "Parameter not set", - "parametersSet": "Parameters Set", - "parametersNotSet": "Parameters Not Set", - "parametersNotSetDesc": "No metadata found for this image.", - "parametersFailed": "Problem loading parameters", - "parametersFailedDesc": "Unable to load init image.", - "seedSet": "Seed Set", - "seedNotSet": "Seed Not Set", - "seedNotSetDesc": "Could not find seed for this image.", - "promptSet": "Prompt Set", - "promptNotSet": "Prompt Not Set", - "promptNotSetDesc": "Could not find prompt for this image.", - "upscalingFailed": "Upscaling Failed", - "faceRestoreFailed": "Face Restoration Failed", - "metadataLoadFailed": "Failed to load metadata", - "initialImageSet": "Initial Image Set", "initialImageNotSet": "Initial Image Not Set", "initialImageNotSetDesc": "Could not load initial image", - "nodesSaved": "Nodes Saved", + "initialImageSet": "Initial Image Set", + "metadataLoadFailed": "Failed to load metadata", + "modelAdded": "Model Added: {{modelName}}", + "modelAddedSimple": "Model Added", + "modelAddFailed": "Model Add Failed", + "nodesBrokenConnections": "Cannot load. Some connections are broken.", + "nodesCleared": "Nodes Cleared", + "nodesCorruptedGraph": "Cannot load. Graph seems to be corrupted.", "nodesLoaded": "Nodes Loaded", + "nodesLoadedFailed": "Failed To Load Nodes", "nodesNotValidGraph": "Not a valid InvokeAI Node Graph", "nodesNotValidJSON": "Not a valid JSON", - "nodesCorruptedGraph": "Cannot load. Graph seems to be corrupted.", + "nodesSaved": "Nodes Saved", "nodesUnrecognizedTypes": "Cannot load. Graph has unrecognized types", - "nodesBrokenConnections": "Cannot load. Some connections are broken.", - "nodesLoadedFailed": "Failed To Load Nodes", - "nodesCleared": "Nodes Cleared" + "parameterNotSet": "Parameter not set", + "parameterSet": "Parameter set", + "parametersFailed": "Problem loading parameters", + "parametersFailedDesc": "Unable to load init image.", + "parametersNotSet": "Parameters Not Set", + "parametersNotSetDesc": "No metadata found for this image.", + "parametersSet": "Parameters Set", + "problemCopyingImage": "Unable to Copy Image", + "problemCopyingImageLink": "Unable to Copy Image Link", + "promptNotSet": "Prompt Not Set", + "promptNotSetDesc": "Could not find prompt for this image.", + "promptSet": "Prompt Set", + "seedNotSet": "Seed Not Set", + "seedNotSetDesc": "Could not find seed for this image.", + "seedSet": "Seed Set", + "sentToImageToImage": "Sent To Image To Image", + "sentToUnifiedCanvas": "Sent to Unified Canvas", + "serverError": "Server Error", + "tempFoldersEmptied": "Temp Folder Emptied", + "uploadFailed": "Upload failed", + "uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image", + "uploadFailedUnableToLoadDesc": "Unable to load file", + "upscalingFailed": "Upscaling Failed" }, "tooltip": { "feature": { - "prompt": "This is the prompt field. Prompt includes generation objects and stylistic terms. You can add weight (token importance) in the prompt as well, but CLI commands and parameters will not work.", - "gallery": "Gallery displays generations from the outputs folder as they're created. Settings are stored within files and accesed by context menu.", - "other": "These options will enable alternative processing modes for Invoke. 'Seamless tiling' will create repeating patterns in the output. 'High resolution' is generation in two steps with img2img: use this setting when you want a larger and more coherent image without artifacts. It will take longer than usual txt2img.", - "seed": "Seed value affects the initial noise from which the image is formed. You can use the already existing seeds from previous images. 'Noise Threshold' is used to mitigate artifacts at high CFG values (try the 0-10 range), and Perlin to add Perlin noise during generation: both serve to add variation to your outputs.", - "variations": "Try a variation with a value between 0.1 and 1.0 to change the result for a given seed. Interesting variations of the seed are between 0.1 and 0.3.", - "upscale": "Use ESRGAN to enlarge the image immediately after generation.", - "faceCorrection": "Face correction with GFPGAN or Codeformer: the algorithm detects faces in the image and corrects any defects. High value will change the image more, resulting in more attractive faces. Codeformer with a higher fidelity preserves the original image at the expense of stronger face correction.", - "imageToImage": "Image to Image loads any image as initial, which is then used to generate a new one along with the prompt. The higher the value, the more the result image will change. Values from 0.0 to 1.0 are possible, the recommended range is .25-.75", "boundingBox": "The bounding box is the same as the Width and Height settings for Text to Image or Image to Image. Only the area in the box will be processed.", + "faceCorrection": "Face correction with GFPGAN or Codeformer: the algorithm detects faces in the image and corrects any defects. High value will change the image more, resulting in more attractive faces. Codeformer with a higher fidelity preserves the original image at the expense of stronger face correction.", + "gallery": "Gallery displays generations from the outputs folder as they're created. Settings are stored within files and accesed by context menu.", + "imageToImage": "Image to Image loads any image as initial, which is then used to generate a new one along with the prompt. The higher the value, the more the result image will change. Values from 0.0 to 1.0 are possible, the recommended range is .25-.75", + "infillAndScaling": "Manage infill methods (used on masked or erased areas of the canvas) and scaling (useful for small bounding box sizes).", + "other": "These options will enable alternative processing modes for Invoke. 'Seamless tiling' will create repeating patterns in the output. 'High resolution' is generation in two steps with img2img: use this setting when you want a larger and more coherent image without artifacts. It will take longer than usual txt2img.", + "prompt": "This is the prompt field. Prompt includes generation objects and stylistic terms. You can add weight (token importance) in the prompt as well, but CLI commands and parameters will not work.", "seamCorrection": "Controls the handling of visible seams that occur between generated images on the canvas.", - "infillAndScaling": "Manage infill methods (used on masked or erased areas of the canvas) and scaling (useful for small bounding box sizes)." + "seed": "Seed value affects the initial noise from which the image is formed. You can use the already existing seeds from previous images. 'Noise Threshold' is used to mitigate artifacts at high CFG values (try the 0-10 range), and Perlin to add Perlin noise during generation: both serve to add variation to your outputs.", + "upscale": "Use ESRGAN to enlarge the image immediately after generation.", + "variations": "Try a variation with a value between 0.1 and 1.0 to change the result for a given seed. Interesting variations of the seed are between 0.1 and 0.3." } }, + "ui": { + "hideProgressImages": "Hide Progress Images", + "lockRatio": "Lock Ratio", + "showProgressImages": "Show Progress Images", + "swapSizes": "Swap Sizes" + }, "unifiedCanvas": { - "layer": "Layer", - "base": "Base", - "mask": "Mask", - "maskingOptions": "Masking Options", - "enableMask": "Enable Mask", - "preserveMaskedArea": "Preserve Masked Area", - "clearMask": "Clear Mask", - "brush": "Brush", - "eraser": "Eraser", - "fillBoundingBox": "Fill Bounding Box", - "eraseBoundingBox": "Erase Bounding Box", - "colorPicker": "Color Picker", - "brushOptions": "Brush Options", - "brushSize": "Size", - "move": "Move", - "resetView": "Reset View", - "mergeVisible": "Merge Visible", - "saveToGallery": "Save To Gallery", - "copyToClipboard": "Copy to Clipboard", - "downloadAsImage": "Download As Image", - "undo": "Undo", - "redo": "Redo", - "clearCanvas": "Clear Canvas", - "canvasSettings": "Canvas Settings", - "showIntermediates": "Show Intermediates", - "showGrid": "Show Grid", - "snapToGrid": "Snap to Grid", - "darkenOutsideSelection": "Darken Outside Selection", - "autoSaveToGallery": "Auto Save to Gallery", - "saveBoxRegionOnly": "Save Box Region Only", - "limitStrokesToBox": "Limit Strokes to Box", - "showCanvasDebugInfo": "Show Additional Canvas Info", - "clearCanvasHistory": "Clear Canvas History", - "clearHistory": "Clear History", - "clearCanvasHistoryMessage": "Clearing the canvas history leaves your current canvas intact, but irreversibly clears the undo and redo history.", - "clearCanvasHistoryConfirm": "Are you sure you want to clear the canvas history?", - "emptyTempImageFolder": "Empty Temp Image Folder", - "emptyFolder": "Empty Folder", - "emptyTempImagesFolderMessage": "Emptying the temp image folder also fully resets the Unified Canvas. This includes all undo/redo history, images in the staging area, and the canvas base layer.", - "emptyTempImagesFolderConfirm": "Are you sure you want to empty the temp folder?", - "activeLayer": "Active Layer", - "canvasScale": "Canvas Scale", - "boundingBox": "Bounding Box", - "scaledBoundingBox": "Scaled Bounding Box", - "boundingBoxPosition": "Bounding Box Position", - "canvasDimensions": "Canvas Dimensions", - "canvasPosition": "Canvas Position", - "cursorPosition": "Cursor Position", - "previous": "Previous", - "next": "Next", "accept": "Accept", - "showHide": "Show/Hide", - "discardAll": "Discard All", + "activeLayer": "Active Layer", + "antialiasing": "Antialiasing", + "autoSaveToGallery": "Auto Save to Gallery", + "base": "Base", "betaClear": "Clear", "betaDarkenOutside": "Darken Outside", "betaLimitToBox": "Limit To Box", "betaPreserveMasked": "Preserve Masked", - "antialiasing": "Antialiasing" - }, - "ui": { - "showProgressImages": "Show Progress Images", - "hideProgressImages": "Hide Progress Images", - "swapSizes": "Swap Sizes", - "lockRatio": "Lock Ratio" - }, - "nodes": { - "reloadNodeTemplates": "Reload Node Templates", - "downloadWorkflow": "Download Workflow JSON", - "loadWorkflow": "Load Workflow", - "resetWorkflow": "Reset Workflow", - "resetWorkflowDesc": "Are you sure you want to reset this workflow?", - "resetWorkflowDesc2": "Resetting the workflow will clear all nodes, edges and workflow details.", - "zoomInNodes": "Zoom In", - "zoomOutNodes": "Zoom Out", - "fitViewportNodes": "Fit View", - "hideGraphNodes": "Hide Graph Overlay", - "showGraphNodes": "Show Graph Overlay", - "hideLegendNodes": "Hide Field Type Legend", - "showLegendNodes": "Show Field Type Legend", - "hideMinimapnodes": "Hide MiniMap", - "showMinimapnodes": "Show MiniMap" + "boundingBox": "Bounding Box", + "boundingBoxPosition": "Bounding Box Position", + "brush": "Brush", + "brushOptions": "Brush Options", + "brushSize": "Size", + "canvasDimensions": "Canvas Dimensions", + "canvasPosition": "Canvas Position", + "canvasScale": "Canvas Scale", + "canvasSettings": "Canvas Settings", + "clearCanvas": "Clear Canvas", + "clearCanvasHistory": "Clear Canvas History", + "clearCanvasHistoryConfirm": "Are you sure you want to clear the canvas history?", + "clearCanvasHistoryMessage": "Clearing the canvas history leaves your current canvas intact, but irreversibly clears the undo and redo history.", + "clearHistory": "Clear History", + "clearMask": "Clear Mask", + "colorPicker": "Color Picker", + "copyToClipboard": "Copy to Clipboard", + "cursorPosition": "Cursor Position", + "darkenOutsideSelection": "Darken Outside Selection", + "discardAll": "Discard All", + "downloadAsImage": "Download As Image", + "emptyFolder": "Empty Folder", + "emptyTempImageFolder": "Empty Temp Image Folder", + "emptyTempImagesFolderConfirm": "Are you sure you want to empty the temp folder?", + "emptyTempImagesFolderMessage": "Emptying the temp image folder also fully resets the Unified Canvas. This includes all undo/redo history, images in the staging area, and the canvas base layer.", + "enableMask": "Enable Mask", + "eraseBoundingBox": "Erase Bounding Box", + "eraser": "Eraser", + "fillBoundingBox": "Fill Bounding Box", + "layer": "Layer", + "limitStrokesToBox": "Limit Strokes to Box", + "mask": "Mask", + "maskingOptions": "Masking Options", + "mergeVisible": "Merge Visible", + "move": "Move", + "next": "Next", + "preserveMaskedArea": "Preserve Masked Area", + "previous": "Previous", + "redo": "Redo", + "resetView": "Reset View", + "saveBoxRegionOnly": "Save Box Region Only", + "saveToGallery": "Save To Gallery", + "scaledBoundingBox": "Scaled Bounding Box", + "showCanvasDebugInfo": "Show Additional Canvas Info", + "showGrid": "Show Grid", + "showHide": "Show/Hide", + "showIntermediates": "Show Intermediates", + "snapToGrid": "Snap to Grid", + "undo": "Undo" } } diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts index 8eaabeeedf..dd21afe459 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts @@ -6,6 +6,7 @@ import { isInvocationNode } from 'features/nodes/types/types'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { forEach, map } from 'lodash-es'; import { getConnectedEdges } from 'reactflow'; +import i18n from 'i18next'; const selector = createSelector( [stateSelector, activeTabNameSelector], @@ -19,22 +20,22 @@ const selector = createSelector( // Cannot generate if already processing an image if (isProcessing) { - reasons.push('System busy'); + reasons.push(i18n.t('parameters.invoke.systemBusy')); } // Cannot generate if not connected if (!isConnected) { - reasons.push('System disconnected'); + reasons.push(i18n.t('parameters.invoke.systemDisconnected')); } if (activeTabName === 'img2img' && !initialImage) { - reasons.push('No initial image selected'); + reasons.push(i18n.t('parameters.invoke.noInitialImageSelected')); } if (activeTabName === 'nodes') { if (nodes.shouldValidateGraph) { if (!nodes.nodes.length) { - reasons.push('No nodes in graph'); + reasons.push(i18n.t('parameters.invoke.noNodesInGraph')); } nodes.nodes.forEach((node) => { @@ -46,7 +47,7 @@ const selector = createSelector( if (!nodeTemplate) { // Node type not found - reasons.push('Missing node template'); + reasons.push(i18n.t('parameters.invoke.missingNodeTemplate')); return; } @@ -60,7 +61,7 @@ const selector = createSelector( ); if (!fieldTemplate) { - reasons.push('Missing field template'); + reasons.push(i18n.t('parameters.invoke.missingFieldTemplate')); return; } @@ -70,9 +71,10 @@ const selector = createSelector( !hasConnection ) { reasons.push( - `${node.data.label || nodeTemplate.title} -> ${ - field.label || fieldTemplate.title - } missing input` + i18n.t('parameters.invoke.missingInputForField', { + nodeLabel: node.data.label || nodeTemplate.title, + fieldLabel: field.label || fieldTemplate.title, + }) ); return; } @@ -81,7 +83,7 @@ const selector = createSelector( } } else { if (!model) { - reasons.push('No model selected'); + reasons.push(i18n.t('parameters.invoke.noModelSelected')); } if (state.controlNet.isEnabled) { @@ -90,7 +92,9 @@ const selector = createSelector( return; } if (!controlNet.model) { - reasons.push(`ControlNet ${i + 1} has no model selected.`); + reasons.push( + i18n.t('parameters.invoke.noModelForControlNet', { index: i + 1 }) + ); } if ( @@ -98,7 +102,11 @@ const selector = createSelector( (!controlNet.processedControlImage && controlNet.processorType !== 'none') ) { - reasons.push(`ControlNet ${i + 1} has no control image`); + reasons.push( + i18n.t('parameters.invoke.noControlImageForControlNet', { + index: i + 1, + }) + ); } }); } diff --git a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx index 2443fa6081..6bdf434d52 100644 --- a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx +++ b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx @@ -21,6 +21,7 @@ import { useRemoveImagesFromBoardMutation, } from 'services/api/endpoints/images'; import { changeBoardReset, isModalOpenChanged } from '../store/slice'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( [stateSelector], @@ -42,10 +43,11 @@ const ChangeBoardModal = () => { const { imagesToChange, isModalOpen } = useAppSelector(selector); const [addImagesToBoard] = useAddImagesToBoardMutation(); const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation(); + const { t } = useTranslation(); const data = useMemo(() => { const data: { label: string; value: string }[] = [ - { label: 'Uncategorized', value: 'none' }, + { label: t('boards.uncategorized'), value: 'none' }, ]; (boards ?? []).forEach((board) => data.push({ @@ -55,7 +57,7 @@ const ChangeBoardModal = () => { ); return data; - }, [boards]); + }, [boards, t]); const handleClose = useCallback(() => { dispatch(changeBoardReset()); @@ -97,7 +99,7 @@ const ChangeBoardModal = () => { - Change Board + {t('boards.changeBoard')} @@ -107,7 +109,9 @@ const ChangeBoardModal = () => { {`${imagesToChange.length > 1 ? 's' : ''}`} to board: setSelectedBoard(v)} value={selectedBoard} @@ -117,10 +121,10 @@ const ChangeBoardModal = () => { - Cancel + {t('boards.cancel')} - Move + {t('boards.move')} diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx index 1f70542494..d40a234495 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx @@ -28,6 +28,7 @@ import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd'; import ParamControlNetControlMode from './parameters/ParamControlNetControlMode'; import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect'; import ParamControlNetResizeMode from './parameters/ParamControlNetResizeMode'; +import { useTranslation } from 'react-i18next'; type ControlNetProps = { controlNet: ControlNetConfig; @@ -37,6 +38,7 @@ const ControlNet = (props: ControlNetProps) => { const { controlNet } = props; const { controlNetId } = controlNet; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const activeTabName = useAppSelector(activeTabNameSelector); @@ -95,8 +97,8 @@ const ControlNet = (props: ControlNetProps) => { > @@ -117,23 +119,31 @@ const ControlNet = (props: ControlNetProps) => { )} } /> } /> { } = controlNet; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const { pendingControlImages, autoAddBoardId } = useAppSelector(selector); const activeTabName = useAppSelector(activeTabNameSelector); @@ -208,18 +210,18 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => { : undefined} - tooltip="Reset Control Image" + tooltip={t('controlnet.resetControlImage')} /> : undefined} - tooltip="Save Control Image" + tooltip={t('controlnet.saveControlImage')} styleOverrides={{ marginTop: 6 }} /> : undefined} - tooltip="Set Control Image Dimensions To W/H" + tooltip={t('controlnet.setControlImageDimensions')} styleOverrides={{ marginTop: 12 }} /> diff --git a/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx b/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx index 0e044d4575..76f1cb9dfe 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ParamControlNetShouldAutoConfig.tsx @@ -6,6 +6,7 @@ import { } from 'features/controlNet/store/controlNetSlice'; import { selectIsBusy } from 'features/system/store/systemSelectors'; import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; type Props = { controlNet: ControlNetConfig; @@ -15,6 +16,7 @@ const ParamControlNetShouldAutoConfig = (props: Props) => { const { controlNetId, isEnabled, shouldAutoConfig } = props.controlNet; const dispatch = useAppDispatch(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleShouldAutoConfigChanged = useCallback(() => { dispatch(controlNetAutoConfigToggled({ controlNetId })); @@ -22,8 +24,8 @@ const ParamControlNetShouldAutoConfig = (props: Props) => { return ( { const { controlNet } = props; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleImportImageFromCanvas = useCallback(() => { dispatch(canvasImageToControlNet({ controlNet })); @@ -36,15 +38,15 @@ const ControlNetCanvasImageImports = ( } - tooltip="Import Image From Canvas" - aria-label="Import Image From Canvas" + tooltip={t('controlnet.importImageFromCanvas')} + aria-label={t('controlnet.importImageFromCanvas')} onClick={handleImportImageFromCanvas} /> } - tooltip="Import Mask From Canvas" - aria-label="Import Mask From Canvas" + tooltip={t('controlnet.importMaskFromCanvas')} + aria-label={t('controlnet.importMaskFromCanvas')} onClick={handleImportMaskFromCanvas} /> diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx index 1219239e5d..f34c863cff 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetBeginEnd.tsx @@ -16,6 +16,7 @@ import { controlNetEndStepPctChanged, } from 'features/controlNet/store/controlNetSlice'; import { memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; type Props = { controlNet: ControlNetConfig; @@ -27,6 +28,7 @@ const ParamControlNetBeginEnd = (props: Props) => { const { beginStepPct, endStepPct, isEnabled, controlNetId } = props.controlNet; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleStepPctChanged = useCallback( (v: number[]) => { @@ -48,10 +50,10 @@ const ParamControlNetBeginEnd = (props: Props) => { return ( - Begin / End Step Percentage + {t('controlnet.beginEndStepPercent')} { @@ -34,7 +36,7 @@ export default function ParamControlNetControlMode( return ( { const isBusy = useAppSelector(selectIsBusy); const { mainModel } = useAppSelector(selector); + const { t } = useTranslation(); const { data: controlNetModels } = useGetControlNetModelsQuery(); @@ -58,13 +60,13 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => { group: MODEL_TYPE_MAP[model.base_model], disabled, tooltip: disabled - ? `Incompatible base model: ${model.base_model}` + ? `${t('controlnet.incompatibleBaseModel')} ${model.base_model}` : undefined, }); }); return data; - }, [controlNetModels, mainModel?.base_model]); + }, [controlNetModels, mainModel?.base_model, t]); // grab the full model entity from the RTK Query cache const selectedModel = useMemo( @@ -105,7 +107,7 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => { error={ !selectedModel || mainModel?.base_model !== selectedModel.base_model } - placeholder="Select a model" + placeholder={t('controlnet.selectModel')} value={selectedModel?.id ?? null} onChange={handleModelChanged} disabled={isBusy || !isEnabled} diff --git a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx index 190b1bc012..a357547403 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx @@ -15,6 +15,7 @@ import { controlNetProcessorTypeChanged, } from '../../store/controlNetSlice'; import { ControlNetProcessorType } from '../../store/types'; +import { useTranslation } from 'react-i18next'; type ParamControlNetProcessorSelectProps = { controlNet: ControlNetConfig; @@ -57,6 +58,7 @@ const ParamControlNetProcessorSelect = ( const { controlNetId, isEnabled, processorNode } = props.controlNet; const isBusy = useAppSelector(selectIsBusy); const controlNetProcessors = useAppSelector(selector); + const { t } = useTranslation(); const handleProcessorTypeChanged = useCallback( (v: string | null) => { @@ -72,7 +74,7 @@ const ParamControlNetProcessorSelect = ( return ( { @@ -33,7 +35,7 @@ export default function ParamControlNetResizeMode( return ( { const { weight, isEnabled, controlNetId } = props.controlNet; const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleWeightChanged = useCallback( (weight: number) => { dispatch(controlNetWeightChanged({ controlNetId, weight })); @@ -23,7 +25,7 @@ const ParamControlNetWeight = (props: ParamControlNetWeightProps) => { return ( { const { low_threshold, high_threshold } = processorNode; const isBusy = useAppSelector(selectIsBusy); const processorChanged = useProcessorNodeChanged(); + const { t } = useTranslation(); const handleLowThresholdChanged = useCallback( (v: number) => { @@ -52,7 +54,7 @@ const CannyProcessor = (props: CannyProcessorProps) => { { /> { const { image_resolution, detect_resolution, w, h, f } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -90,7 +92,7 @@ const ContentShuffleProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { } = props; const isBusy = useAppSelector(selectIsBusy); const processorChanged = useProcessorNodeChanged(); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -62,7 +64,7 @@ const HedPreprocessor = (props: HedProcessorProps) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -51,7 +53,7 @@ const LineartAnimeProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution, coarse } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -59,7 +61,7 @@ const LineartProcessor = (props: LineartProcessorProps) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { const { max_faces, min_confidence } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleMaxFacesChanged = useCallback( (v: number) => { @@ -47,7 +49,7 @@ const MediapipeFaceProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { const { a_mult, bg_th } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleAMultChanged = useCallback( (v: number) => { @@ -47,7 +49,7 @@ const MidasDepthProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -73,7 +75,7 @@ const MlsdImageProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -51,7 +53,7 @@ const NormalBaeProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution, hand_and_face } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -59,7 +61,7 @@ const OpenposeProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> { const { image_resolution, detect_resolution, scribble, safe } = processorNode; const processorChanged = useProcessorNodeChanged(); const isBusy = useAppSelector(selectIsBusy); + const { t } = useTranslation(); const handleDetectResolutionChanged = useCallback( (v: number) => { @@ -66,7 +68,7 @@ const PidiProcessor = (props: Props) => { return ( { isDisabled={isBusy || !isEnabled} /> { isDisabled={isBusy || !isEnabled} /> ; - /** * A dict of ControlNet processors, including: * - type @@ -25,16 +25,24 @@ type ControlNetProcessorsDict = Record< export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { none: { type: 'none', - label: 'none', - description: '', + get label() { + return i18n.t('controlnet.none'); + }, + get description() { + return i18n.t('controlnet.noneDescription'); + }, default: { type: 'none', }, }, canny_image_processor: { type: 'canny_image_processor', - label: 'Canny', - description: '', + get label() { + return i18n.t('controlnet.canny'); + }, + get description() { + return i18n.t('controlnet.cannyDescription'); + }, default: { id: 'canny_image_processor', type: 'canny_image_processor', @@ -44,8 +52,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, content_shuffle_image_processor: { type: 'content_shuffle_image_processor', - label: 'Content Shuffle', - description: '', + get label() { + return i18n.t('controlnet.contentShuffle'); + }, + get description() { + return i18n.t('controlnet.contentShuffleDescription'); + }, default: { id: 'content_shuffle_image_processor', type: 'content_shuffle_image_processor', @@ -58,8 +70,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, hed_image_processor: { type: 'hed_image_processor', - label: 'HED', - description: '', + get label() { + return i18n.t('controlnet.hed'); + }, + get description() { + return i18n.t('controlnet.hedDescription'); + }, default: { id: 'hed_image_processor', type: 'hed_image_processor', @@ -70,8 +86,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, lineart_anime_image_processor: { type: 'lineart_anime_image_processor', - label: 'Lineart Anime', - description: '', + get label() { + return i18n.t('controlnet.lineartAnime'); + }, + get description() { + return i18n.t('controlnet.lineartAnimeDescription'); + }, default: { id: 'lineart_anime_image_processor', type: 'lineart_anime_image_processor', @@ -81,8 +101,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, lineart_image_processor: { type: 'lineart_image_processor', - label: 'Lineart', - description: '', + get label() { + return i18n.t('controlnet.lineart'); + }, + get description() { + return i18n.t('controlnet.lineartDescription'); + }, default: { id: 'lineart_image_processor', type: 'lineart_image_processor', @@ -93,8 +117,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, mediapipe_face_processor: { type: 'mediapipe_face_processor', - label: 'Mediapipe Face', - description: '', + get label() { + return i18n.t('controlnet.mediapipeFace'); + }, + get description() { + return i18n.t('controlnet.mediapipeFaceDescription'); + }, default: { id: 'mediapipe_face_processor', type: 'mediapipe_face_processor', @@ -104,8 +132,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, midas_depth_image_processor: { type: 'midas_depth_image_processor', - label: 'Depth (Midas)', - description: '', + get label() { + return i18n.t('controlnet.depthMidas'); + }, + get description() { + return i18n.t('controlnet.depthMidasDescription'); + }, default: { id: 'midas_depth_image_processor', type: 'midas_depth_image_processor', @@ -115,8 +147,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, mlsd_image_processor: { type: 'mlsd_image_processor', - label: 'M-LSD', - description: '', + get label() { + return i18n.t('controlnet.mlsd'); + }, + get description() { + return i18n.t('controlnet.mlsdDescription'); + }, default: { id: 'mlsd_image_processor', type: 'mlsd_image_processor', @@ -128,8 +164,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, normalbae_image_processor: { type: 'normalbae_image_processor', - label: 'Normal BAE', - description: '', + get label() { + return i18n.t('controlnet.normalBae'); + }, + get description() { + return i18n.t('controlnet.normalBaeDescription'); + }, default: { id: 'normalbae_image_processor', type: 'normalbae_image_processor', @@ -139,8 +179,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, openpose_image_processor: { type: 'openpose_image_processor', - label: 'Openpose', - description: '', + get label() { + return i18n.t('controlnet.openPose'); + }, + get description() { + return i18n.t('controlnet.openPoseDescription'); + }, default: { id: 'openpose_image_processor', type: 'openpose_image_processor', @@ -151,8 +195,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, pidi_image_processor: { type: 'pidi_image_processor', - label: 'PIDI', - description: '', + get label() { + return i18n.t('controlnet.pidi'); + }, + get description() { + return i18n.t('controlnet.pidiDescription'); + }, default: { id: 'pidi_image_processor', type: 'pidi_image_processor', @@ -164,8 +212,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { }, zoe_depth_image_processor: { type: 'zoe_depth_image_processor', - label: 'Depth (Zoe)', - description: '', + get label() { + return i18n.t('controlnet.depthZoe'); + }, + get description() { + return i18n.t('controlnet.depthZoeDescription'); + }, default: { id: 'zoe_depth_image_processor', type: 'zoe_depth_image_processor', @@ -186,4 +238,6 @@ export const CONTROLNET_MODEL_DEFAULT_PROCESSORS: { shuffle: 'content_shuffle_image_processor', openpose: 'openpose_image_processor', mediapipe: 'mediapipe_face_processor', + pidi: 'pidi_image_processor', + zoe: 'zoe_depth_image_processor', }; diff --git a/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx b/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx index 3ed7f3f05f..de1782b439 100644 --- a/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx +++ b/invokeai/frontend/web/src/features/deleteImageModal/components/ImageUsageMessage.tsx @@ -2,16 +2,19 @@ import { ListItem, Text, UnorderedList } from '@chakra-ui/react'; import { some } from 'lodash-es'; import { memo } from 'react'; import { ImageUsage } from '../store/types'; +import { useTranslation } from 'react-i18next'; + type Props = { imageUsage?: ImageUsage; topMessage?: string; bottomMessage?: string; }; const ImageUsageMessage = (props: Props) => { + const { t } = useTranslation(); const { imageUsage, - topMessage = 'This image is currently in use in the following features:', - bottomMessage = 'If you delete this image, those features will immediately be reset.', + topMessage = t('gallery.currentlyInUse'), + bottomMessage = t('gallery.featuresWillReset'), } = props; if (!imageUsage) { @@ -26,10 +29,18 @@ const ImageUsageMessage = (props: Props) => { <> {topMessage} - {imageUsage.isInitialImage && Image to Image} - {imageUsage.isCanvasImage && Unified Canvas} - {imageUsage.isControlNetImage && ControlNet} - {imageUsage.isNodesImage && Node Editor} + {imageUsage.isInitialImage && ( + {t('common.img2img')} + )} + {imageUsage.isCanvasImage && ( + {t('common.unifiedCanvas')} + )} + {imageUsage.isControlNetImage && ( + {t('common.controlNet')} + )} + {imageUsage.isNodesImage && ( + {t('common.nodeEditor')} + )} {bottomMessage} diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx index 70aadcd4ce..b9fd655a43 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCollapse.tsx @@ -9,6 +9,7 @@ import { useFeatureStatus } from '../../system/hooks/useFeatureStatus'; import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial'; import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled'; import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -22,6 +23,7 @@ const selector = createSelector( const ParamDynamicPromptsCollapse = () => { const { activeLabel } = useAppSelector(selector); + const { t } = useTranslation(); const isDynamicPromptingEnabled = useFeatureStatus('dynamicPrompting').isFeatureEnabled; @@ -31,7 +33,7 @@ const ParamDynamicPromptsCollapse = () => { } return ( - + diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx index c028a5d55c..406dc8e216 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsCombinatorial.tsx @@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; import { memo, useCallback } from 'react'; import { combinatorialToggled } from '../store/dynamicPromptsSlice'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -19,6 +20,7 @@ const selector = createSelector( const ParamDynamicPromptsCombinatorial = () => { const { combinatorial, isDisabled } = useAppSelector(selector); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleChange = useCallback(() => { dispatch(combinatorialToggled()); @@ -27,7 +29,7 @@ const ParamDynamicPromptsCombinatorial = () => { return ( diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx index 1b31147937..a1d16b5361 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsEnabled.tsx @@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; import { memo, useCallback } from 'react'; import { isEnabledToggled } from '../store/dynamicPromptsSlice'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -19,6 +20,7 @@ const selector = createSelector( const ParamDynamicPromptsToggle = () => { const dispatch = useAppDispatch(); const { isEnabled } = useAppSelector(selector); + const { t } = useTranslation(); const handleToggleIsEnabled = useCallback(() => { dispatch(isEnabledToggled()); @@ -26,7 +28,7 @@ const ParamDynamicPromptsToggle = () => { return ( diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx index f374f1cb15..158fab91d9 100644 --- a/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx +++ b/invokeai/frontend/web/src/features/dynamicPrompts/components/ParamDynamicPromptsMaxPrompts.tsx @@ -8,6 +8,7 @@ import { maxPromptsChanged, maxPromptsReset, } from '../store/dynamicPromptsSlice'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -31,6 +32,7 @@ const ParamDynamicPromptsMaxPrompts = () => { const { maxPrompts, min, sliderMax, inputMax, isDisabled } = useAppSelector(selector); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleChange = useCallback( (v: number) => { @@ -45,7 +47,7 @@ const ParamDynamicPromptsMaxPrompts = () => { return ( void; @@ -8,11 +9,12 @@ type Props = { const AddEmbeddingButton = (props: Props) => { const { onClick } = props; + const { t } = useTranslation(); return ( } sx={{ p: 2, diff --git a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx index 93daaf946f..164fd01a1f 100644 --- a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx +++ b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx @@ -16,6 +16,7 @@ import { forEach } from 'lodash-es'; import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react'; import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; +import { useTranslation } from 'react-i18next'; type Props = PropsWithChildren & { onSelect: (v: string) => void; @@ -27,6 +28,7 @@ const ParamEmbeddingPopover = (props: Props) => { const { onSelect, isOpen, onClose, children } = props; const { data: embeddingQueryData } = useGetTextualInversionModelsQuery(); const inputRef = useRef(null); + const { t } = useTranslation(); const currentMainModel = useAppSelector( (state: RootState) => state.generation.model @@ -52,7 +54,7 @@ const ParamEmbeddingPopover = (props: Props) => { group: MODEL_TYPE_MAP[embedding.base_model], disabled, tooltip: disabled - ? `Incompatible base model: ${embedding.base_model}` + ? `${t('embedding.incompatibleModel')} ${embedding.base_model}` : undefined, }); }); @@ -63,7 +65,7 @@ const ParamEmbeddingPopover = (props: Props) => { ); return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1)); - }, [embeddingQueryData, currentMainModel?.base_model]); + }, [embeddingQueryData, currentMainModel?.base_model, t]); const handleChange = useCallback( (v: string | null) => { @@ -118,10 +120,10 @@ const ParamEmbeddingPopover = (props: Props) => { { const dispatch = useAppDispatch(); + const { t } = useTranslation(); const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } = useAppSelector(selector); const inputRef = useRef(null); @@ -63,13 +65,13 @@ const BoardAutoAddSelect = () => { return ( diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx index c711ad892b..e5eb92028d 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx @@ -16,6 +16,7 @@ import { menuListMotionProps } from 'theme/components/menu'; import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems'; import NoBoardContextMenuItems from './NoBoardContextMenuItems'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { useTranslation } from 'react-i18next'; type Props = { board?: BoardDTO; @@ -59,6 +60,8 @@ const BoardContextMenu = ({ e.preventDefault(); }, []); + const { t } = useTranslation(); + return ( menuProps={{ size: 'sm', isLazy: true }} @@ -78,7 +81,7 @@ const BoardContextMenu = ({ isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick} onClick={handleSetAutoAdd} > - Auto-add to this Board + {t('boards.menuItemAutoAdd')} {!board && } {board && ( diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx index ebd08e94d5..96739f4c84 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/AddBoardButton.tsx @@ -2,22 +2,22 @@ import IAIIconButton from 'common/components/IAIIconButton'; import { memo, useCallback } from 'react'; import { FaPlus } from 'react-icons/fa'; import { useCreateBoardMutation } from 'services/api/endpoints/boards'; - -const DEFAULT_BOARD_NAME = 'My Board'; +import { useTranslation } from 'react-i18next'; const AddBoardButton = () => { + const { t } = useTranslation(); const [createBoard, { isLoading }] = useCreateBoardMutation(); - + const DEFAULT_BOARD_NAME = t('boards.myBoard'); const handleCreateBoard = useCallback(() => { createBoard(DEFAULT_BOARD_NAME); - }, [createBoard]); + }, [createBoard, DEFAULT_BOARD_NAME]); return ( } isLoading={isLoading} - tooltip="Add Board" - aria-label="Add Board" + tooltip={t('boards.addBoard')} + aria-label={t('boards.addBoard')} onClick={handleCreateBoard} size="sm" /> diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx index d7db96a938..2d2a85597c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/BoardsSearch.tsx @@ -18,6 +18,7 @@ import { useEffect, useRef, } from 'react'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( [stateSelector], @@ -32,6 +33,7 @@ const BoardsSearch = () => { const dispatch = useAppDispatch(); const { boardSearchText } = useAppSelector(selector); const inputRef = useRef(null); + const { t } = useTranslation(); const handleBoardSearch = useCallback( (searchTerm: string) => { @@ -73,7 +75,7 @@ const BoardsSearch = () => { { onClick={clearBoardSearch} size="xs" variant="ghost" - aria-label="Clear Search" + aria-label={t('boards.clearSearch')} opacity={0.5} icon={} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx index f09cf131e2..2c54f06cec 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/DeleteBoardModal.tsx @@ -132,8 +132,8 @@ const DeleteBoardModal = (props: Props) => { ) : ( )} Deleted boards cannot be restored. diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx index 2576c8e9e3..b16820a38f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImagePreview.tsx @@ -19,6 +19,7 @@ import { FaImage } from 'react-icons/fa'; import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import ImageMetadataViewer from '../ImageMetadataViewer/ImageMetadataViewer'; import NextPrevImageButtons from '../NextPrevImageButtons'; +import { useTranslation } from 'react-i18next'; export const imagesSelector = createSelector( [stateSelector, selectLastSelectedImage], @@ -117,6 +118,8 @@ const CurrentImagePreview = () => { const timeoutId = useRef(0); + const { t } = useTranslation(); + const handleMouseOver = useCallback(() => { setShouldShowNextPrevButtons(true); window.clearTimeout(timeoutId.current); @@ -164,7 +167,7 @@ const CurrentImagePreview = () => { isUploadDisabled={true} fitContainer useThumbailFallback - dropLabel="Set as Current Image" + dropLabel={t('gallery.setCurrentImage')} noContentFallback={ } diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx index 35d6cc3361..fe9d891b23 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx @@ -18,6 +18,7 @@ import { useUnstarImagesMutation, } from 'services/api/endpoints/images'; import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon'; +import { useTranslation } from 'react-i18next'; interface HoverableImageProps { imageName: string; @@ -28,6 +29,7 @@ const GalleryImage = (props: HoverableImageProps) => { const { imageName } = props; const { currentData: imageDTO } = useGetImageDTOQuery(imageName); const shift = useAppSelector((state) => state.hotkeys.shift); + const { t } = useTranslation(); const { handleClick, isSelected, selection, selectionCount } = useMultiselect(imageDTO); @@ -136,7 +138,7 @@ const GalleryImage = (props: HoverableImageProps) => { } - tooltip="Delete" + tooltip={t('gallery.deleteImage')} styleOverrides={{ bottom: 2, top: 'auto', diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx index bacd5c38ad..dc41a2ef2a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx @@ -95,7 +95,7 @@ const GalleryImageGrid = () => { justifyContent: 'center', }} > - + ); } @@ -140,7 +140,7 @@ const GalleryImageGrid = () => { onClick={handleLoadMoreImages} isDisabled={!areMoreAvailable} isLoading={isFetching} - loadingText="Loading" + loadingText={t('gallery.loading')} flexShrink={0} > {`Load More (${currentData.ids.length} of ${currentViewTotal})`} @@ -153,7 +153,7 @@ const GalleryImageGrid = () => { return ( diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx index 2267bf15c2..ed7df88e3a 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/DataViewer.tsx @@ -3,6 +3,7 @@ import { isString } from 'lodash-es'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { memo, useCallback, useMemo } from 'react'; import { FaCopy, FaDownload } from 'react-icons/fa'; +import { useTranslation } from 'react-i18next'; type Props = { label: string; @@ -33,6 +34,8 @@ const DataViewer = (props: Props) => { a.remove(); }, [dataString, label, fileName]); + const { t } = useTranslation(); + return ( { {withDownload && ( - + } variant="ghost" opacity={0.7} @@ -84,9 +87,9 @@ const DataViewer = (props: Props) => { )} {withCopy && ( - + } variant="ghost" opacity={0.7} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx index e82b03360e..c1124477e2 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx @@ -2,6 +2,7 @@ import { CoreMetadata } from 'features/nodes/types/types'; import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; import { memo, useCallback } from 'react'; import ImageMetadataItem from './ImageMetadataItem'; +import { useTranslation } from 'react-i18next'; type Props = { metadata?: CoreMetadata; @@ -10,6 +11,8 @@ type Props = { const ImageMetadataActions = (props: Props) => { const { metadata } = props; + const { t } = useTranslation(); + const { recallPositivePrompt, recallNegativePrompt, @@ -70,17 +73,20 @@ const ImageMetadataActions = (props: Props) => { return ( <> {metadata.created_by && ( - + )} {metadata.generation_mode && ( )} {metadata.positive_prompt && ( { )} {metadata.negative_prompt && ( { )} {metadata.seed !== undefined && metadata.seed !== null && ( @@ -105,63 +111,63 @@ const ImageMetadataActions = (props: Props) => { metadata.model !== null && metadata.model.model_name && ( )} {metadata.width && ( )} {metadata.height && ( )} {/* {metadata.threshold !== undefined && ( dispatch(setThreshold(Number(metadata.threshold)))} /> )} {metadata.perlin !== undefined && ( dispatch(setPerlin(Number(metadata.perlin)))} /> )} */} {metadata.scheduler && ( )} {metadata.steps && ( )} {metadata.cfg_scale !== undefined && metadata.cfg_scale !== null && ( )} {/* {metadata.variations && metadata.variations.length > 0 && ( dispatch(setSeamless(metadata.seamless))} /> )} {metadata.hires_fix && ( dispatch(setHiresFix(metadata.hires_fix))} /> @@ -187,7 +193,7 @@ const ImageMetadataActions = (props: Props) => { {/* {init_image_path && ( dispatch(setInitialImage(init_image_path))} @@ -195,14 +201,14 @@ const ImageMetadataActions = (props: Props) => { )} */} {metadata.strength && ( )} {/* {metadata.fit && ( dispatch(setShouldFitToWidthHeight(metadata.fit))} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx index bec5125657..5be0b08700 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataViewer.tsx @@ -17,6 +17,7 @@ import DataViewer from './DataViewer'; import ImageMetadataActions from './ImageMetadataActions'; import { useAppSelector } from '../../../../app/store/storeHooks'; import { configSelector } from '../../../system/store/configSelectors'; +import { useTranslation } from 'react-i18next'; type ImageMetadataViewerProps = { image: ImageDTO; @@ -28,6 +29,7 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { // useHotkeys('esc', () => { // dispatch(setShouldShowImageDetails(false)); // }); + const { t } = useTranslation(); const { shouldFetchMetadataFromApi } = useAppSelector(configSelector); @@ -70,31 +72,31 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => { sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }} > - Metadata - Image Details - Workflow + {t('metadata.metadata')} + {t('metadata.imageDetails')} + {t('metadata.workflow')} {metadata ? ( - + ) : ( - + )} {image ? ( - + ) : ( - + )} {workflow ? ( - + ) : ( - + )} diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx index 4cefdbb20b..3675dd9af9 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx @@ -12,9 +12,11 @@ import TopCenterPanel from './flow/panels/TopCenterPanel/TopCenterPanel'; import TopRightPanel from './flow/panels/TopRightPanel/TopRightPanel'; import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel'; import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel'; +import { useTranslation } from 'react-i18next'; const NodeEditor = () => { const isReady = useAppSelector((state) => state.nodes.isReady); + const { t } = useTranslation(); return ( { }} > diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx index 83f7482177..4433adf4ab 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx @@ -24,6 +24,7 @@ import { HotkeyCallback } from 'react-hotkeys-hook/dist/types'; import 'reactflow/dist/style.css'; import { AnyInvocationType } from 'services/events/types'; import { AddNodePopoverSelectItem } from './AddNodePopoverSelectItem'; +import { useTranslation } from 'react-i18next'; type NodeTemplate = { label: string; @@ -48,43 +49,45 @@ const filter = (value: string, item: NodeTemplate) => { ); }; -const selector = createSelector( - [stateSelector], - ({ nodes }) => { - const data: NodeTemplate[] = map(nodes.nodeTemplates, (template) => { - return { - label: template.title, - value: template.type, - description: template.description, - tags: template.tags, - }; - }); - - data.push({ - label: 'Progress Image', - value: 'current_image', - description: 'Displays the current image in the Node Editor', - tags: ['progress'], - }); - - data.push({ - label: 'Notes', - value: 'notes', - description: 'Add notes about your workflow', - tags: ['notes'], - }); - - data.sort((a, b) => a.label.localeCompare(b.label)); - - return { data }; - }, - defaultSelectorOptions -); - const AddNodePopover = () => { const dispatch = useAppDispatch(); const buildInvocation = useBuildNodeData(); const toaster = useAppToaster(); + const { t } = useTranslation(); + + const selector = createSelector( + [stateSelector], + ({ nodes }) => { + const data: NodeTemplate[] = map(nodes.nodeTemplates, (template) => { + return { + label: template.title, + value: template.type, + description: template.description, + tags: template.tags, + }; + }); + + data.push({ + label: t('nodes.currentImage'), + value: 'current_image', + description: t('nodes.currentImageDescription'), + tags: ['progress'], + }); + + data.push({ + label: t('nodes.notes'), + value: 'notes', + description: t('nodes.notesDescription'), + tags: ['notes'], + }); + + data.sort((a, b) => a.label.localeCompare(b.label)); + + return { data, t }; + }, + defaultSelectorOptions + ); + const { data } = useAppSelector(selector); const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen); const inputRef = useRef(null); @@ -92,18 +95,20 @@ const AddNodePopover = () => { const addNode = useCallback( (nodeType: AnyInvocationType) => { const invocation = buildInvocation(nodeType); - if (!invocation) { + const errorMessage = t('nodes.unknownInvocation', { + nodeType: nodeType, + }); toaster({ status: 'error', - title: `Unknown Invocation type ${nodeType}`, + title: errorMessage, }); return; } dispatch(nodeAdded(invocation)); }, - [dispatch, buildInvocation, toaster] + [dispatch, buildInvocation, toaster, t] ); const handleChange = useCallback( @@ -179,11 +184,11 @@ const AddNodePopover = () => { { const label = useNodeLabel(nodeId); const title = useNodeTemplateTitle(nodeId); const doVersionsMatch = useDoNodeVersionsMatch(nodeId); + const { t } = useTranslation(); return ( <> @@ -65,7 +67,7 @@ const InvocationNodeNotes = ({ nodeId }: Props) => { - {label || title || 'Unknown Node'} + {label || title || t('nodes.unknownNode')} @@ -82,6 +84,7 @@ export default memo(InvocationNodeNotes); const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { const data = useNodeData(nodeId); const nodeTemplate = useNodeTemplate(nodeId); + const { t } = useTranslation(); const title = useMemo(() => { if (data?.label && nodeTemplate?.title) { @@ -96,8 +99,8 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { return nodeTemplate.title; } - return 'Unknown Node'; - }, [data, nodeTemplate]); + return t('nodes.unknownNode'); + }, [data, nodeTemplate, t]); const versionComponent = useMemo(() => { if (!isInvocationNodeData(data) || !nodeTemplate) { @@ -107,7 +110,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { if (!data.version) { return ( - Version unknown + {t('nodes.versionUnknown')} ); } @@ -115,7 +118,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { if (!nodeTemplate.version) { return ( - Version {data.version} (unknown template) + {t('nodes.version')} {data.version} ({t('nodes.unknownTemplate')}) ); } @@ -123,7 +126,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { if (compare(data.version, nodeTemplate.version, '<')) { return ( - Version {data.version} (update node) + {t('nodes.version')} {data.version} ({t('nodes.updateNode')}) ); } @@ -131,16 +134,20 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { if (compare(data.version, nodeTemplate.version, '>')) { return ( - Version {data.version} (update app) + {t('nodes.version')} {data.version} ({t('nodes.updateApp')}) ); } - return Version {data.version}; - }, [data, nodeTemplate]); + return ( + + {t('nodes.version')} {data.version} + + ); + }, [data, nodeTemplate, t]); if (!isInvocationNodeData(data)) { - return Unknown Node; + return {t('nodes.unknownNode')}; } return ( diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx index 6e1da90ad8..28f6e08a4e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx @@ -14,6 +14,7 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; import { NodeExecutionState, NodeStatus } from 'features/nodes/types/types'; import { memo, useMemo } from 'react'; import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa'; +import { useTranslation } from 'react-i18next'; type Props = { nodeId: string; @@ -72,10 +73,10 @@ type TooltipLabelProps = { const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => { const { status, progress, progressImage } = nodeExecutionState; + const { t } = useTranslation(); if (status === NodeStatus.PENDING) { return Pending; } - if (status === NodeStatus.IN_PROGRESS) { if (progressImage) { return ( @@ -97,18 +98,22 @@ const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => { } if (progress !== null) { - return In Progress ({Math.round(progress * 100)}%); + return ( + + {t('nodes.executionStateInProgress')} ({Math.round(progress * 100)}%) + + ); } - return In Progress; + return {t('nodes.executionStateInProgress')}; } if (status === NodeStatus.COMPLETED) { - return Completed; + return {t('nodes.executionStateCompleted')}; } if (status === NodeStatus.FAILED) { - return nodeExecutionState.error; + return {t('nodes.executionStateError')}; } return null; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/NotesTextarea.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/NotesTextarea.tsx index 68967096f9..5e85f5ba3c 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/NotesTextarea.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/NotesTextarea.tsx @@ -5,10 +5,12 @@ import { useNodeData } from 'features/nodes/hooks/useNodeData'; import { nodeNotesChanged } from 'features/nodes/store/nodesSlice'; import { isInvocationNodeData } from 'features/nodes/types/types'; import { ChangeEvent, memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; const NotesTextarea = ({ nodeId }: { nodeId: string }) => { const dispatch = useAppDispatch(); const data = useNodeData(nodeId); + const { t } = useTranslation(); const handleNotesChanged = useCallback( (e: ChangeEvent) => { dispatch(nodeNotesChanged({ nodeId, notes: e.target.value })); @@ -20,7 +22,7 @@ const NotesTextarea = ({ nodeId }: { nodeId: string }) => { } return ( - Notes + {t('nodes.notes')} { } = props; const label = useFieldLabel(nodeId, fieldName); const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind); + const { t } = useTranslation(); const dispatch = useAppDispatch(); const [localTitle, setLocalTitle] = useState( - label || fieldTemplateTitle || 'Unknown Field' + label || fieldTemplateTitle || t('nodes.unknownFeild') ); const handleSubmit = useCallback( @@ -44,10 +46,10 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => { if (newTitle && (newTitle === label || newTitle === fieldTemplateTitle)) { return; } - setLocalTitle(newTitle || fieldTemplateTitle || 'Unknown Field'); + setLocalTitle(newTitle || fieldTemplateTitle || t('nodes.unknownField')); dispatch(fieldLabelChanged({ nodeId, fieldName, label: newTitle })); }, - [label, fieldTemplateTitle, dispatch, nodeId, fieldName] + [label, fieldTemplateTitle, dispatch, nodeId, fieldName, t] ); const handleChange = useCallback((newTitle: string) => { @@ -56,8 +58,8 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => { useEffect(() => { // Another component may change the title; sync local title with global state - setLocalTitle(label || fieldTemplateTitle || 'Unknown Field'); - }, [label, fieldTemplateTitle]); + setLocalTitle(label || fieldTemplateTitle || t('nodes.unknownField')); + }, [label, fieldTemplateTitle, t]); return ( { const label = useFieldLabel(nodeId, fieldName); const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind); const input = useFieldInputKind(nodeId, fieldName); + const { t } = useTranslation(); const skipEvent = useCallback((e: MouseEvent) => { e.preventDefault(); @@ -119,7 +121,9 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => { motionProps={menuListMotionProps} onContextMenu={skipEvent} > - + {menuItems} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx index 1a47e81aa3..be66214a59 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx @@ -8,6 +8,7 @@ import { } from 'features/nodes/types/types'; import { startCase } from 'lodash-es'; import { memo, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; interface Props { nodeId: string; @@ -19,6 +20,7 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => { const field = useFieldData(nodeId, fieldName); const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind); const isInputTemplate = isInputFieldTemplate(fieldTemplate); + const { t } = useTranslation(); const fieldTitle = useMemo(() => { if (isInputFieldValue(field)) { if (field.label && fieldTemplate?.title) { @@ -33,11 +35,11 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => { return fieldTemplate.title; } - return 'Unknown Field'; + return t('nodes.unknownField'); } else { - return fieldTemplate?.title || 'Unknown Field'; + return fieldTemplate?.title || t('nodes.unknownField'); } - }, [field, fieldTemplate]); + }, [field, fieldTemplate, t]); return ( diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/LinearViewField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/LinearViewField.tsx index e3983560a8..a9416380d4 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/LinearViewField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/LinearViewField.tsx @@ -17,6 +17,7 @@ import { FaInfoCircle, FaTrash } from 'react-icons/fa'; import EditableFieldTitle from './EditableFieldTitle'; import FieldTooltipContent from './FieldTooltipContent'; import InputFieldRenderer from './InputFieldRenderer'; +import { useTranslation } from 'react-i18next'; type Props = { nodeId: string; @@ -27,7 +28,7 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => { const dispatch = useAppDispatch(); const { isMouseOverNode, handleMouseOut, handleMouseOver } = useMouseOverNode(nodeId); - + const { t } = useTranslation(); const handleRemoveField = useCallback(() => { dispatch(workflowExposedFieldRemoved({ nodeId, fieldName })); }, [dispatch, fieldName, nodeId]); @@ -75,8 +76,8 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => { { if (!loraModels) { @@ -92,9 +94,11 @@ const LoRAModelInputFieldComponent = ( 0 ? 'Select a LoRA' : 'No LoRAs available'} + placeholder={ + data.length > 0 ? t('models.selectLoRA') : t('models.noLoRAsAvailable') + } data={data} - nothingFound="No matching LoRAs" + nothingFound={t('models.noMatchingLoRAs')} itemComponent={IAIMantineSelectItemWithTooltip} disabled={data.length === 0} filter={(value, item: SelectItem) => diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx index f89177576c..08483986e3 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx @@ -19,6 +19,7 @@ import { useGetMainModelsQuery, useGetOnnxModelsQuery, } from 'services/api/endpoints/models'; +import { useTranslation } from 'react-i18next'; const MainModelInputFieldComponent = ( props: FieldComponentProps< @@ -29,7 +30,7 @@ const MainModelInputFieldComponent = ( const { nodeId, field } = props; const dispatch = useAppDispatch(); const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled; - + const { t } = useTranslation(); const { data: onnxModels, isLoading: isLoadingOnnxModels } = useGetOnnxModelsQuery(NON_SDXL_MAIN_MODELS); const { data: mainModels, isLoading: isLoadingMainModels } = @@ -127,7 +128,9 @@ const MainModelInputFieldComponent = ( tooltip={selectedModel?.description} value={selectedModel?.id} placeholder={ - data.length > 0 ? 'Select a model' : 'No models available' + data.length > 0 + ? t('models.selectModel') + : t('models.noModelsAvailable') } data={data} error={!selectedModel} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx index edad33d342..19f2c5ac8e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx @@ -89,7 +89,7 @@ const RefinerModelInputFieldComponent = ( return isLoading ? ( @@ -99,7 +99,11 @@ const RefinerModelInputFieldComponent = ( className="nowheel nodrag" tooltip={selectedModel?.description} value={selectedModel?.id} - placeholder={data.length > 0 ? 'Select a model' : 'No models available'} + placeholder={ + data.length > 0 + ? t('models.selectModel') + : t('models.noModelsAvailable') + } data={data} error={!selectedModel} disabled={data.length === 0} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx index ffb4d8d412..89cb8d5150 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx @@ -116,7 +116,7 @@ const ModelInputFieldComponent = ( return isLoading ? ( @@ -126,7 +126,11 @@ const ModelInputFieldComponent = ( className="nowheel nodrag" tooltip={selectedModel?.description} value={selectedModel?.id} - placeholder={data.length > 0 ? 'Select a model' : 'No models available'} + placeholder={ + data.length > 0 + ? t('models.selectModel') + : t('models.noModelsAvailable') + } data={data} error={!selectedModel} disabled={data.length === 0} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx index 283e5d115d..e31ac19be0 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx @@ -12,6 +12,7 @@ import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle' import { nodeLabelChanged } from 'features/nodes/store/nodesSlice'; import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; import { MouseEvent, memo, useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; type Props = { nodeId: string; @@ -22,16 +23,17 @@ const NodeTitle = ({ nodeId, title }: Props) => { const dispatch = useAppDispatch(); const label = useNodeLabel(nodeId); const templateTitle = useNodeTemplateTitle(nodeId); + const { t } = useTranslation(); const [localTitle, setLocalTitle] = useState(''); const handleSubmit = useCallback( async (newTitle: string) => { dispatch(nodeLabelChanged({ nodeId, label: newTitle })); setLocalTitle( - newTitle || title || templateTitle || 'Problem Setting Title' + label || title || templateTitle || t('nodes.problemSettingTitle') ); }, - [dispatch, nodeId, title, templateTitle] + [dispatch, nodeId, title, templateTitle, label, t] ); const handleChange = useCallback((newTitle: string) => { @@ -40,8 +42,10 @@ const NodeTitle = ({ nodeId, title }: Props) => { useEffect(() => { // Another component may change the title; sync local title with global state - setLocalTitle(label || title || templateTitle || 'Problem Setting Title'); - }, [label, templateTitle, title]); + setLocalTitle( + label || title || templateTitle || t('nodes.problemSettingTitle') + ); + }, [label, templateTitle, title, t]); return ( state.nodes.nodeOpacity); + const { t } = useTranslation(); const handleChange = useCallback( (v: number) => { @@ -23,7 +25,7 @@ export default function NodeOpacitySlider() { return ( { const dispatch = useAppDispatch(); - + const { t } = useTranslation(); const handleOpenAddNodePopover = useCallback(() => { dispatch(addNodePopoverOpened()); }, [dispatch]); @@ -15,8 +16,8 @@ const TopLeftPanel = () => { return ( } onClick={handleOpenAddNodePopover} /> diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx index c423750cd8..b822b2abb9 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx @@ -29,6 +29,7 @@ import { ChangeEvent, memo, useCallback } from 'react'; import { FaCog } from 'react-icons/fa'; import { SelectionMode } from 'reactflow'; import ReloadNodeTemplatesButton from '../TopCenterPanel/ReloadSchemaButton'; +import { useTranslation } from 'react-i18next'; const formLabelProps: FormLabelProps = { fontWeight: 600, @@ -101,12 +102,14 @@ const WorkflowEditorSettings = forwardRef((_, ref) => { [dispatch] ); + const { t } = useTranslation(); + return ( <> } onClick={onOpen} /> @@ -114,7 +117,7 @@ const WorkflowEditorSettings = forwardRef((_, ref) => { - Workflow Editor Settings + {t('nodes.workflowSettings')} { formLabelProps={formLabelProps} onChange={handleChangeShouldAnimate} isChecked={shouldAnimateEdges} - label="Animated Edges" - helperText="Animate selected edges and edges connected to selected nodes" + label={t('nodes.animatedEdges')} + helperText={t('nodes.animatedEdgesHelp')} /> Advanced @@ -162,8 +165,8 @@ const WorkflowEditorSettings = forwardRef((_, ref) => { formLabelProps={formLabelProps} isChecked={shouldValidateGraph} onChange={handleChangeShouldValidate} - label="Validate Connections and Graph" - helperText="Prevent invalid connections from being made, and invalid graphs from being invoked" + label={t('nodes.validateConnections')} + helperText={t('nodes.validateConnectionsHelp')} /> diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx index d626a92021..ffc260b95a 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx @@ -9,6 +9,7 @@ import { memo } from 'react'; import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea'; import NodeTitle from '../../flow/nodes/common/NodeTitle'; import ScrollableContent from '../ScrollableContent'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -34,9 +35,12 @@ const selector = createSelector( const InspectorDetailsTab = () => { const { data, template } = useAppSelector(selector); + const { t } = useTranslation(); if (!template || !data) { - return ; + return ( + + ); } return ; diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx index f6b229f997..b6a194051e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx @@ -11,6 +11,7 @@ import { ImageOutput } from 'services/api/types'; import { AnyResult } from 'services/events/types'; import ScrollableContent from '../ScrollableContent'; import ImageOutputPreview from './outputs/ImageOutputPreview'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -40,13 +41,18 @@ const selector = createSelector( const InspectorOutputsTab = () => { const { node, template, nes } = useAppSelector(selector); + const { t } = useTranslation(); if (!node || !nes || !isInvocationNode(node)) { - return ; + return ( + + ); } if (nes.outputs.length === 0) { - return ; + return ( + + ); } return ( @@ -77,7 +83,7 @@ const InspectorOutputsTab = () => { /> )) ) : ( - + )} diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorTemplateTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorTemplateTab.tsx index 525b58b1cb..b5e358239a 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorTemplateTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorTemplateTab.tsx @@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer'; import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -29,12 +30,15 @@ const selector = createSelector( const NodeTemplateInspector = () => { const { template } = useAppSelector(selector); + const { t } = useTranslation(); if (!template) { - return ; + return ( + + ); } - return ; + return ; }; export default memo(NodeTemplateInspector); diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowGeneralTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowGeneralTab.tsx index e36675b71f..ad1070096e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowGeneralTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowGeneralTab.tsx @@ -16,6 +16,7 @@ import { } from 'features/nodes/store/nodesSlice'; import { ChangeEvent, memo, useCallback } from 'react'; import ScrollableContent from '../ScrollableContent'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -85,6 +86,8 @@ const WorkflowGeneralTab = () => { [dispatch] ); + const { t } = useTranslation(); + return ( { }} > - + - + - Short Description + {t('nodes.workflowDescription')} { /> - Notes + {t('nodes.workflowNotes')} { const workflow = useWorkflow(); + const { t } = useTranslation(); return ( { h: 'full', }} > - + ); }; diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx index d1cecefbff..cf9e11d9d6 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx @@ -7,6 +7,7 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { memo } from 'react'; import LinearViewField from '../../flow/nodes/Invocation/fields/LinearViewField'; import ScrollableContent from '../ScrollableContent'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -20,6 +21,7 @@ const selector = createSelector( const WorkflowLinearTab = () => { const { fields } = useAppSelector(selector); + const { t } = useTranslation(); return ( { )) ) : ( )} diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx b/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx index 7f015ac5eb..890fa7a72d 100644 --- a/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx +++ b/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx @@ -9,10 +9,12 @@ import { memo, useCallback } from 'react'; import { ZodError } from 'zod'; import { fromZodError, fromZodIssue } from 'zod-validation-error'; import { workflowLoadRequested } from '../store/actions'; +import { useTranslation } from 'react-i18next'; export const useLoadWorkflowFromFile = () => { const dispatch = useAppDispatch(); const logger = useLogger('nodes'); + const { t } = useTranslation(); const loadWorkflowFromFile = useCallback( (file: File | null) => { if (!file) { @@ -28,7 +30,7 @@ export const useLoadWorkflowFromFile = () => { if (!result.success) { const { message } = fromZodError(result.error, { - prefix: 'Workflow Validation Error', + prefix: t('nodes.workflowValidation'), }); logger.error({ error: parseify(result.error) }, message); @@ -36,7 +38,7 @@ export const useLoadWorkflowFromFile = () => { dispatch( addToast( makeToast({ - title: 'Unable to Validate Workflow', + title: t('nodes.unableToValidateWorkflow'), status: 'error', duration: 5000, }) @@ -54,7 +56,7 @@ export const useLoadWorkflowFromFile = () => { dispatch( addToast( makeToast({ - title: 'Unable to Load Workflow', + title: t('nodes.unableToLoadWorkflow'), status: 'error', }) ) @@ -64,7 +66,7 @@ export const useLoadWorkflowFromFile = () => { reader.readAsText(file); }, - [dispatch, logger] + [dispatch, logger, t] ); return loadWorkflowFromFile; diff --git a/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts b/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts index 5cb6d557e8..ac157bb476 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts @@ -9,6 +9,7 @@ import { } from 'features/nodes/types/constants'; import { FieldType } from 'features/nodes/types/types'; import { HandleType } from 'reactflow'; +import i18n from 'i18next'; /** * NOTE: The logic here must be duplicated in `invokeai/frontend/web/src/features/nodes/hooks/useIsValidConnection.ts` @@ -20,17 +21,17 @@ export const makeConnectionErrorSelector = ( fieldName: string, handleType: HandleType, fieldType?: FieldType -) => - createSelector(stateSelector, (state) => { +) => { + return createSelector(stateSelector, (state) => { if (!fieldType) { - return 'No field type'; + return i18n.t('nodes.noFieldType'); } const { currentConnectionFieldType, connectionStartParams, nodes, edges } = state.nodes; if (!connectionStartParams || !currentConnectionFieldType) { - return 'No connection in progress'; + return i18n.t('nodes.noConnectionInProgress'); } const { @@ -40,7 +41,7 @@ export const makeConnectionErrorSelector = ( } = connectionStartParams; if (!connectionHandleType || !connectionNodeId || !connectionFieldName) { - return 'No connection data'; + return i18n.t('nodes.noConnectionData'); } const targetType = @@ -49,14 +50,14 @@ export const makeConnectionErrorSelector = ( handleType === 'source' ? fieldType : currentConnectionFieldType; if (nodeId === connectionNodeId) { - return 'Cannot connect to self'; + return i18n.t('nodes.cannotConnectToSelf'); } if (handleType === connectionHandleType) { if (handleType === 'source') { - return 'Cannot connect output to output'; + return i18n.t('nodes.cannotConnectOutputToOutput'); } - return 'Cannot connect input to input'; + return i18n.t('nodes.cannotConnectInputToInput'); } if ( @@ -66,7 +67,7 @@ export const makeConnectionErrorSelector = ( // except CollectionItem inputs can have multiples targetType !== 'CollectionItem' ) { - return 'Input may only have one connection'; + return i18n.t('nodes.inputMayOnlyHaveOneConnection'); } /** @@ -125,7 +126,7 @@ export const makeConnectionErrorSelector = ( isIntToFloat ) ) { - return 'Field types must match'; + return i18n.t('nodes.fieldTypesMustMatch'); } } @@ -137,8 +138,9 @@ export const makeConnectionErrorSelector = ( ); if (!isGraphAcyclic) { - return 'Connection would create a cycle'; + return i18n.t('nodes.connectionWouldCreateCycle'); } return null; }); +}; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx index bca1402571..2d461f7bb4 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Advanced/ParamAdvancedCollapse.tsx @@ -5,6 +5,7 @@ import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAICollapse from 'common/components/IAICollapse'; import ParamClipSkip from './ParamClipSkip'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -22,13 +23,13 @@ export default function ParamAdvancedCollapse() { const shouldShowAdvancedOptions = useAppSelector( (state: RootState) => state.generation.shouldShowAdvancedOptions ); - + const { t } = useTranslation(); if (!shouldShowAdvancedOptions) { return null; } return ( - + diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamCpuNoise.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamCpuNoise.tsx index 45fd7fcf57..f10c3dd1a5 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamCpuNoise.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamCpuNoise.tsx @@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice'; import { ChangeEvent } from 'react'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -21,6 +22,7 @@ const selector = createSelector( export const ParamCpuNoiseToggle = () => { const dispatch = useAppDispatch(); const { isDisabled, shouldUseCpuNoise } = useAppSelector(selector); + const { t } = useTranslation(); const handleChange = (e: ChangeEvent) => dispatch(shouldUseCpuNoiseChanged(e.target.checked)); @@ -28,7 +30,7 @@ export const ParamCpuNoiseToggle = () => { return ( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseToggle.tsx index c1c2fb5119..4ec6b0163b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseToggle.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseToggle.tsx @@ -3,9 +3,11 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAISwitch from 'common/components/IAISwitch'; import { setShouldUseNoiseSettings } from 'features/parameters/store/generationSlice'; import { ChangeEvent } from 'react'; +import { useTranslation } from 'react-i18next'; export const ParamNoiseToggle = () => { const dispatch = useAppDispatch(); + const { t } = useTranslation(); const shouldUseNoiseSettings = useAppSelector( (state: RootState) => state.generation.shouldUseNoiseSettings @@ -16,7 +18,7 @@ export const ParamNoiseToggle = () => { return ( diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx index e7bd36b931..6a80ccb52f 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx @@ -146,7 +146,7 @@ const CancelButton = (props: Props) => { id="cancel-button" {...rest} > - Cancel + {t('parameters.cancel.cancel')} )} diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx index c70f2528bb..0b81fceed7 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx @@ -76,7 +76,7 @@ export default function InvokeButton(props: InvokeButton) { )} {asIconButton ? ( } isDisabled={!isReady} @@ -96,7 +96,7 @@ export default function InvokeButton(props: InvokeButton) { ) : ( } - aria-label={t('parameters.invoke')} + aria-label={t('parameters.invoke.invoke')} type="submit" data-progress={isProcessing} isDisabled={!isReady} @@ -105,7 +105,7 @@ export default function InvokeButton(props: InvokeButton) { id="invoke-button" leftIcon={isProcessing ? undefined : } isLoading={isProcessing} - loadingText={t('parameters.invoke')} + loadingText={t('parameters.invoke.invoke')} sx={{ w: 'full', flexGrow: 1, @@ -138,11 +138,14 @@ export const InvokeButtonTooltipContent = memo(() => { const { isReady, reasons } = useIsReadyToInvoke(); const { autoAddBoardId } = useAppSelector(tooltipSelector); const autoAddBoardName = useBoardName(autoAddBoardId); + const { t } = useTranslation(); return ( - {isReady ? 'Ready to Invoke' : 'Unable to Invoke'} + {isReady + ? t('parameters.invoke.readyToInvoke') + : t('parameters.invoke.unableToInvoke')} {reasons.length > 0 && ( @@ -159,7 +162,7 @@ export const InvokeButtonTooltipContent = memo(() => { _dark={{ borderColor: 'base.900' }} /> - Adding images to{' '} + {t('parameters.invoke.addingImagesTo')}{' '} {autoAddBoardName || 'Uncategorized'} diff --git a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx index 057ee6fe78..94c4a3aeb0 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx @@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; import { FaLink } from 'react-icons/fa'; import { setShouldConcatSDXLStylePrompt } from '../store/sdxlSlice'; +import { useTranslation } from 'react-i18next'; export default function ParamSDXLConcatButton() { const shouldConcatSDXLStylePrompt = useAppSelector( @@ -10,6 +11,7 @@ export default function ParamSDXLConcatButton() { ); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleShouldConcatPromptChange = () => { dispatch(setShouldConcatSDXLStylePrompt(!shouldConcatSDXLStylePrompt)); @@ -17,8 +19,8 @@ export default function ParamSDXLConcatButton() { return ( { return ( { const isReady = useIsReadyToInvoke(); const promptRef = useRef(null); const { isOpen, onClose, onOpen } = useDisclosure(); + const { t } = useTranslation(); const { prompt, activeTabName, shouldConcatSDXLStylePrompt } = useAppSelector(promptInputSelector); @@ -143,7 +145,7 @@ const ParamSDXLNegativeStyleConditioning = () => { name="prompt" ref={promptRef} value={prompt} - placeholder="Negative Style Prompt" + placeholder={t('sdxl.negStylePrompt')} onChange={handleChangePrompt} onKeyDown={handleKeyDown} resize="vertical" diff --git a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLPositiveStyleConditioning.tsx b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLPositiveStyleConditioning.tsx index 8ff2f9f19e..b193e3adbd 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLPositiveStyleConditioning.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLPositiveStyleConditioning.tsx @@ -6,6 +6,7 @@ import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react'; import { createSelector } from '@reduxjs/toolkit'; import { clampSymmetrySteps } from 'features/parameters/store/generationSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; +import { useTranslation } from 'react-i18next'; import { userInvoked } from 'app/store/actions'; import IAITextarea from 'common/components/IAITextarea'; @@ -45,6 +46,7 @@ const ParamSDXLPositiveStyleConditioning = () => { const isReady = useIsReadyToInvoke(); const promptRef = useRef(null); const { isOpen, onClose, onOpen } = useDisclosure(); + const { t } = useTranslation(); const { prompt, activeTabName, shouldConcatSDXLStylePrompt } = useAppSelector(promptInputSelector); @@ -143,7 +145,7 @@ const ParamSDXLPositiveStyleConditioning = () => { name="prompt" ref={promptRef} value={prompt} - placeholder="Positive Style Prompt" + placeholder={t('sdxl.posStylePrompt')} onChange={handleChangePrompt} onKeyDown={handleKeyDown} resize="vertical" diff --git a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx index fcf4d0bcd3..6ac3034834 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx @@ -13,6 +13,7 @@ import ParamSDXLRefinerScheduler from './SDXLRefiner/ParamSDXLRefinerScheduler'; import ParamSDXLRefinerStart from './SDXLRefiner/ParamSDXLRefinerStart'; import ParamSDXLRefinerSteps from './SDXLRefiner/ParamSDXLRefinerSteps'; import ParamUseSDXLRefiner from './SDXLRefiner/ParamUseSDXLRefiner'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( stateSelector, @@ -29,9 +30,10 @@ const selector = createSelector( const ParamSDXLRefinerCollapse = () => { const { activeLabel, shouldUseSliders } = useAppSelector(selector); + const { t } = useTranslation(); return ( - + diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale.tsx index dd678ac0f7..fb16c01676 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerCFGScale.tsx @@ -43,7 +43,7 @@ const ParamSDXLRefinerCFGScale = () => { return shouldUseSliders ? ( { /> ) : ( { const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled; const { model } = useAppSelector(selector); + const { t } = useTranslation(); const { data: refinerModels, isLoading } = useGetMainModelsQuery(REFINER_BASE_MODELS); @@ -81,8 +83,8 @@ const ParamSDXLRefinerModelSelect = () => { return isLoading ? ( @@ -90,9 +92,11 @@ const ParamSDXLRefinerModelSelect = () => { 0 ? 'Select a model' : 'No models available'} + placeholder={ + data.length > 0 ? t('sdxl.selectAModel') : t('sdxl.noModelsAvailable') + } data={data} error={data.length === 0} disabled={data.length === 0} diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore.tsx index 4dad3f519a..bc08d64e9f 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore.tsx @@ -6,6 +6,7 @@ import IAISlider from 'common/components/IAISlider'; import { setRefinerNegativeAestheticScore } from 'features/sdxl/store/sdxlSlice'; import { memo, useCallback } from 'react'; import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( [stateSelector], @@ -27,6 +28,7 @@ const ParamSDXLRefinerNegativeAestheticScore = () => { const isRefinerAvailable = useIsRefinerAvailable(); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleChange = useCallback( (v: number) => dispatch(setRefinerNegativeAestheticScore(v)), @@ -40,7 +42,7 @@ const ParamSDXLRefinerNegativeAestheticScore = () => { return ( { const isRefinerAvailable = useIsRefinerAvailable(); const dispatch = useAppDispatch(); + const { t } = useTranslation(); const handleChange = useCallback( (v: number) => dispatch(setRefinerPositiveAestheticScore(v)), @@ -40,7 +42,7 @@ const ParamSDXLRefinerPositiveAestheticScore = () => { return ( { return ( { (v: number) => dispatch(setRefinerStart(v)), [dispatch] ); + const { t } = useTranslation(); const handleReset = useCallback( () => dispatch(setRefinerStart(0.8)), @@ -34,7 +36,7 @@ const ParamSDXLRefinerStart = () => { return ( { return shouldUseSliders ? ( { /> ) : ( ) => { dispatch(setShouldUseSDXLRefiner(e.target.checked)); @@ -19,7 +21,7 @@ export default function ParamUseSDXLRefiner() { return ( { @@ -114,14 +117,15 @@ export default function AdvancedAddCheckpoint( }} /> @@ -134,14 +138,14 @@ export default function AdvancedAddCheckpoint( ) : ( )} setUseCustomConfig(!useCustomConfig)} - label="Use Custom Config" + label={t('modelManager.useCustomConfig')} /> {t('modelManager.addModel')} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx index 181a6f4234..7c22b4b2ac 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx @@ -47,7 +47,9 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { dispatch( addToast( makeToast({ - title: `Model Added: ${values.model_name}`, + title: t('modelManager.modelAdded', { + modelName: values.model_name, + }), status: 'success', }) ) @@ -63,7 +65,7 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { dispatch( addToast( makeToast({ - title: 'Model Add Failed', + title: t('toast.modelAddFailed'), status: 'error', }) ) @@ -82,16 +84,17 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { { if (advancedAddDiffusersForm.values['model_name'] === '') { @@ -106,14 +109,15 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) { }} /> diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx index a2a12b7f00..b78d0466c0 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddModels.tsx @@ -4,6 +4,7 @@ import IAIMantineSelect from 'common/components/IAIMantineSelect'; import { useState } from 'react'; import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; import AdvancedAddDiffusers from './AdvancedAddDiffusers'; +import { useTranslation } from 'react-i18next'; export const advancedAddModeData: SelectItem[] = [ { label: 'Diffusers', value: 'diffusers' }, @@ -16,10 +17,12 @@ export default function AdvancedAddModels() { const [advancedAddMode, setAdvancedAddMode] = useState('diffusers'); + const { t } = useTranslation(); + return ( { diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx index a44a747438..a2e6158515 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -77,7 +77,7 @@ export default function FoundModelsList() { dispatch( addToast( makeToast({ - title: 'Failed To Add Model', + title: t('toast.modelAddFailed'), status: 'error', }) ) @@ -85,7 +85,7 @@ export default function FoundModelsList() { } }); }, - [dispatch, importMainModel] + [dispatch, importMainModel, t] ); const handleSearchFilter = useCallback((e: ChangeEvent) => { @@ -137,13 +137,13 @@ export default function FoundModelsList() { onClick={quickAddHandler} isLoading={isLoading} > - Quick Add + {t('modelManager.quickAdd')} dispatch(setAdvancedAddScanModel(model))} isLoading={isLoading} > - Advanced + {t('modelManager.advanced')} ) : ( @@ -189,7 +189,7 @@ export default function FoundModelsList() { }, }} > - No Models Found + {t('modelManager.noModels')} ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx index 997341279d..615c76b71e 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx @@ -10,12 +10,15 @@ import { setAdvancedAddScanModel } from '../../store/modelManagerSlice'; import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; import AdvancedAddDiffusers from './AdvancedAddDiffusers'; import { ManualAddMode, advancedAddModeData } from './AdvancedAddModels'; +import { useTranslation } from 'react-i18next'; export default function ScanAdvancedAddModels() { const advancedAddScanModel = useAppSelector( (state: RootState) => state.modelmanager.advancedAddScanModel ); + const { t } = useTranslation(); + const [advancedAddMode, setAdvancedAddMode] = useState('diffusers'); @@ -64,13 +67,13 @@ export default function ScanAdvancedAddModels() { } - aria-label="Close Advanced" + aria-label={t('modelManager.closeAdvanced')} onClick={() => dispatch(setAdvancedAddScanModel(null))} size="sm" /> { diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx index cb0ce627a0..fb1a5167a6 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx @@ -55,7 +55,7 @@ export default function SimpleAddModels() { dispatch( addToast( makeToast({ - title: 'Model Added', + title: t('toast.modelAddSimple'), status: 'success', }) ) @@ -84,13 +84,13 @@ export default function SimpleAddModels() { >