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"