diff --git a/.gitignore b/.gitignore
index 0177e1d7d..6b28b976f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ venv/
web/extensions/*
!web/extensions/logging.js.example
!web/extensions/core/
+!web/extensions/i18n/
diff --git a/web/extensions/core/colorPalette.js b/web/extensions/core/colorPalette.js
index 3695b08e2..d414d4298 100644
--- a/web/extensions/core/colorPalette.js
+++ b/web/extensions/core/colorPalette.js
@@ -435,7 +435,7 @@ app.registerExtension({
$el("td", [
$el("label", {
for: id.replaceAll(".", "-"),
- textContent: "Color palette",
+ textContent: i18next.t("settings.Comfy.ColorPalette"),
}),
]),
$el("td", [
@@ -449,7 +449,7 @@ app.registerExtension({
}, [
$el("input", {
type: "button",
- value: "Export",
+ value: i18next.t("settings.Comfy.ColorPalette.export"),
onclick: async () => {
const colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId);
const colorPalette = await completeColorPalette(getColorPalette(colorPaletteId));
@@ -471,14 +471,14 @@ app.registerExtension({
}),
$el("input", {
type: "button",
- value: "Import",
+ value: i18next.t("settings.Comfy.ColorPalette.import"),
onclick: () => {
fileInput.click();
}
}),
$el("input", {
type: "button",
- value: "Template",
+ value: i18next.t("settings.Comfy.ColorPalette.template"),
onclick: async () => {
const colorPalette = await getColorPaletteTemplate();
const json = JSON.stringify(colorPalette, null, 2); // convert the data to a JSON string
@@ -499,7 +499,7 @@ app.registerExtension({
}),
$el("input", {
type: "button",
- value: "Delete",
+ value: i18next.t("settings.Comfy.ColorPalette.delete"),
onclick: async () => {
let colorPaletteId = app.ui.settings.getSettingValue(id, defaultColorPaletteId);
diff --git a/web/extensions/core/i18n.js b/web/extensions/core/i18n.js
new file mode 100644
index 000000000..883fed988
--- /dev/null
+++ b/web/extensions/core/i18n.js
@@ -0,0 +1,46 @@
+import { app } from "../../scripts/app.js";
+
+app.registerExtension({
+ name: "i18next",
+ addCustomNodeDefs(defs) {
+ for (const k in defs) {
+ defs[k].display_name = i18next.t(`node.title.${k}`)
+
+ if ("input" in defs[k] && defs[k].input.required) {
+ for (const i in defs[k].input.required) {
+ if (defs[k].input.required[i].length > 1) {
+ defs[k].input.required[i][1].label = i18next.t(`node.input.${k}.${i}`)
+ } else {
+ defs[k].input.required[i].push({ label: i18next.t(`node.input.${k}.${i}`) })
+ }
+ }
+ }
+ }
+ },
+ nodeCreated(node) {
+ if ("inputs" in node) {
+ for (const item of node.inputs) {
+ item.label = i18next.t(`node.input.${node.comfyClass}.${item.name}`)
+ }
+ }
+
+ if ("widgets" in node) {
+ for (const item of node.widgets) {
+ item.label = i18next.t(`node.input.${node.comfyClass}.${item.name}`)
+ }
+ }
+
+ if ("outputs" in node) {
+ for (const item of node.outputs) {
+ item.label = i18next.t(`node.output.${node.comfyClass}.${item.name}`)
+ }
+ }
+ },
+ afterNodesRegistrations() {
+ const defs = LiteGraph.registered_node_types
+
+ for (const k in defs) {
+ defs[k].category = i18next.t(`category.${defs[k].category}`)
+ }
+ }
+})
\ No newline at end of file
diff --git a/web/extensions/core/widgetInputs.js b/web/extensions/core/widgetInputs.js
index d9eaf8a0c..e4ab088dd 100644
--- a/web/extensions/core/widgetInputs.js
+++ b/web/extensions/core/widgetInputs.js
@@ -57,6 +57,7 @@ function convertToInput(node, widget, config) {
const sz = node.size;
node.addInput(widget.name, linkType, {
widget: { name: widget.name, config },
+ label: config?.[1] ? i18next.t(config?.[1].label) : void 0,
});
for (const widget of node.widgets) {
diff --git a/web/i18n/en_US.js b/web/i18n/en_US.js
new file mode 100644
index 000000000..4012f47a6
--- /dev/null
+++ b/web/i18n/en_US.js
@@ -0,0 +1,125 @@
+export default {
+ translation: {
+ "ui.queue_btn": "Queue Prompt",
+ "ui.queue_front_btn": "Queue Front",
+ "ui.view_queue_btn": "View Queue",
+ "ui.view_history_btn": "View History",
+ "ui.save_btn": "Save",
+ "ui.load_btn": "Load",
+ "ui.refresh_btn": "Refresh",
+ "ui.clipspace_btn": "Clipspace",
+ "ui.clear_btn": "Clear",
+ "ui.load_default_btn": "Load Default",
+ "ui.close_btn": "Close",
+ "ui.queue_size": "Queue size: ",
+ "ui.extra_options": "Extra options",
+
+ "ui.settings.title": "Settings",
+
+ "ui.canvas_menu_add_node": "Add Node",
+ "ui.canvas_menu_add_group": "Add Group",
+
+ "ui.node_panel.header.properties": "Properties",
+ "ui.node_panel.header.title": "Title",
+ "ui.node_panel.header.mode": "Mode",
+ "ui.node_panel.header.color": "Color",
+
+ "node.title.KSampler": "KSampler",
+ "node.title.KSamplerAdvanced": "KSampler (Advanced)",
+ // Loaders
+ "node.title.CheckpointLoader": "Load Checkpoint (With Config)",
+ "node.title.CheckpointLoaderSimple": "Load Checkpoint",
+ "node.title.VAELoader": "Load VAE",
+ "node.title.LoraLoader": "Load LoRA",
+ "node.title.CLIPLoader": "Load CLIP",
+ "node.title.ControlNetLoader": "Load ControlNet Model",
+ "node.title.DiffControlNetLoader": "Load ControlNet Model (diff)",
+ "node.title.StyleModelLoader": "Load Style Model",
+ "node.title.CLIPVisionLoader": "Load CLIP Vision",
+ "node.title.UpscaleModelLoader": "Load Upscale Model",
+ // Conditioning
+ "node.title.CLIPVisionEncode": "CLIP Vision Encode",
+ "node.title.StyleModelApply": "Apply Style Model",
+ "node.title.CLIPTextEncode": "CLIP Text Encode (Prompt)",
+ "node.title.CLIPSetLastLayer": "CLIP Set Last Layer",
+ "node.title.ConditioningCombine": "Conditioning (Combine)",
+ "node.title.ConditioningAverage ": "Conditioning (Average)",
+ "node.title.ConditioningConcat": "Conditioning (Concat)",
+ "node.title.ConditioningSetArea": "Conditioning (Set Area)",
+ "node.title.ConditioningSetMask": "Conditioning (Set Mask)",
+ "node.title.ControlNetApply": "Apply ControlNet",
+ "node.title.ControlNetApplyAdvanced": "Apply ControlNet (Advanced)",
+ // Latent
+ "node.title.VAEEncodeForInpaint": "VAE Encode (for Inpainting)",
+ "node.title.SetLatentNoiseMask": "Set Latent Noise Mask",
+ "node.title.VAEDecode": "VAE Decode",
+ "node.title.VAEEncode": "VAE Encode",
+ "node.title.LatentRotate": "Rotate Latent",
+ "node.title.LatentFlip": "Flip Latent",
+ "node.title.LatentCrop": "Crop Latent",
+ "node.title.EmptyLatentImage": "Empty Latent Image",
+ "node.title.LatentUpscale": "Upscale Latent",
+ "node.title.LatentUpscaleBy": "Upscale Latent By",
+ "node.title.LatentComposite": "Latent Composite",
+ "node.title.LatentBlend": "Latent Blend",
+ "LatentFromBatch": "Latent From Batch",
+ "node.title.RepeatLatentBatch": "Repeat Latent Batch",
+ // Image
+ "node.title.SaveImage": "Save Image",
+ "node.title.PreviewImage": "Preview Image",
+ "node.title.LoadImage": "Load Image",
+ "node.title.LoadImageMask": "Load Image (as Mask)",
+ "node.title.ImageScale": "Upscale Image",
+ "node.title.ImageScaleBy": "Upscale Image By",
+ "node.title.ImageUpscaleWithModel": "Upscale Image (using Model)",
+ "node.title.ImageInvert": "Invert Image",
+ "node.title.ImagePadForOutpaint": "Pad Image for Outpainting",
+ // _for_testing
+ "node.title.VAEDecodeTiled": "VAE Decode (Tiled)",
+ "node.title.VAEEncodeTiled": "VAE Encode (Tiled)",
+
+ "node.input.SaveImage.filename_prefix": "filename_prefix",
+ "node.input.SaveImage.images": "images",
+
+ "node.output.CheckpointLoaderSimple.MODEL": "MODEL",
+
+ "category.conditioning": "conditioning",
+ "category.loaders": "loaders",
+ "category.latent": "latent",
+ "category.latent/inpaint": "latent/inpaint",
+ "category.latent/batch": "latent/batch",
+ "category.image": "image",
+ "category.mask": "mask",
+ "category.image/upscaling": "image/upscaling",
+ "category.sampling": "sampling",
+ "category._for_testing": "_for_testing",
+ "category.latent/transform": "latent/transform",
+ "category.advanced/loaders": "advanced/loaders",
+ "category.conditioning/style_model": "conditioning/style_model",
+ "category.conditioning/gligen": "conditioning/gligen",
+ "category.advanced/loaders/deprecated": "advanced/loaders/deprecated",
+ "category.advanced/conditioning": "advanced/conditioning",
+ "category.image/postprocessing": "image/postprocessing",
+ "category.advanced/model_merging": "advanced/model_merging",
+ "category.image/preprocessors": "image/preprocessors",
+ "category.utils": "utils",
+
+ "settings.Comfy.ConfirmClear": "Require confirmation when clearing workflow",
+ "settings.Comfy.PromptFilename": "Prompt for filename when saving workflow",
+ "settings.Comfy.PreviewFormat": "When displaying a preview in the image widget, convert it to a lightweight image, e.g. webp, jpeg, webp;50, etc.",
+ "settings.Comfy.DisableSliders": "Disable sliders.",
+ "settings.Comfy.DevMode": "Enable Dev mode Options",
+ "settings.Comfy.ColorPalette": "Color Palette",
+ "settings.Comfy.EditAttention.Delta": "Ctrl+up/down precision",
+ "settings.Comfy.InvertMenuScrolling": "Invert Menu Scrolling",
+ "settings.Comfy.LinkRenderMode": "Link Render Mode",
+ "settings.Comfy.NodeSuggestions.number": "Number of nodes suggestions",
+ "settings.Comfy.SnapToGrid.GridSize": "Grid Size",
+ "settings.Comfy.Logging.Enabled": "Comfy.Logging.Enabled",
+ "settings.Comfy.MenuPosition": "Save menu position",
+ "settings.Comfy.ColorPalette.export": "Export",
+ "settings.Comfy.ColorPalette.import": "Import",
+ "settings.Comfy.ColorPalette.template": "Template",
+ "settings.Comfy.ColorPalette.delete": "Delete",
+ }
+}
\ No newline at end of file
diff --git a/web/i18n/zh_CN.js b/web/i18n/zh_CN.js
new file mode 100644
index 000000000..b018a668e
--- /dev/null
+++ b/web/i18n/zh_CN.js
@@ -0,0 +1,125 @@
+export default {
+ translation: {
+ "ui.queue_btn": "冲冲冲",
+ "ui.queue_front_btn": "插队冲冲冲",
+ "ui.view_queue_btn": "查看队列",
+ "ui.view_history_btn": "查看历史",
+ "ui.save_btn": "保存",
+ "ui.load_btn": "加载",
+ "ui.refresh_btn": "刷新",
+ "ui.clipspace_btn": "Clipspace",
+ "ui.clear_btn": "清空",
+ "ui.load_default_btn": "加载默认配置",
+ "ui.close_btn": "关闭",
+ "ui.queue_size": "队列数量: ",
+ "ui.extra_options": "额外选项",
+
+ "ui.settings.title": "设置",
+
+ "ui.canvas_menu_add_node": "添加节点",
+ "ui.canvas_menu_add_group": "添加组",
+
+ "ui.node_panel.header.properties": "属性",
+ "ui.node_panel.header.title": "标题",
+ "ui.node_panel.header.mode": "模式",
+ "ui.node_panel.header.color": "颜色",
+
+ "node.title.KSampler": "采样器",
+ "node.title.KSamplerAdvanced": "采样器 (高级)",
+ // Loaders
+ "node.title.CheckpointLoader": "Load Checkpoint (With Config)",
+ "node.title.CheckpointLoaderSimple": "加载模型",
+ "node.title.VAELoader": "Load VAE",
+ "node.title.LoraLoader": "Load LoRA",
+ "node.title.CLIPLoader": "Load CLIP",
+ "node.title.ControlNetLoader": "Load ControlNet Model",
+ "node.title.DiffControlNetLoader": "Load ControlNet Model (diff)",
+ "node.title.StyleModelLoader": "Load Style Model",
+ "node.title.CLIPVisionLoader": "Load CLIP Vision",
+ "node.title.UpscaleModelLoader": "Load Upscale Model",
+ // Conditioning
+ "node.title.CLIPVisionEncode": "CLIP Vision Encode",
+ "node.title.StyleModelApply": "Apply Style Model",
+ "node.title.CLIPTextEncode": "CLIP Text Encode (Prompt)",
+ "node.title.CLIPSetLastLayer": "CLIP Set Last Layer",
+ "node.title.ConditioningCombine": "Conditioning (Combine)",
+ "node.title.ConditioningAverage ": "Conditioning (Average)",
+ "node.title.ConditioningConcat": "Conditioning (Concat)",
+ "node.title.ConditioningSetArea": "Conditioning (Set Area)",
+ "node.title.ConditioningSetMask": "Conditioning (Set Mask)",
+ "node.title.ControlNetApply": "Apply ControlNet",
+ "node.title.ControlNetApplyAdvanced": "Apply ControlNet (Advanced)",
+ // Latent
+ "node.title.VAEEncodeForInpaint": "VAE Encode (for Inpainting)",
+ "node.title.SetLatentNoiseMask": "Set Latent Noise Mask",
+ "node.title.VAEDecode": "VAE Decode",
+ "node.title.VAEEncode": "VAE Encode",
+ "node.title.LatentRotate": "Rotate Latent",
+ "node.title.LatentFlip": "Flip Latent",
+ "node.title.LatentCrop": "Crop Latent",
+ "node.title.EmptyLatentImage": "Empty Latent Image",
+ "node.title.LatentUpscale": "Upscale Latent",
+ "node.title.LatentUpscaleBy": "Upscale Latent By",
+ "node.title.LatentComposite": "Latent Composite",
+ "node.title.LatentBlend": "Latent Blend",
+ "LatentFromBatch": "Latent From Batch",
+ "node.title.RepeatLatentBatch": "Repeat Latent Batch",
+ // Image
+ "node.title.SaveImage": "Save Image",
+ "node.title.PreviewImage": "Preview Image",
+ "node.title.LoadImage": "Load Image",
+ "node.title.LoadImageMask": "Load Image (as Mask)",
+ "node.title.ImageScale": "Upscale Image",
+ "node.title.ImageScaleBy": "Upscale Image By",
+ "node.title.ImageUpscaleWithModel": "Upscale Image (using Model)",
+ "node.title.ImageInvert": "Invert Image",
+ "node.title.ImagePadForOutpaint": "Pad Image for Outpainting",
+ // _for_testing
+ "node.title.VAEDecodeTiled": "VAE Decode (Tiled)",
+ "node.title.VAEEncodeTiled": "VAE Encode (Tiled)",
+
+ "node.input.SaveImage.filename_prefix": "文件名前缀",
+ "node.input.SaveImage.images": "图片",
+
+ "node.output.CheckpointLoaderSimple.MODEL": "模型",
+
+ "category.conditioning": "可调参数",
+ "category.loaders": "加载器",
+ "category.latent": "潜在",
+ "category.latent/inpaint": "潜在/修复",
+ "category.latent/batch": "潜在/批量",
+ "category.image": "图像",
+ "category.mask": "遮罩",
+ "category.image/upscaling": "图像/外扩",
+ "category.sampling": "采样",
+ "category._for_testing": "测试",
+ "category.latent/transform": "潜在/转换",
+ "category.advanced/loaders": "高级/加载器",
+ "category.conditioning/style_model": "可调参数/风格模型",
+ "category.conditioning/gligen": "可调参数/gligen",
+ "category.advanced/loaders/deprecated": "高级/加载器/已弃用",
+ "category.advanced/conditioning": "高级/可调参数",
+ "category.image/postprocessing": "图像/后期处理",
+ "category.advanced/model_merging": "高级/模型合并",
+ "category.image/preprocessors": "图像/前期处理",
+ "category.utils": "工具",
+
+ "settings.Comfy.ConfirmClear": "清空工作流需要确认",
+ "settings.Comfy.PromptFilename": "Prompt for filename when saving workflow",
+ "settings.Comfy.PreviewFormat": "预览图格式和压缩尺寸, e.g. webp, jpeg, webp;50, etc.",
+ "settings.Comfy.DisableSliders": "Disable sliders.",
+ "settings.Comfy.DevMode": "启用开发模式",
+ "settings.Comfy.ColorPalette": "主题",
+ "settings.Comfy.EditAttention.Delta": "Ctrl+up/down precision",
+ "settings.Comfy.InvertMenuScrolling": "反转滚动",
+ "settings.Comfy.LinkRenderMode": "链接渲染模式",
+ "settings.Comfy.NodeSuggestions.number": "Number of nodes suggestions",
+ "settings.Comfy.SnapToGrid.GridSize": "单元格尺寸",
+ "settings.Comfy.Logging.Enabled": "记录日志",
+ "settings.Comfy.MenuPosition": "保存菜单位置",
+ "settings.Comfy.ColorPalette.export": "导出",
+ "settings.Comfy.ColorPalette.import": "导入",
+ "settings.Comfy.ColorPalette.template": "模版",
+ "settings.Comfy.ColorPalette.delete": "删除",
+ }
+}
\ No newline at end of file
diff --git a/web/index.html b/web/index.html
index 71067d993..c3620a134 100644
--- a/web/index.html
+++ b/web/index.html
@@ -6,6 +6,8 @@
+
+