refactor: move ellipsis and punctuation into i18n translation strings (#8573)

## 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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

┆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)
This commit is contained in:
Christian Byrne
2026-02-03 15:50:18 -08:00
committed by GitHub
parent 4b43eb5fff
commit 278d491030
23 changed files with 47 additions and 24 deletions

View File

@@ -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/)
}
}

View File

@@ -22,7 +22,7 @@
@click="triggerDownload"
>
<i class="pi pi-download" />
{{ $t('g.download') + ' (' + fileSize + ')' }}
{{ $t('g.downloadWithSize', { size: fileSize }) }}
</Button>
<Button
v-if="(status === null || status === 'error') && !!props.url"

View File

@@ -27,7 +27,7 @@
:title="props.url"
@click="download.triggerBrowserDownload"
>
{{ $t('g.download') + ' (' + fileSize + ')' }}
{{ $t('g.downloadWithSize', { size: fileSize }) }}
</Button>
</div>
<div>

View File

@@ -3,7 +3,9 @@
<template #header>
<SearchBox
v-model="filters['global'].value"
:placeholder="$t('g.searchKeybindings') + '...'"
:placeholder="
$t('g.searchPlaceholder', { subject: $t('g.keybindings') })
"
/>
</template>

View File

@@ -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
})

View File

@@ -118,7 +118,9 @@ const suggestions = ref<ComfyNodeDefImpl[]>([])
const hoveredSuggestion = ref<ComfyNodeDefImpl | null>(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()

View File

@@ -25,7 +25,11 @@
<SearchBox
ref="searchBoxRef"
v-model:model-value="searchQuery"
:placeholder="$t('g.searchModels') + '...'"
:placeholder="
$t('g.searchPlaceholder', {
subject: $t('sideToolbar.labels.models')
})
"
@search="handleSearch"
/>
</div>

View File

@@ -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"

View File

@@ -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"
/>
</div>

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -30,7 +30,7 @@
v-model="searchQuery"
:autofocus="true"
size="lg"
:placeholder="$t('g.searchPlaceholder')"
:placeholder="$t('g.searchPlaceholder', { subject: '' })"
class="max-w-96"
/>
<Button

View File

@@ -2,7 +2,9 @@
<div class="flex gap-3 items-center">
<SearchBox
:model-value="searchQuery"
:placeholder="$t('sideToolbar.searchAssets') + '...'"
:placeholder="
$t('g.searchPlaceholder', { subject: $t('sideToolbar.labels.assets') })
"
@update:model-value="handleSearchChange"
/>
<div class="flex gap-1.5 items-center">

View File

@@ -3,7 +3,9 @@
<template #header>
<SearchBox
v-model="filters['global'].value"
:placeholder="$t('g.searchExtensions') + '...'"
:placeholder="
$t('g.searchPlaceholder', { subject: $t('g.extensions') })
"
/>
<Message
v-if="hasChanges"

View File

@@ -18,7 +18,9 @@
<SearchBox
v-model:model-value="searchQuery"
class="settings-search-box mb-2 w-full"
:placeholder="$t('g.searchSettings') + '...'"
:placeholder="
$t('g.searchPlaceholder', { subject: $t('g.settings') })
"
:debounce-time="128"
autofocus
@search="handleSearch"

View File

@@ -55,7 +55,7 @@ export function useWorkflowActionsService() {
if (settingStore.get('Comfy.PromptFilename')) {
const input = await dialogService.prompt({
title: t('workflowService.exportWorkflow'),
message: t('workflowService.enterFilename') + ':',
message: t('workflowService.enterFilenamePrompt'),
defaultValue: filename
})
// User cancelled the prompt

View File

@@ -35,7 +35,7 @@ export const useWorkflowService = () => {
if (settingStore.get('Comfy.PromptFilename')) {
let filename = await dialogService.prompt({
title: t('workflowService.exportWorkflow'),
message: t('workflowService.enterFilename') + ':',
message: t('workflowService.enterFilenamePrompt'),
defaultValue: defaultName
})
if (!filename) return null

View File

@@ -161,7 +161,7 @@ export class ComfyWorkflow extends UserFile {
async promptSave(): Promise<string | null> {
return await useDialogService().prompt({
title: t('workflowService.saveWorkflow'),
message: t('workflowService.enterFilename') + ':',
message: t('workflowService.enterFilenamePrompt'),
defaultValue: this.filename
})
}

View File

@@ -90,7 +90,7 @@ function handleFocus(event: FocusEvent) {
v-model="searchQuery"
type="text"
class="bg-transparent border-0 outline-0 ring-0 h-5 w-full my-1.5 mx-2"
:placeholder="$t('g.searchPlaceholder')"
:placeholder="$t('g.searchPlaceholder', { subject: '' })"
:autofocus
@focus="handleFocus"
/>

View File

@@ -85,7 +85,7 @@ export function getExtraOptionsForWidget(
callback: async () => {
const newLabel = await useDialogService().prompt({
title: t('g.rename'),
message: t('g.enterNewName') + ':',
message: t('g.enterNewNamePrompt'),
defaultValue: widget.label,
placeholder: widget.name
})

View File

@@ -130,7 +130,7 @@ export const useSubgraphStore = defineStore('subgraph', () => {
override async promptSave(): Promise<string | null> {
return await useDialogService().prompt({
title: t('subgraphStore.saveBlueprint'),
message: t('subgraphStore.blueprintName') + ':',
message: t('subgraphStore.blueprintNamePrompt'),
defaultValue: this.filename
})
}
@@ -270,7 +270,7 @@ export const useSubgraphStore = defineStore('subgraph', () => {
//prompt name
const name = await useDialogService().prompt({
title: t('subgraphStore.saveBlueprint'),
message: t('subgraphStore.blueprintName') + ':',
message: t('subgraphStore.blueprintNamePrompt'),
defaultValue: subgraphNode.title
})
if (!name) return

View File

@@ -239,7 +239,7 @@ onBeforeUnmount(() => {
{{
isTaskInProgress(index)
? t('g.inProgress')
: t('g.completed') + ' ✓'
: t('g.completedWithCheckmark')
}}
</span>
</div>