From affb507b372970c8f5d8a2cb95b9afbddab30c64 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Fri, 5 Sep 2025 10:45:54 +0800 Subject: [PATCH 1/7] feat(sync): enhance translation key synchronization to remove obsolete keys --- scripts/sync_translation_keys.py | 30 +++++++++++++++++++--------- scripts/test_i18n.py => test_i18n.py | 0 2 files changed, 21 insertions(+), 9 deletions(-) rename scripts/test_i18n.py => test_i18n.py (100%) diff --git a/scripts/sync_translation_keys.py b/scripts/sync_translation_keys.py index 4244bde0..8a0edb66 100644 --- a/scripts/sync_translation_keys.py +++ b/scripts/sync_translation_keys.py @@ -78,11 +78,12 @@ class TranslationKeySynchronizer: """ Merge the reference JSON structure with existing target translations. This creates a new structure that matches the reference exactly but preserves - existing translations where available. + existing translations where available. Keys not in reference are removed. """ def merge_recursive(ref_obj, target_obj): if isinstance(ref_obj, (dict, OrderedDict)): result = OrderedDict() + # Only include keys that exist in the reference for key, ref_value in ref_obj.items(): if key in target_obj and isinstance(target_obj[key], type(ref_value)): # Key exists in target with same type @@ -131,6 +132,7 @@ class TranslationKeySynchronizer: reference_lines: List[str], dry_run: bool = False) -> bool: """ Synchronize a locale file using JSON structure merging. + Handles both addition of missing keys and removal of obsolete keys. """ locale_file = os.path.join(self.locales_dir, f'{locale}.json') @@ -144,24 +146,33 @@ class TranslationKeySynchronizer: self.log(f"Error loading {locale_file}: {e}", 'ERROR') return False - # Get keys to check for missing ones + # Get keys to check for differences ref_keys = self.get_all_leaf_keys(reference_data) target_keys = self.get_all_leaf_keys(target_data) missing_keys = set(ref_keys.keys()) - set(target_keys.keys()) + obsolete_keys = set(target_keys.keys()) - set(ref_keys.keys()) - if not missing_keys: + if not missing_keys and not obsolete_keys: self.log(f"Locale {locale} is already up to date") return False - self.log(f"Found {len(missing_keys)} missing keys in {locale}:") - for key in sorted(missing_keys): - self.log(f" - {key}") + # Report changes + if missing_keys: + self.log(f"Found {len(missing_keys)} missing keys in {locale}:") + for key in sorted(missing_keys): + self.log(f" + {key}") + + if obsolete_keys: + self.log(f"Found {len(obsolete_keys)} obsolete keys in {locale}:") + for key in sorted(obsolete_keys): + self.log(f" - {key}") if dry_run: - self.log(f"DRY RUN: Would update {locale} with {len(missing_keys)} new keys") + total_changes = len(missing_keys) + len(obsolete_keys) + self.log(f"DRY RUN: Would update {locale} with {len(missing_keys)} additions and {len(obsolete_keys)} deletions ({total_changes} total changes)") return True - # Merge the structures + # Merge the structures (this will both add missing keys and remove obsolete ones) try: merged_data = self.merge_json_structures(reference_data, target_data) @@ -176,7 +187,8 @@ class TranslationKeySynchronizer: with open(locale_file, 'w', encoding='utf-8') as f: f.writelines(new_lines) - self.log(f"Successfully updated {locale} with {len(missing_keys)} new keys") + total_changes = len(missing_keys) + len(obsolete_keys) + self.log(f"Successfully updated {locale} with {len(missing_keys)} additions and {len(obsolete_keys)} deletions ({total_changes} total changes)") return True except json.JSONDecodeError as e: diff --git a/scripts/test_i18n.py b/test_i18n.py similarity index 100% rename from scripts/test_i18n.py rename to test_i18n.py From 7475de366b3ef3a52cfab9afe1ac68be2c1b51cd Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Fri, 5 Sep 2025 10:55:38 +0800 Subject: [PATCH 2/7] feat(context-menu): enhance bulk workflow options with append and replace actions --- static/css/components/menu.css | 6 ++++-- .../components/ContextMenu/BulkContextMenu.js | 17 ++++++++++++----- static/js/managers/BulkManager.js | 4 ++-- templates/components/context_menu.html | 7 +++++-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/static/css/components/menu.css b/static/css/components/menu.css index 481bb213..b2b1ed24 100644 --- a/static/css/components/menu.css +++ b/static/css/components/menu.css @@ -217,17 +217,19 @@ /* Bulk Context Menu Header */ .bulk-context-header { padding: 10px 12px; - background: var(--lora-accent); - color: var(--lora-text); + background: var(--card-bg); /* Use card background for subtlety */ + color: var(--text-color); /* Use standard text color */ display: flex; align-items: center; gap: 8px; font-weight: 500; font-size: 14px; border-radius: var(--border-radius-xs) var(--border-radius-xs) 0 0; + border-bottom: 1px solid var(--border-color); /* Add subtle separator */ } .bulk-context-header i { width: 16px; text-align: center; + color: var(--lora-accent); /* Accent only the icon for a hint of color */ } \ No newline at end of file diff --git a/static/js/components/ContextMenu/BulkContextMenu.js b/static/js/components/ContextMenu/BulkContextMenu.js index 70030764..0ef43e74 100644 --- a/static/js/components/ContextMenu/BulkContextMenu.js +++ b/static/js/components/ContextMenu/BulkContextMenu.js @@ -27,14 +27,18 @@ export class BulkContextMenu extends BaseContextMenu { // Update button visibility based on model type const addTagsItem = this.menu.querySelector('[data-action="add-tags"]'); - const sendToWorkflowItem = this.menu.querySelector('[data-action="send-to-workflow"]'); + const sendToWorkflowAppendItem = this.menu.querySelector('[data-action="send-to-workflow-append"]'); + const sendToWorkflowReplaceItem = this.menu.querySelector('[data-action="send-to-workflow-replace"]'); const copyAllItem = this.menu.querySelector('[data-action="copy-all"]'); const refreshAllItem = this.menu.querySelector('[data-action="refresh-all"]'); const moveAllItem = this.menu.querySelector('[data-action="move-all"]'); const deleteAllItem = this.menu.querySelector('[data-action="delete-all"]'); - if (sendToWorkflowItem) { - sendToWorkflowItem.style.display = config.sendToWorkflow ? 'flex' : 'none'; + if (sendToWorkflowAppendItem) { + sendToWorkflowAppendItem.style.display = config.sendToWorkflow ? 'flex' : 'none'; + } + if (sendToWorkflowReplaceItem) { + sendToWorkflowReplaceItem.style.display = config.sendToWorkflow ? 'flex' : 'none'; } if (copyAllItem) { copyAllItem.style.display = config.copyAll ? 'flex' : 'none'; @@ -71,8 +75,11 @@ export class BulkContextMenu extends BaseContextMenu { case 'add-tags': bulkManager.showBulkAddTagsModal(); break; - case 'send-to-workflow': - bulkManager.sendAllModelsToWorkflow(); + case 'send-to-workflow-append': + bulkManager.sendAllModelsToWorkflow(false); + break; + case 'send-to-workflow-replace': + bulkManager.sendAllModelsToWorkflow(true); break; case 'copy-all': bulkManager.copyAllModelsSyntax(); diff --git a/static/js/managers/BulkManager.js b/static/js/managers/BulkManager.js index 7ecdadcf..a6499d3b 100644 --- a/static/js/managers/BulkManager.js +++ b/static/js/managers/BulkManager.js @@ -228,7 +228,7 @@ export class BulkManager { await copyToClipboard(loraSyntaxes.join(', '), `Copied ${loraSyntaxes.length} LoRA syntaxes to clipboard`); } - async sendAllModelsToWorkflow() { + async sendAllModelsToWorkflow(replaceMode = false) { if (state.currentPageType !== MODEL_TYPES.LORA) { showToast('toast.loras.sendOnlyForLoras', {}, 'warning'); return; @@ -265,7 +265,7 @@ export class BulkManager { return; } - await sendLoraToWorkflow(loraSyntaxes.join(', '), false, 'lora'); + await sendLoraToWorkflow(loraSyntaxes.join(', '), replaceMode, 'lora'); } showBulkDeleteModal() { diff --git a/templates/components/context_menu.html b/templates/components/context_menu.html index 92e7a5b2..45868caf 100644 --- a/templates/components/context_menu.html +++ b/templates/components/context_menu.html @@ -53,8 +53,11 @@
{{ t('modals.bulkBaseModel.description') }} 0 {{ t('modals.bulkBaseModel.models') }}
+{{ t('modals.bulkBaseModel.description') }} 0 {{ t('modals.bulkBaseModel.models') }}