Skip to content

Commit

Permalink
[i18n] Translate node input/output label and tooltip (#1860)
Browse files Browse the repository at this point in the history
* [i18n] Translate node input/output labels

* nit

* Impl actual translation

* Remove outputs where name is the type

* Pick outputs key

* Translate outputs

* Properly persist label

* Translate input tooltips

* Translate single node

* Translate first node

* Translate 10 nodes

* Exclude input name translation

* Update test expectations [skip ci]

* nit

---------

Co-authored-by: github-actions <[email protected]>
  • Loading branch information
huchenlei and github-actions authored Dec 11, 2024
1 parent 86797d2 commit ac6130a
Show file tree
Hide file tree
Showing 11 changed files with 2,410 additions and 304 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 84 additions & 35 deletions scripts/collect-i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
import { CORE_MENU_COMMANDS } from '../src/constants/coreMenuCommands'
import { SERVER_CONFIG_ITEMS } from '../src/constants/serverConfig'
import { formatCamelCase, normalizeI18nKey } from '../src/utils/formatUtil'
import { ComfyNodeDefImpl } from '../src/stores/nodeDefStore'
import type { ComfyCommandImpl } from '../src/stores/commandStore'
import type { FormItem, SettingParams } from '../src/types/settingTypes'
import type { ComfyApi } from '../src/scripts/api'
import type { ComfyNodeDef } from '../src/types/apiTypes'

const localePath = './src/locales/en.json'
const extractMenuCommandLocaleStrings = (): Set<string> => {
Expand Down Expand Up @@ -111,58 +111,107 @@ test('collect-i18n', async ({ comfyPage }) => {
)

// Node Definitions
const nodeDefs = (await comfyPage.page.evaluate(async () => {
const api = window['app'].api as ComfyApi
return await api.getNodeDefs()
})) as Record<string, ComfyNodeDef>
const nodeDefs: ComfyNodeDefImpl[] = Object.values(
await comfyPage.page.evaluate(async () => {
const api = window['app'].api as ComfyApi
return await api.getNodeDefs()
})
).map((def) => new ComfyNodeDefImpl(def))

console.log(`Collected ${nodeDefs.length} node definitions`)

const allDataTypesLocale = Object.fromEntries(
nodeDefs
.flatMap((nodeDef) => {
const inputDataTypes = Object.values(nodeDef.inputs.all).map(
(inputSpec) => inputSpec.type
)
const outputDataTypes = nodeDef.outputs.all.map((output) => output.type)
const allDataTypes = [...inputDataTypes, ...outputDataTypes].flatMap(
(type: string) => type.split(',')
)
return allDataTypes.map((dataType) => [
normalizeI18nKey(dataType),
dataType
])
})
.sort((a, b) => a[0].localeCompare(b[0]))
)

function extractInputs(nodeDef: ComfyNodeDefImpl) {
const inputs = Object.fromEntries(
nodeDef.inputs.all.flatMap((input) => {
// TODO(huchenlei): translate input name. Somehow `CLIPAttentionMultiply` will
// cause all subsequent translations to fail (Raw english values
// are generated).
const name = undefined
const tooltip = input.tooltip

if (name === undefined && tooltip === undefined) {
return []
}

return [
[
normalizeI18nKey(input.name),
{
name,
tooltip
}
]
]
})
)
return Object.keys(inputs).length > 0 ? inputs : undefined
}

function extractOutputs(nodeDef: ComfyNodeDefImpl) {
const outputs = Object.fromEntries(
nodeDef.outputs.all.flatMap((output, i) => {
// Ignore data types if they are already translated in allDataTypesLocale.
const name = output.name in allDataTypesLocale ? undefined : output.name
const tooltip = output.tooltip

if (name === undefined && tooltip === undefined) {
return []
}

return [
[
i.toString(),
{
name,
tooltip
}
]
]
})
)
return Object.keys(outputs).length > 0 ? outputs : undefined
}

const allNodeDefsLocale = Object.fromEntries(
Object.values(nodeDefs)
nodeDefs
.sort((a, b) => a.name.localeCompare(b.name))
.map((nodeDef) => [
normalizeI18nKey(nodeDef.name),
{
display_name: nodeDef.display_name ?? nodeDef.name,
description: nodeDef.description || undefined
description: nodeDef.description || undefined,
inputs: extractInputs(nodeDef),
outputs: extractOutputs(nodeDef)
}
])
)

const allNodeCategoriesLocale = Object.fromEntries(
Object.values(nodeDefs).flatMap((nodeDef) =>
nodeDefs.flatMap((nodeDef) =>
nodeDef.category
.split('/')
.map((category) => [normalizeI18nKey(category), category])
)
)

const allDataTypesLocale = Object.fromEntries(
Object.values(nodeDefs).flatMap((nodeDef) => {
const inputs = nodeDef.input ?? {}
const requiredInputs = inputs.required ?? {}
const optionalInputs = inputs.optional ?? {}
const allInputs = {
...requiredInputs,
...optionalInputs
}

const inputDataTypes = Object.values(allInputs).map((inputSpec) => {
const typeRaw = inputSpec[0]
const type = Array.isArray(typeRaw) ? 'COMBO' : typeRaw
return type
})
const outputDataTypes = nodeDef.output ?? []
const allDataTypes = [...inputDataTypes, ...outputDataTypes].flatMap(
(type: string) => type.split(',')
)

return allDataTypes.map((dataType) => [
normalizeI18nKey(dataType),
dataType
])
})
)

fs.writeFileSync(
localePath,
JSON.stringify(
Expand Down
21 changes: 17 additions & 4 deletions src/components/graph/NodeTooltip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { LiteGraph } from '@comfyorg/litegraph'
import { app as comfyApp } from '@/scripts/app'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useEventListener } from '@vueuse/core'
import { st } from '@/i18n'
import { normalizeI18nKey } from '@/utils/formatUtil'
let idleTimeout: number
const nodeDefStore = useNodeDefStore()
Expand Down Expand Up @@ -69,7 +71,11 @@ const onIdle = () => {
)
if (inputSlot !== -1) {
const inputName = node.inputs[inputSlot].name
return showTooltip(nodeDef.inputs.getInput(inputName)?.tooltip)
const translatedTooltip = st(
`nodeDefs.${normalizeI18nKey(node.type)}.inputs.${normalizeI18nKey(inputName)}.tooltip`,
nodeDef.inputs.getInput(inputName)?.tooltip
)
return showTooltip(translatedTooltip)
}
const outputSlot = canvas.isOverNodeOutput(
Expand All @@ -79,15 +85,22 @@ const onIdle = () => {
[0, 0]
)
if (outputSlot !== -1) {
return showTooltip(nodeDef.outputs.all?.[outputSlot]?.tooltip)
const translatedTooltip = st(
`nodeDefs.${normalizeI18nKey(node.type)}.outputs.${outputSlot}.tooltip`,
nodeDef.outputs.all?.[outputSlot]?.tooltip
)
return showTooltip(translatedTooltip)
}
const widget = comfyApp.canvas.getWidgetAtCursor()
// Dont show for DOM widgets, these use native browser tooltips as we dont get proper mouse events on these
if (widget && !widget.element) {
return showTooltip(
widget.tooltip ?? nodeDef.inputs.getInput(widget.name)?.tooltip
const translatedTooltip = st(
`nodeDefs.${normalizeI18nKey(node.type)}.inputs.${normalizeI18nKey(widget.name)}.tooltip`,
nodeDef.inputs.getInput(widget.name)?.tooltip
)
// Widget tooltip can be set dynamically, current translation collection does not support this.
return showTooltip(widget.tooltip ?? translatedTooltip)
}
}
Expand Down
Loading

0 comments on commit ac6130a

Please sign in to comment.