Translate command label on top command dropdown menu (#1792)

* collect i18n with playwright

* Add command label translation

* Normalize i18n object key
This commit is contained in:
Chenlei Hu
2024-12-04 10:19:53 -08:00
committed by GitHub
parent 2caa87d35d
commit 7986aebf27
12 changed files with 317 additions and 24 deletions

View File

@@ -29,7 +29,8 @@
"preview": "vite preview",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"locale": "lobe-i18n locale"
"locale": "lobe-i18n locale",
"collect-i18n": "playwright test --config=playwright.i18n.config.ts"
},
"devDependencies": {
"@babel/core": "^7.24.7",

14
playwright.i18n.config.ts Normal file
View File

@@ -0,0 +1,14 @@
import { PlaywrightTestConfig } from '@playwright/test'
const config: PlaywrightTestConfig = {
testDir: './scripts',
use: {
baseURL: 'http://localhost:5173',
headless: true
},
reporter: 'list',
timeout: 10000,
testMatch: /collect-i18n\.ts/
}
export default config

50
scripts/collect-i18n.ts Normal file
View File

@@ -0,0 +1,50 @@
import * as fs from 'fs'
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
import type { ComfyCommandImpl } from '../src/stores/commandStore'
import { CORE_MENU_COMMANDS } from '../src/constants/coreMenuCommands'
import { normalizeI18nKey } from '../src/utils/formatUtil'
const localePath = './src/locales/en.json'
const extractMenuCommandLocaleStrings = (): Set<string> => {
const labels = new Set<string>()
for (const [category, _] of CORE_MENU_COMMANDS) {
category.forEach((category) => labels.add(category))
}
return labels
}
test('collect-i18n', async ({ comfyPage }) => {
const commands = await comfyPage.page.evaluate(() => {
const workspace = window['app'].extensionManager
const commands = workspace.command.commands as ComfyCommandImpl[]
return commands.map((command) => ({
id: command.id,
label: command.label,
menubarLabel: command.menubarLabel
}))
})
const locale = JSON.parse(fs.readFileSync(localePath, 'utf-8'))
const menuLabels = extractMenuCommandLocaleStrings()
const commandMenuLabels = new Set(
commands.map((command) => command.menubarLabel ?? command.label ?? '')
)
const allLabels = new Set([...menuLabels, ...commandMenuLabels])
allLabels.delete('')
const allLabelsLocale = Object.fromEntries(
Array.from(allLabels).map((label) => [normalizeI18nKey(label), label])
)
fs.writeFileSync(
localePath,
JSON.stringify(
{
...locale,
menuLabels: allLabelsLocale
},
null,
2
)
)
})

View File

@@ -1,6 +1,5 @@
import fs from 'fs'
import { CORE_SETTINGS } from '../src/constants/coreSettings'
import { CORE_MENU_COMMANDS } from '../src/constants/coreMenuCommands'
interface SettingLocale {
name: string
@@ -20,17 +19,8 @@ const extractSettingLocaleStrings = (): Record<string, SettingLocale> => {
)
}
const extractMenuCommandLocaleStrings = (): Record<string, string> => {
const labels = new Set<string>()
for (const [category, _] of CORE_MENU_COMMANDS) {
category.forEach((category) => labels.add(category))
}
return Object.fromEntries(Array.from(labels).map((label) => [label, label]))
}
const main = () => {
const settingLocaleStrings = extractSettingLocaleStrings()
const menuCommandLocaleStrings = extractMenuCommandLocaleStrings()
const localePath = './src/locales/en.json'
const globalLocale = JSON.parse(fs.readFileSync(localePath, 'utf-8'))
@@ -42,10 +32,6 @@ const main = () => {
settingsDialog: {
...(globalLocale.settingsDialog ?? {}),
...settingLocaleStrings
},
menuLabels: {
...(globalLocale.menuLabels ?? {}),
...menuCommandLocaleStrings
}
},
null,

View File

@@ -31,6 +31,7 @@
<script setup lang="ts">
import { useMenuItemStore } from '@/stores/menuItemStore'
import { useSettingStore } from '@/stores/settingStore'
import { normalizeI18nKey } from '@/utils/formatUtil'
import Menubar from 'primevue/menubar'
import type { MenuItem } from 'primevue/menuitem'
import { computed } from 'vue'
@@ -44,11 +45,9 @@ const dropdownDirection = computed(() =>
const menuItemsStore = useMenuItemStore()
const { t } = useI18n()
const translateMenuItem = (item: MenuItem): MenuItem => {
const translatedLabel = item.label
? t(
`menuLabels.${item.label}`,
typeof item.label === 'function' ? item.label() : item.label
)
const label = typeof item.label === 'function' ? item.label() : item.label
const translatedLabel = label
? t(`menuLabels.${normalizeI18nKey(label)}`, label)
: undefined
return {

View File

@@ -490,6 +490,64 @@
"menuLabels": {
"Workflow": "Workflow",
"Edit": "Edit",
"Help": "Help"
"Help": "Help",
"New": "New",
"Open": "Open",
"Load Default Workflow": "Load Default Workflow",
"Save": "Save",
"Save As": "Save As",
"Export": "Export",
"Export (API)": "Export (API)",
"Undo": "Undo",
"Redo": "Redo",
"Clear Workflow": "Clear Workflow",
"Reset View": "Reset View",
"Clipspace": "Clipspace",
"Refresh Node Definitions": "Refresh Node Definitions",
"Interrupt": "Interrupt",
"Clear Pending Tasks": "Clear Pending Tasks",
"Browse Templates": "Browse Templates",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Fit view to selected nodes": "Fit view to selected nodes",
"Toggle Lock": "Toggle Lock",
"Toggle Link Visibility": "Toggle Link Visibility",
"Queue Prompt": "Queue Prompt",
"Queue Prompt (Front)": "Queue Prompt (Front)",
"Settings": "Settings",
"Group Selected Nodes": "Group Selected Nodes",
"Next Opened Workflow": "Next Opened Workflow",
"Previous Opened Workflow": "Previous Opened Workflow",
"Mute/Unmute Selected Nodes": "Mute/Unmute Selected Nodes",
"Bypass/Unbypass Selected Nodes": "Bypass/Unbypass Selected Nodes",
"Pin/Unpin Selected Nodes": "Pin/Unpin Selected Nodes",
"Pin/Unpin Selected Items": "Pin/Unpin Selected Items",
"Collapse/Expand Selected Nodes": "Collapse/Expand Selected Nodes",
"Toggle Theme": "Toggle Theme",
"Toggle Bottom Panel": "Toggle Bottom Panel",
"Toggle Focus Mode": "Toggle Focus Mode",
"Fit Group To Contents": "Fit Group To Contents",
"ComfyUI Issues": "ComfyUI Issues",
"ComfyUI Docs": "ComfyUI Docs",
"Comfy-Org Discord": "Comfy-Org Discord",
"Queue": "Queue",
"Node Library": "Node Library",
"Model Library": "Model Library",
"Workflows": "Workflows",
"Logs": "Logs",
"Terminal": "Terminal",
"Convert selected nodes to group node": "Convert selected nodes to group node",
"Ungroup selected group nodes": "Ungroup selected group nodes",
"Manage group nodes": "Manage group nodes",
"Open Logs Folder": "Open Logs Folder",
"Open Models Folder": "Open Models Folder",
"Open Outputs Folder": "Open Outputs Folder",
"Open Inputs Folder": "Open Inputs Folder",
"Open Custom Nodes Folder": "Open Custom Nodes Folder",
"Open extra_model_paths_yaml": "Open extra_model_paths.yaml",
"Open DevTools": "Open DevTools",
"Feedback": "Feedback",
"Reinstall": "Reinstall",
"Restart": "Restart"
}
}

View File

@@ -121,9 +121,67 @@
"toggleBottomPanel": "下部パネルを切り替え"
},
"menuLabels": {
"Browse Templates": "テンプレートを参照",
"Bypass/Unbypass Selected Nodes": "選択したノードのバイパス/バイパス解除",
"Clear Pending Tasks": "保留中のタスクをクリア",
"Clear Workflow": "ワークフローをクリア",
"Clipspace": "クリップスペース",
"Collapse/Expand Selected Nodes": "選択したノードの折りたたみ/展開",
"Comfy-Org Discord": "Comfy-Org Discord",
"ComfyUI Docs": "ComfyUIのドキュメント",
"ComfyUI Issues": "ComfyUIの問題",
"Convert selected nodes to group node": "選択したノードをグループノードに変換",
"Edit": "編集",
"Export": "エクスポート",
"Export (API)": "エクスポート (API)",
"Feedback": "フィードバック",
"Fit Group To Contents": "グループを内容に合わせる",
"Fit view to selected nodes": "選択したノードにビューを合わせる",
"Group Selected Nodes": "選択したノードをグループ化",
"Help": "ヘルプ",
"Workflow": "ワークフロー"
"Interrupt": "中断",
"Load Default Workflow": "デフォルトワークフローを読み込む",
"Logs": "ログ",
"Manage group nodes": "グループノードを管理",
"Model Library": "モデルライブラリ",
"Mute/Unmute Selected Nodes": "選択したノードのミュート/ミュート解除",
"New": "新規",
"Next Opened Workflow": "次に開いたワークフロー",
"Node Library": "ノードライブラリ",
"Open": "開く",
"Open Custom Nodes Folder": "カスタムノードフォルダを開く",
"Open DevTools": "DevToolsを開く",
"Open Inputs Folder": "入力フォルダを開く",
"Open Logs Folder": "ログフォルダを開く",
"Open Models Folder": "モデルフォルダを開く",
"Open Outputs Folder": "出力フォルダを開く",
"Open extra_model_paths_yaml": "extra_model_paths.yamlを開く",
"Pin/Unpin Selected Items": "選択したアイテムのピン留め/ピン留め解除",
"Pin/Unpin Selected Nodes": "選択したノードのピン留め/ピン留め解除",
"Previous Opened Workflow": "前に開いたワークフロー",
"Queue": "キュー",
"Queue Prompt": "キューのプロンプト",
"Queue Prompt (Front)": "キューのプロンプト (前面)",
"Redo": "やり直す",
"Refresh Node Definitions": "ノード定義を更新",
"Reinstall": "再インストール",
"Reset View": "ビューをリセット",
"Restart": "再起動",
"Save": "保存",
"Save As": "名前を付けて保存",
"Settings": "設定",
"Terminal": "ターミナル",
"Toggle Bottom Panel": "下部パネルの切り替え",
"Toggle Focus Mode": "フォーカスモードの切り替え",
"Toggle Link Visibility": "リンクの表示/非表示を切り替え",
"Toggle Lock": "ロックの切り替え",
"Toggle Theme": "テーマの切り替え",
"Undo": "元に戻す",
"Ungroup selected group nodes": "選択したグループノードのグループ解除",
"Workflow": "ワークフロー",
"Workflows": "ワークフロー",
"Zoom In": "ズームイン",
"Zoom Out": "ズームアウト"
},
"newFolder": "新しいフォルダー",
"noResultsFound": "結果が見つかりませんでした",

View File

@@ -121,9 +121,67 @@
"toggleBottomPanel": "Переключить нижнюю панель"
},
"menuLabels": {
"Browse Templates": "Просмотреть шаблоны",
"Bypass/Unbypass Selected Nodes": "Обойти/восстановить выбранные узлы",
"Clear Pending Tasks": "Очистить ожидающие задачи",
"Clear Workflow": "Очистить рабочий процесс",
"Clipspace": "Клиппространство",
"Collapse/Expand Selected Nodes": "Свернуть/развернуть выбранные узлы",
"Comfy-Org Discord": "Discord Comfy-Org",
"ComfyUI Docs": "Документация ComfyUI",
"ComfyUI Issues": "Проблемы ComfyUI",
"Convert selected nodes to group node": "Преобразовать выбранные узлы в групповой узел",
"Edit": "Редактировать",
"Export": "Экспортировать",
"Export (API)": "Экспорт (API)",
"Feedback": "Обратная связь",
"Fit Group To Contents": "Подогнать группу под содержимое",
"Fit view to selected nodes": "Подогнать вид под выбранные узлы",
"Group Selected Nodes": "Сгруппировать выбранные узлы",
"Help": "Помощь",
"Workflow": "Рабочий процесс"
"Interrupt": "Прервать",
"Load Default Workflow": "Загрузить стандартный рабочий процесс",
"Logs": "Журналы",
"Manage group nodes": "Управление групповыми узлами",
"Model Library": "Библиотека моделей",
"Mute/Unmute Selected Nodes": "Отключить/включить звук для выбранных узлов",
"New": "Новый",
"Next Opened Workflow": "Следующий открытый рабочий процесс",
"Node Library": "Библиотека узлов",
"Open": "Открыть",
"Open Custom Nodes Folder": "Открыть папку пользовательских узлов",
"Open DevTools": "Открыть инструменты разработчика",
"Open Inputs Folder": "Открыть папку входных данных",
"Open Logs Folder": "Открыть папку журналов",
"Open Models Folder": "Открыть папку моделей",
"Open Outputs Folder": "Открыть папку выходных данных",
"Open extra_model_paths.yaml": "Открыть extra_model_paths.yaml",
"Pin/Unpin Selected Items": "Закрепить/открепить выбранные элементы",
"Pin/Unpin Selected Nodes": "Закрепить/открепить выбранные узлы",
"Previous Opened Workflow": "Предыдущий открытый рабочий процесс",
"Queue": "Очередь",
"Queue Prompt": "Запрос в очереди",
"Queue Prompt (Front)": "Запрос в очереди (спереди)",
"Redo": "Повторить",
"Refresh Node Definitions": "Обновить определения узлов",
"Reinstall": "Переустановить",
"Reset View": "Сбросить вид",
"Restart": "Перезапустить",
"Save": "Сохранить",
"Save As": "Сохранить как",
"Settings": "Настройки",
"Terminal": "Терминал",
"Toggle Bottom Panel": "Переключить нижнюю панель",
"Toggle Focus Mode": "Переключить режим фокуса",
"Toggle Link Visibility": "Переключить видимость ссылок",
"Toggle Lock": "Переключить блокировку",
"Toggle Theme": "Переключить тему",
"Undo": "Отменить",
"Ungroup selected group nodes": "Разгруппировать выбранные групповые узлы",
"Workflow": "Рабочий процесс",
"Workflows": "Рабочие процессы",
"Zoom In": "Увеличить",
"Zoom Out": "Уменьшить"
},
"newFolder": "Новая папка",
"noResultsFound": "Ничего не найдено",

View File

@@ -121,9 +121,67 @@
"toggleBottomPanel": "底部面板"
},
"menuLabels": {
"Browse Templates": "浏览模板",
"Bypass/Unbypass Selected Nodes": "旁路/取消旁路选定节点",
"Clear Pending Tasks": "清除待处理任务",
"Clear Workflow": "清除工作流",
"Clipspace": "剪辑空间",
"Collapse/Expand Selected Nodes": "折叠/展开选定节点",
"Comfy-Org Discord": "Comfy-Org Discord",
"ComfyUI Docs": "ComfyUI 文档",
"ComfyUI Issues": "ComfyUI 问题",
"Convert selected nodes to group node": "将选定节点转换为组节点",
"Edit": "编辑",
"Export": "导出",
"Export (API)": "导出 (API)",
"Feedback": "反馈",
"Fit Group To Contents": "适应组内容",
"Fit view to selected nodes": "适应视图到选定节点",
"Group Selected Nodes": "分组选定节点",
"Help": "帮助",
"Workflow": "工作流"
"Interrupt": "中断",
"Load Default Workflow": "加载默认工作流",
"Logs": "日志",
"Manage group nodes": "管理组节点",
"Model Library": "模型库",
"Mute/Unmute Selected Nodes": "静音/取消静音选定节点",
"New": "新建",
"Next Opened Workflow": "下一个打开的工作流",
"Node Library": "节点库",
"Open": "打开",
"Open Custom Nodes Folder": "打开自定义节点文件夹",
"Open DevTools": "打开开发者工具",
"Open Inputs Folder": "打开输入文件夹",
"Open Logs Folder": "打开日志文件夹",
"Open Models Folder": "打开模型文件夹",
"Open Outputs Folder": "打开输出文件夹",
"Open extra_model_paths_yaml": "打开 extra_model_paths.yaml",
"Pin/Unpin Selected Items": "固定/取消固定选定项目",
"Pin/Unpin Selected Nodes": "固定/取消固定选定节点",
"Previous Opened Workflow": "上一个打开的工作流",
"Queue": "队列",
"Queue Prompt": "队列提示",
"Queue Prompt (Front)": "队列提示 (前面)",
"Redo": "重做",
"Refresh Node Definitions": "刷新节点定义",
"Reinstall": "重新安装",
"Reset View": "重置视图",
"Restart": "重启",
"Save": "保存",
"Save As": "另存为",
"Settings": "设置",
"Terminal": "终端",
"Toggle Bottom Panel": "切换底部面板",
"Toggle Focus Mode": "切换专注模式",
"Toggle Link Visibility": "切换链接可见性",
"Toggle Lock": "切换锁定",
"Toggle Theme": "切换主题",
"Undo": "撤销",
"Ungroup selected group nodes": "取消选定组节点的分组",
"Workflow": "工作流",
"Workflows": "工作流",
"Zoom In": "放大",
"Zoom Out": "缩小"
},
"newFolder": "新建文件夹",
"noResultsFound": "未找到结果",

View File

@@ -20,6 +20,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
const toast = computed<ToastManager>(() => useToastStore())
const queueSettings = computed(() => useQueueSettingsStore())
const command = computed(() => ({
commands: useCommandStore().commands,
execute: useCommandStore().execute
}))
const sidebarTab = computed(() => useSidebarTabStore())

View File

@@ -1,3 +1,4 @@
import type { ComfyCommand } from '@/stores/commandStore'
import { Component } from 'vue'
export interface BaseSidebarTabExtension {
@@ -108,5 +109,6 @@ export interface ExtensionManager {
}
export interface CommandManager {
commands: ComfyCommand[]
execute(command: string, errorHandler?: (error: any) => void): void
}

View File

@@ -127,3 +127,11 @@ export function getPathDetails(path: string) {
const fullFilename = path.split('/').pop() ?? path
return { directory, fullFilename, ...getFilenameDetails(fullFilename) }
}
/**
* Normalizes a string to be used as an i18n key.
* Replaces dots with underscores.
*/
export function normalizeI18nKey(key: string) {
return key.replace(/\./g, '_')
}