1080 lines
66 KiB
JavaScript
1080 lines
66 KiB
JavaScript
export const i18n = {
|
||
zh: {
|
||
title: '📦 Anomalous 模型浏览器',
|
||
scan: '🔄 扫描目录并同步 C 站数据',
|
||
scanTitle: '扫描目录',
|
||
scanConfirm: '注意:\n扫描过程将会自动比对 Civitai 并重命名本地模型,同时清理损坏文件。\n确定要开始扫描吗?',
|
||
scanning: '扫描中...',
|
||
scanBg: '🚀 扫描后台启动!',
|
||
scanDone: '完成',
|
||
scanCompleteMsg: '✅ 扫描完成!\n\n⚠️ 提示:由于部分模型已被重命名或去重,请点击 ComfyUI 的 [Refresh] 按钮以同步最新的模型列表。',
|
||
eco: '🔋 视频悬浮播放 (节能模式)',
|
||
autoPlay: '🎬 自动播放视频封面 (高能耗)',
|
||
togglePlayTitle: '切换播放模式',
|
||
clean: '🧹 清理无效的 .info 残留文件',
|
||
cleanTitle: '清理重复 Info',
|
||
cleanConfirm: '将扫描并删除所有无用的 .civitai.info 残留文件。是否继续?',
|
||
cleaning: '清理中',
|
||
cleanDone: '✅ 清理完毕!删除了',
|
||
files: '个文件。',
|
||
cleanFail: '❌ 清理失败: ',
|
||
cleanErr: '❌ 清理出错: ',
|
||
folders: '📂 文件夹',
|
||
collapseAll: '➖ 收起全部',
|
||
expandAll: '➕ 展开全部',
|
||
noModels: '此文件夹中没有找到模型。',
|
||
noPreview: '暂无预览图',
|
||
clickScan: '点击扫描',
|
||
back: '⬅️ 返回',
|
||
delModel: '🗑️ 删除',
|
||
delConfirm: '确定彻底删除',
|
||
delConfirm2: '及其所有关联配置、预览图和缓存文件吗?此操作不可逆!',
|
||
delSuccess: '✅ 删除成功!\n',
|
||
delNote: '\n\n⚠️ 提示:请重启 ComfyUI,否则由于缓存继续使用会报错',
|
||
delFail: '❌ 删除失败: ',
|
||
copyAll: '📋 复制全部',
|
||
copied: '✅ 已复制!',
|
||
clickToCopy: '点击复制: ',
|
||
compatibleModels: '🔗 兼容模型',
|
||
loadingCompatible: '加载中...',
|
||
backToPrev: '🔙 返回上一层',
|
||
applyToCanvas: '➕ 插入节点',
|
||
applySuccess: '✅ 已添加至工作流',
|
||
help: '帮助',
|
||
helpTitle: '📖 使用手册',
|
||
helpContent: `
|
||
<div style="line-height: 1.6; font-size: 0.95em; color: #eee; padding: 10px;">
|
||
<h3 style="color:#00ffcc; margin-top:0;">📖 标准操作说明</h3>
|
||
<p><strong>1. 🔄 前置准备 (扫描向导)</strong><br>具体位置:界面左侧底部的 <strong>扫描向导 (🔄)</strong>。<br>这是最重要的前置操作!请先打开扫描向导执行扫描。切记保持网络畅通。部分 C站限制级模型信息需要在设置中填入 API-KEY。扫描完成后,插件的各项功能基本激活。</p>
|
||
<p><strong>2. 🩺 拯救爆红 (模型医生)</strong><br>具体位置:左侧底部第二个 <strong>听诊器 (🩺)</strong> 按钮。<br>当导入别人使用该插件输出的工作流或者图片时,若模型路径爆红,点击模型医生就能智能识别当前所有节点实现正确路径的替换。</p>
|
||
<p><strong>3. 🤖 选中交互 (节点助手)</strong><br>具体位置:左侧第三个 <strong>机器人 (🤖)</strong> 按钮。<br>打开后在画布上选择模型节点。新版操作卡可以替换当前模型,也可在兼容的 MODEL + CLIP 链前方或后方插入 LoRA。模型卡会标注类别与基础模型类型;选择 LoRA 时会优先按已连接主模型的类型自动筛选,也可切回全部 LoRA。文件夹、完整路径搜索、排序和多分支安全保护仍然保留。</p>
|
||
<p><strong>4. ⚙️ 个性化配置 (设置面板)</strong><br>具体位置:左侧底部的 <strong>齿轮 (⚙️)</strong> 按钮。<br>可以调节语言、主页面字体大小;进入“模型设置”可选择视频封面始终播放或悬停播放,以及卡片使用流畅缩略图或原始封面。详情页始终保留原图画质。</p>
|
||
<p><strong>5. 📑 顶部导航与笔记本功能</strong><br>具体位置:右侧顶部的按钮分别为 <strong>模型 (📦)</strong>、<strong>图库 (🖼️)</strong>、<strong>笔记本 (📑)</strong>、<strong>停靠侧边栏 (◧)</strong>。<br>点击笔记本,点击新建 <strong>➕</strong> 号。确认后选择基础模型(如SD1.5),接着选择主模型和对应的 lora。点击下方的 <strong>📝纯文本/粘贴</strong> 填入提示词,选择左上角的语言即可实现双语对照。确认后自动分块标签,支持查找替换和直接修改单个标签。最后点击 <strong>🚀 发送到画布</strong> 即可一键连线!</p>
|
||
<p><strong>6. 📁 文件夹管理 (Folder Manager)</strong><br>具体位置:设置 (⚙️) 面板内的 <strong>📁 文件夹管理</strong>。<br>可以用来隐藏不需要的内置文件夹。隐藏后的文件夹不仅从侧边栏消失,后端的静默扫描和本地读取也会完全跳过,实现真正的“零性能损耗”。并且可以在这里拖拽调整文件夹在侧边栏显示的顺序。</p>
|
||
<p><strong>7. 📥 工作流导出与导入 (分享码)</strong><br>具体位置:设置 (⚙️) 按钮左侧的 <strong>导入/导出 (📥)</strong> 按钮。<br>该功能可以将当前的工作流一键编码导出为一段专属分享码,方便发到社交平台。别人只需复制这串分享码,在此界面粘贴即可直接还原导入完整的工作流,并且在导入前系统会自动校验模型是否在本地存在。</p>
|
||
<p><strong>8. 💬 提交反馈 / 报告问题</strong><br>具体位置:设置 (⚙️) 面板内的 <strong>💬 提交反馈 / 报告问题</strong>。<br>当你遇到插件报错或有新功能建议时,点击该按钮可一键直达 GitHub Issues 页面提交反馈。</p>
|
||
</div>`,
|
||
notebooks: '笔记本',
|
||
notebookTitle: '笔记本管理',
|
||
workspace: '创作工作台',
|
||
workspaceTitle: '创作工作台',
|
||
promptNotes: '提示词笔记',
|
||
createNotebook: '新建',
|
||
saveNotebook: '💾 保存',
|
||
deleteNotebook: '🗑️ 删除',
|
||
renameNotebook: '✏️ 重命名',
|
||
baseModel: 'Base Model (基础模型)',
|
||
mainModel: 'Main Model (主模型)',
|
||
sendToCanvas: '🚀 发送到画布',
|
||
translate: '翻译',
|
||
newNotebookName: '新笔记本名称',
|
||
noBaseModel: '请先选择 Base Model',
|
||
dialogOk: '确定',
|
||
dialogCancel: '取消',
|
||
editModel: '编辑模型 (Edit)',
|
||
scanModelPrecisely: '精准扫描此模型',
|
||
unsupportedAutoApply: '不支持该类型模型的自动应用。',
|
||
createNodeFailed: '创建节点失败: ',
|
||
galleryLoading: '加载中...',
|
||
galleryErrorPrefix: '错误: ',
|
||
galleryDelete: '删除',
|
||
galleryDeleteConfirm: '彻底删除这张图片?',
|
||
galleryDeleteConfirmHint: '删除后无法恢复。',
|
||
galleryDeleting: '删除中...',
|
||
galleryDeleteFailed: '删除失败: ',
|
||
galleryNoMore: '没有更多图片了',
|
||
galleryScrollMore: '向下滚动加载更多',
|
||
galleryLoadFailed: '加载失败',
|
||
galleryEmpty: '图库为空',
|
||
galleryClose: '关闭图库',
|
||
galleryHistoryTitle: '历史生成图库: {name}',
|
||
galleryScanning: '加载中,正在扫描图片元数据...',
|
||
galleryNoModelImages: '没有找到使用此模型生成的历史图片。',
|
||
gallerySetCover: '设为封面',
|
||
gallerySetCoverConfirm: '确定将此图片设为封面吗?',
|
||
galleryCoverSuccess: '设置成功!',
|
||
gallerySelectingCoverPrefix: '正在为模型 ',
|
||
gallerySelectingCoverSuffix: ' 选择封面。请点击下方的图片。',
|
||
galleryLoadImagesError: '加载历史图片失败',
|
||
galleryCancel: '取消',
|
||
detailBackDoctor: '⬅ 返回医生面板',
|
||
detailBackAssistant: '⬅ 返回助手面板',
|
||
detailJumpToBottom: '跳到底部',
|
||
detailEdit: '⚙️ 编辑',
|
||
detailNotesTitle: '📓 专属备注 (Notes)',
|
||
detailEditNotes: '编辑备注',
|
||
detailGeneratedGallery: '🖼️ 查看历史生成作品',
|
||
detailTrainedWords: '训练词:',
|
||
detailDescription: '描述:',
|
||
detailNotes: '备注:',
|
||
detailNoCompatibleModels: '没有找到兼容模型。',
|
||
detailCompatibleLoadFailed: '加载失败。',
|
||
detailToggleSound: '开启/关闭声音',
|
||
detailNoCover: '暂无封面',
|
||
detailPickGallery: '从历史图库挑选',
|
||
detailUploadLocal: '从本地上传',
|
||
detailUploadError: '错误: ',
|
||
detailUploadFailed: '上传失败: ',
|
||
detailModelInfo: '编辑模型信息',
|
||
detailFile: '文件:',
|
||
detailCustomNamePlaceholder: '自定义名称 (留空则使用原名)',
|
||
detailNotesPlaceholder: '📓 专属备注... (支持多行)',
|
||
detailPhysicalRename: '同步物理重命名',
|
||
detailPhysicalRenameDesc: '若不勾选则仅虚拟重命名,安全不破坏原文件。',
|
||
detailResetAll: '重置设置',
|
||
detailResetConfirm: '确定要重置该模型的所有自定义信息(名称、封面、备注)吗?',
|
||
detailResetCoverPreserved: '未找到可恢复的 C 站备份或原始封面。为避免丢失唯一图片,当前封面已保留;自定义名称和备注仍已重置。',
|
||
detailResetCoverFailed: '封面恢复未能完整完成,请检查文件权限或占用情况;自定义名称和备注仍已重置。',
|
||
detailResetFailed: '重置失败。',
|
||
detailCancel: '取消',
|
||
detailSaveChanges: '保存更改',
|
||
detailSaving: '保存中...',
|
||
detailSelectModels: '选择模型',
|
||
detailFolders: '目录列表',
|
||
detailSelectedItems: '已选 {count} 项',
|
||
detailSelectedItemsPrefix: '已选 ',
|
||
detailSelectedItemsSuffix: ' 项',
|
||
detailNothingSelected: '暂未选择',
|
||
detailLoadedModels: '已加载 {loaded} / {total} 个模型',
|
||
detailComputing: '计算中...',
|
||
detailSelectNoPreview: '选无预览图',
|
||
detailSelectNoDesc: '选无介绍',
|
||
detailSelectAll: '全选',
|
||
detailClear: '清空',
|
||
detailConfirm: '确定',
|
||
detailLoading: '加载中 (Loading)...',
|
||
detailAllLoaded: '✅ 已加载全部',
|
||
detailScrollMore: '滑动加载更多...',
|
||
detailNoModels: '没有找到模型。',
|
||
detailApiRestart: '❌ 请求后端 API 失败!\n\n你必须重启 ComfyUI 进程,刚写入后端的代码才能生效。',
|
||
doctorTitle: '全局体检中心',
|
||
doctorAutoScanOn: 'ComfyUI 启动时自动检测并替换缺失模型 [已开启]',
|
||
doctorAutoScanOff: 'ComfyUI 启动时自动检测并替换缺失模型 [默认关闭]',
|
||
doctorRefresh: '🔄 刷新缓存',
|
||
doctorRefreshTitle: '重新读取最新模型列表并清除报错',
|
||
doctorGraphUnavailable: '当前画布或节点不可用。',
|
||
doctorMissingChainInputs: '节点没有完整的 MODEL/CLIP 输入。',
|
||
doctorUnconnectedChainInputs: 'MODEL/CLIP 输入尚未全部连接。',
|
||
doctorMissingChainOutputs: '节点没有完整的 MODEL/CLIP 输出。',
|
||
doctorAmbiguousBranches: '检测到多个下游分支,请先整理或选择明确链路。',
|
||
doctorInvalidDownstream: '现有下游连线无效,无法安全改写。',
|
||
doctorUnsupportedInsertion: '当前节点不支持此插入方式。',
|
||
doctorCreateLoraFailed: '无法创建标准 LoRA Loader 节点。',
|
||
doctorLoraNotReady: 'LoRA 列表尚未就绪,请刷新 ComfyUI 模型列表后重试。',
|
||
assistantSelectNode: '请在画布中点击选中任意节点',
|
||
assistantNoModelParameter: '该节点没有受支持的模型参数',
|
||
assistantSelectedNode: '当前选中节点',
|
||
assistantQuickActions: '快捷操作',
|
||
assistantChangeWidget: '更换 {name}',
|
||
assistantChangeCurrent: '更换当前模型',
|
||
assistantVisualPicker: '{type} · 可视化选择',
|
||
assistantInsertBefore: '前方插入 LoRA',
|
||
assistantInsertAfter: '后方插入 LoRA',
|
||
assistantConnectInputs: '接入节点输入端',
|
||
assistantConnectOutputs: '接入节点输出端',
|
||
doctorTotal: '总模型',
|
||
doctorHealthy: '🟢 健康',
|
||
doctorMissing: '🔴 缺失',
|
||
doctorNoWorkflowModels: '当前工作流没有任何模型节点',
|
||
doctorAutoRedirected: '自动重定向',
|
||
doctorReady: '正常',
|
||
doctorMissingStatus: '丢失',
|
||
doctorCivitai: '🌐 C站',
|
||
doctorDeepHashScan: '🔍 深度哈希扫描',
|
||
doctorScanStarting: '⏳ 正在启动扫描引擎...',
|
||
doctorScanning: '⏳ 扫描中 ({current}/{total}) {filename}',
|
||
doctorScanError: '❌ 扫描过程中发生错误: ',
|
||
doctorMatching: '⏳ 正在匹配并替换飘红节点...',
|
||
doctorDeepScanSummary: '共深度扫描了 {total} 个缺失信息的模型。',
|
||
doctorNoLocalMatch: '❌ 扫描结束,本地未匹配到模型。\n\n{summary}\n\n这说明模型可能真的不在您的硬盘里,或者您删除了原本记录着哈希的源文件。\n请点击卡片上的【🌐 C站】去云端下载。',
|
||
doctorScanSuccess: '✅ 深度扫描成功!已自动修复该节点!\n\n{summary}',
|
||
doctorPollError: '❌ 状态轮询出错: ',
|
||
doctorScanFailed: '❌ 扫描出错: ',
|
||
doctorManualReplace: '🔀 手动替换',
|
||
doctorOriginalPath: '原路径: {path}',
|
||
assistantPreviewFailed: '无法加载预览',
|
||
assistantHistoryTitle: '🖼️ 历史生成图 ({count})',
|
||
assistantViewAll: '查看全部 →',
|
||
assistantRestoreWorkflow: '点击恢复此工作流',
|
||
assistantViewGallery: '点击查看完整图库',
|
||
pickerNoCompatible: '没有可供选择的兼容模型。',
|
||
pickerEyebrow: '节点助手 · 模型选择器',
|
||
pickerInsertBeforeTitle: '⬅ 在节点前方插入 LoRA',
|
||
pickerInsertAfterTitle: '在节点后方插入 LoRA ➡',
|
||
pickerChangeTitle: '🔀 更换当前模型',
|
||
pickerFolders: '📁 文件夹',
|
||
pickerSearch: '🔍 搜索名称或完整路径…',
|
||
pickerMainAll: '主模型:全部类型',
|
||
pickerNameAsc: '名称 A–Z',
|
||
pickerNameDesc: '名称 Z–A',
|
||
pickerByFolder: '按文件夹',
|
||
pickerLoadingCovers: '⏳ 正在加载模型封面…',
|
||
pickerCancel: '取消',
|
||
pickerInsertConfirm: '插入并自动连线',
|
||
pickerReplaceConfirm: '确认替换',
|
||
pickerCoversFailed: '⚠️ 封面加载失败,仍可按名称选择模型。',
|
||
doctorNodeLabel: '节点',
|
||
doctorAllHealthy: '🎉 太棒了!当前工作流中所有模型均在本地就绪。',
|
||
doctorLoadingPreview: '加载预览...',
|
||
doctorNoPreview: '暂无预览图',
|
||
doctorProfile: '📖 查看档案',
|
||
doctorBase: '基础模型',
|
||
doctorTriggerWords: '触发词',
|
||
doctorClickCopy: '点击复制',
|
||
doctorCopyAll: '📋 全部复制',
|
||
doctorNotes: '📝 我的备注',
|
||
pickerSelected: '已选择: {path}',
|
||
pickerChooseModel: '请选择一个模型。节点连接和强度不会被改动。',
|
||
pickerCompatibleMain: '✨ 已根据主模型 {name} 自动筛选兼容 LoRA;可在上方切换为全部类型。',
|
||
pickerCompatibleTag: '✨ 未找到已连接主模型,已根据当前 LoRA 的 {name} 标注筛选。',
|
||
pickerCompatibleNoTags: 'ℹ 已识别主模型 {name},但没有同类 LoRA 标注,因此暂时显示全部。',
|
||
pickerCompatibleUnknown: 'ℹ 未识别到主模型类型,暂时显示全部 LoRA;你仍可手动按基础模型筛选。',
|
||
pickerAllModels: '全部模型',
|
||
pickerResults: '{count} 个结果',
|
||
pickerCurrent: '当前',
|
||
pickerBaseModel: '基础模型',
|
||
pickerRoot: '根目录',
|
||
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: '侧边停靠',
|
||
replaceAll: '全部替换',
|
||
editRaw: '📝 纯文本/粘贴',
|
||
findPlaceholder: '查找...',
|
||
replacePlaceholder: '替换为...',
|
||
models: '模型',
|
||
gallery: '图库',
|
||
apiKeyConfig: '🔑 Civitai API 密钥配置 (用于下载封面)',
|
||
apiKeyPrompt: '请输入你的 Civitai API Key:',
|
||
settingsBtn: '设置',
|
||
closeSettings: '✖ 关闭设置面板',
|
||
doctorBtn: '节点助手',
|
||
importBtn: '📥 导入 / 导出工作流 (分享码)',
|
||
recipeTitle: '工作流配方',
|
||
recipeClose: '关闭工作流配方',
|
||
recipeSaveCurrent: '保存当前工作流',
|
||
recipeSaveTitle: '保存为工作流配方',
|
||
recipeName: '名称',
|
||
recipeDefaultName: '新工作流配方',
|
||
recipeNameRequired: '请填写配方名称。',
|
||
recipeTags: '标签',
|
||
recipeTagsHint: '例如:人像, SDXL, 放大',
|
||
recipeNotes: '备注',
|
||
recipeNotesHint: '记录这个版本为什么好用……',
|
||
recipeThumbnail: '工作流缩略图',
|
||
recipeCancel: '取消',
|
||
recipeSave: '保存',
|
||
recipeSaving: '正在保存…',
|
||
recipeSaveReceipt: '已保存:{nodes} 个节点,{links} 条连线,{groups} 个分组。',
|
||
recipeSaveReceiptMismatch: '已保存,但完整性回执与当前画布不一致;请重新打开配方核对。',
|
||
recipeEmpty: '还没有工作流配方。保存一个已经跑通的工作流吧。',
|
||
recipeLoadError: '无法加载工作流配方。',
|
||
recipeUntitled: '未命名配方',
|
||
recipeSearchPlaceholder: '搜索名称、笔记或标签…',
|
||
recipeClearFilters: '清除筛选',
|
||
recipeNoTags: '暂无标签',
|
||
recipeNoMatches: '没有匹配的配方。请清除搜索或标签筛选。',
|
||
recipeFilterByTag: '按此标签筛选',
|
||
recipeImport: '导入配方包',
|
||
recipeExport: '导出配方包',
|
||
recipeExportError: '导出工作流配方包失败。',
|
||
recipeImportError: '导入工作流配方包失败。',
|
||
recipeExportSnapshotsConfirm: '导出时包含模型预览快照吗?\n\n快照是展示数据,不包含模型文件;视频封面只保留轻量静态快照,不会打包原视频。',
|
||
recipeExportHistoryConfirm: '导出时包含历史版本吗?\n\n历史可能包含旧提示词和备注。',
|
||
recipeExportRedactIdentityConfirm: '是否移除模型 SHA-256、大小和来源信息?\n\n点击“确定”将移除这些身份信息。',
|
||
recipeImportSummary: '配方包检查完成。确认导入以下内容吗?',
|
||
recipeImportAssets: '预览资产',
|
||
recipeImportHistory: '历史版本',
|
||
recipeImportRenamePrompt: '配方名称已存在,请输入导入后的名称:',
|
||
recipeModel: '模型',
|
||
recipeLoras: 'LoRA',
|
||
recipePrompt: '正向提示词',
|
||
recipeSteps: '步数',
|
||
recipeRestore: '恢复版本',
|
||
recipeAppendCanvas: '追加到画布',
|
||
recipeAppendError: '无法追加此配方到当前画布。',
|
||
recipeDelete: '删除',
|
||
recipeDeleteConfirm: '确定删除这个工作流配方吗?',
|
||
recipeDeleteError: '删除工作流配方失败。',
|
||
recipeSaveError: '保存工作流配方失败。',
|
||
recipeNodeParams: '节点参数',
|
||
recipeUnknownNode: '未知节点',
|
||
recipeCopyParameter: '复制参数值',
|
||
recipeCopied: '已复制',
|
||
recipeCopyFailed: '复制失败',
|
||
recipeAdvancedInfo: '高级信息',
|
||
recipeInlineEditName: '点击修改名称',
|
||
recipeInlineEditNotes: '点击修改备注',
|
||
recipeInlineEditTags: '编辑标签',
|
||
recipeInlineSave: '保存',
|
||
recipeInlineCancel: '取消',
|
||
recipeInlineSaving: '保存中…',
|
||
recipeInlineError: '保存失败,请重试。',
|
||
recipeInlineNameRequired: '名称不能为空。',
|
||
recipeSomeParamsInWorkflow: '其余复杂或敏感参数仍保存在完整工作流中。',
|
||
recipeMoreNodesInWorkflow: '其余节点仍保存在完整工作流中。',
|
||
recipeMissingNodes: '当前缺少以下节点类型,恢复后可能无法运行',
|
||
recipeBindImage: '绑定封面图片',
|
||
recipeNoImage: '不绑定图片',
|
||
recipeCanvasPreview: '当前画布',
|
||
recipeLoadingRecentImages: '正在读取最近生成结果…',
|
||
recipeRecentImages: '最近生成结果',
|
||
recipeRecentImagesUnavailable: '暂时无法读取最近生成结果。',
|
||
recipeChoosePinnedParams: '选择关键参数',
|
||
recipePinnedParams: '关键参数',
|
||
recipeKeepImage: '保留当前封面',
|
||
recipeEdit: '编辑',
|
||
recipeEditTitle: '编辑工作流配方',
|
||
recipeEditNodeParams: '编辑节点参数',
|
||
recipeComplexParam: '复杂值仅展示;完整值仍安全保存在工作流中。',
|
||
recipeHistory: '版本历史',
|
||
recipeHistoryEmpty: '还没有历史版本。更新配方后会自动保留旧版本。',
|
||
recipeUnknownVersion: '未知时间',
|
||
recipeRestoreVersion: '恢复此版本',
|
||
recipeRestoreVersionConfirm: '恢复此版本会覆盖当前配方,但当前内容会先自动存入历史。继续吗?',
|
||
recipeEditCanvas: '载入画布编辑',
|
||
recipeEditCanvasConfirm: '载入会替换当前画布。完成结构编辑后,点击“更新当前配方”即可保存为新版本。继续吗?',
|
||
recipeUpdate: '保存更新',
|
||
recipeUpdateCurrent: '更新当前配方',
|
||
recipeUpdateError: '更新工作流配方失败。',
|
||
recipeViewDetails: '查看详情',
|
||
recipeDetailBack: '返回配方列表',
|
||
recipeDetailOverview: '概览',
|
||
recipeGallery: '图库',
|
||
recipeGalleryLoading: '正在查找匹配的历史出图…',
|
||
recipeGalleryEmpty: '未在最近 200 张含工作流数据的 PNG 中找到与此配方结构匹配的出图。',
|
||
recipeGalleryRefresh: '刷新匹配结果',
|
||
recipeGallerySetCover: '设为配方封面',
|
||
recipeGalleryCoverSaved: '已设为配方封面',
|
||
recipeGalleryCoverError: '无法设置配方封面。',
|
||
recipeGalleryLoadError: '无法读取配方图库。',
|
||
recipeGalleryScanHint: '已检查最近 {count} 张含工作流数据的 PNG。',
|
||
recipeGalleryOpenImage: '查看原图',
|
||
recipeGalleryCompare: '参数对比',
|
||
recipeGalleryComparison: '配方与出图参数',
|
||
recipeGalleryComparisonLoading: '正在读取这张图片的参数…',
|
||
recipeGalleryComparisonError: '无法读取这张图片的参数。',
|
||
recipeGalleryNoDifferences: '没有发现可显示的参数差异。',
|
||
recipeGalleryDifferenceHint: '以下是节点参数的差异;图库匹配本身只关注节点类型。',
|
||
recipeGalleryRecipeValue: '配方',
|
||
recipeGalleryImageValue: '图片',
|
||
recipeDetailModels: '模型与复现',
|
||
recipeDetailParameters: '参数笔记本',
|
||
recipeDetailParametersHint: '这里展示配方保存的完整参数;长文本可展开并复制。参数图库只匹配相同参数,不会因为本地路径不同而失效。',
|
||
recipeParameterSnapshots: '参数快照',
|
||
recipeParameterSnapshotsHint: '每次保存配方都会留下一个只读版本。',
|
||
recipeParameterRefresh: '刷新快照',
|
||
recipeParameterLoading: '正在读取参数快照…',
|
||
recipeParameterLoadError: '无法读取参数快照。',
|
||
recipeParameterNoSnapshots: '这个配方还没有参数快照。',
|
||
recipeParameterUntitled: '未命名参数快照',
|
||
recipeParameterNew: '新建参数笔记',
|
||
recipeParameterReadCurrent: '读取当前并新建',
|
||
recipeParameterReadCurrentSaving: '正在读取并保存…',
|
||
recipeParameterReadCurrentError: '无法读取当前画布参数。',
|
||
recipeParameterSkeletonMismatch: '当前画布缺少配方骨架节点,未执行任何修改。',
|
||
recipeParameterApply: '应用到当前工作流',
|
||
recipeParameterApplying: '正在检查骨架并应用…',
|
||
recipeParameterApplied: '已应用 {count} 项参数',
|
||
recipeParameterApplyError: '应用参数失败,当前画布未完成修改。',
|
||
recipeParameterNewHint: '基于当前配方骨架编辑节点参数;种子等运行时变化值会被忽略。保存后会生成新的只读快照,不会覆盖历史记录。',
|
||
recipeParameterName: '笔记名称',
|
||
recipeParameterSave: '保存参数笔记',
|
||
recipeParameterCancel: '取消编辑',
|
||
recipeParameterSaveError: '参数笔记保存失败。',
|
||
recipeParameterSensitiveHidden: '敏感控件已隐藏',
|
||
recipeDetailParameterSummary: '常用参数',
|
||
recipeDetailNodeParameters: '节点参数',
|
||
recipeDetailUnknownNode: '未命名节点',
|
||
recipeDetailWidget: '控件',
|
||
recipeDetailScheduler: '调度器',
|
||
recipeDetailSteps: '步数',
|
||
recipeDetailCFG: 'CFG',
|
||
recipeDetailDenoise: '降噪',
|
||
recipeDetailSeed: '种子',
|
||
recipeParameterGallery: '相同参数的出图',
|
||
recipeParameterGalleryLoading: '正在查找相同参数的历史出图…',
|
||
recipeParameterGalleryEmpty: '没有找到参数完全一致的历史出图。',
|
||
recipeParameterGalleryError: '无法读取参数图库。',
|
||
recipeDetailNoSavedParameters: '此配方没有可展示的参数。',
|
||
recipeDetailVersions: '版本',
|
||
recipeDetailSummary: '摘要',
|
||
recipeDetailModelComposition: '模型组成',
|
||
recipeDetailBaseModel: '主模型',
|
||
recipeDetailIdentity: '已验证身份',
|
||
recipeDetailUnverified: '未验证模型',
|
||
recipeDetailMissingNodes: '缺失节点类型',
|
||
recipeDetailFingerprint: '工作流指纹',
|
||
recipeDetailNotIndexed: '尚未索引',
|
||
recipeDetailLoraSummary: 'LoRA',
|
||
recipeDetailSampler: '采样器',
|
||
recipeDetailResolution: '分辨率',
|
||
recipeDetailUnavailable: '不可用',
|
||
recipeDetailEditOrigin: '✏️ 编辑来源',
|
||
recipeOriginDialogTitle: '编辑模型来源信息',
|
||
recipeOriginOfficialName: '官方模型名称',
|
||
recipeOriginModelUrl: '官方网址 (Model URL)',
|
||
recipeOriginFetchHash: '🌍 通过 Hash 自动获取',
|
||
recipeOriginFetching: '获取中...',
|
||
recipeOriginFetchFailed: '未找到或网络异常',
|
||
recipeOriginFetchSuccess: '已成功获取!',
|
||
recipeDetailNoNotes: '没有备注。',
|
||
recipeDetailUpdated: '更新时间',
|
||
recipeDetailCopyPrompt: '复制提示词',
|
||
recipeDetailCopyFingerprint: '复制工作流指纹',
|
||
recipeDetailCopyHash: '复制完整哈希',
|
||
recipeDetailPrompts: '提示词',
|
||
recipeDetailPositivePrompt: '正向提示词',
|
||
recipeDetailNegativePrompt: '负面提示词',
|
||
recipeDetailNoPrompts: '此配方没有可安全展示的提示词摘要。',
|
||
recipeDetailVolatileIgnored: '运行时变化,已忽略',
|
||
recipeDetailExpandValue: '展开完整内容',
|
||
recipeDetailCollapseValue: '收起内容',
|
||
recipeIdentityVerified: '身份已核验',
|
||
recipeIdentityUnverified: '本地可用,未核验',
|
||
recipeIdentityUnavailable: '本机未找到',
|
||
recipeIdentityHelpDesc: '核验的是物理文件是否一致,不代表判断模型画质或可用性。',
|
||
recipeDetailRefreshAvailability: '检查本机可用性',
|
||
recipeDetailRefreshOrigin: '刷新来源信息',
|
||
recipeDetailRefreshOriginDesc: '刷新当前配方中的所有模型来源信息(支持补充旧配方数据)',
|
||
recipeDetailRefreshing: '检查中…',
|
||
recipeDetailRefreshError: '检查本机可用性失败',
|
||
recipeDetailAvailable: '本机可用',
|
||
recipeDetailMissing: '本机缺失',
|
||
recipeDetailAvailabilityNotChecked: '尚未检查本机',
|
||
recipeDetailNoModelReferences: '没有识别到受支持的模型加载器。其他自定义控件保留在参数和完整工作流中。',
|
||
recipeDetailModel: '模型',
|
||
recipeModelPath: '模型路径',
|
||
recipeOpenLocalModel: '打开本地模型详情',
|
||
recipeMatchLocalModel: '匹配本地模型',
|
||
recipeMatchingLocalModel: '正在匹配本地模型…',
|
||
recipeMatchRecipeModels: '匹配配方模型',
|
||
recipeMatchRecipeModelsDesc: '根据模型哈希、文件大小和模型类别,在本地模型目录中查找对应文件;不会使用原作者路径。',
|
||
recipeMatchingRecipeModels: '正在匹配配方模型…',
|
||
recipeRecipeMatchSummary: '已找到 {found}/{total} 个本地匹配',
|
||
recipeAllModelsMatched: '配方中的模型都已匹配或无需匹配',
|
||
recipeApplyLocalMatch: '应用此匹配',
|
||
recipeApplyLocalMatchDesc: '将匹配到的本地模型路径写入配方工作流',
|
||
recipeApplyingLocalMatch: '正在应用匹配…',
|
||
recipeApplyLocalMatchSuccess: '已应用本地模型匹配',
|
||
recipeApplyLocalMatchError: '无法应用此模型匹配',
|
||
recipeLocalModelNotFound: '暂未找到匹配的本地模型。',
|
||
recipeLocalModelAmbiguous: '找到多个候选,请先补充模型身份信息。',
|
||
recipeLocalModelMatchError: '匹配失败,请稍后重试。',
|
||
recipeDetailSearchParameters: '按节点、类型或控件名称搜索…',
|
||
recipeDetailWidget: '控件',
|
||
recipeDetailNoParameters: '没有匹配的安全参数。',
|
||
recipeDetailCurrentVersion: '当前版本',
|
||
recipeCompareVersion: '对比此版本',
|
||
recipeDiffLoading: '正在比较…',
|
||
recipeDiffSummary: '变更摘要',
|
||
recipeDiffBefore: '之前',
|
||
recipeDiffAfter: '之后',
|
||
recipeDiffNoChanges: '两个版本没有检测到有意义的变更。',
|
||
recipeDiffError: '无法读取此历史版本。',
|
||
recipeDiffPinned: '钉选参数',
|
||
recipeDiffPrompts: '提示词',
|
||
recipeDiffModels: '模型引用',
|
||
recipeDiffParameters: '参数摘要',
|
||
recipeDiffWorkflow: '工作流结构',
|
||
recipeDiffPresentation: '展示信息',
|
||
recipeDiffOther: '其他',
|
||
recipeDetailUnknownTime: '未知时间',
|
||
recipeDetailLoadingPreviews: '正在加载本机模型预览…',
|
||
recipeDetailCurrentPreview: '当前本机预览',
|
||
recipeDetailNoPreview: '暂无预览',
|
||
recipeSaveModelPreviewSnapshots: '打包模型封面图 (内置到配方中)',
|
||
recipeSaveModelPreviewSnapshotsHint: '将当前模型的小尺寸封面图直接打包进配方文件中。当你将配方分享给他人时,对方即使没有该模型也能看到精美的封面图。(为节省体积,仅保存静态图,不包含视频)',
|
||
recipeDetailSavedSnapshot: '已保存快照',
|
||
|
||
},
|
||
en: {
|
||
title: '📦 Anomalous Model Browser',
|
||
scan: '🔄 Scan folder and sync Civitai data',
|
||
scanTitle: 'Scan Folder',
|
||
scanConfirm: 'Notice:\nThe scan process will automatically compare with Civitai and rename your local files.\nStart scan?',
|
||
scanning: 'Scanning...',
|
||
scanBg: '🚀 Scan started in background!',
|
||
scanDone: 'Done',
|
||
scanCompleteMsg: '✅ Scan Complete!\n\n⚠️ Note: Please click [Refresh] to sync the model list since files were renamed.',
|
||
eco: '🔋 Eco Mode (Play video on hover)',
|
||
autoPlay: '🎬 AutoPlay Videos (High perf cost)',
|
||
togglePlayTitle: 'Toggle Play Mode',
|
||
clean: '🧹 Clean orphaned .info files',
|
||
cleanTitle: 'Clean Duplicate Info',
|
||
cleanConfirm: 'This will globally scan and delete all redundant .civitai.info files. Continue?',
|
||
cleaning: 'Cleaning',
|
||
cleanDone: '✅ Clean Complete! Deleted',
|
||
files: 'files.',
|
||
cleanFail: '❌ Failed: ',
|
||
cleanErr: '❌ Error: ',
|
||
folders: '📂 Folders',
|
||
collapseAll: '➖ Collapse All',
|
||
expandAll: '➕ Expand All',
|
||
noModels: 'No models found in this folder.',
|
||
noPreview: 'No preview available',
|
||
clickScan: 'Click Scan',
|
||
back: '⬅️ Back',
|
||
delModel: '🗑️ Delete',
|
||
delConfirm: 'Are you sure you want to permanently delete',
|
||
delConfirm2: ' and all its associated configs, previews, and cache files? This action is irreversible!',
|
||
delSuccess: '✅ Delete Complete!\n',
|
||
delNote: '\n\n⚠️ Note: Please restart ComfyUI, otherwise continuing to use it due to cache will cause errors.',
|
||
delFail: '❌ Delete Failed: ',
|
||
copyAll: '📋 Copy All',
|
||
copied: '✅ Copied!',
|
||
clickToCopy: 'Click to copy: ',
|
||
compatibleModels: '🔗 Compatible Models',
|
||
loadingCompatible: 'Loading...',
|
||
backToPrev: '🔙 Back to prev',
|
||
applyToCanvas: '➕ Add Node',
|
||
applySuccess: '✅ Added to workflow',
|
||
help: 'Help',
|
||
helpTitle: '📖 User Manual',
|
||
helpContent: `
|
||
<div style="line-height: 1.6; font-size: 0.95em; color: #eee; padding: 10px;">
|
||
<h3 style="color:#00ffcc; margin-top:0;">📖 Step-by-Step Guide</h3>
|
||
<p><strong>1. 🔄 Prerequisites (Scan Wizard)</strong><br>Location: The <strong>Scan (🔄)</strong> icon at the bottom left.<br>This is the most crucial step! Open the Scan Wizard and execute a scan. Keep your network connected. (API-KEY may be needed in Settings for NSFW Civitai models). Scanning activates all core features.</p>
|
||
<p><strong>2. 🩺 Fix Broken Nodes (Model Doctor)</strong><br>Location: The 2nd icon <strong>(Stethoscope 🩺)</strong> from the bottom left.<br>When importing a workflow/image exported by this plugin and nodes turn red, open the Model Doctor to intelligently identify and replace them with correct local paths.</p>
|
||
<p><strong>3. 🤖 Visual Info (Node Assistant)</strong><br>Location: The 3rd icon <strong>(Robot 🤖)</strong> from the bottom left.<br>The redesigned action card can replace the current model or insert a LoRA before/after a compatible MODEL + CLIP chain. Picker cards show category and base-model badges. LoRA choices default to the connected main model's type when metadata is available, with an option to show all LoRAs. Folder browsing, full-path search, sorting, and ambiguous-branch protection remain available.</p>
|
||
<p><strong>4. ⚙️ Settings Panel</strong><br>Location: The <strong>Gear (⚙️)</strong> icon at the bottom left.<br>Adjust language and UI scale. Open “Model Settings” to choose always-play or hover-play video covers and optimized thumbnails or original covers. Detail pages always retain original image quality.</p>
|
||
<p><strong>5. 📑 Top Navigation & Notebook</strong><br>Location: Top right tabs <strong>Models (📦)</strong>, <strong>Gallery (🖼️)</strong>, <strong>Notebook (📑)</strong>, <strong>Dock (◧)</strong>.<br>Click Notebook -> click <strong>➕</strong> -> select Base Model (e.g. SD1.5) -> pick Main Model & LoRA. Click <strong>📝 Edit Raw/Paste</strong> to input prompts, select language on top-left for bilingual translation. Confirm to auto-chunk tags (supports find/replace & direct edit). Finally, click <strong>🚀 Send to Canvas</strong>!</p>
|
||
<p><strong>6. 📁 Folder Manager</strong><br>Location: Inside the Settings (⚙️) panel, click <strong>📁 Manage Folders</strong>.<br>Use this to hide unneeded built-in folders. Hidden folders will be completely skipped by background scans and I/O reads, achieving true "zero performance cost". You can also drag and drop to reorder how folders appear in the sidebar.</p>
|
||
<p><strong>7. 📥 Workflow Export & Import (Share Code)</strong><br>Location: The <strong>Import/Export (📥)</strong> icon to the left of the Settings gear.<br>You can encode your current workflow into a single share code for easy sharing on social platforms. Others can simply copy this code and paste it here to instantly import the workflow. The system will also automatically pre-check if the required models exist locally.</p>
|
||
<p><strong>8. 💬 Submit Feedback / Report Bug</strong><br>Location: Inside the Settings (⚙️) panel, click <strong>💬 Submit Feedback / Report Bug</strong>.<br>If you encounter bugs or have feature requests, click this to go directly to our GitHub Issues page.</p>
|
||
</div>`,
|
||
notebooks: 'Notebooks',
|
||
notebookTitle: 'Notebook Manager',
|
||
workspace: 'Workspace',
|
||
workspaceTitle: 'Creative Workspace',
|
||
promptNotes: 'Prompt Notes',
|
||
createNotebook: 'New',
|
||
saveNotebook: '💾 Save',
|
||
deleteNotebook: '🗑️ Delete',
|
||
renameNotebook: '✏️ Rename',
|
||
baseModel: 'Base Model',
|
||
mainModel: 'Main Model',
|
||
sendToCanvas: '🚀 Send to Canvas',
|
||
translate: 'Translate',
|
||
newNotebookName: 'New Notebook Name',
|
||
noBaseModel: 'Select a Base Model first',
|
||
dialogOk: 'OK',
|
||
dialogCancel: 'Cancel',
|
||
editModel: 'Edit Model',
|
||
scanModelPrecisely: 'Scan This Model',
|
||
unsupportedAutoApply: 'Unsupported model type for auto-apply.',
|
||
createNodeFailed: 'Failed to create node: ',
|
||
galleryLoading: 'Loading...',
|
||
galleryErrorPrefix: 'Error: ',
|
||
galleryDelete: 'Delete',
|
||
galleryDeleteConfirm: 'Delete this image permanently?',
|
||
galleryDeleteConfirmHint: 'This cannot be undone.',
|
||
galleryDeleting: 'Deleting...',
|
||
galleryDeleteFailed: 'Delete failed: ',
|
||
galleryNoMore: 'No more images',
|
||
galleryScrollMore: 'Scroll down to load more',
|
||
galleryLoadFailed: 'Load failed',
|
||
galleryEmpty: 'Gallery is empty',
|
||
galleryClose: 'Close Gallery',
|
||
galleryHistoryTitle: 'Generated History: {name}',
|
||
galleryScanning: 'Loading, scanning metadata...',
|
||
galleryNoModelImages: 'No images found generated by this model.',
|
||
gallerySetCover: 'Set Cover',
|
||
gallerySetCoverConfirm: 'Set this image as the cover?',
|
||
galleryCoverSuccess: 'Cover set successfully!',
|
||
gallerySelectingCoverPrefix: 'Selecting cover for ',
|
||
gallerySelectingCoverSuffix: '. Click an image below.',
|
||
galleryLoadImagesError: 'Error loading images',
|
||
galleryCancel: 'Cancel',
|
||
detailBackDoctor: '⬅ Back to Doctor',
|
||
detailBackAssistant: '⬅ Back to Assistant',
|
||
detailJumpToBottom: 'Jump to bottom',
|
||
detailEdit: '⚙️ Edit',
|
||
detailNotesTitle: '📓 Custom Notes',
|
||
detailEditNotes: 'Edit Notes',
|
||
detailGeneratedGallery: '🖼️ View Generated Gallery',
|
||
detailTrainedWords: 'Trained Words:',
|
||
detailDescription: 'Description:',
|
||
detailNotes: 'Notes:',
|
||
detailNoCompatibleModels: 'No compatible models found.',
|
||
detailCompatibleLoadFailed: 'Failed to load.',
|
||
detailToggleSound: 'Toggle Sound',
|
||
detailNoCover: 'No Cover',
|
||
detailPickGallery: 'Pick from Gallery',
|
||
detailUploadLocal: 'Upload Local',
|
||
detailUploadError: 'Error: ',
|
||
detailUploadFailed: 'Upload failed: ',
|
||
detailModelInfo: 'Edit Model Info',
|
||
detailFile: 'File:',
|
||
detailCustomNamePlaceholder: 'Custom Name (Leave empty to use original)',
|
||
detailNotesPlaceholder: '📓 Custom Notes...',
|
||
detailPhysicalRename: 'Physical Rename on Disk',
|
||
detailPhysicalRenameDesc: 'Virtual rename only if unchecked. Safe for originals.',
|
||
detailResetAll: 'Reset All',
|
||
detailResetConfirm: 'Reset all custom info for this model?',
|
||
detailResetCoverPreserved: 'No recoverable Civitai backup or original cover was found. The current cover was kept to avoid losing the only image; the custom name and notes were still reset.',
|
||
detailResetCoverFailed: 'The cover restore could not be completed. Check file permissions or locks; the custom name and notes were still reset.',
|
||
detailResetFailed: 'Reset failed.',
|
||
detailCancel: 'Cancel',
|
||
detailSaveChanges: 'Save Changes',
|
||
detailSaving: 'Saving...',
|
||
detailSelectModels: 'Select Models',
|
||
detailFolders: 'Folders',
|
||
detailSelectedItems: 'Selected {count} items',
|
||
detailSelectedItemsPrefix: 'Selected ',
|
||
detailSelectedItemsSuffix: ' items',
|
||
detailNothingSelected: 'Nothing selected',
|
||
detailLoadedModels: 'Loaded {loaded} / {total} models',
|
||
detailComputing: 'Computing...',
|
||
detailSelectNoPreview: 'Select No Preview',
|
||
detailSelectNoDesc: 'Select No Desc',
|
||
detailSelectAll: 'Select All',
|
||
detailClear: 'Clear',
|
||
detailConfirm: 'Confirm',
|
||
detailLoading: 'Loading...',
|
||
detailAllLoaded: '✅ All models loaded',
|
||
detailScrollMore: 'Scroll for more...',
|
||
detailNoModels: 'No models found.',
|
||
detailApiRestart: '❌ Backend API request failed!\n\nYou must restart the ComfyUI process for the newly written backend code to take effect.',
|
||
doctorTitle: 'Global Health Center',
|
||
doctorAutoScanOn: 'Auto-Detect on ComfyUI Startup [ON]',
|
||
doctorAutoScanOff: 'Auto-Detect on ComfyUI Startup [OFF]',
|
||
doctorRefresh: '🔄 Refresh',
|
||
doctorRefreshTitle: 'Reload model list and clear errors',
|
||
doctorGraphUnavailable: 'The current graph or node is unavailable.',
|
||
doctorMissingChainInputs: 'The node does not expose both MODEL and CLIP inputs.',
|
||
doctorUnconnectedChainInputs: 'Both MODEL and CLIP inputs must already be connected.',
|
||
doctorMissingChainOutputs: 'The node does not expose both MODEL and CLIP outputs.',
|
||
doctorAmbiguousBranches: 'Multiple downstream branches were detected; choose an explicit chain first.',
|
||
doctorInvalidDownstream: 'An existing downstream link is invalid and cannot be safely changed.',
|
||
doctorUnsupportedInsertion: 'This insertion is not available for the selected node.',
|
||
doctorCreateLoraFailed: 'Unable to create a standard LoRA Loader node.',
|
||
doctorLoraNotReady: 'The LoRA list is not ready. Refresh ComfyUI model lists and try again.',
|
||
assistantSelectNode: 'select any node in the canvas',
|
||
assistantNoModelParameter: 'No supported model parameter on this node',
|
||
assistantSelectedNode: 'Selected node',
|
||
assistantQuickActions: 'Quick actions',
|
||
assistantChangeWidget: 'Change {name}',
|
||
assistantChangeCurrent: 'Change current model',
|
||
assistantVisualPicker: '{type} · Visual picker',
|
||
assistantInsertBefore: 'Insert LoRA before',
|
||
assistantInsertAfter: 'Insert LoRA after',
|
||
assistantConnectInputs: 'Connect to node inputs',
|
||
assistantConnectOutputs: 'Connect to node outputs',
|
||
doctorTotal: 'Total',
|
||
doctorHealthy: '🟢 Healthy',
|
||
doctorMissing: '🔴 Missing',
|
||
doctorNoWorkflowModels: 'No models found in this workflow',
|
||
doctorAutoRedirected: 'Auto-Redirected',
|
||
doctorReady: 'Ready',
|
||
doctorMissingStatus: 'Missing',
|
||
doctorCivitai: '🌐 Civitai',
|
||
doctorDeepHashScan: '🔍 Deep Hash Scan',
|
||
doctorScanStarting: '⏳ Starting scan engine...',
|
||
doctorScanning: '⏳ Scanning ({current}/{total}) {filename}',
|
||
doctorScanError: '❌ Scan error: ',
|
||
doctorMatching: '⏳ Matching and resolving red nodes...',
|
||
doctorDeepScanSummary: 'Deep scanned {total} models with missing info.',
|
||
doctorNoLocalMatch: '❌ Scan finished, but no local match found.\n\n{summary}\n\nThis means the model is truly missing from your disk, or the original source file with hash was deleted.\nPlease click [🌐 Civitai] to download it from the cloud.',
|
||
doctorScanSuccess: '✅ Deep Scan successful! Node auto-healed!\n\n{summary}',
|
||
doctorPollError: '❌ Poll Error: ',
|
||
doctorScanFailed: '❌ Scan Error: ',
|
||
doctorManualReplace: '🔀 Manual Replace',
|
||
doctorOriginalPath: 'Original: {path}',
|
||
assistantPreviewFailed: 'Failed to load preview',
|
||
assistantHistoryTitle: '🖼️ History ({count})',
|
||
assistantViewAll: 'View All →',
|
||
assistantRestoreWorkflow: 'Click to restore workflow',
|
||
assistantViewGallery: 'Click to view full gallery',
|
||
pickerNoCompatible: 'No compatible models are available.',
|
||
pickerEyebrow: 'Node Assistant · Model Picker',
|
||
pickerInsertBeforeTitle: '⬅ Insert LoRA Before Node',
|
||
pickerInsertAfterTitle: 'Insert LoRA After Node ➡',
|
||
pickerChangeTitle: '🔀 Change Current Model',
|
||
pickerFolders: '📁 Folders',
|
||
pickerSearch: '🔍 Search name or full path…',
|
||
pickerMainAll: 'Main model: All types',
|
||
pickerNameAsc: 'Name A–Z',
|
||
pickerNameDesc: 'Name Z–A',
|
||
pickerByFolder: 'By Folder',
|
||
pickerLoadingCovers: '⏳ Loading model covers…',
|
||
pickerCancel: 'Cancel',
|
||
pickerInsertConfirm: 'Insert & Auto-Connect',
|
||
pickerReplaceConfirm: 'Confirm Replacement',
|
||
pickerCoversFailed: '⚠️ Covers failed to load; models remain selectable by name.',
|
||
doctorNodeLabel: 'Node',
|
||
doctorAllHealthy: '🎉 Awesome! All models in this workflow are healthy.',
|
||
doctorLoadingPreview: 'Loading preview...',
|
||
doctorNoPreview: 'No preview',
|
||
doctorProfile: '📖 View Profile',
|
||
doctorBase: 'Base',
|
||
doctorTriggerWords: 'Trigger Words',
|
||
doctorClickCopy: 'Click to copy',
|
||
doctorCopyAll: '📋 Copy All',
|
||
doctorNotes: '📝 My Notes',
|
||
pickerSelected: 'Selected: {path}',
|
||
pickerChooseModel: 'Choose a model. Existing connections and strengths will be preserved.',
|
||
pickerCompatibleMain: '✨ Compatible LoRAs are filtered for the main model {name}. Use the filter above to show all types.',
|
||
pickerCompatibleTag: '✨ No connected main model was identified, so the current LoRA tag {name} is used.',
|
||
pickerCompatibleNoTags: 'ℹ Main model {name} was identified, but no matching LoRA tags were found, so all models remain visible.',
|
||
pickerCompatibleUnknown: 'ℹ The main model type could not be identified. All LoRAs remain visible and can be filtered manually.',
|
||
pickerAllModels: 'All Models',
|
||
pickerResults: '{count} results',
|
||
pickerCurrent: 'Current',
|
||
pickerBaseModel: 'Base model',
|
||
pickerRoot: 'Root',
|
||
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',
|
||
replaceAll: 'Replace All',
|
||
editRaw: '📝 Edit Raw / Paste',
|
||
findPlaceholder: 'Find...',
|
||
replacePlaceholder: 'Replace with...',
|
||
models: 'Models',
|
||
gallery: 'Gallery',
|
||
apiKeyConfig: '🔑 Civitai API Key Config (For downloading previews)',
|
||
apiKeyPrompt: 'Enter your Civitai API Key:',
|
||
settingsBtn: 'Settings',
|
||
closeSettings: '✖ Close Settings',
|
||
doctorBtn: 'Node Assistant',
|
||
importBtn: '📥 Import / Export Workflow (Share Code)',
|
||
recipeTitle: 'Workflow Recipes',
|
||
recipeClose: 'Close Workflow Recipes',
|
||
recipeSaveCurrent: 'Save Current Workflow',
|
||
recipeSaveTitle: 'Save Workflow Recipe',
|
||
recipeName: 'Name',
|
||
recipeDefaultName: 'New Workflow Recipe',
|
||
recipeNameRequired: 'Enter a recipe name.',
|
||
recipeTags: 'Tags',
|
||
recipeTagsHint: 'For example: portrait, SDXL, upscale',
|
||
recipeNotes: 'Notes',
|
||
recipeNotesHint: 'Why this version works well…',
|
||
recipeThumbnail: 'Workflow thumbnail',
|
||
recipeCancel: 'Cancel',
|
||
recipeSave: 'Save',
|
||
recipeSaving: 'Saving…',
|
||
recipeSaveReceipt: 'Saved: {nodes} nodes, {links} links, {groups} groups.',
|
||
recipeSaveReceiptMismatch: 'Saved, but the integrity receipt differs from the current canvas. Reopen the recipe to verify it.',
|
||
recipeEmpty: 'No Workflow Recipes yet. Save a workflow that is working well.',
|
||
recipeLoadError: 'Could not load Workflow Recipes.',
|
||
recipeUntitled: 'Untitled Recipe',
|
||
recipeSearchPlaceholder: 'Search names, notes, or tags…',
|
||
recipeClearFilters: 'Clear filters',
|
||
recipeNoTags: 'No tags yet',
|
||
recipeNoMatches: 'No recipes match. Clear the search or tag filters.',
|
||
recipeFilterByTag: 'Filter by this tag',
|
||
recipeImport: 'Import package',
|
||
recipeExport: 'Export package',
|
||
recipeExportError: 'Could not export the Workflow Recipe package.',
|
||
recipeImportError: 'Could not import the Workflow Recipe package.',
|
||
recipeExportSnapshotsConfirm: 'Include model preview snapshots in the export?\n\nSnapshots are presentation data; video covers are reduced to lightweight stills and original videos are never packed.',
|
||
recipeExportHistoryConfirm: 'Include historical versions in the export?\n\nHistory may contain older prompts and notes.',
|
||
recipeExportRedactIdentityConfirm: 'Remove model SHA-256, size, and provenance information?\n\nChoose OK to remove those identity fields.',
|
||
recipeDialogNo: 'No',
|
||
recipeImportSummary: 'The recipe package passed inspection. Import these contents?',
|
||
recipeImportAssets: 'Preview assets',
|
||
recipeImportHistory: 'Historical versions',
|
||
recipeImportRenamePrompt: 'This recipe name already exists. Enter the imported name:',
|
||
recipeModel: 'Model',
|
||
recipeLoras: 'LoRAs',
|
||
recipePrompt: 'Positive prompt',
|
||
recipeSteps: 'Steps',
|
||
recipeRestore: 'Restore version',
|
||
recipeAppendCanvas: 'Append to Canvas',
|
||
recipeAppendError: 'Could not append this recipe to the current canvas.',
|
||
recipeDelete: 'Delete',
|
||
recipeDeleteConfirm: 'Delete this Workflow Recipe?',
|
||
recipeDeleteError: 'Could not delete this Workflow Recipe.',
|
||
recipeSaveError: 'Could not save this Workflow Recipe.',
|
||
recipeNodeParams: 'Node parameters',
|
||
recipeUnknownNode: 'Unknown node',
|
||
recipeCopyParameter: 'Copy parameter value',
|
||
recipeCopied: 'Copied',
|
||
recipeCopyFailed: 'Copy failed',
|
||
recipeAdvancedInfo: 'Advanced information',
|
||
recipeInlineEditName: 'Click to edit the name',
|
||
recipeInlineEditNotes: 'Click to edit notes',
|
||
recipeInlineEditTags: 'Edit tags',
|
||
recipeInlineSave: 'Save',
|
||
recipeInlineCancel: 'Cancel',
|
||
recipeInlineSaving: 'Saving…',
|
||
recipeInlineError: 'Could not save. Try again.',
|
||
recipeInlineNameRequired: 'Name cannot be empty.',
|
||
recipeSomeParamsInWorkflow: 'Other complex or sensitive parameters remain in the full workflow.',
|
||
recipeMoreNodesInWorkflow: 'Other nodes remain in the full workflow.',
|
||
recipeMissingNodes: 'These node types are currently missing; the restored workflow may not run',
|
||
recipeBindImage: 'Bind cover image',
|
||
recipeNoImage: 'No image',
|
||
recipeCanvasPreview: 'Current canvas',
|
||
recipeLoadingRecentImages: 'Loading recent outputs…',
|
||
recipeRecentImages: 'Recent outputs',
|
||
recipeRecentImagesUnavailable: 'Recent outputs are currently unavailable.',
|
||
recipeChoosePinnedParams: 'Choose key parameters',
|
||
recipePinnedParams: 'Key parameters',
|
||
recipeKeepImage: 'Keep current cover',
|
||
recipeEdit: 'Edit',
|
||
recipeEditTitle: 'Edit Workflow Recipe',
|
||
recipeEditNodeParams: 'Edit node parameters',
|
||
recipeComplexParam: 'Complex values are shown only; the full value remains safely stored in the workflow.',
|
||
recipeHistory: 'Version history',
|
||
recipeHistoryEmpty: 'No prior version yet. The previous version is retained automatically on update.',
|
||
recipeUnknownVersion: 'Unknown time',
|
||
recipeRestoreVersion: 'Restore this version',
|
||
recipeRestoreVersionConfirm: 'Restore this version? The current recipe will first be retained in history.',
|
||
recipeEditCanvas: 'Edit on canvas',
|
||
recipeEditCanvasConfirm: 'Loading replaces the current canvas. After structural edits, use Update Current Recipe to save a new version. Continue?',
|
||
recipeUpdate: 'Save updates',
|
||
recipeUpdateCurrent: 'Update Current Recipe',
|
||
recipeUpdateError: 'Could not update this Workflow Recipe.',
|
||
recipeViewDetails: 'View details',
|
||
recipeDetailBack: 'Back to recipes',
|
||
recipeDetailOverview: 'Overview',
|
||
recipeGallery: 'Gallery',
|
||
recipeGalleryLoading: 'Finding matching history images…',
|
||
recipeGalleryEmpty: 'No matching result was found in the latest 200 PNGs with workflow metadata.',
|
||
recipeGalleryRefresh: 'Refresh matches',
|
||
recipeGallerySetCover: 'Set as recipe cover',
|
||
recipeGalleryCoverSaved: 'Recipe cover set',
|
||
recipeGalleryCoverError: 'Could not set the recipe cover.',
|
||
recipeGalleryLoadError: 'Could not load the recipe gallery.',
|
||
recipeGalleryScanHint: 'Checked the latest {count} PNGs with workflow metadata.',
|
||
recipeGalleryOpenImage: 'View full image',
|
||
recipeGalleryCompare: 'Compare parameters',
|
||
recipeGalleryComparison: 'Recipe vs image parameters',
|
||
recipeGalleryComparisonLoading: 'Reading this image parameters…',
|
||
recipeGalleryComparisonError: 'Could not read this image parameters.',
|
||
recipeGalleryNoDifferences: 'No displayable parameter differences were found.',
|
||
recipeGalleryDifferenceHint: 'These are node-parameter differences; gallery matching only uses node types.',
|
||
recipeGalleryRecipeValue: 'Recipe',
|
||
recipeGalleryImageValue: 'Image',
|
||
recipeDetailModels: 'Models & reproducibility',
|
||
recipeDetailParameters: 'Parameter Notebook',
|
||
recipeDetailParametersHint: 'This view contains the saved parameters. Long values can be expanded and copied. The parameter gallery matches values, not local paths.',
|
||
recipeParameterSnapshots: 'Parameter snapshots',
|
||
recipeParameterSnapshotsHint: 'Each recipe save creates one read-only snapshot.',
|
||
recipeParameterRefresh: 'Refresh snapshots',
|
||
recipeParameterLoading: 'Loading parameter snapshots…',
|
||
recipeParameterLoadError: 'Could not load parameter snapshots.',
|
||
recipeParameterNoSnapshots: 'This recipe has no parameter snapshots yet.',
|
||
recipeParameterUntitled: 'Untitled parameter snapshot',
|
||
recipeParameterNew: 'New parameter note',
|
||
recipeParameterReadCurrent: 'Read current and create',
|
||
recipeParameterReadCurrentSaving: 'Reading and saving…',
|
||
recipeParameterReadCurrentError: 'Could not read parameters from the current canvas.',
|
||
recipeParameterSkeletonMismatch: 'The current canvas is missing recipe skeleton nodes; nothing was changed.',
|
||
recipeParameterApply: 'Apply to current workflow',
|
||
recipeParameterApplying: 'Checking the skeleton and applying…',
|
||
recipeParameterApplied: 'Applied {count} parameters',
|
||
recipeParameterApplyError: 'Could not apply parameters; the current canvas was not fully changed.',
|
||
recipeParameterNewHint: 'Edit node parameters from the current recipe skeleton. Runtime-changing values such as seeds are ignored. Saving creates a new read-only snapshot and never overwrites history.',
|
||
recipeParameterName: 'Note name',
|
||
recipeParameterSave: 'Save parameter note',
|
||
recipeParameterCancel: 'Cancel editing',
|
||
recipeParameterSaveError: 'Could not save the parameter note.',
|
||
recipeParameterSensitiveHidden: 'Sensitive widget hidden',
|
||
recipeDetailParameterSummary: 'Common parameters',
|
||
recipeDetailNodeParameters: 'Node parameters',
|
||
recipeDetailUnknownNode: 'Unnamed node',
|
||
recipeDetailWidget: 'Widget',
|
||
recipeDetailScheduler: 'Scheduler',
|
||
recipeDetailSteps: 'Steps',
|
||
recipeDetailCFG: 'CFG',
|
||
recipeDetailDenoise: 'Denoise',
|
||
recipeDetailSeed: 'Seed',
|
||
recipeParameterGallery: 'Outputs with the same parameters',
|
||
recipeParameterGalleryLoading: 'Finding historical outputs with the same parameters…',
|
||
recipeParameterGalleryEmpty: 'No historical output with identical parameters was found.',
|
||
recipeParameterGalleryError: 'Could not load the parameter gallery.',
|
||
recipeDetailNoSavedParameters: 'This recipe has no displayable parameters.',
|
||
recipeDetailVersions: 'Versions',
|
||
recipeDetailSummary: 'Summary',
|
||
recipeDetailModelComposition: 'Model composition',
|
||
recipeDetailBaseModel: 'Base model',
|
||
recipeDetailIdentity: 'Verified identities',
|
||
recipeDetailUnverified: 'Unverified models',
|
||
recipeDetailMissingNodes: 'Missing node types',
|
||
recipeDetailFingerprint: 'Workflow fingerprint',
|
||
recipeDetailNotIndexed: 'Not indexed yet',
|
||
recipeDetailLoraSummary: 'LoRA',
|
||
recipeDetailSampler: 'Sampler',
|
||
recipeDetailResolution: 'Resolution',
|
||
recipeDetailUnavailable: 'Unavailable',
|
||
recipeDetailEditOrigin: '✏️ Edit Origin',
|
||
recipeOriginDialogTitle: 'Edit Model Origin',
|
||
recipeOriginOfficialName: 'Official Model Name',
|
||
recipeOriginModelUrl: 'Official Model URL',
|
||
recipeOriginFetchHash: '🌍 Auto-fetch via Hash',
|
||
recipeOriginFetching: 'Fetching...',
|
||
recipeOriginFetchFailed: 'Not found or network error',
|
||
recipeOriginFetchSuccess: 'Successfully fetched!',
|
||
recipeDetailNoNotes: 'No notes.',
|
||
recipeDetailUpdated: 'Updated',
|
||
recipeDetailCopyPrompt: 'Copy prompt',
|
||
recipeDetailCopyFingerprint: 'Copy workflow fingerprint',
|
||
recipeDetailCopyHash: 'Copy full hash',
|
||
recipeDetailPrompts: 'Prompts',
|
||
recipeDetailPositivePrompt: 'Positive prompt',
|
||
recipeDetailNegativePrompt: 'Negative prompt',
|
||
recipeDetailNoPrompts: 'This recipe has no safe prompt summary to show.',
|
||
recipeDetailVolatileIgnored: 'Runtime-changing value ignored',
|
||
recipeDetailExpandValue: 'Show full value',
|
||
recipeDetailCollapseValue: 'Collapse value',
|
||
recipeIdentityVerified: 'Identity verified',
|
||
recipeIdentityUnverified: 'Local available, unverified',
|
||
recipeIdentityUnavailable: 'Not found locally',
|
||
recipeIdentityHelpDesc: 'Verification checks if the physical file matches the saved hash, it does not judge model quality.',
|
||
recipeDetailRefreshAvailability: 'Check local availability',
|
||
recipeDetailRefreshOrigin: 'Refresh Origin Info',
|
||
recipeDetailRefreshOriginDesc: 'Refresh all model origin information in this recipe (supports backfilling old recipes)',
|
||
recipeDetailRefreshing: 'Checking…',
|
||
recipeDetailRefreshError: 'Could not check local availability.',
|
||
recipeDetailAvailable: 'Available locally',
|
||
recipeDetailMissing: 'Missing locally',
|
||
recipeDetailAvailabilityNotChecked: 'Not checked locally',
|
||
recipeDetailNoModelReferences: 'No supported model loaders were detected. Other custom widgets remain in Parameters and the full workflow.',
|
||
recipeDetailModel: 'Model',
|
||
recipeModelPath: 'Model path',
|
||
recipeOpenLocalModel: 'Open local model details',
|
||
recipeMatchLocalModel: 'Match local model',
|
||
recipeMatchingLocalModel: 'Matching local model…',
|
||
recipeMatchRecipeModels: 'Match recipe models',
|
||
recipeMatchRecipeModelsDesc: 'Find local files by model hash, file size, and model category; the author’s path is not used.',
|
||
recipeMatchingRecipeModels: 'Matching recipe models…',
|
||
recipeRecipeMatchSummary: 'Found {found}/{total} local matches',
|
||
recipeAllModelsMatched: 'All recipe models are already matched or need no match',
|
||
recipeApplyLocalMatch: 'Apply match',
|
||
recipeApplyLocalMatchDesc: 'Write the matched local model path into the recipe workflow',
|
||
recipeApplyingLocalMatch: 'Applying match…',
|
||
recipeApplyLocalMatchSuccess: 'Local model match applied',
|
||
recipeApplyLocalMatchError: 'Could not apply this model match',
|
||
recipeLocalModelNotFound: 'No matching local model was found.',
|
||
recipeLocalModelAmbiguous: 'Multiple candidates found; add model identity information first.',
|
||
recipeLocalModelMatchError: 'Matching failed. Try again later.',
|
||
recipeDetailSearchParameters: 'Search node, type, or widget name…',
|
||
recipeDetailWidget: 'Widget',
|
||
recipeDetailNoParameters: 'No safe parameters match the search.',
|
||
recipeDetailCurrentVersion: 'Current version',
|
||
recipeCompareVersion: 'Compare version',
|
||
recipeDiffLoading: 'Comparing…',
|
||
recipeDiffSummary: 'Change summary',
|
||
recipeDiffBefore: 'Before',
|
||
recipeDiffAfter: 'After',
|
||
recipeDiffNoChanges: 'No meaningful changes were detected between these versions.',
|
||
recipeDiffError: 'Could not read this historical version.',
|
||
recipeDiffPinned: 'Pinned parameters',
|
||
recipeDiffPrompts: 'Prompts',
|
||
recipeDiffModels: 'Model references',
|
||
recipeDiffParameters: 'Parameter summary',
|
||
recipeDiffWorkflow: 'Workflow structure',
|
||
recipeDiffPresentation: 'Presentation',
|
||
recipeDiffOther: 'Other',
|
||
recipeDetailUnknownTime: 'Unknown time',
|
||
recipeDetailLoadingPreviews: 'Loading local model previews…',
|
||
recipeDetailCurrentPreview: 'Current local preview',
|
||
recipeDetailNoPreview: 'No preview available',
|
||
recipeSaveModelPreviewSnapshots: 'Embed Model Thumbnails',
|
||
recipeSaveModelPreviewSnapshotsHint: 'Embed small static thumbnails of the models directly into the recipe file. When you share this recipe, others will be able to see the model covers even if they don\'t have the models locally. (Videos are excluded to save space)',
|
||
recipeDetailSavedSnapshot: 'Saved snapshot',
|
||
}
|
||
};
|
||
|
||
const DEFAULT_LOCALE = 'zh';
|
||
const FALLBACK_LOCALE = 'en';
|
||
const hasOwn = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
|
||
|
||
export const supportedLocales = Object.freeze(Object.keys(i18n));
|
||
|
||
export function normalizeLocale(value) {
|
||
const raw = String(value ?? '').trim().toLowerCase().replace(/_/g, '-');
|
||
if (!raw) return '';
|
||
const base = raw.split('-')[0];
|
||
return hasOwn(i18n, base) ? base : '';
|
||
}
|
||
|
||
export function resolveLocale(value = globalThis.anomalous_browser_lang) {
|
||
return normalizeLocale(value) || DEFAULT_LOCALE;
|
||
}
|
||
|
||
export function formatTranslation(template, params = {}) {
|
||
if (typeof template !== 'string') return template;
|
||
return template.replace(/\{([A-Za-z0-9_.-]+)\}/g, (token, key) => (
|
||
hasOwn(params, key) ? String(params[key]) : token
|
||
));
|
||
}
|
||
|
||
export function translate(key, params = {}, locale = resolveLocale()) {
|
||
const activeLocale = resolveLocale(locale);
|
||
const template = i18n[activeLocale]?.[key]
|
||
?? i18n[FALLBACK_LOCALE]?.[key]
|
||
?? i18n[DEFAULT_LOCALE]?.[key]
|
||
?? key;
|
||
return formatTranslation(template, params);
|
||
}
|
||
|
||
export function createTranslator(locale) {
|
||
return (key, params = {}) => translate(key, params, locale);
|
||
}
|