diff --git a/invokeai/frontend/web/.eslintrc.js b/invokeai/frontend/web/.eslintrc.js index 976aea9739..1777729b67 100644 --- a/invokeai/frontend/web/.eslintrc.js +++ b/invokeai/frontend/web/.eslintrc.js @@ -20,10 +20,16 @@ module.exports = { ecmaVersion: 2018, sourceType: 'module', }, - plugins: ['react', '@typescript-eslint', 'eslint-plugin-react-hooks'], + plugins: [ + 'react', + '@typescript-eslint', + 'eslint-plugin-react-hooks', + 'i18next', + ], root: true, rules: { curly: 'error', + 'i18next/no-literal-string': 2, 'react/jsx-no-bind': ['error', { allowBind: true }], 'react/jsx-curly-brace-presence': [ 'error', diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index 055334f939..6e6c919b09 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -131,6 +131,7 @@ "concurrently": "^8.2.2", "eslint": "^8.53.0", "eslint-config-prettier": "^9.0.0", + "eslint-plugin-i18next": "^6.0.3", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "husky": "^8.0.3", diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index e3dd84bf5c..b210ff6b4f 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1,6 +1,7 @@ { "accessibility": { "copyMetadataJson": "Copy metadata JSON", + "createIssue":"Create Issue", "exitViewer": "Exit Viewer", "flipHorizontally": "Flip Horizontally", "flipVertically": "Flip Vertically", @@ -12,6 +13,7 @@ "nextImage": "Next Image", "previousImage": "Previous Image", "reset": "Reset", + "resetUI":"$t(accessibility.reset) UI", "rotateClockwise": "Rotate Clockwise", "rotateCounterClockwise": "Rotate Counter-Clockwise", "showGalleryPanel": "Show Gallery Panel", @@ -38,6 +40,8 @@ "loading": "Loading...", "menuItemAutoAdd": "Auto-add to this Board", "move": "Move", + "movingImagesToBoard_one": "Moving {{count}} image to board:", + "movingImagesToBoard_other": "Moving {{count}} images to board:", "myBoard": "My Board", "noMatching": "No matching Boards", "searchBoard": "Search Boards...", @@ -49,11 +53,13 @@ "common": { "accept": "Accept", "advanced": "Advanced", + "ai": "ai", "areYouSure": "Are you sure?", "auto": "Auto", "back": "Back", "batch": "Batch Manager", "cancel": "Cancel", + "copyError":"$t(gallery.copy) Error", "close": "Close", "on": "On", "checkpoint": "Checkpoint", @@ -67,6 +73,10 @@ "darkMode": "Dark Mode", "discordLabel": "Discord", "dontAskMeAgain": "Don't ask me again", + "error": "Error", + "file": "File", + "folder": "Folder", + "format":"format", "generate": "Generate", "githubLabel": "Github", "hotkeysLabel": "Hotkeys", @@ -74,6 +84,8 @@ "imageFailedToLoad": "Unable to Load Image", "img2img": "Image To Image", "inpaint": "inpaint", + "input": "Input", + "installed": "Installed", "langArabic": "العربية", "langBrPortuguese": "Português do Brasil", "langDutch": "Nederlands", @@ -101,6 +113,7 @@ "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.", + "notInstalled": "Not $t(common.installed)", "openInNewTab": "Open in New Tab", "outpaint": "outpaint", "outputs": "Outputs", @@ -114,6 +127,7 @@ "safetensors": "Safetensors", "settingsLabel": "Settings", "simple": "Simple", + "somethingWentWrong": "Something went wrong", "statusConnected": "Connected", "statusConvertingModel": "Converting Model", "statusDisconnected": "Disconnected", @@ -252,6 +266,7 @@ "embedding": { "addEmbedding": "Add Embedding", "incompatibleModel": "Incompatible base model:", + "noEmbeddingsLoaded": "No Embeddings Loaded", "noMatchingEmbedding": "No matching Embeddings" }, "queue": { @@ -330,7 +345,8 @@ "enableFailed": "Problem Enabling Invocation Cache", "disable": "Disable", "disableSucceeded": "Invocation Cache Disabled", - "disableFailed": "Problem Disabling Invocation Cache" + "disableFailed": "Problem Disabling Invocation Cache", + "useCache": "Use Cache" }, "gallery": { "allImagesLoaded": "All Images Loaded", @@ -339,6 +355,9 @@ "autoSwitchNewImages": "Auto-Switch to New Images", "copy": "Copy", "currentlyInUse": "This image is currently in use in the following features:", + "drop":"Drop", + "dropOrUpload":"$t(gallery.drop) or Upload", + "dropToUpload":"$t(gallery.drop) to Upload", "deleteImage": "Delete Image", "deleteImageBin": "Deleted images will be sent to your operating system's Bin.", "deleteImagePermanent": "Deleted images cannot be restored.", @@ -348,7 +367,7 @@ "galleryImageSize": "Image Size", "gallerySettings": "Gallery Settings", "generations": "Generations", - "images": "Images", + "image": "image", "loading": "Loading", "loadMore": "Load More", "maintainAspectRatio": "Maintain Aspect Ratio", @@ -360,6 +379,7 @@ "singleColumnLayout": "Single Column Layout", "unableToLoad": "Unable to load Gallery", "uploads": "Uploads", + "deleteSelection": "Delete Selection", "downloadSelection": "Download Selection", "preparingDownload": "Preparing Download", "preparingDownloadFailed": "Problem Preparing Download" @@ -629,6 +649,7 @@ "closeAdvanced": "Close Advanced", "config": "Config", "configValidationMsg": "Path to the config file of your model.", + "conversionNotSupported": "Conversion Not Supported", "convert": "Convert", "convertingModelBegin": "Converting Model. Please wait.", "convertToDiffusers": "Convert To Diffusers", @@ -754,6 +775,7 @@ "esrganModel": "ESRGAN Model", "loading": "loading", "noLoRAsAvailable": "No LoRAs available", + "noLoRAsLoaded":"No LoRAs Loaded", "noMatchingLoRAs": "No matching LoRAs", "noMatchingModels": "No matching Models", "noModelsAvailable": "No models available", @@ -765,6 +787,7 @@ "nodes": { "addNode": "Add Node", "addNodeToolTip": "Add Node (Shift+A, Space)", + "addLinearView":"Add to Linear View", "animatedEdges": "Animated Edges", "animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes", "boardField": "Board", @@ -885,6 +908,7 @@ "noMatchingNodes": "No matching nodes", "noNodeSelected": "No node selected", "nodeOpacity": "Node Opacity", + "nodeVersion": "Node Version", "noOutputRecorded": "No outputs recorded", "noOutputSchemaName": "No output schema name found in ref object", "notes": "Notes", @@ -892,6 +916,7 @@ "oNNXModelField": "ONNX Model", "oNNXModelFieldDescription": "ONNX model field.", "outputField": "Output Field", + "outputFieldInInput": "Output field in input", "outputFields": "Output Fields", "outputNode": "Output node", "outputSchemaNotFound": "Output schema not found", @@ -937,10 +962,14 @@ "uNetFieldDescription": "UNet submodel.", "unhandledInputProperty": "Unhandled input property", "unhandledOutputProperty": "Unhandled output property", - "unknownField": "Unknown Field", + "unknownField": "Unknown field", + "unknownFieldType": "$(nodes.unknownField) type", "unknownNode": "Unknown Node", + "unknownNodeType":"$t(nodes.unknownNode) type", "unknownTemplate": "Unknown Template", + "unknownInput": "Unknown input", "unkownInvocation": "Unknown Invocation type", + "unknownOutput": "Unknown output", "updateNode": "Update Node", "updateAllNodes": "Update All Nodes", "updateApp": "Update App", @@ -1171,7 +1200,8 @@ "clearIntermediatesWithCount_other": "Clear {{count}} Intermediates", "intermediatesCleared_one": "Cleared {{count}} Intermediate", "intermediatesCleared_other": "Cleared {{count}} Intermediates", - "intermediatesClearedFailed": "Problem Clearing Intermediates" + "intermediatesClearedFailed": "Problem Clearing Intermediates", + "reloadingIn": "Reloading in" }, "toast": { "addedToBoard": "Added to board", @@ -1199,6 +1229,7 @@ "initialImageNotSet": "Initial Image Not Set", "initialImageNotSetDesc": "Could not load initial image", "initialImageSet": "Initial Image Set", + "invalidUpload": "Invalid Upload", "loadedWithWarnings": "Workflow Loaded with Warnings", "maskSavedAssets": "Mask Saved to Assets", "maskSentControlnetAssets": "Mask Sent to ControlNet & Assets", @@ -1517,7 +1548,7 @@ "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", + "clearMask": "Clear Mask (Shift+C)", "colorPicker": "Color Picker", "copyToClipboard": "Copy to Clipboard", "cursorPosition": "Cursor Position", @@ -1544,6 +1575,7 @@ "redo": "Redo", "resetView": "Reset View", "saveBoxRegionOnly": "Save Box Region Only", + "saveMask":"Save $t(unifiedCanvas.mask)", "saveToGallery": "Save To Gallery", "scaledBoundingBox": "Scaled Bounding Box", "showCanvasDebugInfo": "Show Additional Canvas Info", diff --git a/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx b/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx index 76a34388eb..29f4016ad9 100644 --- a/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx +++ b/invokeai/frontend/web/src/app/components/AppErrorBoundaryFallback.tsx @@ -2,6 +2,7 @@ import { Flex, Heading, Link, Text, useToast } from '@chakra-ui/react'; import IAIButton from 'common/components/IAIButton'; import newGithubIssueUrl from 'new-github-issue-url'; import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import { FaCopy, FaExternalLinkAlt } from 'react-icons/fa'; import { FaArrowRotateLeft } from 'react-icons/fa6'; import { serializeError } from 'serialize-error'; @@ -13,6 +14,7 @@ type Props = { const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => { const toast = useToast(); + const { t } = useTranslation(); const handleCopy = useCallback(() => { const text = JSON.stringify(serializeError(error), null, 2); @@ -53,7 +55,7 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => { p: 16, }} > - Something went wrong + {t('common.somethingWentWrong')} { leftIcon={} onClick={resetErrorBoundary} > - Reset UI + {t('accessibility.resetUI')} } onClick={handleCopy}> - Copy Error + {t('common.copyError')} - }>Create Issue + }> + {t('accessibility.createIssue')} + diff --git a/invokeai/frontend/web/src/common/components/ImageUploadOverlay.tsx b/invokeai/frontend/web/src/common/components/ImageUploadOverlay.tsx index 5c91a7ceda..c96a853ed0 100644 --- a/invokeai/frontend/web/src/common/components/ImageUploadOverlay.tsx +++ b/invokeai/frontend/web/src/common/components/ImageUploadOverlay.tsx @@ -1,6 +1,7 @@ import { Box, Flex, Heading } from '@chakra-ui/react'; import { memo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; +import { useTranslation } from 'react-i18next'; type ImageUploadOverlayProps = { isDragAccept: boolean; @@ -9,6 +10,7 @@ type ImageUploadOverlayProps = { }; const ImageUploadOverlay = (props: ImageUploadOverlayProps) => { + const { t } = useTranslation(); const { isDragAccept, isDragReject: _isDragAccept, @@ -76,11 +78,13 @@ const ImageUploadOverlay = (props: ImageUploadOverlayProps) => { }} > {isDragAccept ? ( - Drop to Upload + {t('gallery.dropToUpload')} ) : ( <> - Invalid Upload - Must be single JPEG or PNG image + {t('toast.invalidUpload')} + + {t('toast.uploadFailedInvalidUploadDesc')} + )} diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx index e1b2105bbc..289cd43ebd 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx @@ -155,10 +155,10 @@ const IAICanvasMaskOptions = () => { } onClick={handleSaveMask}> - Save Mask + {t('unifiedCanvas.saveMask')} } onClick={handleClearMask}> - {t('unifiedCanvas.clearMask')} (Shift+C) + {t('unifiedCanvas.clearMask')} diff --git a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx index 122b2b7d38..11a8d55bde 100644 --- a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx +++ b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx @@ -110,8 +110,10 @@ const ChangeBoardModal = () => { - Moving {`${imagesToChange.length}`} image - {`${imagesToChange.length > 1 ? 's' : ''}`} to board: + {t('boards.movingImagesToBoard', { + count: imagesToChange.length, + })} + : { - Cancel + {t('boards.cancel')} - Delete + {t('controlnet.delete')} diff --git a/invokeai/frontend/web/src/features/dnd/components/DragPreview.tsx b/invokeai/frontend/web/src/features/dnd/components/DragPreview.tsx index 0ee5d34b1a..5933744110 100644 --- a/invokeai/frontend/web/src/features/dnd/components/DragPreview.tsx +++ b/invokeai/frontend/web/src/features/dnd/components/DragPreview.tsx @@ -1,6 +1,7 @@ import { Box, ChakraProps, Flex, Heading, Image, Text } from '@chakra-ui/react'; import { memo } from 'react'; import { TypesafeDraggableData } from '../types'; +import { useTranslation } from 'react-i18next'; type OverlayDragImageProps = { dragData: TypesafeDraggableData | null; @@ -26,6 +27,7 @@ const STYLES: ChakraProps['sx'] = { }; const DragPreview = (props: OverlayDragImageProps) => { + const { t } = useTranslation(); if (!props.dragData) { return null; } @@ -89,7 +91,7 @@ const DragPreview = (props: OverlayDragImageProps) => { }} > {props.dragData.payload.imageDTOs.length} - Images + {t('parameters.images')} ); } diff --git a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx index 32334a9f88..02565eefc7 100644 --- a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx +++ b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx @@ -121,7 +121,7 @@ const ParamEmbeddingPopover = (props: Props) => { _dark: { color: 'base.700' }, }} > - No Embeddings Loaded + {t('embedding.noEmbeddingsLoaded')} ) : ( { setLocalBoardName(newBoardName); }, []); - + const { t } = useTranslation(); return ( Move} + dropLabel={ + {t('unifiedCanvas.move')} + } /> diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx index 2bcc466108..c139113022 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/NoBoardBoard.tsx @@ -19,6 +19,7 @@ import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery, } from 'services/api/endpoints/boards'; +import { useTranslation } from 'react-i18next'; interface Props { isSelected: boolean; @@ -71,7 +72,7 @@ const NoBoardBoard = memo(({ isSelected }: Props) => { }), [] ); - + const { t } = useTranslation(); return ( { /> Move} + dropLabel={ + {t('unifiedCanvas.move')} + } /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx index a067050290..8eee974bd4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/MultipleSelectionMenuItems.tsx @@ -111,7 +111,7 @@ const MultipleSelectionMenuItems = () => { icon={} onClickCapture={handleDeleteSelection} > - Delete Selection + {t('gallery.deleteSelection')} ); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx index fa640bf202..41754c8f06 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx @@ -113,7 +113,7 @@ const ImageGalleryContent = () => { leftIcon={} data-testid="images-tab" > - {t('gallery.images')} + {t('parameters.images')} { }} > - File: + {t('common.file')}: {image.image_name} diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/FoundModelsList.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/FoundModelsList.tsx index 6f19776421..499a8e5cc3 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/FoundModelsList.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/FoundModelsList.tsx @@ -165,7 +165,7 @@ export default function FoundModelsList() { }, }} > - Installed + {t('common.installed')} )} @@ -215,7 +215,7 @@ export default function FoundModelsList() { /> - Models Found: {foundModels.length} + {t('modelManager.modelsFound')}: {foundModels.length} - Not Installed: {filteredModels.length} + {t('common.notInstalled')}: {filteredModels.length} diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx index 599705518a..ad9713f666 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SearchFolderForm.tsx @@ -76,7 +76,7 @@ function SearchFolderForm() { _dark: { color: 'base.300' }, }} > - Folder + {t('common.folder')} {!searchFolder ? ( - {MODEL_TYPE_MAP[model.base_model]} Model + {MODEL_TYPE_MAP[model.base_model]} {t('modelManager.model')} {![''].includes(model.base_model) ? ( @@ -128,7 +128,7 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) { _dark: { bg: 'error.400' }, }} > - Conversion Not Supported + {t('modelManager.conversionNotSupported')} )} diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx index 67ec1f53db..d7e55fb86a 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx @@ -95,7 +95,7 @@ export default function DiffusersModelEdit(props: DiffusersModelEditProps) { {model.model_name} - {MODEL_TYPE_MAP[model.base_model]} Model + {MODEL_TYPE_MAP[model.base_model]} {t('modelManager.model')} diff --git a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx index 284ce95b3a..2e86675162 100644 --- a/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx @@ -95,8 +95,8 @@ export default function LoRAModelEdit(props: LoRAModelEditProps) { {model.model_name} - {MODEL_TYPE_MAP[model.base_model]} Model ⋅{' '} - {LORA_MODEL_FORMAT_MAP[model.model_format]} format + {MODEL_TYPE_MAP[model.base_model]} {t('modelManager.model')} ⋅{' '} + {LORA_MODEL_FORMAT_MAP[model.model_format]} {t('common.format')} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx index 57cdc6a8db..8ceadba304 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/CurrentImage/CurrentImageNode.tsx @@ -10,6 +10,7 @@ import IAIDndImage from 'common/components/IAIDndImage'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; import { stateSelector } from 'app/store/store'; +import { useTranslation } from 'react-i18next'; const selector = createSelector(stateSelector, ({ system, gallery }) => { const imageDTO = gallery.selection[gallery.selection.length - 1]; @@ -66,7 +67,7 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => { const handleMouseLeave = useCallback(() => { setIsHovering(false); }, []); - + const { t } = useTranslation(); return ( ) => { _dark: { color: 'base.200' }, }} > - Current Image + {t('nodes.currentImage')} { + const { t } = useTranslation(); const dispatch = useAppDispatch(); const withWorkflow = useWithWorkflow(nodeId); const embedWorkflow = useEmbedWorkflow(nodeId); @@ -27,7 +29,9 @@ const EmbedWorkflowCheckbox = ({ nodeId }: { nodeId: string }) => { return ( - Workflow + + {t('metadata.workflow')} + { const { status, progress, progressImage } = nodeExecutionState; const { t } = useTranslation(); if (status === NodeStatus.PENDING) { - return Pending; + return {t('queue.pending')}; } if (status === NodeStatus.IN_PROGRESS) { if (progressImage) { diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeUnknownFallback.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeUnknownFallback.tsx index 7ec59f00f0..cd95e2f79f 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeUnknownFallback.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeUnknownFallback.tsx @@ -3,6 +3,7 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; import { memo } from 'react'; import NodeCollapseButton from '../common/NodeCollapseButton'; import NodeWrapper from '../common/NodeWrapper'; +import { useTranslation } from 'react-i18next'; type Props = { nodeId: string; @@ -19,6 +20,7 @@ const InvocationNodeUnknownFallback = ({ type, selected, }: Props) => { + const { t } = useTranslation(); return ( - Unknown node type: + {t('nodes.unknownNodeType')}: {type} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/SaveToGalleryCheckbox.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/SaveToGalleryCheckbox.tsx index 2779cf13dc..d721db62e0 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/SaveToGalleryCheckbox.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/SaveToGalleryCheckbox.tsx @@ -4,8 +4,10 @@ import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput'; import { useIsIntermediate } from 'features/nodes/hooks/useIsIntermediate'; import { nodeIsIntermediateChanged } from 'features/nodes/store/nodesSlice'; import { ChangeEvent, memo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => { + const { t } = useTranslation(); const dispatch = useAppDispatch(); const hasImageOutput = useHasImageOutput(nodeId); const isIntermediate = useIsIntermediate(nodeId); @@ -27,7 +29,9 @@ const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => { return ( - Save to Gallery + + {t('hotkeys.saveToGallery.title')} + { const dispatch = useAppDispatch(); @@ -18,10 +19,12 @@ const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => { }, [dispatch, nodeId] ); - + const { t } = useTranslation(); return ( - Use Cache + + {t('invocationCache.useCache')} + { icon={} onClick={handleExposeField} > - Add to Linear View + {t('nodes.addLinearView')} ); } @@ -90,7 +90,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => { icon={} onClick={handleUnexposeField} > - Remove from Linear View + {t('nodes.removeLinearView')} ); } @@ -102,6 +102,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => { isExposed, mayExpose, nodeId, + t, ]); const renderMenuFunc = useCallback( 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 be66214a59..9973f0bc81 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 @@ -49,8 +49,16 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => { {fieldTemplate.description} )} - {fieldTemplate && Type: {FIELDS[fieldTemplate.type].title}} - {isInputTemplate && Input: {startCase(fieldTemplate.input)}} + {fieldTemplate && ( + + {t('parameters.type')}: {FIELDS[fieldTemplate.type].title} + + )} + {isInputTemplate && ( + + {t('common.input')}: {startCase(fieldTemplate.input)} + + )} ); }; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx index bee5264e00..4a48971602 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx @@ -7,6 +7,7 @@ import EditableFieldTitle from './EditableFieldTitle'; import FieldContextMenu from './FieldContextMenu'; import FieldHandle from './FieldHandle'; import InputFieldRenderer from './InputFieldRenderer'; +import { useTranslation } from 'react-i18next'; interface Props { nodeId: string; @@ -14,6 +15,7 @@ interface Props { } const InputField = ({ nodeId, fieldName }: Props) => { + const { t } = useTranslation(); const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'input'); const doesFieldHaveValue = useDoesInputHaveValue(nodeId, fieldName); @@ -49,7 +51,7 @@ const InputField = ({ nodeId, fieldName }: Props) => { - Unknown input: {fieldName} + {t('nodes.unknownInput')}: {fieldName} ); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer.tsx index 13695c44c3..201a2bce28 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer.tsx @@ -18,6 +18,7 @@ import VaeModelInputField from './inputs/VaeModelInputField'; import IPAdapterModelInputField from './inputs/IPAdapterModelInputField'; import T2IAdapterModelInputField from './inputs/T2IAdapterModelInputField'; import BoardInputField from './inputs/BoardInputField'; +import { useTranslation } from 'react-i18next'; type InputFieldProps = { nodeId: string; @@ -25,11 +26,16 @@ type InputFieldProps = { }; const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => { + const { t } = useTranslation(); const field = useFieldData(nodeId, fieldName); const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'input'); if (fieldTemplate?.fieldKind === 'output') { - return Output field in input: {field?.type}; + return ( + + {t('nodes.outputFieldInInput')}: {field?.type} + + ); } if ( @@ -249,7 +255,7 @@ const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => { _dark: { color: 'error.300' }, }} > - Unknown field type: {field?.type} + {t('nodes.unknownFieldType')}: {field?.type} ); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputField.tsx index e717423f65..4b7ca647f8 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputField.tsx @@ -5,6 +5,7 @@ import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants'; import { PropsWithChildren, memo } from 'react'; import FieldHandle from './FieldHandle'; import FieldTooltipContent from './FieldTooltipContent'; +import { useTranslation } from 'react-i18next'; interface Props { nodeId: string; @@ -12,6 +13,7 @@ interface Props { } const OutputField = ({ nodeId, fieldName }: Props) => { + const { t } = useTranslation(); const fieldTemplate = useFieldTemplate(nodeId, fieldName, 'output'); const { @@ -28,7 +30,7 @@ const OutputField = ({ nodeId, fieldName }: Props) => { - Unknown output: {fieldName} + {t('nodes.unknownOutput')}: {fieldName} ); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageInputField.tsx index 6099593c2a..94095f2612 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageInputField.tsx @@ -16,6 +16,7 @@ import { ImagePolymorphicInputFieldValue, } from 'features/nodes/types/types'; import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import { FaUndo } from 'react-icons/fa'; import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { PostUploadAction } from 'services/api/types'; @@ -103,18 +104,24 @@ const ImageInputFieldComponent = ( export default memo(ImageInputFieldComponent); -const UploadElement = memo(() => ( - - Drop or Upload - -)); +const UploadElement = memo(() => { + const { t } = useTranslation(); + return ( + + {t('gallery.dropOrUpload')} + + ); +}); UploadElement.displayName = 'UploadElement'; -const DropLabel = memo(() => ( - - Drop - -)); +const DropLabel = memo(() => { + const { t } = useTranslation(); + return ( + + {t('gallery.drop')} + + ); +}); DropLabel.displayName = 'DropLabel'; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LoRAModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LoRAModelInputField.tsx index 712b1ff5a0..dc79436ec6 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LoRAModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LoRAModelInputField.tsx @@ -91,7 +91,7 @@ const LoRAModelInputFieldComponent = ( return ( - No LoRAs Loaded + {t('models.noLoRAsLoaded')} ); 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 a627f33f24..d906557dd3 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 @@ -90,7 +90,7 @@ const Content = (props: { - Node Type + {t('nodes.nodeType')} {props.template.title} @@ -102,7 +102,7 @@ const Content = (props: { w="full" > - Node Version + {t('nodes.nodeVersion')} {props.node.data.version} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx index ee08d4ca99..56054e3505 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/InitialImageDisplay.tsx @@ -10,6 +10,7 @@ import { memo, useCallback } from 'react'; import { FaUndo, FaUpload } from 'react-icons/fa'; import InitialImage from './InitialImage'; import { PostUploadAction } from 'services/api/types'; +import { useTranslation } from 'react-i18next'; const selector = createSelector( [stateSelector], @@ -38,6 +39,8 @@ const InitialImageDisplay = () => { dispatch(clearInitialImage()); }, [dispatch]); + const { t } = useTranslation(); + return ( { }, }} > - Initial Image + {t('metadata.initImage')} { flexDir="column" > - Error + {t('common.error')}
{queueItem.error}
diff --git a/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx b/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx index 10349afd52..bb26d18ae0 100644 --- a/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx +++ b/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx @@ -1,3 +1,5 @@ +/* eslint-disable i18next/no-literal-string */ + import { Flex, Image, Text } from '@chakra-ui/react'; import InvokeAILogoImage from 'assets/images/logo.png'; import { AnimatePresence, motion } from 'framer-motion'; diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index 79fb7807a5..45edba4fe6 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -423,7 +423,8 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { - {t('settings.resetComplete')} Reloading in {countdown}... + {t('settings.resetComplete')} {t('settings.reloadingIn')}{' '} + {countdown}... diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock index 38c40de411..2b5beeac76 100644 --- a/invokeai/frontend/web/yarn.lock +++ b/invokeai/frontend/web/yarn.lock @@ -3517,6 +3517,14 @@ eslint-config-prettier@^9.0.0: resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== +eslint-plugin-i18next@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-i18next/-/eslint-plugin-i18next-6.0.3.tgz#a388f9982deb040102c1c4498e4dc38d9bd6ffd9" + integrity sha512-RtQXYfg6PZCjejIQ/YG+dUj/x15jPhufJ9hUDGH0kCpJ6CkVMAWOQ9exU1CrbPmzeykxLjrXkjAaOZF/V7+DOA== + dependencies: + lodash "^4.17.21" + requireindex "~1.1.0" + eslint-plugin-react-hooks@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" @@ -5617,6 +5625,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +requireindex@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162" + integrity sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg== + requirejs-config-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc"