From 278d4910308d4985c42f65c61607fa6c58fffa24 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Tue, 3 Feb 2026 15:50:18 -0800 Subject: [PATCH] refactor: move ellipsis and punctuation into i18n translation strings (#8573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Move ellipsis and punctuation characters into i18n translation strings for proper internationalization support. ## Changes - Add 12 new translation keys with punctuation included: - Placeholder keys with trailing ellipsis (e.g., `searchNodesPlaceholder: "Search Nodes..."`) - `downloadWithSize` with interpolation: `"Download ({size})"` - `completedWithCheckmark`: `"Completed ✓"` - Prompt keys with colons (e.g., `enterNewNamePrompt: "Enter new name:"`) - Update 20 files to use new translation keys instead of string concatenation ## Review Focus This eliminates string concatenation patterns like `$t('key') + '...'` that break proper internationalization, since different languages may use different punctuation or may not need ellipsis/colons in the same contexts. Fixes #7333 ## Summary by CodeRabbit * **Chores** * Standardized localization across the app: unified search placeholders and input hints; updated dialog prompt texts for renaming, saving/exporting, and related prompts. * **New Features** * Download buttons now show file size via localized text. * Completed status displays a localized label with a checkmark. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8573-refactor-move-ellipsis-and-punctuation-into-i18n-translation-strings-2fc6d73d365081828ad3f257bcac7799) by [Unito](https://www.unito.io) --- browser_tests/fixtures/ComfyPage.ts | 2 +- src/components/common/ElectronFileDownload.vue | 2 +- src/components/common/FileDownload.vue | 2 +- .../dialog/content/setting/KeybindingPanel.vue | 4 +++- .../rightSidePanel/parameters/WidgetActions.vue | 2 +- src/components/searchbox/NodeSearchBox.vue | 4 +++- src/components/sidebar/tabs/ModelLibrarySidebarTab.vue | 6 +++++- src/components/sidebar/tabs/NodeLibrarySidebarTab.vue | 2 +- src/components/sidebar/tabs/WorkflowsSidebarTab.vue | 4 +++- src/composables/queue/useJobMenu.ts | 2 +- src/composables/useCoreCommands.ts | 2 +- src/locales/en/main.json | 9 ++++++++- src/platform/assets/components/AssetBrowserModal.vue | 2 +- src/platform/assets/components/MediaAssetFilterBar.vue | 4 +++- src/platform/settings/components/ExtensionPanel.vue | 4 +++- .../settings/components/SettingDialogContent.vue | 4 +++- .../workflow/core/services/workflowActionsService.ts | 2 +- src/platform/workflow/core/services/workflowService.ts | 2 +- src/platform/workflow/management/stores/workflowStore.ts | 2 +- .../vueNodes/widgets/components/form/FormSearchInput.vue | 2 +- src/services/litegraphService.ts | 2 +- src/stores/subgraphStore.ts | 4 ++-- .../manager/components/ManagerProgressToast.vue | 2 +- 23 files changed, 47 insertions(+), 24 deletions(-) diff --git a/browser_tests/fixtures/ComfyPage.ts b/browser_tests/fixtures/ComfyPage.ts index 2ed1759c7..643907163 100644 --- a/browser_tests/fixtures/ComfyPage.ts +++ b/browser_tests/fixtures/ComfyPage.ts @@ -46,7 +46,7 @@ class ComfyPropertiesPanel { constructor(readonly page: Page) { this.root = page.getByTestId(TestIds.propertiesPanel.root) this.panelTitle = this.root.locator('h3') - this.searchBox = this.root.getByPlaceholder('Search...') + this.searchBox = this.root.getByPlaceholder(/^Search/) } } diff --git a/src/components/common/ElectronFileDownload.vue b/src/components/common/ElectronFileDownload.vue index 0a85a197b..eb86541af 100644 --- a/src/components/common/ElectronFileDownload.vue +++ b/src/components/common/ElectronFileDownload.vue @@ -22,7 +22,7 @@ @click="triggerDownload" > - {{ $t('g.download') + ' (' + fileSize + ')' }} + {{ $t('g.downloadWithSize', { size: fileSize }) }}
diff --git a/src/components/dialog/content/setting/KeybindingPanel.vue b/src/components/dialog/content/setting/KeybindingPanel.vue index 0bc15b9cf..dff5f7dfb 100644 --- a/src/components/dialog/content/setting/KeybindingPanel.vue +++ b/src/components/dialog/content/setting/KeybindingPanel.vue @@ -3,7 +3,9 @@ diff --git a/src/components/rightSidePanel/parameters/WidgetActions.vue b/src/components/rightSidePanel/parameters/WidgetActions.vue index ba20eac35..e5c3cf16c 100644 --- a/src/components/rightSidePanel/parameters/WidgetActions.vue +++ b/src/components/rightSidePanel/parameters/WidgetActions.vue @@ -46,7 +46,7 @@ const isFavorited = computed(() => async function handleRename() { const newLabel = await dialogService.prompt({ title: t('g.rename'), - message: t('g.enterNewName') + ':', + message: t('g.enterNewNamePrompt'), defaultValue: widget.label, placeholder: widget.name }) diff --git a/src/components/searchbox/NodeSearchBox.vue b/src/components/searchbox/NodeSearchBox.vue index abda187a5..fcf63b1eb 100644 --- a/src/components/searchbox/NodeSearchBox.vue +++ b/src/components/searchbox/NodeSearchBox.vue @@ -118,7 +118,9 @@ const suggestions = ref([]) const hoveredSuggestion = ref(null) const currentQuery = ref('') const placeholder = computed(() => { - return filters.length === 0 ? t('g.searchNodes') + '...' : '' + return filters.length === 0 + ? t('g.searchPlaceholder', { subject: t('g.nodes') }) + : '' }) const nodeDefStore = useNodeDefStore() diff --git a/src/components/sidebar/tabs/ModelLibrarySidebarTab.vue b/src/components/sidebar/tabs/ModelLibrarySidebarTab.vue index 218655231..74c5e9c91 100644 --- a/src/components/sidebar/tabs/ModelLibrarySidebarTab.vue +++ b/src/components/sidebar/tabs/ModelLibrarySidebarTab.vue @@ -25,7 +25,11 @@
diff --git a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue index 9e6e6b7a4..b34949602 100644 --- a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue +++ b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue @@ -91,7 +91,7 @@ v-model:model-value="searchQuery" data-testid="node-library-search" class="node-lib-search-box" - :placeholder="$t('g.searchNodes') + '...'" + :placeholder="$t('g.searchPlaceholder', { subject: $t('g.nodes') })" filter-icon="pi pi-filter" :filters @search="handleSearch" diff --git a/src/components/sidebar/tabs/WorkflowsSidebarTab.vue b/src/components/sidebar/tabs/WorkflowsSidebarTab.vue index fbb8d6880..7dae539bb 100644 --- a/src/components/sidebar/tabs/WorkflowsSidebarTab.vue +++ b/src/components/sidebar/tabs/WorkflowsSidebarTab.vue @@ -22,7 +22,9 @@ ref="searchBoxRef" v-model:model-value="searchQuery" class="workflows-search-box" - :placeholder="$t('g.searchWorkflows') + '...'" + :placeholder=" + $t('g.searchPlaceholder', { subject: $t('g.workflow') }) + " @search="handleSearch" /> diff --git a/src/composables/queue/useJobMenu.ts b/src/composables/queue/useJobMenu.ts index bdf7033f6..0dd990853 100644 --- a/src/composables/queue/useJobMenu.ts +++ b/src/composables/queue/useJobMenu.ts @@ -190,7 +190,7 @@ export function useJobMenu( if (settingStore.get('Comfy.PromptFilename')) { const input = await useDialogService().prompt({ title: t('workflowService.exportWorkflow'), - message: t('workflowService.enterFilename') + ':', + message: t('workflowService.enterFilenamePrompt'), defaultValue: filename }) if (!input) return diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 644d33ac9..bb6257a6a 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -199,7 +199,7 @@ export function useCoreCommands(): ComfyCommand[] { const newName = await dialogService.prompt({ title: t('g.rename'), - message: t('workflowService.enterFilename') + ':', + message: t('workflowService.enterFilenamePrompt'), defaultValue: workflow.filename }) if (!newName || newName === workflow.filename) return diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 8825fbf9c..3b5fe6435 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -136,7 +136,8 @@ "searchKeybindings": "Search Keybindings", "searchExtensions": "Search Extensions", "search": "Search", - "searchPlaceholder": "Search...", + "searchPlaceholder": "Search {subject}...", + "downloadWithSize": "Download ({size})", "noResultsFound": "No Results Found", "noResults": "No Results", "searchFailedMessage": "We couldn't find any settings matching your search. Try adjusting your search terms.", @@ -152,6 +153,8 @@ "custom": "Custom", "command": "Command", "keybinding": "Keybinding", + "keybindings": "Keybindings", + "extensions": "Extensions", "upload": "Upload", "export": "Export", "workflow": "Workflow", @@ -192,6 +195,7 @@ "missing": "Missing", "inProgress": "In progress", "completed": "Completed", + "completedWithCheckmark": "Completed ✓", "downloading": "Downloading", "interrupted": "Interrupted", "queued": "Queued", @@ -255,6 +259,7 @@ "batchRename": "Batch rename", "enterBaseName": "Enter base name", "enterNewName": "Enter new name", + "enterNewNamePrompt": "Enter new name:", "selectItemsToRename": "Select items to rename", "nothingToRename": "Nothing to rename", "moreWorkflows": "More workflows", @@ -1003,6 +1008,7 @@ "workflowService": { "exportWorkflow": "Export Workflow", "enterFilename": "Enter the filename", + "enterFilenamePrompt": "Enter the filename:", "saveWorkflow": "Save workflow" }, "subgraphStore": { @@ -1012,6 +1018,7 @@ "overwriteBlueprintTitle": "Overwrite existing blueprint?", "overwriteBlueprint": "Saving will overwrite the current blueprint with your changes", "blueprintName": "Subgraph name", + "blueprintNamePrompt": "Subgraph name:", "promoteOutsideSubgraph": "Can't promote widget when not in subgraph", "publish": "Publish Subgraph", "publishSuccess": "Saved to Nodes Library", diff --git a/src/platform/assets/components/AssetBrowserModal.vue b/src/platform/assets/components/AssetBrowserModal.vue index 779be651e..8745f3cc7 100644 --- a/src/platform/assets/components/AssetBrowserModal.vue +++ b/src/platform/assets/components/AssetBrowserModal.vue @@ -30,7 +30,7 @@ v-model="searchQuery" :autofocus="true" size="lg" - :placeholder="$t('g.searchPlaceholder')" + :placeholder="$t('g.searchPlaceholder', { subject: '' })" class="max-w-96" />