Migrate main workflow i18n
This commit is contained in:
@@ -1,5 +1,25 @@
|
||||
# AI Changelog
|
||||
|
||||
## [Snapshot] 2026-08-06 - Migrate main workflow import and share-code i18n
|
||||
|
||||
**Implemented**
|
||||
|
||||
- Migrated `web/main.js` preflight workflow import UI to the shared translator.
|
||||
- Migrated workflow share-code export, import, and unified modal labels, status messages, placeholders, and node-count interpolation.
|
||||
- Composed preflight result headings and empty states with DOM nodes while keeping workflow/model/node data dynamic.
|
||||
- Kept the locale conditional used by the translation API only for the backend `zh-CN`/`en` service code mapping; it is not a user-facing copy branch.
|
||||
|
||||
**Validation**
|
||||
|
||||
- JavaScript syntax checks passed for every file under `web/`.
|
||||
- All 218 static translator calls in the migrated UI modules resolve in both `zh` and `en` dictionaries.
|
||||
- `node tests/recipe_parser_roundtrip.mjs` passed.
|
||||
- Targeted `git diff --check` passed; the existing user change in `README.md` was kept outside this snapshot.
|
||||
|
||||
**Next**
|
||||
|
||||
- Migrate `web/modules/ui_sidebar.js` as its own reviewable snapshot; Recipe and hash-resolver branches remain untouched.
|
||||
|
||||
## [Snapshot] 2026-08-06 - Migrate detail, doctor, assistant, picker, and notebook i18n
|
||||
|
||||
**Implemented**
|
||||
|
||||
@@ -58,6 +58,8 @@ The second migration snapshot extends the shared translation contract to `ui_gri
|
||||
|
||||
The third migration snapshot extends the same contract to `ui_detail.js`, `ui_doctor.js`, and `ui_notebooks.js`. Detail editing, model selection, Doctor diagnostics, the Node Assistant, the model picker, and Notebook validation now resolve user-facing strings through `translate` with parameter interpolation for counts, paths, model names, and scan status. Dynamic model/path/node data remains outside the locale dictionaries; rich UI state is composed with DOM nodes where translated text replaced previously formatted status HTML.
|
||||
|
||||
The fourth migration snapshot applies the contract to `web/main.js` for preflight workflow import and workflow share-code export/import. These modal surfaces use shared keys for labels, status messages, and count interpolation; the remaining locale conditional in `translateText` only maps the active locale to the backend service's `zh-CN` or `en` code and is not a UI copy branch. `ui_sidebar.js` remains a separate pending snapshot because its scan wizard, settings hub, language toggle, and folder manager require their own key extraction review.
|
||||
|
||||
## 2. Directory Structure (核心文件结构)
|
||||
|
||||
```text
|
||||
|
||||
+44
-43
@@ -173,13 +173,13 @@ class AnomalousBrowser {
|
||||
const header = document.createElement('h2');
|
||||
header.style.margin = '0';
|
||||
header.style.color = '#fff';
|
||||
header.innerText = window.anomalous_browser_lang === 'zh' ? '📥 导入工作流 (预检海关)' : '📥 Pre-flight Import Workflow';
|
||||
header.textContent = t('mainPreflightTitle');
|
||||
modal.appendChild(header);
|
||||
|
||||
const desc = document.createElement('div');
|
||||
desc.style.color = '#aaa';
|
||||
desc.style.fontSize = '14px';
|
||||
desc.innerText = window.anomalous_browser_lang === 'zh' ? '请在此处粘贴工作流的 JSON 代码(未来将支持分享码)。我们将为您提取并校验所有模型依赖。' : 'Paste the workflow JSON here. We will extract and validate all model dependencies before loading.';
|
||||
desc.textContent = t('mainPreflightDesc');
|
||||
modal.appendChild(desc);
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
@@ -215,7 +215,7 @@ class AnomalousBrowser {
|
||||
btnRow.style.gap = '10px';
|
||||
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.innerText = window.anomalous_browser_lang === 'zh' ? '取消' : 'Cancel';
|
||||
closeBtn.textContent = t('mainCancel');
|
||||
closeBtn.style.padding = '8px 16px';
|
||||
closeBtn.style.background = 'transparent';
|
||||
closeBtn.style.color = '#ccc';
|
||||
@@ -226,7 +226,7 @@ class AnomalousBrowser {
|
||||
|
||||
const analyzeBtn = document.createElement('button');
|
||||
analyzeBtn.id = 'anomalous-import-analyze-btn';
|
||||
analyzeBtn.innerText = window.anomalous_browser_lang === 'zh' ? '🔍 解析并预检' : '🔍 Analyze & Pre-flight';
|
||||
analyzeBtn.textContent = t('mainAnalyzePreflight');
|
||||
analyzeBtn.style.padding = '8px 16px';
|
||||
analyzeBtn.style.background = '#1a73e8';
|
||||
analyzeBtn.style.color = '#fff';
|
||||
@@ -236,7 +236,7 @@ class AnomalousBrowser {
|
||||
|
||||
const loadBtn = document.createElement('button');
|
||||
loadBtn.id = 'anomalous-import-load-btn';
|
||||
loadBtn.innerText = window.anomalous_browser_lang === 'zh' ? '🚀 强制载入画布' : '🚀 Force Load to Canvas';
|
||||
loadBtn.textContent = t('mainForceLoad');
|
||||
loadBtn.style.padding = '8px 16px';
|
||||
loadBtn.style.background = '#28a745';
|
||||
loadBtn.style.color = '#fff';
|
||||
@@ -279,11 +279,19 @@ class AnomalousBrowser {
|
||||
loadBtn.style.display = 'block';
|
||||
|
||||
if (models.length === 0) {
|
||||
resultArea.innerHTML = `<div style="color:#28a745;">${window.anomalous_browser_lang === 'zh' ? '✅ 未检测到任何模型依赖。' : '✅ No model dependencies detected.'}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
resultArea.innerHTML = `<div style="color:#fff; font-weight:bold; margin-bottom:10px;">${window.anomalous_browser_lang === 'zh' ? `扫描到 ${models.length} 个模型资源:` : `Detected ${models.length} model resources:`}</div>`;
|
||||
const noDependencies = document.createElement('div');
|
||||
noDependencies.style.color = '#28a745';
|
||||
noDependencies.textContent = t('mainNoDependencies');
|
||||
resultArea.replaceChildren(noDependencies);
|
||||
return;
|
||||
}
|
||||
|
||||
const detectedHeader = document.createElement('div');
|
||||
detectedHeader.style.color = '#fff';
|
||||
detectedHeader.style.fontWeight = 'bold';
|
||||
detectedHeader.style.marginBottom = '10px';
|
||||
detectedHeader.textContent = t('mainDetectedModels', { count: models.length });
|
||||
resultArea.replaceChildren(detectedHeader);
|
||||
|
||||
for (const m of models) {
|
||||
const val = m.value;
|
||||
@@ -334,14 +342,14 @@ class AnomalousBrowser {
|
||||
const searchStr = searchHash || basename.replace('.safetensors', '').replace('.ckpt', '').replace('.pt', '').replace('.sft', '');
|
||||
right.href = `https://civitai.com/search/models?sortBy=models_v9&query=${encodeURIComponent(searchStr)}`;
|
||||
right.target = '_blank';
|
||||
right.innerText = window.anomalous_browser_lang === 'zh' ? '去 C站下载' : 'Download';
|
||||
right.textContent = t('mainDownloadCivitai');
|
||||
right.style.color = '#1a73e8';
|
||||
right.style.fontSize = '12px';
|
||||
right.style.textDecoration = 'none';
|
||||
item.appendChild(right);
|
||||
} else {
|
||||
const right = document.createElement('span');
|
||||
right.innerText = window.anomalous_browser_lang === 'zh' ? '已就绪' : 'Ready';
|
||||
right.textContent = t('mainReady');
|
||||
right.style.color = '#28a745';
|
||||
right.style.fontSize = '12px';
|
||||
item.appendChild(right);
|
||||
@@ -351,7 +359,7 @@ class AnomalousBrowser {
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
alert('Invalid JSON! ' + e.message);
|
||||
alert(t('mainInvalidJson') + ' ' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -480,14 +488,12 @@ app.registerExtension({
|
||||
} catch (error) {
|
||||
console.error('[Anomalous Model Browser] UI initialization failed:', error);
|
||||
btn.classList.add('anomalous-trigger-error');
|
||||
btn.title = window.anomalous_browser_lang === 'zh'
|
||||
? '插件界面初始化失败,点击可重试'
|
||||
: 'The plugin UI failed to initialize. Click to retry.';
|
||||
btn.title = currentLang === 'zh' ? t('mainRetryInit') : t('mainRetryInitEn');
|
||||
btn.setAttribute('aria-label', btn.title);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
btn.title = window.anomalous_browser_lang === 'zh' ? '打开 Anomalous 模型浏览器' : 'Open Anomalous Model Browser';
|
||||
btn.title = t('mainOpenTitle');
|
||||
let isDragging = false;
|
||||
let startX, startY, initialX, initialY;
|
||||
|
||||
@@ -799,9 +805,7 @@ const AMB_WorkflowShare = {
|
||||
},
|
||||
|
||||
showExportModal() {
|
||||
const lang = window.anomalous_browser_lang || 'en';
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'amb-export-modal';
|
||||
overlay.style.cssText = `
|
||||
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
|
||||
@@ -819,17 +823,17 @@ const AMB_WorkflowShare = {
|
||||
|
||||
const title = document.createElement('h2');
|
||||
title.style.margin = '0';
|
||||
title.textContent = lang === 'zh' ? '📦 导出工作流分享码' : '📦 Export Workflow Share Code';
|
||||
title.textContent = t('mainExportTitle');
|
||||
|
||||
const typeSelectContainer = document.createElement('div');
|
||||
typeSelectContainer.innerHTML = `
|
||||
<label style="display: block; margin-bottom: 8px; cursor: pointer;">
|
||||
<input type="radio" name="amb-share-type" value="skeleton" checked />
|
||||
${lang === 'zh' ? '精简版 (推荐 B站评论区, 体积小, 丢失坐标)' : 'Skeleton (Compact, best for comments, loses coords)'}
|
||||
${t('mainSkeletonOption')}
|
||||
</label>
|
||||
<label style="display: block; cursor: pointer;">
|
||||
<input type="radio" name="amb-share-type" value="full" />
|
||||
${lang === 'zh' ? '完整版 (保留所有坐标与颜色, 较长)' : 'Full (Keeps coords and colors, longer string)'}
|
||||
${t('mainFullOption')}
|
||||
</label>
|
||||
`;
|
||||
|
||||
@@ -845,15 +849,15 @@ const AMB_WorkflowShare = {
|
||||
btnGroup.style.cssText = `display: flex; gap: 10px; justify-content: flex-end;`;
|
||||
|
||||
const generateBtn = document.createElement('button');
|
||||
generateBtn.textContent = lang === 'zh' ? '生成分享码' : 'Generate';
|
||||
generateBtn.textContent = t('mainGenerate');
|
||||
generateBtn.style.cssText = `padding: 8px 16px; background: #4a90e2; color: #fff; border: none; border-radius: 6px; cursor: pointer;`;
|
||||
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.textContent = lang === 'zh' ? '复制到剪贴板' : 'Copy';
|
||||
copyBtn.textContent = t('mainCopyClipboard');
|
||||
copyBtn.style.cssText = `padding: 8px 16px; background: #5cb85c; color: #fff; border: none; border-radius: 6px; cursor: pointer; display: none;`;
|
||||
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.textContent = lang === 'zh' ? '关闭' : 'Close';
|
||||
closeBtn.textContent = t('mainClose');
|
||||
closeBtn.style.cssText = `padding: 8px 16px; background: #555; color: #fff; border: none; border-radius: 6px; cursor: pointer;`;
|
||||
|
||||
closeBtn.onclick = () => overlay.remove();
|
||||
@@ -877,7 +881,7 @@ const AMB_WorkflowShare = {
|
||||
copyBtn.onclick = () => {
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
AMB_WorkflowShare.showToast(lang === 'zh' ? '✅ 复制成功!' : '✅ Copied successfully!', '#5cb85c');
|
||||
AMB_WorkflowShare.showToast(t('mainCopied'), '#5cb85c');
|
||||
};
|
||||
|
||||
btnGroup.appendChild(generateBtn);
|
||||
@@ -894,9 +898,7 @@ const AMB_WorkflowShare = {
|
||||
},
|
||||
|
||||
showImportModal() {
|
||||
const lang = window.anomalous_browser_lang || 'en';
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'amb-import-modal';
|
||||
overlay.style.cssText = `
|
||||
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
|
||||
@@ -914,10 +916,10 @@ const AMB_WorkflowShare = {
|
||||
|
||||
const title = document.createElement('h2');
|
||||
title.style.margin = '0';
|
||||
title.textContent = lang === 'zh' ? '📥 导入工作流分享码' : '📥 Import Workflow Share Code';
|
||||
title.textContent = t('mainImportTitle');
|
||||
|
||||
const inputArea = document.createElement('textarea');
|
||||
inputArea.placeholder = lang === 'zh' ? '粘贴 AMB0- 或 AMB1- 开头的分享码...' : 'Paste AMB0- or AMB1- share code here...';
|
||||
inputArea.placeholder = t('mainSharePlaceholder');
|
||||
inputArea.style.cssText = `
|
||||
width: 100%; height: 100px; background: #1e1e1f; color: #eee;
|
||||
border: 1px solid #444; border-radius: 6px; padding: 10px;
|
||||
@@ -928,11 +930,11 @@ const AMB_WorkflowShare = {
|
||||
btnGroup.style.cssText = `display: flex; gap: 10px; justify-content: flex-end;`;
|
||||
|
||||
const loadBtn = document.createElement('button');
|
||||
loadBtn.textContent = lang === 'zh' ? '导入并加载' : 'Import & Load';
|
||||
loadBtn.textContent = t('mainImportLoad');
|
||||
loadBtn.style.cssText = `padding: 8px 16px; background: #e07a5f; color: #fff; border: none; border-radius: 6px; cursor: pointer;`;
|
||||
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.textContent = lang === 'zh' ? '取消' : 'Cancel';
|
||||
closeBtn.textContent = t('mainCancel');
|
||||
closeBtn.style.cssText = `padding: 8px 16px; background: #555; color: #fff; border: none; border-radius: 6px; cursor: pointer;`;
|
||||
|
||||
closeBtn.onclick = () => overlay.remove();
|
||||
@@ -940,7 +942,7 @@ const AMB_WorkflowShare = {
|
||||
loadBtn.onclick = async () => {
|
||||
const code = inputArea.value.trim();
|
||||
if (!code) {
|
||||
AMB_WorkflowShare.showToast(lang === 'zh' ? '❌ 分享码为空' : '❌ Share code is empty', '#ff6b6b');
|
||||
AMB_WorkflowShare.showToast(t('mainShareEmpty'), '#ff6b6b');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -949,13 +951,13 @@ const AMB_WorkflowShare = {
|
||||
overlay.remove();
|
||||
|
||||
const nodesCount = pendingWorkflow.nodes ? pendingWorkflow.nodes.length : 0;
|
||||
AMB_WorkflowShare.showToast(lang === 'zh' ? `✅ 成功导入 ${nodesCount} 个节点配置` : `✅ Successfully imported ${nodesCount} nodes`, '#5cb85c');
|
||||
AMB_WorkflowShare.showToast(t('mainImportedNodes', { count: nodesCount }), '#5cb85c');
|
||||
|
||||
// Auto close the main browser panel
|
||||
const mainCloseBtn = document.getElementById('anomalous-close');
|
||||
if (mainCloseBtn) mainCloseBtn.click();
|
||||
} catch (err) {
|
||||
AMB_WorkflowShare.showToast(lang === 'zh' ? `❌ 解析失败: ${err.message}` : `❌ Decode Failed: ${err.message}`, '#ff6b6b');
|
||||
AMB_WorkflowShare.showToast(t('mainDecodeFailed') + err.message, '#ff6b6b');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -970,8 +972,7 @@ const AMB_WorkflowShare = {
|
||||
document.body.appendChild(overlay);
|
||||
},
|
||||
showUnifiedModal() {
|
||||
const lang = window.anomalous_browser_lang || 'en';
|
||||
const overlay = document.createElement('div');
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.cssText = `
|
||||
position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
|
||||
background: rgba(0,0,0,0.6); backdrop-filter: blur(5px);
|
||||
@@ -988,20 +989,20 @@ const AMB_WorkflowShare = {
|
||||
|
||||
const title = document.createElement('h2');
|
||||
title.style.margin = '0';
|
||||
title.textContent = lang === 'zh' ? '🔄 工作流分享与导入' : '🔄 Workflow Share & Import';
|
||||
title.textContent = t('mainUnifiedTitle');
|
||||
|
||||
const exportBtn = document.createElement('button');
|
||||
exportBtn.textContent = lang === 'zh' ? '📤 导出当前工作流为分享码' : '📤 Export Workflow to Share Code';
|
||||
exportBtn.textContent = t('mainExportWorkflow');
|
||||
exportBtn.style.cssText = `padding: 12px; background: #4a90e2; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 14px;`;
|
||||
exportBtn.onclick = () => { overlay.remove(); this.showExportModal(); };
|
||||
|
||||
const importBtn = document.createElement('button');
|
||||
importBtn.textContent = lang === 'zh' ? '📥 从分享码导入工作流' : '📥 Import Workflow from Share Code';
|
||||
importBtn.textContent = t('mainImportWorkflow');
|
||||
importBtn.style.cssText = `padding: 12px; background: #e07a5f; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 14px;`;
|
||||
importBtn.onclick = () => { overlay.remove(); this.showImportModal(); };
|
||||
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.textContent = lang === 'zh' ? '关闭' : 'Close';
|
||||
closeBtn.textContent = t('mainClose');
|
||||
closeBtn.style.cssText = `padding: 8px; background: #555; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; margin-top: 10px;`;
|
||||
closeBtn.onclick = () => overlay.remove();
|
||||
|
||||
|
||||
@@ -237,6 +237,35 @@ export const i18n = {
|
||||
pickerNoMatches: '没有符合条件的模型。',
|
||||
pickerOperationFailed: '操作失败:',
|
||||
notebookSelectMain: '请先选择一个主模型。',
|
||||
mainPreflightTitle: '📥 导入工作流 (预检海关)',
|
||||
mainPreflightDesc: '请在此处粘贴工作流的 JSON 代码(未来将支持分享码)。我们将为您提取并校验所有模型依赖。',
|
||||
mainCancel: '取消',
|
||||
mainAnalyzePreflight: '🔍 解析并预检',
|
||||
mainForceLoad: '🚀 强制载入画布',
|
||||
mainNoDependencies: '✅ 未检测到任何模型依赖。',
|
||||
mainDetectedModels: '扫描到 {count} 个模型资源:',
|
||||
mainDownloadCivitai: '去 C站下载',
|
||||
mainReady: '已就绪',
|
||||
mainInvalidJson: '无效的 JSON!',
|
||||
mainOpenTitle: '打开 Anomalous 模型浏览器',
|
||||
mainRetryInit: '插件界面初始化失败,点击可重试',
|
||||
mainRetryInitEn: 'The plugin UI failed to initialize. Click to retry.',
|
||||
mainExportTitle: '📦 导出工作流分享码',
|
||||
mainSkeletonOption: '精简版 (推荐 B站评论区, 体积小, 丢失坐标)',
|
||||
mainFullOption: '完整版 (保留所有坐标与颜色, 较长)',
|
||||
mainGenerate: '生成分享码',
|
||||
mainCopyClipboard: '复制到剪贴板',
|
||||
mainClose: '关闭',
|
||||
mainCopied: '✅ 复制成功!',
|
||||
mainImportTitle: '📥 导入工作流分享码',
|
||||
mainSharePlaceholder: '粘贴 AMB0- 或 AMB1- 开头的分享码...',
|
||||
mainImportLoad: '导入并加载',
|
||||
mainShareEmpty: '❌ 分享码为空',
|
||||
mainImportedNodes: '✅ 成功导入 {count} 个节点配置',
|
||||
mainDecodeFailed: '❌ 解析失败: ',
|
||||
mainUnifiedTitle: '🔄 工作流分享与导入',
|
||||
mainExportWorkflow: '📤 导出当前工作流为分享码',
|
||||
mainImportWorkflow: '📥 从分享码导入工作流',
|
||||
closeHelp: '关闭说明',
|
||||
delSure: '⚠️ 确定吗?',
|
||||
dockTitle: '侧边停靠',
|
||||
@@ -728,6 +757,35 @@ export const i18n = {
|
||||
pickerNoMatches: 'No models match the current filters.',
|
||||
pickerOperationFailed: 'Operation failed: ',
|
||||
notebookSelectMain: 'Please select a Main Model first.',
|
||||
mainPreflightTitle: '📥 Pre-flight Import Workflow',
|
||||
mainPreflightDesc: 'Paste the workflow JSON here. We will extract and validate all model dependencies before loading.',
|
||||
mainCancel: 'Cancel',
|
||||
mainAnalyzePreflight: '🔍 Analyze & Pre-flight',
|
||||
mainForceLoad: '🚀 Force Load to Canvas',
|
||||
mainNoDependencies: '✅ No model dependencies detected.',
|
||||
mainDetectedModels: 'Detected {count} model resources:',
|
||||
mainDownloadCivitai: 'Download',
|
||||
mainReady: 'Ready',
|
||||
mainInvalidJson: 'Invalid JSON!',
|
||||
mainOpenTitle: 'Open Anomalous Model Browser',
|
||||
mainRetryInit: '插件界面初始化失败,点击可重试',
|
||||
mainRetryInitEn: 'The plugin UI failed to initialize. Click to retry.',
|
||||
mainExportTitle: '📦 Export Workflow Share Code',
|
||||
mainSkeletonOption: 'Skeleton (Compact, best for comments, loses coords)',
|
||||
mainFullOption: 'Full (Keeps coords and colors, longer string)',
|
||||
mainGenerate: 'Generate',
|
||||
mainCopyClipboard: 'Copy',
|
||||
mainClose: 'Close',
|
||||
mainCopied: '✅ Copied successfully!',
|
||||
mainImportTitle: '📥 Import Workflow Share Code',
|
||||
mainSharePlaceholder: 'Paste AMB0- or AMB1- share code here...',
|
||||
mainImportLoad: 'Import & Load',
|
||||
mainShareEmpty: '❌ Share code is empty',
|
||||
mainImportedNodes: '✅ Successfully imported {count} nodes',
|
||||
mainDecodeFailed: '❌ Decode Failed: ',
|
||||
mainUnifiedTitle: '🔄 Workflow Share & Import',
|
||||
mainExportWorkflow: '📤 Export Workflow to Share Code',
|
||||
mainImportWorkflow: '📥 Import Workflow from Share Code',
|
||||
closeHelp: 'Close Manual',
|
||||
delSure: '⚠️ Sure?',
|
||||
dockTitle: 'Dock to Left',
|
||||
|
||||
Reference in New Issue
Block a user