diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml deleted file mode 100644 index b7bd9746..00000000 --- a/.github/workflows/ci-python.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - tests: - runs-on: ubuntu-22.04 - strategy: - matrix: - python: ["3.12"] - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install pytest pytest-asyncio - - name: Run tests - run: | - python -m pytest -q diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 578ad93d..2564c62f 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -7,25 +7,44 @@ on: branches: [ main, master ] jobs: - test-and-typecheck: + test: runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: - python-version: ["3.12"] + python-version: ["3.10", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - - name: Set up Python + + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install pytest pytest-asyncio mypy + pip install pytest pytest-asyncio + - name: Run pytest + run: python -m pytest -q + + typecheck: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies run: | - python -m pytest -q - - name: Run mypy (strict) - run: | - python -m mypy --config-file mypy.ini + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install mypy + + - name: Run mypy + run: python -m mypy --config-file mypy.ini diff --git a/backend/adapters/db/schema.py b/backend/adapters/db/schema.py index a413167e..be342824 100644 --- a/backend/adapters/db/schema.py +++ b/backend/adapters/db/schema.py @@ -268,6 +268,11 @@ async def ensure_indexes_and_triggers(db) -> Result[bool]: if not result.ok: logger.error("Failed to ensure indexes/triggers: %s", result.error) return result + # Repair assets_fts first (content-backed FTS table), then asset_metadata_fts + repair_assets = await _repair_assets_fts(db) + if repair_assets and not repair_assets.ok: + logger.warning("Failed to repair assets_fts: %s", repair_assets.error) + repair_result = await _repair_asset_metadata_fts(db) if repair_result and not repair_result.ok: logger.warning("Failed to repair asset_metadata_fts: %s", repair_result.error) @@ -452,6 +457,94 @@ async def _ensure_schema(db) -> Result[bool]: return Result.Ok(True) +async def _repair_assets_fts(db) -> Result[bool]: + """ + Ensure the `assets_fts` virtual table and its triggers are correctly defined and + repopulated from the `assets` table. This prevents stale/missing FTS rows when + triggers were not present or the FTS table got out of sync. + """ + try: + table_row = await db.aquery("SELECT sql FROM sqlite_master WHERE type='table' AND name='assets_fts' LIMIT 1") + ddl = "" + if table_row.ok and table_row.data: + ddl = str(table_row.data[0].get("sql") or "") + ddl_lower = ddl.lower() + + # Check existing trigger presence + trig_row = await db.aquery("SELECT name FROM sqlite_master WHERE type='trigger' AND name LIKE 'assets_fts_%'") + triggers_exist = trig_row.ok and bool(trig_row.data) + + needs_rebuild = False + # If virtual table ddl looks wrong or triggers missing -> rebuild + if not ddl_lower or "fts5" not in ddl_lower or not triggers_exist: + needs_rebuild = True + + if not needs_rebuild: + return Result.Ok(True) + except Exception: + return Result.Ok(True) + + logger.warning("Repairing assets_fts (schema/triggers)") + try: + async with db.atransaction(mode="immediate") as tx: + if not tx.ok: + return Result.Err("DB_ERROR", tx.error or "Failed to begin transaction") + + await db.aexecutescript( + """ + DROP TRIGGER IF EXISTS assets_fts_insert; + DROP TRIGGER IF EXISTS assets_fts_delete; + DROP TRIGGER IF EXISTS assets_fts_update; + DROP TABLE IF EXISTS assets_fts; + """ + ) + + await db.aexecutescript( + """ + CREATE VIRTUAL TABLE IF NOT EXISTS assets_fts USING fts5( + filename, + subfolder, + content='assets', + content_rowid='id' + ); + """ + ) + + await db.aexecutescript( + """ + CREATE TRIGGER IF NOT EXISTS assets_fts_insert AFTER INSERT ON assets BEGIN + INSERT INTO assets_fts(rowid, filename, subfolder) + VALUES (new.id, new.filename, new.subfolder); + END; + + CREATE TRIGGER IF NOT EXISTS assets_fts_delete AFTER DELETE ON assets BEGIN + DELETE FROM assets_fts WHERE rowid = old.id; + END; + + CREATE TRIGGER IF NOT EXISTS assets_fts_update AFTER UPDATE ON assets BEGIN + UPDATE assets_fts SET filename = new.filename, subfolder = new.subfolder + WHERE rowid = new.id; + END; + """ + ) + + # Rebuild FTS contents from assets table + await db.aexecutescript( + """ + DELETE FROM assets_fts; + INSERT INTO assets_fts(rowid, filename, subfolder) + SELECT id, COALESCE(filename, ''), COALESCE(subfolder, '') FROM assets; + """ + ) + + if not tx.ok: + return Result.Err("DB_ERROR", tx.error or "Commit failed") + return Result.Ok(True) + except Exception as exc: + logger.warning("Failed to repair assets_fts: %s", exc) + return Result.Err("FTS_REPAIR_FAILED", str(exc)) + + async def init_schema(db) -> Result[bool]: """ Initialize the schema (useful for tests or first-time installs). diff --git a/backend/features/index/searcher.py b/backend/features/index/searcher.py index 344b792c..4e54d4ed 100644 --- a/backend/features/index/searcher.py +++ b/backend/features/index/searcher.py @@ -321,6 +321,20 @@ class IndexSearcher: logger.debug("Found %s results (total=%s)", len(assets), total if include_total else "skipped") payload: Dict[str, Any] = {"assets": assets, "limit": limit, "offset": offset, "query": query} payload["total"] = int(total or 0) if include_total else None + # If scoped search returned no results, try a global fallback (ignore roots) + try: + if (not assets) and cleaned_roots: + # Avoid fallback for browse-all queries + if query and str(query).strip() != "*": + fallback = await self.search(query, limit=limit, offset=offset, filters=filters, include_total=include_total) + if fallback and fallback.ok and isinstance(fallback.data, dict): + # Mark that this result came from a global fallback so UI can indicate it + fallback.data["fallback_scope"] = True + return fallback + except Exception: + # On any error, ignore fallback and return scoped payload + pass + return Result.Ok(payload) async def search_scoped( self, diff --git a/js/app/config.js b/js/app/config.js index b2dbd420..801830e2 100644 --- a/js/app/config.js +++ b/js/app/config.js @@ -80,6 +80,8 @@ export const APP_DEFAULTS = Object.freeze({ // Upper bound for a single list request. The backend allows more, but very large // pages can create huge DOM batches; keep this as a safety cap. MAX_PAGE_SIZE: 2000, + // Client-side default for search request limit (user-adjustable in settings) + SEARCH_DEFAULT_LIMIT: 500, INFINITE_SCROLL_ENABLED: true, INFINITE_SCROLL_ROOT_MARGIN: "800px", INFINITE_SCROLL_THRESHOLD: 0.01, diff --git a/js/app/i18n.generated.js b/js/app/i18n.generated.js new file mode 100644 index 00000000..824f2ffa --- /dev/null +++ b/js/app/i18n.generated.js @@ -0,0 +1,2177 @@ +/** + * Auto-generated locale packs from en-US source strings. + * Do not edit manually. + */ + +export const GENERATED_TRANSLATIONS = { + "zh-CN": { + "cat.grid": "网格", + "cat.cards": "牌", + "cat.badges": "徽章", + "cat.viewer": "观众", + "cat.scanning": "扫描", + "cat.advanced": "先进的", + "cat.security": "安全", + "cat.remote": "远程访问", + "setting.grid.minsize.name": "主要:缩略图大小(px)", + "setting.grid.minsize.desc": "网格中缩略图的最小尺寸。可能需要重新打开面板。", + "setting.grid.cardSize.group": "卡片尺寸", + "setting.grid.cardSize.name": "专业:卡片尺寸", + "setting.grid.cardSize.desc": "选择预设的卡片尺寸:小、中或大。", + "setting.grid.cardSize.small": "小的", + "setting.grid.cardSize.medium": "中等的", + "setting.grid.cardSize.large": "大的", + "setting.grid.gap.name": "主要:间隙(px)", + "setting.grid.gap.desc": "缩略图之间的空间。", + "setting.sidebar.pos.name": "专业:侧边栏位置", + "setting.sidebar.pos.desc": "在左侧或右侧显示详细信息侧边栏。需要重新加载。", + "setting.siblings.hide.name": "专业:隐藏 PNG 兄弟姐妹", + "setting.siblings.hide.desc": "如果视频有相应的 .png 预览,请从网格中隐藏 .png。", + "setting.nav.infinite.name": "专业:无限卷轴", + "setting.nav.infinite.desc": "滚动时自动加载更多文件。", + "setting.grid.pagesize.name": "主要:网格页面大小", + "setting.grid.pagesize.desc": "网格中每页/请求加载的资源数量。", + "setting.viewer.pan.name": "主要:平移而不缩放", + "setting.viewer.pan.desc": "即使在缩放级别 1 下也允许平移图像。", + "setting.minimap.enabled.name": "主要:启用小地图", + "setting.minimap.enabled.desc": "工作流程小地图的全局激活。", + "setting.scan.startup.name": "主要:启动时自动扫描", + "setting.scan.startup.desc": "ComfyUI 加载后立即开始后台扫描。", + "setting.watcher.name": "专业:文件观察者", + "setting.watcher.desc": "监视输出和自定义文件夹中手动添加的文件并实时自动索引它们。", + "setting.watcher.debounce.name": "主要:观察者反跳延迟", + "setting.watcher.debounce.desc": "索引之前批处理观察程序事件的延迟(毫秒)。", + "setting.watcher.debounce.error": "无法更新观察器去抖延迟。", + "setting.watcher.dedupe.name": "主要:Watcher 重复数据删除窗口", + "setting.watcher.dedupe.desc": "文件在事件发生后被视为已处理的持续时间(毫秒)。", + "setting.watcher.dedupe.error": "无法更新观察程序重复数据删除窗口。", + "setting.sync.rating.name": "专业:将评级/标签同步到文件", + "setting.sync.rating.desc": "将评级和标签写入文件元数据 (ExifTool)。", + "cat.badgeColors": "徽章颜色", + "setting.starColor": "星星颜色", + "setting.starColor.tooltip": "缩略图上评级星星的颜色(十六进制,例如#FFD45A)", + "setting.badgeImageColor": "图像徽章颜色", + "setting.badgeImageColor.tooltip": "图像徽章的颜色:PNG、JPG、WEBP、GIF、BMP、TIF(十六进制)", + "setting.badgeVideoColor": "视频徽章颜色", + "setting.badgeVideoColor.tooltip": "视频徽章的颜色:MP4、WEBM、MOV、AVI、MKV(十六进制)", + "setting.badgeAudioColor": "音频徽章颜色", + "setting.badgeAudioColor.tooltip": "音频徽章的颜色:MP3、WAV、OGG、FLAC(十六进制)", + "setting.badgeModel3dColor": "3D模型徽章颜色", + "setting.badgeModel3dColor.tooltip": "3D 模型徽章的颜色:OBJ、FBX、GLB、GLTF(十六进制)", + "setting.obs.enabled.name": "主要:启用详细日志", + "setting.obs.enabled.desc": "启用详细的后端日志以进行调试。", + "setting.probe.mode.name": "专业:元数据后端", + "setting.probe.mode.desc": "选择直接用于提取元数据的工具。", + "setting.language.name": "专业:语言", + "setting.language.desc": "选择 Assets Manager 界面的语言。需要重新加载才能完全应用。", + "setting.sec.safe.name": "专业:安全模式", + "setting.sec.safe.desc": "启用后,除非明确授权,否则评级/标签写入将被阻止。", + "setting.sec.remote.name": "主要:允许远程完全访问", + "setting.sec.remote.desc": "允许非本地客户端执行写操作。除非配置了令牌,否则禁用块写入。", + "setting.sec.write.name": "专业:允许写入", + "setting.sec.write.desc": "允许书写评级和标签。", + "setting.sec.del.name": "专业:允许删除", + "setting.sec.del.desc": "允许删除文件。", + "setting.sec.ren.name": "专业:允许重命名", + "setting.sec.ren.desc": "允许重命名文件。", + "setting.sec.open.name": "专业:允许在文件夹中打开", + "setting.sec.open.desc": "允许在操作系统文件管理器中打开文件位置。", + "setting.sec.reset.name": "主要:允许索引重置", + "setting.sec.reset.desc": "允许重置索引缓存并触发完全重新扫描。", + "setting.sec.token.name": "主要:API Token", + "setting.sec.token.desc": "存储写入授权令牌。 Majoor 将其插入授权和 X-MJR-Token 标头中。", + "setting.sec.token.placeholder": "留空以禁用。", + "tab.output": "输出", + "tab.input": "输入", + "tab.custom": "风俗", + "btn.add": "添加...", + "btn.remove": "消除", + "btn.adding": "添加...", + "btn.removing": "正在删除...", + "btn.retry": "重试", + "btn.clear": "清除", + "btn.refresh": "刷新", + "btn.scan": "扫描", + "btn.scanning": "扫描...", + "btn.resetIndex": "重置索引", + "btn.resetting": "正在重置...", + "btn.deleteDb": "删除数据库", + "btn.deletingDb": "正在删除数据库...", + "btn.retryServices": "重试服务", + "btn.retrying": "正在重试...", + "btn.loadWorkflow": "加载工作流程", + "btn.copyPrompt": "复制提示", + "btn.close": "关闭", + "label.folder": "文件夹", + "label.type": "类型", + "label.workflow": "工作流程", + "label.rating": "等级", + "label.dateRange": "日期范围", + "label.agenda": "议程", + "label.sort": "种类", + "label.collections": "收藏", + "label.filters": "过滤器", + "label.selectFolder": "选择文件夹…", + "label.thisFolder": "这个文件夹", + "filter.all": "全部", + "filter.images": "图片", + "filter.videos": "视频", + "filter.onlyWithWorkflow": "仅适用于工作流程", + "filter.anyRating": "任意评级", + "filter.minStars": "{n}+ 星星", + "filter.anytime": "随时", + "filter.today": "今天", + "filter.yesterday": "昨天", + "filter.thisWeek": "本星期", + "filter.thisMonth": "本月", + "filter.last7days": "过去 7 天", + "filter.last30days": "过去 30 天", + "sort.newest": "最新的优先", + "sort.oldest": "最老的在前", + "sort.nameAZ": "名称 A-Z", + "sort.nameZA": "名称 Z-A", + "sort.ratingHigh": "评级(高)", + "sort.ratingLow": "评级(低)", + "sort.sizeDesc": "尺寸(大)", + "sort.sizeAsc": "尺寸(小)", + "status.checking": "检查...", + "status.ready": "准备好", + "status.scanning": "扫描...", + "status.error": "错误", + "status.capabilities": "能力", + "status.toolStatus": "工具状态", + "status.selectCustomFolder": "首先选择自定义文件夹", + "status.errorGetConfig": "错误:无法获取配置", + "status.discoveringTools": "能力:发现工具...", + "status.indexStatus": "指数状态", + "status.toolStatusChecking": "工具状态:正在检查...", + "status.resetIndexHint": "重置索引缓存(需要设置中的allowResetIndex)。", + "status.scanningHint": "这可能需要一段时间", + "status.toolAvailable": "{tool} 可用", + "status.toolUnavailable": "{tool} 不可用", + "status.unknown": "未知", + "status.available": "可用的", + "status.missing": "丢失的", + "status.path": "小路", + "status.pathAuto": "自动/未配置", + "status.noAssets": "尚未编入索引的资产 ({scope})", + "status.clickToScan": "单击该点开始扫描", + "status.assetsIndexed": "{count} 资产索引 ({scope})", + "status.imagesVideos": "图片:{images} - 视频:{videos}", + "status.withWorkflows": "使用工作流程:{workflows} - 生成数据:{gendata}", + "status.dbSize": "数据库大小:{size}", + "status.lastScan": "最后一次扫描:{date}", + "status.scanStats": "添加:{added} - 更新:{updated} - 跳过:{skipped}", + "status.watcher.enabled": "观察者:已启用", + "status.watcher.enabledScoped": "观察者:已启用 ({scope})", + "status.watcher.disabled": "观察者:已禁用", + "status.watcher.disabledScoped": "观察者:已禁用 ({scope})", + "status.apiNotFound": "未找到主要 API 端点 (404)", + "status.apiNotFoundHint": "后端路由未加载。重新启动 ComfyUI 并检查终端是否存在重大导入错误。", + "status.errorChecking": "检查状态时出错", + "status.dbCorrupted": "数据库已损坏", + "status.dbCorruptedHint": "使用下面的“删除数据库”按钮强制删除并重建索引。", + "status.retryFailed": "重试失败", + "scope.all": "输入+输出", + "scope.allFull": "全部(输入+输出)", + "scope.input": "输入", + "scope.output": "输出", + "scope.custom": "风俗", + "tool.exiftool": "Exif工具元数据", + "tool.exiftool.hint": "PNG/WEBP 工作流程数据(使用 ExifTool)", + "tool.ffprobe": "FFprobe 视频统计", + "tool.ffprobe.hint": "视频时长、FPS 和分辨率(使用 FFprobe)", + "msg.noCollections": "还没有收藏。", + "msg.addCustomFolder": "添加自定义文件夹进行浏览。", + "msg.noResults": "没有找到结果。", + "msg.loading": "加载中...", + "msg.errorLoading": "加载错误", + "msg.errorLoadingFolders": "加载文件夹时出错", + "msg.noGenerationData": "找不到该文件的生成数据。", + "msg.rawMetadata": "原始元数据", + "viewer.genInfo": "一代信息", + "viewer.workflow": "工作流程", + "viewer.metadata": "元数据", + "viewer.noWorkflow": "无工作流程数据", + "viewer.noMetadata": "没有可用的元数据", + "viewer.copySuccess": "已复制到剪贴板!", + "viewer.copyFailed": "复制失败", + "sidebar.details": "细节", + "sidebar.preview": "预览", + "sidebar.rating": "等级", + "sidebar.tags": "标签", + "sidebar.addTag": "添加标签...", + "sidebar.noTags": "没有标签", + "sidebar.filename": "文件名", + "sidebar.dimensions": "方面", + "sidebar.date": "日期", + "sidebar.size": "尺寸", + "sidebar.genTime": "生成时间", + "ctx.openViewer": "在查看器中打开", + "ctx.loadWorkflow": "加载工作流程", + "ctx.copyPath": "复制路径", + "ctx.openInFolder": "在文件夹中打开", + "ctx.rename": "重命名", + "ctx.delete": "删除", + "ctx.addToCollection": "添加到收藏", + "ctx.removeFromCollection": "从集合中删除", + "ctx.newCollection": "新系列...", + "ctx.rescanMetadata": "重新扫描元数据", + "ctx.createCollection": "创建集合...", + "ctx.exitCollection": "退出集合视图", + "dialog.confirm": "确认", + "dialog.cancel": "取消", + "dialog.delete.title": "删除文件?", + "dialog.delete.msg": "您确定要删除该文件吗?此操作无法撤消。", + "dialog.rename.title": "重命名文件", + "dialog.rename.placeholder": "新文件名", + "dialog.newCollection.title": "新系列", + "dialog.newCollection.placeholder": "藏品名称", + "dialog.resetIndex.title": "重置索引?", + "dialog.resetIndex.msg": "这将删除数据库并重新扫描所有文件。继续?", + "dialog.securityWarning": "这看起来像一个系统或非常广泛的目录。\n\n添加它可以通过查看器/自定义根功能公开敏感文件。\n\n继续?", + "dialog.securityWarningTitle": "专业:安全警告", + "dialog.enterFolderPath": "输入要添加为自定义根目录的文件夹路径:", + "dialog.customFoldersTitle": "专业:自定义文件夹", + "dialog.removeFolder": "删除自定义文件夹“{name}”?", + "dialog.deleteCollection": "删除集合“{name}”?", + "dialog.createCollection": "创建收藏", + "dialog.collectionPlaceholder": "我的收藏", + "toast.scanStarted": "扫描开始", + "toast.scanComplete": "扫描完成", + "toast.scanFailed": "扫描失败", + "toast.resetTriggered": "重置已触发:正在重新索引所有文件...", + "toast.resetStarted": "索引重置开始。文件将在后台重新索引。", + "toast.resetFailed": "重置索引失败", + "toast.resetFailedCorrupt": "重置失败 — 数据库已损坏。使用“删除数据库”按钮强制删除和重建。", + "toast.dbDeleteTriggered": "删除数据库并重建...", + "toast.dbDeleteSuccess": "数据库被删除并重建。正在重新索引文件。", + "toast.dbDeleteFailed": "删除数据库失败", + "dialog.dbDelete.confirm": "这将永久删除索引数据库并从头开始重建。所有评级、标签和缓存的元数据都将丢失。\n\n继续?", + "toast.deleted": "文件已删除", + "toast.renamed": "文件重命名", + "toast.addedToCollection": "已添加至收藏", + "toast.removedFromCollection": "从集合中删除", + "toast.collectionCreated": "收藏已创建", + "toast.permissionDenied": "没有权限", + "toast.tagAdded": "已添加标签", + "toast.tagRemoved": "标签已删除", + "toast.ratingSaved": "评级已保存", + "toast.failedAddFolder": "添加自定义文件夹失败", + "toast.failedRemoveFolder": "无法删除自定义文件夹", + "toast.folderLinked": "文件夹链接成功", + "toast.folderRemoved": "文件夹已删除", + "toast.nativeBrowserUnavailable": "本机文件夹浏览器不可用。请手动输入路径。", + "toast.errorAddingFolder": "添加自定义文件夹时出错", + "toast.errorRemovingFolder": "删除自定义文件夹时出错", + "toast.failedCreateCollection": "创建集合失败", + "toast.failedDeleteCollection": "删除集合失败", + "toast.languageChanged": "语言变了。重新加载页面以获得完整效果。", + "toast.ratingUpdateFailed": "无法更新评分", + "toast.ratingUpdateError": "更新评级时出错", + "toast.tagsUpdateFailed": "更新标签失败", + "toast.watcherToggleFailed": "无法切换观察者", + "toast.noValidAssetsSelected": "未选择有效资产。", + "toast.failedCreateCollectionDot": "创建集合失败。", + "toast.failedAddAssetsToCollection": "无法将资产添加到集合中。", + "toast.removeFromCollectionFailed": "无法从集合中删除。", + "toast.copyClipboardFailed": "无法复制到剪贴板", + "toast.metadataRefreshFailed": "无法刷新元数据。", + "toast.ratingCleared": "评级已清除", + "toast.ratingSetN": "评级设置为 {n} 星", + "toast.tagsUpdated": "标签已更新", + "toast.fileRenamedSuccess": "文件重命名成功!", + "toast.fileRenameFailed": "重命名文件失败。", + "toast.fileDeletedSuccess": "文件删除成功!", + "toast.fileDeleteFailed": "删除文件失败。", + "toast.openedInFolder": "在文件夹中打开", + "toast.openFolderFailed": "无法打开文件夹。", + "toast.pathCopied": "文件路径复制到剪贴板", + "toast.pathCopyFailed": "复制路径失败", + "toast.noFilePath": "该资源没有可用的文件路径。", + "toast.downloadingFile": "正在下载 {filename}...", + "toast.playbackRate": "回放 {rate}x", + "toast.metadataRefreshed": "元数据已刷新{suffix}", + "toast.errorRenaming": "重命名文件时出错:{error}", + "toast.errorDeleting": "删除文件时出错:{error}", + "toast.tagMergeFailed": "标签合并失败:{error}", + "toast.deleteFailed": "删除失败:{error}", + "toast.analysisNotStarted": "分析未开始:{error}", + "toast.dupAnalysisStarted": "重复分析开始", + "toast.tagsMerged": "标签已合并", + "toast.duplicatesDeleted": "重复项已删除", + "toast.playbackVideoOnly": "播放速度仅适用于视频媒体", + "toast.filesDeletedSuccessN": "{n} 文件删除成功!", + "toast.filesDeletedPartial": "{success} 文件已删除,{failed} 失败。", + "toast.filesDeletedShort": "{n} 文件已删除", + "toast.filesDeletedShortPartial": "{success} 已删除,{failed} 失败", + "toast.copiedToClipboardNamed": "{name} 已复制到剪贴板!", + "toast.rescanningFile": "重新扫描文件…", + "hotkey.scan": "扫描(小)", + "hotkey.search": "搜索(Ctrl+F)", + "hotkey.details": "切换详细信息 (D)", + "hotkey.delete": "删除(Del)", + "hotkey.viewer": "打开查看器(输入)", + "hotkey.escape": "关闭(Esc)" + }, + "ja-JP": { + "cat.grid": "グリッド", + "cat.cards": "カード", + "cat.badges": "バッジ", + "cat.viewer": "ビューア", + "cat.scanning": "走査", + "cat.advanced": "高度な", + "cat.security": "安全", + "cat.remote": "リモートアクセス", + "setting.grid.minsize.name": "メジャー: サムネイル サイズ (px)", + "setting.grid.minsize.desc": "グリッド内のサムネイルの最小サイズ。パネルを再度開く必要がある場合があります。", + "setting.grid.cardSize.group": "カードサイズ", + "setting.grid.cardSize.name": "メジャー: カードサイズ", + "setting.grid.cardSize.desc": "カード サイズのプリセット (小、中、または大) を選択します。", + "setting.grid.cardSize.small": "小さい", + "setting.grid.cardSize.medium": "中くらい", + "setting.grid.cardSize.large": "大きい", + "setting.grid.gap.name": "メジャー: ギャップ (ピクセル)", + "setting.grid.gap.desc": "サムネイル間のスペース。", + "setting.sidebar.pos.name": "メジャー: サイドバーの位置", + "setting.sidebar.pos.desc": "左側または右側に詳細サイドバーを表示します。リロードが必要です。", + "setting.siblings.hide.name": "メジャー: PNG 兄弟を非表示にする", + "setting.siblings.hide.desc": "ビデオに対応する .png プレビューがある場合は、グリッドから .png を非表示にします。", + "setting.nav.infinite.name": "メジャー: 無限スクロール", + "setting.nav.infinite.desc": "スクロール時にさらに多くのファイルを自動的にロードします。", + "setting.grid.pagesize.name": "メジャー: グリッド ページ サイズ", + "setting.grid.pagesize.desc": "グリッド内のページ/リクエストごとにロードされるアセットの数。", + "setting.viewer.pan.name": "メジャー: ズームなしのパン", + "setting.viewer.pan.desc": "ズーム レベル 1 であっても画像のパンを許可します。", + "setting.minimap.enabled.name": "メジャー: ミニマップを有効にする", + "setting.minimap.enabled.desc": "ワークフロー ミニマップのグローバルなアクティブ化。", + "setting.scan.startup.name": "メジャー: 起動時に自動スキャン", + "setting.scan.startup.desc": "ComfyUI が読み込まれるとすぐにバックグラウンド スキャンを開始します。", + "setting.watcher.name": "メジャー: ファイルウォッチャー", + "setting.watcher.desc": "手動で追加されたファイルの出力フォルダーとカスタム フォルダーを監視し、リアルタイムで自動インデックスを作成します。", + "setting.watcher.debounce.name": "メジャー: ウォッチャーのデバウンス遅延", + "setting.watcher.debounce.desc": "インデックス作成前のウォッチャー イベントのバッチ処理の遅延 (ミリ秒)。", + "setting.watcher.debounce.error": "ウォッチャーのデバウンス遅延を更新できませんでした。", + "setting.watcher.dedupe.name": "メジャー: Watcher 重複排除ウィンドウ", + "setting.watcher.dedupe.desc": "イベント後にファイルが処理済みとして扱われる期間 (ミリ秒)。", + "setting.watcher.dedupe.error": "ウォッチャーの重複排除ウィンドウを更新できませんでした。", + "setting.sync.rating.name": "メジャー: 評価/タグをファイルに同期", + "setting.sync.rating.desc": "評価とタグをファイルのメタデータに書き込みます (ExifTool)。", + "cat.badgeColors": "バッジの色", + "setting.starColor": "スターカラー", + "setting.starColor.tooltip": "サムネイル上の評価星の色 (16 進数、例: #FFD45A)", + "setting.badgeImageColor": "イメージバッジの色", + "setting.badgeImageColor.tooltip": "画像バッジの色: PNG、JPG、WEBP、GIF、BMP、TIF (16 進数)", + "setting.badgeVideoColor": "ビデオバッジの色", + "setting.badgeVideoColor.tooltip": "ビデオバッジの色: MP4、WEBM、MOV、AVI、MKV (16 進数)", + "setting.badgeAudioColor": "オーディオバッジの色", + "setting.badgeAudioColor.tooltip": "オーディオバッジの色: MP3、WAV、OGG、FLAC (16 進数)", + "setting.badgeModel3dColor": "3Dモデルのバッジの色", + "setting.badgeModel3dColor.tooltip": "3D モデルバッジの色: OBJ、FBX、GLB、GLTF (16 進数)", + "setting.obs.enabled.name": "メジャー: 詳細ログを有効にする", + "setting.obs.enabled.desc": "デバッグ用に詳細なバックエンド ログを有効にします。", + "setting.probe.mode.name": "メジャー: メタデータ バックエンド", + "setting.probe.mode.desc": "メタデータの抽出に直接使用するツールを選択します。", + "setting.language.name": "専攻: 言語", + "setting.language.desc": "Assets Manager インターフェイスの言語を選択します。完全に適用するにはリロードが必要です。", + "setting.sec.safe.name": "メジャー: セーフモード", + "setting.sec.safe.desc": "有効にすると、明示的に許可されない限り、評価/タグの書き込みはブロックされます。", + "setting.sec.remote.name": "メジャー: リモートのフルアクセスを許可する", + "setting.sec.remote.desc": "非ローカルクライアントが書き込み操作を実行できるようにします。無効にすると、トークンが構成されていない限り書き込みがブロックされます。", + "setting.sec.write.name": "メジャー: 書き込みを許可", + "setting.sec.write.desc": "評価とタグの書き込みを許可します。", + "setting.sec.del.name": "メジャー: 削除を許可する", + "setting.sec.del.desc": "ファイルの削除を許可します。", + "setting.sec.ren.name": "メジャー: 名前の変更を許可する", + "setting.sec.ren.desc": "ファイル名の変更を許可します。", + "setting.sec.open.name": "メジャー: フォルダー内で開くことを許可する", + "setting.sec.open.desc": "OS ファイルマネージャーでファイルの場所を開くことを許可します。", + "setting.sec.reset.name": "メジャー: インデックスのリセットを許可する", + "setting.sec.reset.desc": "インデックス キャッシュのリセットと完全な再スキャンのトリガーを許可します。", + "setting.sec.token.name": "メジャー: API トークン", + "setting.sec.token.desc": "書き込み認可トークンを保存します。 Major は、これを Authorization ヘッダーと X-MJR-Token ヘッダーに挿入します。", + "setting.sec.token.placeholder": "無効にするには空白のままにします。", + "tab.output": "出力", + "tab.input": "入力", + "tab.custom": "カスタム", + "btn.add": "追加...", + "btn.remove": "取り除く", + "btn.adding": "追加中...", + "btn.removing": "削除中...", + "btn.retry": "リトライ", + "btn.clear": "クリア", + "btn.refresh": "リフレッシュ", + "btn.scan": "スキャン", + "btn.scanning": "走査...", + "btn.resetIndex": "インデックスをリセットする", + "btn.resetting": "リセット中...", + "btn.deleteDb": "DBの削除", + "btn.deletingDb": "DBを削除中...", + "btn.retryServices": "再試行サービス", + "btn.retrying": "再試行中...", + "btn.loadWorkflow": "ロードワークフロー", + "btn.copyPrompt": "プロンプトのコピー", + "btn.close": "近い", + "label.folder": "フォルダ", + "label.type": "タイプ", + "label.workflow": "ワークフロー", + "label.rating": "評価", + "label.dateRange": "日付範囲", + "label.agenda": "議題", + "label.sort": "選別", + "label.collections": "コレクション", + "label.filters": "フィルター", + "label.selectFolder": "フォルダーを選択...", + "label.thisFolder": "このフォルダ", + "filter.all": "全て", + "filter.images": "画像", + "filter.videos": "動画", + "filter.onlyWithWorkflow": "ワークフローのみ", + "filter.anyRating": "任意の評価", + "filter.minStars": "{n} つ星以上", + "filter.anytime": "いつでも", + "filter.today": "今日", + "filter.yesterday": "昨日", + "filter.thisWeek": "今週", + "filter.thisMonth": "今月", + "filter.last7days": "過去 7 日間", + "filter.last30days": "過去 30 日間", + "sort.newest": "新しい順", + "sort.oldest": "古い順", + "sort.nameAZ": "名前 A ~ Z", + "sort.nameZA": "名前 Z-A", + "sort.ratingHigh": "評価(高)", + "sort.ratingLow": "評価(低)", + "sort.sizeDesc": "サイズ(大)", + "sort.sizeAsc": "サイズ(小)", + "status.checking": "チェック中...", + "status.ready": "準備ができて", + "status.scanning": "走査...", + "status.error": "エラー", + "status.capabilities": "能力", + "status.toolStatus": "ツールのステータス", + "status.selectCustomFolder": "最初にカスタムフォルダーを選択してください", + "status.errorGetConfig": "エラー: 構成の取得に失敗しました", + "status.discoveringTools": "機能: ツールの発見...", + "status.indexStatus": "インデックスステータス", + "status.toolStatusChecking": "ツールのステータス: チェック中...", + "status.resetIndexHint": "インデックス キャッシュをリセットします (設定でallowResetIndexが必要です)。", + "status.scanningHint": "しばらく時間がかかる場合があります", + "status.toolAvailable": "{tool} 利用可能", + "status.toolUnavailable": "{tool}は利用できません", + "status.unknown": "未知", + "status.available": "利用可能", + "status.missing": "ない", + "status.path": "パス", + "status.pathAuto": "自動 / 未設定", + "status.noAssets": "アセットのインデックスがまだ作成されていません ({scope})", + "status.clickToScan": "ドットをクリックしてスキャンを開始します", + "status.assetsIndexed": "{count} 資産のインデックス付け ({scope})", + "status.imagesVideos": "画像: {images} - 動画: {videos}", + "status.withWorkflows": "ワークフローあり: {workflows} - 生成データ: {gendata}", + "status.dbSize": "データベースのサイズ: {size}", + "status.lastScan": "最終スキャン: {date}", + "status.scanStats": "追加: {added} - 更新: {updated} - スキップ: {skipped}", + "status.watcher.enabled": "ウォッチャー: 有効", + "status.watcher.enabledScoped": "ウォッチャー: 有効 ({scope})", + "status.watcher.disabled": "ウォッチャー: 無効化", + "status.watcher.disabledScoped": "ウォッチャー: 無効 ({scope})", + "status.apiNotFound": "主要な API エンドポイントが見つかりません (404)", + "status.apiNotFoundHint": "バックエンドルートが読み込まれていません。 ComfyUI を再起動し、ターミナルに重大なインポート エラーがないか確認します。", + "status.errorChecking": "エラーチェックステータス", + "status.dbCorrupted": "データベースが破損しています", + "status.dbCorruptedHint": "インデックスを強制削除して再構築するには、下の「DB を削除」ボタンを使用します。", + "status.retryFailed": "再試行に失敗しました", + "scope.all": "入力 + 出力", + "scope.allFull": "すべて (入力 + 出力)", + "scope.input": "入力", + "scope.output": "出力", + "scope.custom": "カスタム", + "tool.exiftool": "ExifTool メタデータ", + "tool.exiftool.hint": "PNG/WEBP ワークフロー データ (ExifTool を使用)", + "tool.ffprobe": "FFprobe ビデオ統計", + "tool.ffprobe.hint": "ビデオの長さ、FPS、解像度 (FFprobe を使用)", + "msg.noCollections": "まだコレクションはありません。", + "msg.addCustomFolder": "参照するカスタム フォルダーを追加します。", + "msg.noResults": "結果が見つかりませんでした。", + "msg.loading": "読み込み中...", + "msg.errorLoading": "読み込みエラー", + "msg.errorLoadingFolders": "フォルダーの読み込みエラー", + "msg.noGenerationData": "このファイルの世代データは見つかりませんでした。", + "msg.rawMetadata": "生のメタデータ", + "viewer.genInfo": "世代情報", + "viewer.workflow": "ワークフロー", + "viewer.metadata": "メタデータ", + "viewer.noWorkflow": "ワークフローデータがありません", + "viewer.noMetadata": "利用可能なメタデータがありません", + "viewer.copySuccess": "クリップボードにコピーされました!", + "viewer.copyFailed": "コピーに失敗しました", + "sidebar.details": "詳細", + "sidebar.preview": "プレビュー", + "sidebar.rating": "評価", + "sidebar.tags": "タグ", + "sidebar.addTag": "タグを追加...", + "sidebar.noTags": "タグなし", + "sidebar.filename": "ファイル名", + "sidebar.dimensions": "寸法", + "sidebar.date": "日付", + "sidebar.size": "サイズ", + "sidebar.genTime": "生成時間", + "ctx.openViewer": "ビューアで開く", + "ctx.loadWorkflow": "ロードワークフロー", + "ctx.copyPath": "パスをコピーする", + "ctx.openInFolder": "フォルダ内で開く", + "ctx.rename": "名前の変更", + "ctx.delete": "消去", + "ctx.addToCollection": "コレクションに追加", + "ctx.removeFromCollection": "コレクションから削除", + "ctx.newCollection": "新しいコレクション...", + "ctx.rescanMetadata": "メタデータの再スキャン", + "ctx.createCollection": "コレクションを作成...", + "ctx.exitCollection": "コレクションビューを終了する", + "dialog.confirm": "確認する", + "dialog.cancel": "キャンセル", + "dialog.delete.title": "ファイルを削除しますか?", + "dialog.delete.msg": "このファイルを削除してもよろしいですか?これを元に戻すことはできません。", + "dialog.rename.title": "ファイル名の変更", + "dialog.rename.placeholder": "新しいファイル名", + "dialog.newCollection.title": "新しいコレクション", + "dialog.newCollection.placeholder": "コレクション名", + "dialog.resetIndex.title": "インデックスをリセットしますか?", + "dialog.resetIndex.msg": "これにより、データベースが削除され、すべてのファイルが再スキャンされます。続く?", + "dialog.securityWarning": "これはシステムまたは非常に広範なディレクトリのように見えます。\n\nこれを追加すると、ビューア/カスタム ルート機能を介して機密ファイルが公開される可能性があります。\n\n続く?", + "dialog.securityWarningTitle": "重大: セキュリティ警告", + "dialog.enterFolderPath": "カスタム ルートとして追加するフォルダー パスを入力します。", + "dialog.customFoldersTitle": "メジャー: カスタム フォルダー", + "dialog.removeFolder": "カスタムフォルダー「{name}」を削除しますか?", + "dialog.deleteCollection": "コレクション「{name}」を削除しますか?", + "dialog.createCollection": "コレクションの作成", + "dialog.collectionPlaceholder": "私のコレクション", + "toast.scanStarted": "スキャンが開始されました", + "toast.scanComplete": "スキャンが完了しました", + "toast.scanFailed": "スキャンに失敗しました", + "toast.resetTriggered": "リセットがトリガーされました: すべてのファイルのインデックスを再作成しています...", + "toast.resetStarted": "インデックスのリセットが開始されました。ファイルのインデックスはバックグラウンドで再作成されます。", + "toast.resetFailed": "インデックスのリセットに失敗しました", + "toast.resetFailedCorrupt": "リセットに失敗しました – データベースが破損しています。 「DB削除」ボタンで強制削除して再構築します。", + "toast.dbDeleteTriggered": "データベースを削除して再構築しています...", + "toast.dbDeleteSuccess": "データベースが削除され、再構築されました。ファイルのインデックスが再作成されています。", + "toast.dbDeleteFailed": "データベースの削除に失敗しました", + "dialog.dbDelete.confirm": "これにより、インデックス データベースが完全に削除され、最初から再構築されます。すべての評価、タグ、キャッシュされたメタデータが失われます。\n\n続く?", + "toast.deleted": "ファイルが削除されました", + "toast.renamed": "ファイル名が変更されました", + "toast.addedToCollection": "コレクションに追加されました", + "toast.removedFromCollection": "コレクションから削除されました", + "toast.collectionCreated": "コレクションが作成されました", + "toast.permissionDenied": "許可が拒否されました", + "toast.tagAdded": "タグが追加されました", + "toast.tagRemoved": "タグが削除されました", + "toast.ratingSaved": "評価が保存されました", + "toast.failedAddFolder": "カスタムフォルダーの追加に失敗しました", + "toast.failedRemoveFolder": "カスタムフォルダーの削除に失敗しました", + "toast.folderLinked": "フォルダーが正常にリンクされました", + "toast.folderRemoved": "フォルダーが削除されました", + "toast.nativeBrowserUnavailable": "ネイティブ フォルダー ブラウザーは使用できません。パスを手動で入力してください。", + "toast.errorAddingFolder": "カスタムフォルダーの追加中にエラーが発生しました", + "toast.errorRemovingFolder": "カスタムフォルダーの削除中にエラーが発生しました", + "toast.failedCreateCollection": "コレクションの作成に失敗しました", + "toast.failedDeleteCollection": "コレクションの削除に失敗しました", + "toast.languageChanged": "言語が変わりました。完全な効果を得るには、ページをリロードしてください。", + "toast.ratingUpdateFailed": "評価を更新できませんでした", + "toast.ratingUpdateError": "評価更新エラー", + "toast.tagsUpdateFailed": "タグの更新に失敗しました", + "toast.watcherToggleFailed": "ウォッチャーの切り替えに失敗しました", + "toast.noValidAssetsSelected": "有効なアセットが選択されていません。", + "toast.failedCreateCollectionDot": "コレクションの作成に失敗しました。", + "toast.failedAddAssetsToCollection": "アセットをコレクションに追加できませんでした。", + "toast.removeFromCollectionFailed": "コレクションからの削除に失敗しました。", + "toast.copyClipboardFailed": "クリップボードへのコピーに失敗しました", + "toast.metadataRefreshFailed": "メタデータの更新に失敗しました。", + "toast.ratingCleared": "評価がクリアされました", + "toast.ratingSetN": "評価が__PH0___つ星に設定されました", + "toast.tagsUpdated": "タグが更新されました", + "toast.fileRenamedSuccess": "ファイルの名前が正常に変更されました。", + "toast.fileRenameFailed": "ファイル名の変更に失敗しました。", + "toast.fileDeletedSuccess": "ファイルは正常に削除されました。", + "toast.fileDeleteFailed": "ファイルの削除に失敗しました。", + "toast.openedInFolder": "フォルダ内で開かれています", + "toast.openFolderFailed": "フォルダーを開けませんでした。", + "toast.pathCopied": "クリップボードにコピーされたファイルパス", + "toast.pathCopyFailed": "パスのコピーに失敗しました", + "toast.noFilePath": "このアセットに使用できるファイル パスがありません。", + "toast.downloadingFile": "{filename} をダウンロードしています...", + "toast.playbackRate": "再生 {rate}x", + "toast.metadataRefreshed": "メタデータが更新されました{suffix}", + "toast.errorRenaming": "ファイル名の変更中にエラーが発生しました: {error}", + "toast.errorDeleting": "ファイル削除エラー: {error}", + "toast.tagMergeFailed": "タグのマージに失敗しました: {error}", + "toast.deleteFailed": "削除に失敗しました: {error}", + "toast.analysisNotStarted": "分析が開始されていません: {error}", + "toast.dupAnalysisStarted": "重複分析が開始されました", + "toast.tagsMerged": "タグが統合されました", + "toast.duplicatesDeleted": "重複が削除されました", + "toast.playbackVideoOnly": "再生速度はビデオ メディアのみで使用できます", + "toast.filesDeletedSuccessN": "{n} ファイルは正常に削除されました。", + "toast.filesDeletedPartial": "{success} ファイルが削除されました。__PH1___ は失敗しました。", + "toast.filesDeletedShort": "{n} ファイルが削除されました", + "toast.filesDeletedShortPartial": "{success} は削除されました、__PH1___ は失敗しました", + "toast.copiedToClipboardNamed": "{name} がクリップボードにコピーされました!", + "toast.rescanningFile": "ファイルを再スキャンしています....", + "hotkey.scan": "スキャン(S)", + "hotkey.search": "検索 (Ctrl+F)", + "hotkey.details": "詳細の切り替え (D)", + "hotkey.delete": "削除 (Del)", + "hotkey.viewer": "ビューアを開く (Enter)", + "hotkey.escape": "閉じる (Esc)" + }, + "ko-KR": { + "cat.grid": "그리드", + "cat.cards": "카드", + "cat.badges": "배지", + "cat.viewer": "뷰어", + "cat.scanning": "스캐닝", + "cat.advanced": "고급의", + "cat.security": "보안", + "cat.remote": "원격 액세스", + "setting.grid.minsize.name": "전공: 썸네일 크기(px)", + "setting.grid.minsize.desc": "그리드의 축소판 최소 크기입니다. 패널을 다시 열어야 할 수도 있습니다.", + "setting.grid.cardSize.group": "카드 크기", + "setting.grid.cardSize.name": "전공: 카드 크기", + "setting.grid.cardSize.desc": "카드 크기 사전 설정을 선택합니다(소형, 중형, 대형).", + "setting.grid.cardSize.small": "작은", + "setting.grid.cardSize.medium": "중간", + "setting.grid.cardSize.large": "크기가 큰", + "setting.grid.gap.name": "전공: 간격(px)", + "setting.grid.gap.desc": "썸네일 사이의 공간.", + "setting.sidebar.pos.name": "전공: 사이드바 위치", + "setting.sidebar.pos.desc": "왼쪽이나 오른쪽에 세부정보 사이드바를 표시합니다. 새로고침이 필요합니다.", + "setting.siblings.hide.name": "전공: PNG 형제 숨기기", + "setting.siblings.hide.desc": "비디오에 해당하는 .png 미리 보기가 있는 경우 그리드에서 .png를 숨깁니다.", + "setting.nav.infinite.name": "전공: 무한 스크롤", + "setting.nav.infinite.desc": "스크롤할 때 자동으로 더 많은 파일을 로드합니다.", + "setting.grid.pagesize.name": "전공: 그리드 페이지 크기", + "setting.grid.pagesize.desc": "그리드의 페이지/요청당 로드된 자산 수입니다.", + "setting.viewer.pan.name": "전공: 확대/축소 없이 이동", + "setting.viewer.pan.desc": "확대/축소 수준 1에서도 이미지 패닝을 허용합니다.", + "setting.minimap.enabled.name": "주요: 미니맵 활성화", + "setting.minimap.enabled.desc": "작업흐름 미니맵의 전역 활성화.", + "setting.scan.startup.name": "주요: 시작 시 자동 스캔", + "setting.scan.startup.desc": "ComfyUI가 로드되자마자 백그라운드 스캔을 시작하세요.", + "setting.watcher.name": "전공: 파일 감시자", + "setting.watcher.desc": "수동으로 추가된 파일의 출력 및 사용자 정의 폴더를 관찰하고 실시간으로 자동 색인화합니다.", + "setting.watcher.debounce.name": "주요: 감시자 디바운스 지연", + "setting.watcher.debounce.desc": "인덱싱 전 감시자 이벤트 일괄 처리에 대한 지연(ms)입니다.", + "setting.watcher.debounce.error": "감시자 디바운스 지연을 업데이트하지 못했습니다.", + "setting.watcher.dedupe.name": "전공: 감시자 중복 제거 창", + "setting.watcher.dedupe.desc": "이벤트 발생 후 파일이 이미 처리된 것으로 처리되는 기간(ms)입니다.", + "setting.watcher.dedupe.error": "감시자 중복 제거 창을 업데이트하지 못했습니다.", + "setting.sync.rating.name": "전공: 등급/태그를 파일에 동기화", + "setting.sync.rating.desc": "등급과 태그를 파일 메타데이터(ExifTool)에 기록합니다.", + "cat.badgeColors": "배지 색상", + "setting.starColor": "별색", + "setting.starColor.tooltip": "미리보기 이미지의 별점 색상(16진수, 예: #FFD45A)", + "setting.badgeImageColor": "이미지 배지 색상", + "setting.badgeImageColor.tooltip": "이미지 배지 색상: PNG, JPG, WEBP, GIF, BMP, TIF(16진수)", + "setting.badgeVideoColor": "동영상 배지 색상", + "setting.badgeVideoColor.tooltip": "비디오 배지 색상: MP4, WEBM, MOV, AVI, MKV(16진수)", + "setting.badgeAudioColor": "오디오 배지 색상", + "setting.badgeAudioColor.tooltip": "오디오 배지 색상: MP3, WAV, OGG, FLAC(16진수)", + "setting.badgeModel3dColor": "3D 모델 배지 색상", + "setting.badgeModel3dColor.tooltip": "3D 모델 배지 색상: OBJ, FBX, GLB, GLTF(16진수)", + "setting.obs.enabled.name": "주요: 상세 로그 활성화", + "setting.obs.enabled.desc": "디버깅을 위해 자세한 백엔드 로그를 활성화합니다.", + "setting.probe.mode.name": "전공: 메타데이터 백엔드", + "setting.probe.mode.desc": "메타데이터를 추출하는 데 직접 사용되는 도구를 선택하세요.", + "setting.language.name": "전공: 언어", + "setting.language.desc": "Assets Manager 인터페이스의 언어를 선택합니다. 완전히 적용하려면 다시 로드해야 합니다.", + "setting.sec.safe.name": "주요: 안전 모드", + "setting.sec.safe.desc": "활성화되면 명시적으로 승인되지 않는 한 등급/태그 쓰기가 차단됩니다.", + "setting.sec.remote.name": "주요: 원격 전체 액세스 허용", + "setting.sec.remote.desc": "로컬이 아닌 클라이언트가 쓰기 작업을 수행하도록 허용합니다. 토큰이 구성되지 않은 경우 블록 쓰기를 비활성화합니다.", + "setting.sec.write.name": "전공: 쓰기 허용", + "setting.sec.write.desc": "등급 및 태그 작성을 허용합니다.", + "setting.sec.del.name": "주요: 삭제 허용", + "setting.sec.del.desc": "파일 삭제를 허용합니다.", + "setting.sec.ren.name": "전공: 이름 바꾸기 허용", + "setting.sec.ren.desc": "파일 이름 바꾸기를 허용합니다.", + "setting.sec.open.name": "전공: 폴더에서 열기 허용", + "setting.sec.open.desc": "OS 파일 관리자에서 파일 위치 열기를 허용합니다.", + "setting.sec.reset.name": "주요: 인덱스 재설정 허용", + "setting.sec.reset.desc": "인덱스 캐시 재설정 및 전체 재검색 트리거를 허용합니다.", + "setting.sec.token.name": "전공: API 토큰", + "setting.sec.token.desc": "쓰기 인증 토큰을 저장합니다. Major는 이를 Authorization 및 X-MJR-Token 헤더에 삽입합니다.", + "setting.sec.token.placeholder": "비활성화하려면 비워 두세요.", + "tab.output": "산출", + "tab.input": "입력", + "tab.custom": "관습", + "btn.add": "추가…", + "btn.remove": "제거하다", + "btn.adding": "첨가...", + "btn.removing": "풀이...", + "btn.retry": "다시 해 보다", + "btn.clear": "분명한", + "btn.refresh": "새로 고치다", + "btn.scan": "주사", + "btn.scanning": "스캐닝...", + "btn.resetIndex": "인덱스 재설정", + "btn.resetting": "재설정 중...", + "btn.deleteDb": "DB 삭제", + "btn.deletingDb": "DB 삭제 중...", + "btn.retryServices": "재시도 서비스", + "btn.retrying": "재시도 중...", + "btn.loadWorkflow": "워크플로 로드", + "btn.copyPrompt": "복사 프롬프트", + "btn.close": "닫다", + "label.folder": "접는 사람", + "label.type": "유형", + "label.workflow": "작업흐름", + "label.rating": "평가", + "label.dateRange": "기간", + "label.agenda": "의제", + "label.sort": "종류", + "label.collections": "컬렉션", + "label.filters": "필터", + "label.selectFolder": "폴더 선택…", + "label.thisFolder": "이 폴더", + "filter.all": "모두", + "filter.images": "이미지", + "filter.videos": "비디오", + "filter.onlyWithWorkflow": "워크플로우에서만", + "filter.anyRating": "모든 평가", + "filter.minStars": "_{n}_개 이상의 별", + "filter.anytime": "언제든지", + "filter.today": "오늘", + "filter.yesterday": "어제", + "filter.thisWeek": "이번주", + "filter.thisMonth": "이번 달", + "filter.last7days": "지난 7일", + "filter.last30days": "지난 30일", + "sort.newest": "최신순", + "sort.oldest": "오래된 것부터", + "sort.nameAZ": "이름 A~Z", + "sort.nameZA": "이름 Z-A", + "sort.ratingHigh": "평점(높음)", + "sort.ratingLow": "평점(낮음)", + "sort.sizeDesc": "사이즈(대)", + "sort.sizeAsc": "사이즈(소)", + "status.checking": "확인 중...", + "status.ready": "준비가 된", + "status.scanning": "스캐닝...", + "status.error": "오류", + "status.capabilities": "기능", + "status.toolStatus": "도구 상태", + "status.selectCustomFolder": "먼저 사용자 정의 폴더를 선택하세요", + "status.errorGetConfig": "오류: 구성을 가져오지 못했습니다.", + "status.discoveringTools": "기능: 도구 검색...", + "status.indexStatus": "지수현황", + "status.toolStatusChecking": "도구 상태: 확인 중...", + "status.resetIndexHint": "인덱스 캐시를 재설정합니다(설정에서 허용ResetIndex 필요).", + "status.scanningHint": "시간이 좀 걸릴 수 있습니다.", + "status.toolAvailable": "_{tool} 사용 가능", + "status.toolUnavailable": "_{tool} 사용할 수 없음", + "status.unknown": "알려지지 않은", + "status.available": "사용 가능", + "status.missing": "없어진", + "status.path": "길", + "status.pathAuto": "자동 / 구성되지 않음", + "status.noAssets": "아직 색인이 생성된 자산이 없습니다({scope}).", + "status.clickToScan": "스캔을 시작하려면 점을 클릭하세요", + "status.assetsIndexed": "_{count} 자산 색인화됨({scope})", + "status.imagesVideos": "이미지: {images} - 동영상: {videos}", + "status.withWorkflows": "워크플로우 포함: {workflows} - 생성 데이터: {gendata}", + "status.dbSize": "데이터베이스 크기: {size}", + "status.lastScan": "마지막 스캔: {date}", + "status.scanStats": "추가됨: {added} - 업데이트됨: {updated} - 건너뛰음: {skipped}", + "status.watcher.enabled": "감시자: 활성화됨", + "status.watcher.enabledScoped": "감시자: 활성화됨 ({scope})", + "status.watcher.disabled": "감시자: 비활성화됨", + "status.watcher.disabledScoped": "감시자: 비활성화됨 ({scope})", + "status.apiNotFound": "주요 API 엔드포인트를 찾을 수 없습니다(404)", + "status.apiNotFoundHint": "백엔드 경로가 로드되지 않습니다. ComfyUI를 다시 시작하고 터미널에서 주요 가져오기 오류를 확인하세요.", + "status.errorChecking": "상태 확인 오류", + "status.dbCorrupted": "데이터베이스가 손상되었습니다", + "status.dbCorruptedHint": "인덱스를 강제 삭제하고 다시 작성하려면 아래 \"DB 삭제\" 버튼을 사용하세요.", + "status.retryFailed": "재시도 실패", + "scope.all": "입력 + 출력", + "scope.allFull": "모두(입력 + 출력)", + "scope.input": "입력", + "scope.output": "출력", + "scope.custom": "관습", + "tool.exiftool": "ExifTool 메타데이터", + "tool.exiftool.hint": "PNG/WEBP 워크플로우 데이터(ExifTool 사용)", + "tool.ffprobe": "FFprobe 비디오 통계", + "tool.ffprobe.hint": "비디오 지속 시간, FPS 및 해상도(FFprobe 사용)", + "msg.noCollections": "아직 컬렉션이 없습니다.", + "msg.addCustomFolder": "탐색할 사용자 정의 폴더를 추가하세요.", + "msg.noResults": "검색된 결과가 없습니다.", + "msg.loading": "로드 중...", + "msg.errorLoading": "로드 중 오류 발생", + "msg.errorLoadingFolders": "폴더를 로드하는 중에 오류가 발생했습니다.", + "msg.noGenerationData": "이 파일에 대한 생성 데이터가 없습니다.", + "msg.rawMetadata": "원시 메타데이터", + "viewer.genInfo": "세대 정보", + "viewer.workflow": "작업흐름", + "viewer.metadata": "메타데이터", + "viewer.noWorkflow": "워크플로 데이터 없음", + "viewer.noMetadata": "사용 가능한 메타데이터가 없습니다.", + "viewer.copySuccess": "클립보드에 복사되었습니다!", + "viewer.copyFailed": "복사하지 못했습니다.", + "sidebar.details": "세부", + "sidebar.preview": "시사", + "sidebar.rating": "평가", + "sidebar.tags": "태그", + "sidebar.addTag": "태그 추가...", + "sidebar.noTags": "태그 없음", + "sidebar.filename": "파일 이름", + "sidebar.dimensions": "치수", + "sidebar.date": "날짜", + "sidebar.size": "크기", + "sidebar.genTime": "생성 시간", + "ctx.openViewer": "뷰어에서 열기", + "ctx.loadWorkflow": "워크플로 로드", + "ctx.copyPath": "경로 복사", + "ctx.openInFolder": "폴더에서 열기", + "ctx.rename": "이름 바꾸기", + "ctx.delete": "삭제", + "ctx.addToCollection": "컬렉션에 추가", + "ctx.removeFromCollection": "컬렉션에서 제거", + "ctx.newCollection": "새로운 컬렉션...", + "ctx.rescanMetadata": "메타데이터 재검사", + "ctx.createCollection": "컬렉션 만들기...", + "ctx.exitCollection": "컬렉션 보기 종료", + "dialog.confirm": "확인하다", + "dialog.cancel": "취소", + "dialog.delete.title": "파일을 삭제하시겠습니까?", + "dialog.delete.msg": "이 파일을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.", + "dialog.rename.title": "파일 이름 바꾸기", + "dialog.rename.placeholder": "새 파일 이름", + "dialog.newCollection.title": "새로운 컬렉션", + "dialog.newCollection.placeholder": "컬렉션 이름", + "dialog.resetIndex.title": "색인을 재설정하시겠습니까?", + "dialog.resetIndex.msg": "그러면 데이터베이스가 삭제되고 모든 파일이 다시 검사됩니다. 계속하다?", + "dialog.securityWarning": "이는 시스템 또는 매우 광범위한 디렉토리처럼 보입니다.\n\n이를 추가하면 뷰어/사용자 정의 루트 기능을 통해 민감한 파일이 노출될 수 있습니다.\n\n계속하다?", + "dialog.securityWarningTitle": "전공: 보안 경고", + "dialog.enterFolderPath": "사용자 정의 루트로 추가할 폴더 경로를 입력하세요.", + "dialog.customFoldersTitle": "전공: 사용자 정의 폴더", + "dialog.removeFolder": "사용자 정의 폴더 \"{name}\"을(를) 제거하시겠습니까?", + "dialog.deleteCollection": "'{name}' 컬렉션을 삭제하시겠습니까?", + "dialog.createCollection": "컬렉션 만들기", + "dialog.collectionPlaceholder": "내 컬렉션", + "toast.scanStarted": "스캔이 시작되었습니다", + "toast.scanComplete": "스캔 완료", + "toast.scanFailed": "스캔 실패", + "toast.resetTriggered": "재설정이 트리거됨: 모든 파일의 색인을 다시 생성하는 중...", + "toast.resetStarted": "인덱스 재설정이 시작되었습니다. 파일은 백그라운드에서 다시 색인화됩니다.", + "toast.resetFailed": "인덱스를 재설정하지 못했습니다.", + "toast.resetFailedCorrupt": "재설정 실패… 데이터베이스가 손상되었습니다. 강제 삭제 및 재구축은 \"DB 삭제\" 버튼을 이용하세요.", + "toast.dbDeleteTriggered": "데이터베이스 삭제 및 재구축 중...", + "toast.dbDeleteSuccess": "데이터베이스가 삭제되고 재구축되었습니다. 파일의 색인을 다시 생성하는 중입니다.", + "toast.dbDeleteFailed": "데이터베이스를 삭제하지 못했습니다.", + "dialog.dbDelete.confirm": "이렇게 하면 인덱스 데이터베이스가 영구적으로 삭제되고 처음부터 다시 작성됩니다. 모든 평가, 태그, 캐시된 메타데이터가 손실됩니다.\n\n계속하다?", + "toast.deleted": "파일이 삭제되었습니다.", + "toast.renamed": "파일 이름이 변경됨", + "toast.addedToCollection": "컬렉션에 추가됨", + "toast.removedFromCollection": "컬렉션에서 삭제됨", + "toast.collectionCreated": "컬렉션이 생성되었습니다.", + "toast.permissionDenied": "허가가 거부되었습니다", + "toast.tagAdded": "태그가 추가됨", + "toast.tagRemoved": "태그가 삭제되었습니다.", + "toast.ratingSaved": "평가가 저장되었습니다", + "toast.failedAddFolder": "맞춤 폴더를 추가하지 못했습니다.", + "toast.failedRemoveFolder": "맞춤 폴더를 삭제하지 못했습니다.", + "toast.folderLinked": "폴더가 성공적으로 연결되었습니다.", + "toast.folderRemoved": "폴더가 삭제되었습니다.", + "toast.nativeBrowserUnavailable": "기본 폴더 브라우저를 사용할 수 없습니다. 경로를 직접 입력해 주세요.", + "toast.errorAddingFolder": "맞춤 폴더를 추가하는 중에 오류가 발생했습니다.", + "toast.errorRemovingFolder": "사용자 정의 폴더를 제거하는 중 오류가 발생했습니다.", + "toast.failedCreateCollection": "컬렉션을 생성하지 못했습니다.", + "toast.failedDeleteCollection": "컬렉션을 삭제하지 못했습니다.", + "toast.languageChanged": "언어가 변경되었습니다. 전체 효과를 보려면 페이지를 다시 로드하세요.", + "toast.ratingUpdateFailed": "평점을 업데이트하지 못했습니다.", + "toast.ratingUpdateError": "평점을 업데이트하는 중에 오류가 발생했습니다.", + "toast.tagsUpdateFailed": "태그를 업데이트하지 못했습니다.", + "toast.watcherToggleFailed": "감시자를 전환하지 못했습니다.", + "toast.noValidAssetsSelected": "유효한 자산을 선택하지 않았습니다.", + "toast.failedCreateCollectionDot": "컬렉션을 생성하지 못했습니다.", + "toast.failedAddAssetsToCollection": "컬렉션에 자산을 추가하지 못했습니다.", + "toast.removeFromCollectionFailed": "컬렉션에서 제거하지 못했습니다.", + "toast.copyClipboardFailed": "클립보드에 복사하지 못했습니다.", + "toast.metadataRefreshFailed": "메타데이터를 새로 고치지 못했습니다.", + "toast.ratingCleared": "등급이 지워졌습니다.", + "toast.ratingSetN": "평점이 별 {n}개로 설정되었습니다.", + "toast.tagsUpdated": "태그가 업데이트되었습니다.", + "toast.fileRenamedSuccess": "파일 이름이 성공적으로 변경되었습니다!", + "toast.fileRenameFailed": "파일 이름을 바꾸지 못했습니다.", + "toast.fileDeletedSuccess": "파일이 삭제되었습니다!", + "toast.fileDeleteFailed": "파일을 삭제하지 못했습니다.", + "toast.openedInFolder": "폴더에서 열림", + "toast.openFolderFailed": "폴더를 열지 못했습니다.", + "toast.pathCopied": "파일 경로가 클립보드에 복사되었습니다.", + "toast.pathCopyFailed": "경로를 복사하지 못했습니다.", + "toast.noFilePath": "이 자산에 사용할 수 있는 파일 경로가 없습니다.", + "toast.downloadingFile": "_{filename} 다운로드 중...", + "toast.playbackRate": "재생 {rate}x", + "toast.metadataRefreshed": "메타데이터가 새로 고쳐졌습니다{suffix}", + "toast.errorRenaming": "파일 이름을 바꾸는 중에 오류가 발생했습니다: {error}", + "toast.errorDeleting": "파일 삭제 오류: {error}", + "toast.tagMergeFailed": "태그 병합 실패: {error}", + "toast.deleteFailed": "삭제 실패: {error}", + "toast.analysisNotStarted": "분석이 시작되지 않았습니다: {error}", + "toast.dupAnalysisStarted": "중복 분석이 시작되었습니다.", + "toast.tagsMerged": "태그가 병합되었습니다.", + "toast.duplicatesDeleted": "중복 항목이 삭제되었습니다.", + "toast.playbackVideoOnly": "재생 속도는 비디오 미디어에만 사용할 수 있습니다.", + "toast.filesDeletedSuccessN": "_{n} 파일이 성공적으로 삭제되었습니다!", + "toast.filesDeletedPartial": "_{success} 파일이 삭제되었으며 {failed}이 실패했습니다.", + "toast.filesDeletedShort": "_{n} 파일이 삭제되었습니다.", + "toast.filesDeletedShortPartial": "_{success} 삭제됨, {failed} 실패", + "toast.copiedToClipboardNamed": "_{name} 클립보드에 복사되었습니다!", + "toast.rescanningFile": "파일을 다시 검사하는 중...", + "hotkey.scan": "스캔(S)", + "hotkey.search": "검색(Ctrl+F)", + "hotkey.details": "세부정보 전환(D)", + "hotkey.delete": "삭제(Del)", + "hotkey.viewer": "뷰어 열기(Enter)", + "hotkey.escape": "닫기(Esc)" + }, + "hi-IN": { + "cat.grid": "ग्रिड", + "cat.cards": "कार्ड", + "cat.badges": "बैज", + "cat.viewer": "दर्शक", + "cat.scanning": "स्कैनिंग", + "cat.advanced": "विकसित", + "cat.security": "सुरक्षा", + "cat.remote": "दूरदराज का उपयोग", + "setting.grid.minsize.name": "प्रमुख: थंबनेल आकार (px)", + "setting.grid.minsize.desc": "ग्रिड में थंबनेल का न्यूनतम आकार. पैनल को दोबारा खोलने की आवश्यकता हो सकती है.", + "setting.grid.cardSize.group": "कार्ड का आकार", + "setting.grid.cardSize.name": "प्रमुख: कार्ड का आकार", + "setting.grid.cardSize.desc": "एक कार्ड आकार प्रीसेट चुनें: छोटा, मध्यम या बड़ा।", + "setting.grid.cardSize.small": "छोटा", + "setting.grid.cardSize.medium": "मध्यम", + "setting.grid.cardSize.large": "बड़ा", + "setting.grid.gap.name": "मेजर: गैप (px)", + "setting.grid.gap.desc": "थंबनेल के बीच का स्थान.", + "setting.sidebar.pos.name": "प्रमुख: साइडबार स्थिति", + "setting.sidebar.pos.desc": "बायीं या दायीं ओर विवरण साइडबार दिखाएँ। पुनः लोड करना आवश्यक है.", + "setting.siblings.hide.name": "मेजर: पीएनजी भाई-बहन छुपाएं", + "setting.siblings.hide.desc": "यदि किसी वीडियो में संबंधित .png पूर्वावलोकन है, तो .png को ग्रिड से छिपाएँ।", + "setting.nav.infinite.name": "मेजर: अनंत स्क्रॉल", + "setting.nav.infinite.desc": "स्क्रॉल करते समय स्वचालित रूप से अधिक फ़ाइलें लोड करें।", + "setting.grid.pagesize.name": "प्रमुख: ग्रिड पृष्ठ का आकार", + "setting.grid.pagesize.desc": "ग्रिड में प्रति पृष्ठ/अनुरोध पर लोड की गई संपत्तियों की संख्या।", + "setting.viewer.pan.name": "मेजर: ज़ूम के बिना पैन", + "setting.viewer.pan.desc": "ज़ूम स्तर 1 पर भी छवि को पैन करने की अनुमति दें।", + "setting.minimap.enabled.name": "मेजर: मिनिमैप सक्षम करें", + "setting.minimap.enabled.desc": "वर्कफ़्लो मिनिमैप का वैश्विक सक्रियण।", + "setting.scan.startup.name": "प्रमुख: स्टार्टअप पर ऑटो-स्कैन", + "setting.scan.startup.desc": "ComfyUI लोड होते ही बैकग्राउंड स्कैन शुरू करें।", + "setting.watcher.name": "मेजर: फाइल देखने वाला", + "setting.watcher.desc": "मैन्युअल रूप से जोड़ी गई फ़ाइलों के लिए आउटपुट और कस्टम फ़ोल्डर देखें और वास्तविक समय में उन्हें ऑटो-इंडेक्स करें।", + "setting.watcher.debounce.name": "मेजर: द्रष्टा बहस देरी", + "setting.watcher.debounce.desc": "अनुक्रमणिका से पहले वॉचर इवेंट को बैचने के लिए विलंब (एमएस)।", + "setting.watcher.debounce.error": "वॉचर डिबाउंस विलंब को अद्यतन करने में विफल।", + "setting.watcher.dedupe.name": "मेजर: वॉचर डिडुपे विंडो", + "setting.watcher.dedupe.desc": "अवधि (एमएस) में किसी फ़ाइल को किसी घटना के बाद पहले ही संसाधित माना जाता है।", + "setting.watcher.dedupe.error": "वॉचर डिडुप विंडो को अपडेट करने में विफल।", + "setting.sync.rating.name": "प्रमुख: रेटिंग/टैग को फाइलों में सिंक करें", + "setting.sync.rating.desc": "फ़ाइल मेटाडेटा (ExifTool) में रेटिंग और टैग लिखें।", + "cat.badgeColors": "बैज रंग", + "setting.starColor": "सितारा रंग", + "setting.starColor.tooltip": "थंबनेल पर रेटिंग सितारों का रंग (हेक्स, उदाहरण के लिए #FFD45A)", + "setting.badgeImageColor": "छवि बैज रंग", + "setting.badgeImageColor.tooltip": "छवि बैज के लिए रंग: पीएनजी, जेपीजी, वेबपी, जीआईएफ, बीएमपी, टीआईएफ (हेक्स)", + "setting.badgeVideoColor": "वीडियो बैज रंग", + "setting.badgeVideoColor.tooltip": "वीडियो बैज के लिए रंग: MP4, WEBM, MOV, AVI, MKV (हेक्स)", + "setting.badgeAudioColor": "ऑडियो बैज रंग", + "setting.badgeAudioColor.tooltip": "ऑडियो बैज के लिए रंग: MP3, WAV, OGG, FLAC (हेक्स)", + "setting.badgeModel3dColor": "3डी मॉडल बैज रंग", + "setting.badgeModel3dColor.tooltip": "3डी मॉडल बैज के लिए रंग: ओबीजे, एफबीएक्स, जीएलबी, जीएलटीएफ (हेक्स)", + "setting.obs.enabled.name": "प्रमुख: विस्तृत लॉग सक्षम करें", + "setting.obs.enabled.desc": "डिबगिंग के लिए विस्तृत बैकएंड लॉग सक्षम करें।", + "setting.probe.mode.name": "प्रमुख: मेटाडेटा बैकएंड", + "setting.probe.mode.desc": "मेटाडेटा निकालने के लिए सीधे उपयोग किया जाने वाला उपकरण चुनें।", + "setting.language.name": "मेजर: भाषा", + "setting.language.desc": "एसेट मैनेजर इंटरफ़ेस के लिए भाषा चुनें। पूरी तरह से लागू करने के लिए पुनः लोड करना आवश्यक है।", + "setting.sec.safe.name": "प्रमुख: सुरक्षित मोड", + "setting.sec.safe.desc": "सक्षम होने पर, रेटिंग/टैग लिखना तब तक अवरुद्ध रहता है जब तक कि स्पष्ट रूप से अधिकृत न किया गया हो।", + "setting.sec.remote.name": "मेजर: रिमोट पूर्ण एक्सेस की अनुमति दें", + "setting.sec.remote.desc": "गैर-स्थानीय ग्राहकों को लेखन कार्य करने की अनुमति दें। जब तक कोई टोकन कॉन्फ़िगर नहीं किया जाता तब तक ब्लॉक लिखना अक्षम करना।", + "setting.sec.write.name": "मेजर: लिखने की अनुमति दें", + "setting.sec.write.desc": "रेटिंग और टैग लिखने की अनुमति दें.", + "setting.sec.del.name": "मेजर: डिलीट की अनुमति दें", + "setting.sec.del.desc": "फ़ाइलें हटाने की अनुमति दें.", + "setting.sec.ren.name": "मेजर: नाम बदलने की अनुमति दें", + "setting.sec.ren.desc": "फ़ाइलों का नाम बदलने की अनुमति दें.", + "setting.sec.open.name": "प्रमुख: फ़ोल्डर में खोलने की अनुमति दें", + "setting.sec.open.desc": "OS फ़ाइल प्रबंधक में फ़ाइल स्थान खोलने की अनुमति दें।", + "setting.sec.reset.name": "मेजर: इंडेक्स रीसेट की अनुमति दें", + "setting.sec.reset.desc": "इंडेक्स कैश को रीसेट करने और पूर्ण पुनः स्कैन ट्रिगर करने की अनुमति दें।", + "setting.sec.token.name": "प्रमुख: एपीआई टोकन", + "setting.sec.token.desc": "लेखन प्राधिकरण टोकन संग्रहीत करें। मेजर इसे प्राधिकरण और एक्स-एमजेआर-टोकन हेडर में सम्मिलित करता है।", + "setting.sec.token.placeholder": "अक्षम करने के लिए खाली छोड़ दें.", + "tab.output": "उत्पादन", + "tab.input": "इनपुट", + "tab.custom": "रिवाज़", + "btn.add": "जोड़ें...", + "btn.remove": "निकालना", + "btn.adding": "जोड़ा जा रहा है...", + "btn.removing": "हटाया जा रहा है...", + "btn.retry": "पुन: प्रयास करें", + "btn.clear": "स्पष्ट", + "btn.refresh": "ताज़ा करना", + "btn.scan": "स्कैन", + "btn.scanning": "स्कैन किया जा रहा है...", + "btn.resetIndex": "सूचकांक रीसेट करें", + "btn.resetting": "रीसेट किया जा रहा है...", + "btn.deleteDb": "डीबी हटाएं", + "btn.deletingDb": "डीबी हटाया जा रहा है...", + "btn.retryServices": "सेवाओं का पुनः प्रयास करें", + "btn.retrying": "पुनः प्रयास कर रहा हूँ...", + "btn.loadWorkflow": "वर्कफ़्लो लोड करें", + "btn.copyPrompt": "प्रॉम्प्ट कॉपी करें", + "btn.close": "बंद करना", + "label.folder": "फ़ोल्डर", + "label.type": "प्रकार", + "label.workflow": "कार्यप्रवाह", + "label.rating": "रेटिंग", + "label.dateRange": "तिथि सीमा", + "label.agenda": "कार्यसूची", + "label.sort": "क्रम से लगाना", + "label.collections": "संग्रह", + "label.filters": "फिल्टर", + "label.selectFolder": "फ़ोल्डर चुनें...", + "label.thisFolder": "यह फ़ोल्डर", + "filter.all": "सभी", + "filter.images": "इमेजिस", + "filter.videos": "वीडियो", + "filter.onlyWithWorkflow": "केवल वर्कफ़्लो के साथ", + "filter.anyRating": "कोई रेटिंग", + "filter.minStars": "{n}+ तारे", + "filter.anytime": "कभी भी", + "filter.today": "आज", + "filter.yesterday": "कल", + "filter.thisWeek": "इस सप्ताह", + "filter.thisMonth": "इस महीने", + "filter.last7days": "पिछले 7 दिन", + "filter.last30days": "पिछले 30 दिन", + "sort.newest": "सबसे पहले नवीनतम", + "sort.oldest": "सबसे पुराना पहले", + "sort.nameAZ": "नाम A-Z", + "sort.nameZA": "नाम Z-A", + "sort.ratingHigh": "रेटिंग (उच्च)", + "sort.ratingLow": "रेटिंग (कम)", + "sort.sizeDesc": "बड़े आकार)", + "sort.sizeAsc": "आकार (छोटा)", + "status.checking": "जाँच हो रही है...", + "status.ready": "तैयार", + "status.scanning": "स्कैन किया जा रहा है...", + "status.error": "गलती", + "status.capabilities": "क्षमताओं", + "status.toolStatus": "उपकरण की स्थिति", + "status.selectCustomFolder": "पहले एक कस्टम फ़ोल्डर चुनें", + "status.errorGetConfig": "त्रुटि: कॉन्फ़िगरेशन प्राप्त करने में विफल", + "status.discoveringTools": "क्षमताएं: उपकरणों की खोज...", + "status.indexStatus": "सूचकांक स्थिति", + "status.toolStatusChecking": "टूल स्थिति: जाँच हो रही है...", + "status.resetIndexHint": "इंडेक्स कैश रीसेट करें (सेटिंग्स मेंallowResetIndex की आवश्यकता है)।", + "status.scanningHint": "यह कुछ देर ले सकता है", + "status.toolAvailable": "{tool} उपलब्ध", + "status.toolUnavailable": "{tool} अनुपलब्ध", + "status.unknown": "अज्ञात", + "status.available": "उपलब्ध", + "status.missing": "गुम", + "status.path": "पथ", + "status.pathAuto": "स्वतः/कॉन्फ़िगर नहीं", + "status.noAssets": "अभी तक कोई संपत्ति अनुक्रमित नहीं हुई है ({scope})", + "status.clickToScan": "स्कैन शुरू करने के लिए बिंदु पर क्लिक करें", + "status.assetsIndexed": "{count} संपत्ति अनुक्रमित ({scope})", + "status.imagesVideos": "छवियां: {images} - वीडियो: {videos}", + "status.withWorkflows": "वर्कफ़्लो के साथ: {workflows} - जनरेशन डेटा: {gendata}", + "status.dbSize": "डेटाबेस का आकार: {size}", + "status.lastScan": "अंतिम स्कैन: {date}", + "status.scanStats": "जोड़ा गया: {added} - अपडेट किया गया: {updated} - छोड़ा गया: {skipped}", + "status.watcher.enabled": "द्रष्टा: सक्षम", + "status.watcher.enabledScoped": "वॉचर: सक्षम ({scope})", + "status.watcher.disabled": "द्रष्टा: विकलांग", + "status.watcher.disabledScoped": "चौकीदार: अक्षम ({scope})", + "status.apiNotFound": "प्रमुख एपीआई समापनबिंदु नहीं मिला (404)", + "status.apiNotFoundHint": "बैकएंड रूट लोड नहीं किए गए हैं. ComfyUI को पुनरारंभ करें और प्रमुख आयात त्रुटियों के लिए टर्मिनल की जाँच करें।", + "status.errorChecking": "स्थिति जाँचने में त्रुटि", + "status.dbCorrupted": "डेटाबेस दूषित है", + "status.dbCorruptedHint": "फोर्स-डिलीट और इंडेक्स को फिर से बनाने के लिए नीचे दिए गए \"डिलीट डीबी\" बटन का उपयोग करें।", + "status.retryFailed": "पुनः प्रयास विफल रहा", + "scope.all": "इनपुट + आउटपुट", + "scope.allFull": "सभी (इनपुट + आउटपुट)", + "scope.input": "इनपुट", + "scope.output": "आउटपुट", + "scope.custom": "रिवाज़", + "tool.exiftool": "ExifTool मेटाडेटा", + "tool.exiftool.hint": "PNG/WEBP वर्कफ़्लो डेटा (ExifTool का उपयोग करता है)", + "tool.ffprobe": "एफएफप्रोब वीडियो आँकड़े", + "tool.ffprobe.hint": "वीडियो की अवधि, एफपीएस और रिज़ॉल्यूशन (एफएफप्रोब का उपयोग करता है)", + "msg.noCollections": "अभी तक कोई संग्रह नहीं.", + "msg.addCustomFolder": "ब्राउज़ करने के लिए एक कस्टम फ़ोल्डर जोड़ें.", + "msg.noResults": "कोई परिणाम नहीं मिला.", + "msg.loading": "लोड हो रहा है...", + "msg.errorLoading": "लोड होने मे त्रुटि", + "msg.errorLoadingFolders": "फ़ोल्डर लोड करने में त्रुटि", + "msg.noGenerationData": "इस फ़ाइल के लिए कोई पीढ़ी डेटा नहीं मिला.", + "msg.rawMetadata": "कच्चा मेटाडेटा", + "viewer.genInfo": "पीढ़ी की जानकारी", + "viewer.workflow": "कार्यप्रवाह", + "viewer.metadata": "मेटाडाटा", + "viewer.noWorkflow": "कोई वर्कफ़्लो डेटा नहीं", + "viewer.noMetadata": "कोई मेटाडेटा उपलब्ध नहीं है", + "viewer.copySuccess": "क्लिपबोर्ड पर कॉपी किया गया!", + "viewer.copyFailed": "प्रतिलिपि बनाने में विफल", + "sidebar.details": "विवरण", + "sidebar.preview": "पूर्व दर्शन", + "sidebar.rating": "रेटिंग", + "sidebar.tags": "टैग", + "sidebar.addTag": "टैग जोड़ें...", + "sidebar.noTags": "कोई टैग नहीं", + "sidebar.filename": "फ़ाइलनाम", + "sidebar.dimensions": "DIMENSIONS", + "sidebar.date": "तारीख", + "sidebar.size": "आकार", + "sidebar.genTime": "उत्पादन समय", + "ctx.openViewer": "व्यूअर में खोलें", + "ctx.loadWorkflow": "वर्कफ़्लो लोड करें", + "ctx.copyPath": "पथ कॉपी करें", + "ctx.openInFolder": "फ़ोल्डर में खोलें", + "ctx.rename": "नाम बदलें", + "ctx.delete": "मिटाना", + "ctx.addToCollection": "संग्रह में जोड़ें", + "ctx.removeFromCollection": "संग्रह से हटाएँ", + "ctx.newCollection": "नया संग्रह...", + "ctx.rescanMetadata": "मेटाडेटा पुनः स्कैन करें", + "ctx.createCollection": "संग्रह बनाएं...", + "ctx.exitCollection": "संग्रह दृश्य से बाहर निकलें", + "dialog.confirm": "पुष्टि करना", + "dialog.cancel": "रद्द करना", + "dialog.delete.title": "फ़ाइल नष्ट करें?", + "dialog.delete.msg": "क्या आप द्वारा इस फाइल को हटाया जाना सुनिश्चित है? इसे असंपादित नहीं किया जा सकता है।", + "dialog.rename.title": "फ़ाइल का नाम बदलें", + "dialog.rename.placeholder": "नया फ़ाइल नाम", + "dialog.newCollection.title": "नया संग्रह", + "dialog.newCollection.placeholder": "संग्रह का नाम", + "dialog.resetIndex.title": "सूचकांक रीसेट करें?", + "dialog.resetIndex.msg": "यह डेटाबेस को हटा देगा और सभी फ़ाइलों को पुनः स्कैन कर देगा। जारी रखना?", + "dialog.securityWarning": "यह एक सिस्टम या बहुत व्यापक निर्देशिका जैसा दिखता है।\n\nइसे जोड़ने से व्यूअर/कस्टम रूट्स सुविधा के माध्यम से संवेदनशील फ़ाइलें उजागर हो सकती हैं।\n\nजारी रखना?", + "dialog.securityWarningTitle": "मेजर: सुरक्षा चेतावनी", + "dialog.enterFolderPath": "कस्टम रूट के रूप में जोड़ने के लिए एक फ़ोल्डर पथ दर्ज करें:", + "dialog.customFoldersTitle": "प्रमुख: कस्टम फ़ोल्डर", + "dialog.removeFolder": "कस्टम फ़ोल्डर \"{name}\" हटाएं?", + "dialog.deleteCollection": "संग्रह \"{name}\" हटाएं?", + "dialog.createCollection": "संग्रह बनाएँ", + "dialog.collectionPlaceholder": "मेरा संग्रह", + "toast.scanStarted": "स्कैन प्रारंभ हुआ", + "toast.scanComplete": "पूरा स्कैन करें", + "toast.scanFailed": "स्कैन विफल", + "toast.resetTriggered": "रीसेट ट्रिगर: सभी फ़ाइलों को पुनः अनुक्रमित किया जा रहा है...", + "toast.resetStarted": "इंडेक्स रीसेट प्रारंभ हुआ. फ़ाइलें पृष्ठभूमि में पुनः अनुक्रमित की जाएंगी.", + "toast.resetFailed": "सूचकांक रीसेट करने में विफल", + "toast.resetFailedCorrupt": "रीसेट विफल रहा - डेटाबेस दूषित हो गया है। फोर्स-डिलीट और पुनर्निर्माण के लिए \"डिलीट डीबी\" बटन का उपयोग करें।", + "toast.dbDeleteTriggered": "डेटाबेस हटाया जा रहा है और पुनर्निर्माण किया जा रहा है...", + "toast.dbDeleteSuccess": "डेटाबेस हटाया गया और पुनः बनाया गया. फ़ाइलें पुनः अनुक्रमित की जा रही हैं.", + "toast.dbDeleteFailed": "डेटाबेस हटाने में विफल", + "dialog.dbDelete.confirm": "यह इंडेक्स डेटाबेस को स्थायी रूप से हटा देगा और इसे स्क्रैच से पुनर्निर्माण करेगा। सभी रेटिंग, टैग और कैश्ड मेटाडेटा खो जाएंगे।\n\nजारी रखना?", + "toast.deleted": "फ़ाइल हटा दी गई", + "toast.renamed": "फ़ाइल का नाम बदला गया", + "toast.addedToCollection": "संग्रह में जोड़ा गया", + "toast.removedFromCollection": "संग्रह से हटा दिया गया", + "toast.collectionCreated": "संग्रह बनाया गया", + "toast.permissionDenied": "अनुमति नहीं मिली", + "toast.tagAdded": "टैग जोड़ा गया", + "toast.tagRemoved": "टैग हटा दिया गया", + "toast.ratingSaved": "रेटिंग सहेजी गई", + "toast.failedAddFolder": "कस्टम फ़ोल्डर जोड़ने में विफल", + "toast.failedRemoveFolder": "कस्टम फ़ोल्डर निकालने में विफल", + "toast.folderLinked": "फ़ोल्डर सफलतापूर्वक लिंक हो गया", + "toast.folderRemoved": "फ़ोल्डर हटाया गया", + "toast.nativeBrowserUnavailable": "मूल फ़ोल्डर ब्राउज़र अनुपलब्ध है. कृपया पथ मैन्युअल रूप से दर्ज करें.", + "toast.errorAddingFolder": "कस्टम फ़ोल्डर जोड़ते समय एक त्रुटि उत्पन्न हुई", + "toast.errorRemovingFolder": "कस्टम फ़ोल्डर हटाते समय एक त्रुटि उत्पन्न हुई", + "toast.failedCreateCollection": "संग्रह बनाने में विफल", + "toast.failedDeleteCollection": "संग्रह हटाने में विफल", + "toast.languageChanged": "भाषा बदल गई. पूर्ण प्रभाव के लिए पृष्ठ को पुनः लोड करें.", + "toast.ratingUpdateFailed": "रेटिंग अपडेट करने में विफल", + "toast.ratingUpdateError": "रेटिंग अपडेट करने में त्रुटि", + "toast.tagsUpdateFailed": "टैग अपडेट करने में विफल", + "toast.watcherToggleFailed": "वॉचर को टॉगल करने में विफल", + "toast.noValidAssetsSelected": "कोई वैध संपत्ति चयनित नहीं.", + "toast.failedCreateCollectionDot": "संग्रह बनाने में विफल.", + "toast.failedAddAssetsToCollection": "संग्रह में परिसंपत्तियाँ जोड़ने में विफल.", + "toast.removeFromCollectionFailed": "संग्रह से निकालने में विफल.", + "toast.copyClipboardFailed": "क्लिपबोर्ड पर कॉपी करने में विफल", + "toast.metadataRefreshFailed": "मेटाडेटा ताज़ा करने में विफल.", + "toast.ratingCleared": "रेटिंग साफ़ हो गई", + "toast.ratingSetN": "रेटिंग {n} स्टार पर सेट की गई", + "toast.tagsUpdated": "टैग अपडेट किए गए", + "toast.fileRenamedSuccess": "फ़ाइल का नाम सफलतापूर्वक बदला गया!", + "toast.fileRenameFailed": "फ़ाइल का नाम बदलने में विफल.", + "toast.fileDeletedSuccess": "फ़ाइल सफलतापूर्वक हटा दी गई!", + "toast.fileDeleteFailed": "फ़ाइल हटाने में विफल.", + "toast.openedInFolder": "फ़ोल्डर में खोला गया", + "toast.openFolderFailed": "फ़ोल्डर खोलने में विफल.", + "toast.pathCopied": "फ़ाइल पथ क्लिपबोर्ड पर कॉपी किया गया", + "toast.pathCopyFailed": "पथ की प्रतिलिपि बनाने में विफल", + "toast.noFilePath": "इस संपत्ति के लिए कोई फ़ाइल पथ उपलब्ध नहीं है.", + "toast.downloadingFile": "डाउनलोड हो रहा है {filename}...", + "toast.playbackRate": "प्लेबैक {rate}x", + "toast.metadataRefreshed": "मेटाडेटा ताज़ा किया गया{suffix}", + "toast.errorRenaming": "फ़ाइल का नाम बदलने में त्रुटि: {error}", + "toast.errorDeleting": "फ़ाइल हटाने में त्रुटि: {error}", + "toast.tagMergeFailed": "टैग मर्ज विफल: {error}", + "toast.deleteFailed": "हटाना विफल: {error}", + "toast.analysisNotStarted": "विश्लेषण प्रारंभ नहीं हुआ: {error}", + "toast.dupAnalysisStarted": "डुप्लिकेट विश्लेषण प्रारंभ हुआ", + "toast.tagsMerged": "टैग विलीन हो गए", + "toast.duplicatesDeleted": "डुप्लिकेट हटा दिए गए", + "toast.playbackVideoOnly": "प्लेबैक गति केवल वीडियो मीडिया के लिए उपलब्ध है", + "toast.filesDeletedSuccessN": "{n} फ़ाइलें सफलतापूर्वक हटा दी गईं!", + "toast.filesDeletedPartial": "{success} फ़ाइलें हटा दी गईं, {failed} विफल।", + "toast.filesDeletedShort": "{n} फ़ाइलें हटा दी गईं", + "toast.filesDeletedShortPartial": "{success} हटा दिया गया, {failed} विफल रहा", + "toast.copiedToClipboardNamed": "{name} क्लिपबोर्ड पर कॉपी किया गया!", + "toast.rescanningFile": "फ़ाइल को पुनः स्कैन किया जा रहा है...", + "hotkey.scan": "स्कैन (एस)", + "hotkey.search": "खोजें (Ctrl+F)", + "hotkey.details": "विवरण टॉगल करें (डी)", + "hotkey.delete": "हटाएं (डेल)", + "hotkey.viewer": "व्यूअर खोलें (प्रवेश करें)", + "hotkey.escape": "बंद करें (Esc)" + }, + "pt-PT": { + "cat.grid": "Grade", + "cat.cards": "Cartões", + "cat.badges": "Emblemas", + "cat.viewer": "Visualizador", + "cat.scanning": "Digitalizando", + "cat.advanced": "Avançado", + "cat.security": "Segurança", + "cat.remote": "Acesso remoto", + "setting.grid.minsize.name": "Maior: tamanho da miniatura (px)", + "setting.grid.minsize.desc": "Tamanho mínimo das miniaturas na grade. Pode exigir a reabertura do painel.", + "setting.grid.cardSize.group": "Tamanho do cartão", + "setting.grid.cardSize.name": "Principal: Tamanho do cartão", + "setting.grid.cardSize.desc": "Escolha uma predefinição de tamanho de cartão: pequeno, médio ou grande.", + "setting.grid.cardSize.small": "Pequeno", + "setting.grid.cardSize.medium": "Médio", + "setting.grid.cardSize.large": "Grande", + "setting.grid.gap.name": "Maior: Lacuna (px)", + "setting.grid.gap.desc": "Espaço entre miniaturas.", + "setting.sidebar.pos.name": "Principal: Posição da barra lateral", + "setting.sidebar.pos.desc": "Mostrar barra lateral de detalhes à esquerda ou à direita. É necessário recarregar.", + "setting.siblings.hide.name": "Principal: Ocultar irmãos PNG", + "setting.siblings.hide.desc": "Se um vídeo tiver uma visualização .png correspondente, oculte o .png da grade.", + "setting.nav.infinite.name": "Principal: Pergaminho Infinito", + "setting.nav.infinite.desc": "Carregue automaticamente mais arquivos ao rolar.", + "setting.grid.pagesize.name": "Principal: Tamanho da página da grade", + "setting.grid.pagesize.desc": "Número de ativos carregados por página/solicitação na grade.", + "setting.viewer.pan.name": "Principal: panorâmica sem zoom", + "setting.viewer.pan.desc": "Permitir deslocar a imagem mesmo no nível de zoom 1.", + "setting.minimap.enabled.name": "Principal: Habilitar Minimapa", + "setting.minimap.enabled.desc": "Ativação global do minimapa do fluxo de trabalho.", + "setting.scan.startup.name": "Principal: Verificação automática na inicialização", + "setting.scan.startup.desc": "Inicie uma verificação em segundo plano assim que o ComfyUI carregar.", + "setting.watcher.name": "Principal: Observador de Arquivos", + "setting.watcher.desc": "Observe a saída e as pastas personalizadas para arquivos adicionados manualmente e indexe-os automaticamente em tempo real.", + "setting.watcher.debounce.name": "Principal: Atraso de debounce do observador", + "setting.watcher.debounce.desc": "Atraso (ms) para eventos do observador em lote antes da indexação.", + "setting.watcher.debounce.error": "Falha ao atualizar o atraso do debounce do observador.", + "setting.watcher.dedupe.name": "Principal: janela de desduplicação do observador", + "setting.watcher.dedupe.desc": "Duração (ms) em que um arquivo é tratado como já processado após um evento.", + "setting.watcher.dedupe.error": "Falha ao atualizar a janela de desduplicação do inspetor.", + "setting.sync.rating.name": "Principal: Sincronizar classificação/tags com arquivos", + "setting.sync.rating.desc": "Escreva classificações e tags em metadados de arquivo (ExifTool).", + "cat.badgeColors": "Cores do emblema", + "setting.starColor": "Cor da estrela", + "setting.starColor.tooltip": "Cor das estrelas de avaliação nas miniaturas (hexadecimal, por exemplo, #FFD45A)", + "setting.badgeImageColor": "Cor do emblema da imagem", + "setting.badgeImageColor.tooltip": "Cor para emblemas de imagem: PNG, JPG, WEBP, GIF, BMP, TIF (hex)", + "setting.badgeVideoColor": "Cor do selo de vídeo", + "setting.badgeVideoColor.tooltip": "Cor para emblemas de vídeo: MP4, WEBM, MOV, AVI, MKV (hex)", + "setting.badgeAudioColor": "Cor do selo de áudio", + "setting.badgeAudioColor.tooltip": "Cor para emblemas de áudio: MP3, WAV, OGG, FLAC (hex)", + "setting.badgeModel3dColor": "Cor do emblema do modelo 3D", + "setting.badgeModel3dColor.tooltip": "Cor para emblemas de modelo 3D: OBJ, FBX, GLB, GLTF (hex)", + "setting.obs.enabled.name": "Principal: ativar registros detalhados", + "setting.obs.enabled.desc": "Habilite logs de back-end detalhados para depuração.", + "setting.probe.mode.name": "Principal: back-end de metadados", + "setting.probe.mode.desc": "Escolha a ferramenta usada diretamente para extrair metadados.", + "setting.language.name": "Principal: Idioma", + "setting.language.desc": "Escolha o idioma da interface do Assets Manager. É necessário recarregar para aplicar totalmente.", + "setting.sec.safe.name": "Principal: Modo de segurança", + "setting.sec.safe.desc": "Quando ativado, as gravações de classificações/tags são bloqueadas, a menos que sejam explicitamente autorizadas.", + "setting.sec.remote.name": "Principal: Permitir acesso total remoto", + "setting.sec.remote.desc": "Permitir que clientes não locais executem operações de gravação. A desativação bloqueia gravações, a menos que um token seja configurado.", + "setting.sec.write.name": "Principal: Permitir Gravação", + "setting.sec.write.desc": "Permitir escrever classificações e tags.", + "setting.sec.del.name": "Principal: Permitir Exclusão", + "setting.sec.del.desc": "Permitir exclusão de arquivos.", + "setting.sec.ren.name": "Principal: Permitir Renomear", + "setting.sec.ren.desc": "Permitir renomear arquivos.", + "setting.sec.open.name": "Principal: Permitir abertura na pasta", + "setting.sec.open.desc": "Permitir a abertura do local do arquivo no gerenciador de arquivos do sistema operacional.", + "setting.sec.reset.name": "Principal: Permitir redefinição de índice", + "setting.sec.reset.desc": "Permitir redefinir o cache do índice e acionar uma nova verificação completa.", + "setting.sec.token.name": "Principal: Token API", + "setting.sec.token.desc": "Armazene o token de autorização de gravação. Major insere-o nos cabeçalhos Authorization e X-MJR-Token.", + "setting.sec.token.placeholder": "Deixe em branco para desabilitar.", + "tab.output": "Saída", + "tab.input": "Entrada", + "tab.custom": "Personalizado", + "btn.add": "Adicionar…", + "btn.remove": "Remover", + "btn.adding": "Adicionando...", + "btn.removing": "Removendo...", + "btn.retry": "Tentar novamente", + "btn.clear": "Claro", + "btn.refresh": "Atualizar", + "btn.scan": "Digitalizar", + "btn.scanning": "Digitalizando...", + "btn.resetIndex": "Redefinir índice", + "btn.resetting": "Redefinindo...", + "btn.deleteDb": "Excluir banco de dados", + "btn.deletingDb": "Excluindo banco de dados...", + "btn.retryServices": "Tentar novamente os serviços", + "btn.retrying": "Tentando novamente...", + "btn.loadWorkflow": "Carregar fluxo de trabalho", + "btn.copyPrompt": "Copiar prompt", + "btn.close": "Fechar", + "label.folder": "Pasta", + "label.type": "Tipo", + "label.workflow": "Fluxo de trabalho", + "label.rating": "Avaliação", + "label.dateRange": "Período", + "label.agenda": "Agenda", + "label.sort": "Organizar", + "label.collections": "Coleções", + "label.filters": "Filtros", + "label.selectFolder": "Selecione a pasta…", + "label.thisFolder": "esta pasta", + "filter.all": "Todos", + "filter.images": "Imagens", + "filter.videos": "Vídeos", + "filter.onlyWithWorkflow": "Somente com fluxo de trabalho", + "filter.anyRating": "Qualquer classificação", + "filter.minStars": "{n}+ estrelas", + "filter.anytime": "A qualquer momento", + "filter.today": "Hoje", + "filter.yesterday": "Ontem", + "filter.thisWeek": "Essa semana", + "filter.thisMonth": "Este mês", + "filter.last7days": "Últimos 7 dias", + "filter.last30days": "Últimos 30 dias", + "sort.newest": "O mais novo primeiro", + "sort.oldest": "Mais antigo primeiro", + "sort.nameAZ": "Nome A-Z", + "sort.nameZA": "Nome Z-A", + "sort.ratingHigh": "Classificação (alta)", + "sort.ratingLow": "Classificação (baixa)", + "sort.sizeDesc": "Tamanho (grande)", + "sort.sizeAsc": "Tamanho (pequeno)", + "status.checking": "Verificando...", + "status.ready": "Preparar", + "status.scanning": "Digitalizando...", + "status.error": "Erro", + "status.capabilities": "Capacidades", + "status.toolStatus": "Status da ferramenta", + "status.selectCustomFolder": "Selecione uma pasta personalizada primeiro", + "status.errorGetConfig": "Erro: Falha ao obter configuração", + "status.discoveringTools": "Capacidades: descobrir ferramentas...", + "status.indexStatus": "Status do índice", + "status.toolStatusChecking": "Status da ferramenta: verificando...", + "status.resetIndexHint": "Redefinir cache de índice (requer permitirResetIndex nas configurações).", + "status.scanningHint": "Isso pode demorar um pouco", + "status.toolAvailable": "{tool} disponível", + "status.toolUnavailable": "{tool} indisponível", + "status.unknown": "desconhecido", + "status.available": "disponível", + "status.missing": "ausente", + "status.path": "Caminho", + "status.pathAuto": "automático / não configurado", + "status.noAssets": "Nenhum ativo indexado ainda ({scope})", + "status.clickToScan": "Clique no ponto para iniciar uma varredura", + "status.assetsIndexed": "{count} ativos indexados ({scope})", + "status.imagesVideos": "Imagens: {images} - Vídeos: {videos}", + "status.withWorkflows": "Com fluxos de trabalho: {workflows} - Dados de geração: {gendata}", + "status.dbSize": "Tamanho do banco de dados: {size}", + "status.lastScan": "Última verificação: {date}", + "status.scanStats": "Adicionado: {added} - Atualizado: {updated} - Ignorado: {skipped}", + "status.watcher.enabled": "Observador: habilitado", + "status.watcher.enabledScoped": "Observador: ativado ({scope})", + "status.watcher.disabled": "Observador: desativado", + "status.watcher.disabledScoped": "Observador: desativado ({scope})", + "status.apiNotFound": "Principais endpoints da API não encontrados (404)", + "status.apiNotFoundHint": "As rotas de back-end não são carregadas. Reinicie o ComfyUI e verifique se há erros graves de importação no terminal.", + "status.errorChecking": "Erro ao verificar o status", + "status.dbCorrupted": "O banco de dados está corrompido", + "status.dbCorruptedHint": "Use o botão \"Excluir banco de dados\" abaixo para forçar a exclusão e reconstruir o índice.", + "status.retryFailed": "Falha na nova tentativa", + "scope.all": "Entradas + Saídas", + "scope.allFull": "Todos (Entradas + Saídas)", + "scope.input": "Entradas", + "scope.output": "Resultados", + "scope.custom": "Personalizado", + "tool.exiftool": "Metadados ExifTool", + "tool.exiftool.hint": "Dados de fluxo de trabalho PNG/WEBP (usa ExifTool)", + "tool.ffprobe": "Estatísticas de vídeo do FFprobe", + "tool.ffprobe.hint": "Duração, FPS e resolução do vídeo (usa FFprobe)", + "msg.noCollections": "Ainda não há coleções.", + "msg.addCustomFolder": "Adicione uma pasta personalizada para navegar.", + "msg.noResults": "Nenhum resultado encontrado.", + "msg.loading": "Carregando...", + "msg.errorLoading": "Erro ao carregar", + "msg.errorLoadingFolders": "Erro ao carregar pastas", + "msg.noGenerationData": "Nenhum dado de geração encontrado para este arquivo.", + "msg.rawMetadata": "Metadados brutos", + "viewer.genInfo": "Informações de geração", + "viewer.workflow": "Fluxo de trabalho", + "viewer.metadata": "Metadados", + "viewer.noWorkflow": "Nenhum dado de fluxo de trabalho", + "viewer.noMetadata": "Nenhum metadado disponível", + "viewer.copySuccess": "Copiado para a área de transferência!", + "viewer.copyFailed": "Falha ao copiar", + "sidebar.details": "Detalhes", + "sidebar.preview": "Visualização", + "sidebar.rating": "Avaliação", + "sidebar.tags": "Etiquetas", + "sidebar.addTag": "Adicionar etiqueta...", + "sidebar.noTags": "Sem tags", + "sidebar.filename": "Nome do arquivo", + "sidebar.dimensions": "Dimensões", + "sidebar.date": "Data", + "sidebar.size": "Tamanho", + "sidebar.genTime": "Tempo de geração", + "ctx.openViewer": "Abrir no visualizador", + "ctx.loadWorkflow": "Carregar fluxo de trabalho", + "ctx.copyPath": "Copiar caminho", + "ctx.openInFolder": "Abrir na pasta", + "ctx.rename": "Renomear", + "ctx.delete": "Excluir", + "ctx.addToCollection": "Adicionar à coleção", + "ctx.removeFromCollection": "Remover da coleção", + "ctx.newCollection": "Nova coleção...", + "ctx.rescanMetadata": "Verificar novamente os metadados", + "ctx.createCollection": "Criar coleção...", + "ctx.exitCollection": "Sair da visualização da coleção", + "dialog.confirm": "Confirmar", + "dialog.cancel": "Cancelar", + "dialog.delete.title": "Excluir arquivo?", + "dialog.delete.msg": "Tem certeza de que deseja excluir este arquivo? Isto não pode ser desfeito.", + "dialog.rename.title": "Renomear arquivo", + "dialog.rename.placeholder": "Novo nome de arquivo", + "dialog.newCollection.title": "Nova coleção", + "dialog.newCollection.placeholder": "Nome da coleção", + "dialog.resetIndex.title": "Redefinir índice?", + "dialog.resetIndex.msg": "Isso excluirá o banco de dados e verificará novamente todos os arquivos. Continuar?", + "dialog.securityWarning": "Parece um sistema ou diretório muito amplo.\n\nAdicioná-lo pode expor arquivos confidenciais por meio do recurso visualizador/raízes personalizadas.\n\nContinuar?", + "dialog.securityWarningTitle": "Principal: Aviso de Segurança", + "dialog.enterFolderPath": "Insira um caminho de pasta para adicionar como raiz personalizada:", + "dialog.customFoldersTitle": "Principal: Pastas Personalizadas", + "dialog.removeFolder": "Remover a pasta personalizada \"{name}\"?", + "dialog.deleteCollection": "Excluir coleção \"{name}\"?", + "dialog.createCollection": "Criar coleção", + "dialog.collectionPlaceholder": "Minha coleção", + "toast.scanStarted": "Verificação iniciada", + "toast.scanComplete": "Verificação concluída", + "toast.scanFailed": "Falha na verificação", + "toast.resetTriggered": "Redefinição acionada: Reindexando todos os arquivos...", + "toast.resetStarted": "A redefinição do índice foi iniciada. Os arquivos serão reindexados em segundo plano.", + "toast.resetFailed": "Falha ao redefinir o índice", + "toast.resetFailedCorrupt": "A reinicialização falhou – o banco de dados está corrompido. Use o botão \"Excluir banco de dados\" para forçar a exclusão e a reconstrução.", + "toast.dbDeleteTriggered": "Excluindo banco de dados e reconstruindo...", + "toast.dbDeleteSuccess": "Banco de dados excluído e reconstruído. Os arquivos estão sendo reindexados.", + "toast.dbDeleteFailed": "Falha ao excluir banco de dados", + "dialog.dbDelete.confirm": "Isso excluirá permanentemente o banco de dados de índice e o reconstruirá do zero. Todas as classificações, tags e metadados armazenados em cache serão perdidos.\n\nContinuar?", + "toast.deleted": "Arquivo excluído", + "toast.renamed": "Arquivo renomeado", + "toast.addedToCollection": "Adicionado à coleção", + "toast.removedFromCollection": "Removido da coleção", + "toast.collectionCreated": "Coleção criada", + "toast.permissionDenied": "Permissão negada", + "toast.tagAdded": "Etiqueta adicionada", + "toast.tagRemoved": "Etiqueta removida", + "toast.ratingSaved": "Avaliação salva", + "toast.failedAddFolder": "Falha ao adicionar pasta personalizada", + "toast.failedRemoveFolder": "Falha ao remover a pasta personalizada", + "toast.folderLinked": "Pasta vinculada com sucesso", + "toast.folderRemoved": "Pasta removida", + "toast.nativeBrowserUnavailable": "Navegador de pasta nativo indisponível. Insira o caminho manualmente.", + "toast.errorAddingFolder": "Ocorreu um erro ao adicionar a pasta personalizada", + "toast.errorRemovingFolder": "Ocorreu um erro ao remover a pasta personalizada", + "toast.failedCreateCollection": "Falha ao criar coleção", + "toast.failedDeleteCollection": "Falha ao excluir coleção", + "toast.languageChanged": "A linguagem mudou. Recarregue a página para obter efeito total.", + "toast.ratingUpdateFailed": "Falha ao atualizar a classificação", + "toast.ratingUpdateError": "Erro ao atualizar a classificação", + "toast.tagsUpdateFailed": "Falha ao atualizar tags", + "toast.watcherToggleFailed": "Falha ao alternar o observador", + "toast.noValidAssetsSelected": "Nenhum ativo válido selecionado.", + "toast.failedCreateCollectionDot": "Falha ao criar coleção.", + "toast.failedAddAssetsToCollection": "Falha ao adicionar recursos à coleção.", + "toast.removeFromCollectionFailed": "Falha ao remover da coleção.", + "toast.copyClipboardFailed": "Falha ao copiar para a área de transferência", + "toast.metadataRefreshFailed": "Falha ao atualizar metadados.", + "toast.ratingCleared": "Classificação apagada", + "toast.ratingSetN": "Classificação definida para {n} estrelas", + "toast.tagsUpdated": "Etiquetas atualizadas", + "toast.fileRenamedSuccess": "Arquivo renomeado com sucesso!", + "toast.fileRenameFailed": "Falha ao renomear arquivo.", + "toast.fileDeletedSuccess": "Arquivo excluído com sucesso!", + "toast.fileDeleteFailed": "Falha ao excluir o arquivo.", + "toast.openedInFolder": "Aberto na pasta", + "toast.openFolderFailed": "Falha ao abrir a pasta.", + "toast.pathCopied": "Caminho do arquivo copiado para a área de transferência", + "toast.pathCopyFailed": "Falha ao copiar o caminho", + "toast.noFilePath": "Nenhum caminho de arquivo disponível para este recurso.", + "toast.downloadingFile": "Baixando {filename}...", + "toast.playbackRate": "Reprodução {rate}x", + "toast.metadataRefreshed": "Metadados atualizados{suffix}", + "toast.errorRenaming": "Erro ao renomear arquivo: {error}", + "toast.errorDeleting": "Erro ao excluir arquivo: {error}", + "toast.tagMergeFailed": "Falha na mesclagem de tags: {error}", + "toast.deleteFailed": "Falha na exclusão: {error}", + "toast.analysisNotStarted": "Análise não iniciada: {error}", + "toast.dupAnalysisStarted": "Análise duplicada iniciada", + "toast.tagsMerged": "Tags mescladas", + "toast.duplicatesDeleted": "Duplicatas excluídas", + "toast.playbackVideoOnly": "A velocidade de reprodução está disponível apenas para mídia de vídeo", + "toast.filesDeletedSuccessN": "{n} arquivos excluídos com sucesso!", + "toast.filesDeletedPartial": "{success} arquivos excluídos, {failed} falhou.", + "toast.filesDeletedShort": "{n} arquivos excluídos", + "toast.filesDeletedShortPartial": "{success} excluído, {failed} falhou", + "toast.copiedToClipboardNamed": "{name} copiado para a área de transferência!", + "toast.rescanningFile": "Verificando novamente o arquivo...", + "hotkey.scan": "Digitalizar (S)", + "hotkey.search": "Pesquisar (Ctrl+F)", + "hotkey.details": "Alternar detalhes (D)", + "hotkey.delete": "Excluir (Del)", + "hotkey.viewer": "Abrir visualizador (Enter)", + "hotkey.escape": "Fechar (Esc)" + }, + "es-ES": { + "cat.grid": "Red", + "cat.cards": "Tarjetas", + "cat.badges": "Insignias", + "cat.viewer": "Espectador", + "cat.scanning": "Exploración", + "cat.advanced": "Avanzado", + "cat.security": "Seguridad", + "cat.remote": "Acceso remoto", + "setting.grid.minsize.name": "Mayor: Tamaño de miniatura (px)", + "setting.grid.minsize.desc": "Tamaño mínimo de miniaturas en la cuadrícula. Puede requerir reabrir el panel.", + "setting.grid.cardSize.group": "Tamaño de tarjeta", + "setting.grid.cardSize.name": "Mayor: Tamaño de tarjeta", + "setting.grid.cardSize.desc": "Elija un tamaño de tarjeta preestablecido: pequeño, mediano o grande.", + "setting.grid.cardSize.small": "Pequeño", + "setting.grid.cardSize.medium": "Medio", + "setting.grid.cardSize.large": "Grande", + "setting.grid.gap.name": "Mayor: Brecha (px)", + "setting.grid.gap.desc": "Espacio entre miniaturas.", + "setting.sidebar.pos.name": "Mayor: posición de la barra lateral", + "setting.sidebar.pos.desc": "Mostrar barra lateral de detalles a la izquierda o a la derecha. Se requiere recarga.", + "setting.siblings.hide.name": "Mayor: Ocultar hermanos PNG", + "setting.siblings.hide.desc": "Si un vídeo tiene una vista previa .png correspondiente, oculte el .png de la cuadrícula.", + "setting.nav.infinite.name": "Mayor: Pergamino Infinito", + "setting.nav.infinite.desc": "Cargue automáticamente más archivos al desplazarse.", + "setting.grid.pagesize.name": "Mayor: Tamaño de página de cuadrícula", + "setting.grid.pagesize.desc": "Número de recursos cargados por página/solicitud en la cuadrícula.", + "setting.viewer.pan.name": "Mayor: panorámica sin zoom", + "setting.viewer.pan.desc": "Permita desplazar la imagen incluso en el nivel de zoom 1.", + "setting.minimap.enabled.name": "Mayor: habilitar minimapa", + "setting.minimap.enabled.desc": "Activación global del minimapa del flujo de trabajo.", + "setting.scan.startup.name": "Majoor: Escaneo automático al iniciar", + "setting.scan.startup.desc": "Inicie un análisis en segundo plano tan pronto como se cargue ComfyUI.", + "setting.watcher.name": "Mayor: Vigilante de archivos", + "setting.watcher.desc": "Observe las carpetas de salida y personalizadas para buscar archivos agregados manualmente e indexarlos automáticamente en tiempo real.", + "setting.watcher.debounce.name": "Majoor: Retraso en el rebote del observador", + "setting.watcher.debounce.desc": "Retraso (ms) para procesar por lotes los eventos del observador antes de la indexación.", + "setting.watcher.debounce.error": "No se pudo actualizar el retraso de rebote del observador.", + "setting.watcher.dedupe.name": "Majoor: ventana de deduplicación de Watcher", + "setting.watcher.dedupe.desc": "Duración (ms) que se considera que un archivo ya está procesado después de un evento.", + "setting.watcher.dedupe.error": "No se pudo actualizar la ventana de deduplicación del observador.", + "setting.sync.rating.name": "Majoor: Sincronizar clasificación/etiquetas con archivos", + "setting.sync.rating.desc": "Escriba calificaciones y etiquetas en metadatos de archivos (ExifTool).", + "cat.badgeColors": "Colores de la insignia", + "setting.starColor": "color estrella", + "setting.starColor.tooltip": "Color de las estrellas de calificación en las miniaturas (hexadecimal, por ejemplo, #FFD45A)", + "setting.badgeImageColor": "Color de la insignia de la imagen", + "setting.badgeImageColor.tooltip": "Color de las insignias de imagen: PNG, JPG, WEBP, GIF, BMP, TIF (hexadecimal)", + "setting.badgeVideoColor": "Color de la insignia de vídeo", + "setting.badgeVideoColor.tooltip": "Color para insignias de video: MP4, WEBM, MOV, AVI, MKV (hexadecimal)", + "setting.badgeAudioColor": "Color de la insignia de audio", + "setting.badgeAudioColor.tooltip": "Color de insignias de audio: MP3, WAV, OGG, FLAC (hexadecimal)", + "setting.badgeModel3dColor": "Color de la insignia del modelo 3D", + "setting.badgeModel3dColor.tooltip": "Color para insignias de modelos 3D: OBJ, FBX, GLB, GLTF (hexadecimal)", + "setting.obs.enabled.name": "Mayor: habilitar registros detallados", + "setting.obs.enabled.desc": "Habilite registros de backend detallados para la depuración.", + "setting.probe.mode.name": "Mayor: backend de metadatos", + "setting.probe.mode.desc": "Elija la herramienta utilizada directamente para extraer metadatos.", + "setting.language.name": "Mayor: Idioma", + "setting.language.desc": "Elija el idioma para la interfaz de Assets Manager. Se requiere recarga para aplicar completamente.", + "setting.sec.safe.name": "Mayor: Modo seguro", + "setting.sec.safe.desc": "Cuando está habilitado, las escrituras de calificaciones/etiquetas se bloquean a menos que se autorice explícitamente.", + "setting.sec.remote.name": "Mayor: Permitir acceso completo remoto", + "setting.sec.remote.desc": "Permitir que los clientes no locales realicen operaciones de escritura. Deshabilitar bloques de escritura a menos que se configure un token.", + "setting.sec.write.name": "Mayor: Permitir escritura", + "setting.sec.write.desc": "Permitir escribir calificaciones y etiquetas.", + "setting.sec.del.name": "Mayor: Permitir Eliminar", + "setting.sec.del.desc": "Permitir eliminar archivos.", + "setting.sec.ren.name": "Mayor: Permitir cambiar nombre", + "setting.sec.ren.desc": "Permitir cambiar el nombre de archivos.", + "setting.sec.open.name": "Mayor: Permitir abrir en carpeta", + "setting.sec.open.desc": "Permitir abrir la ubicación del archivo en el administrador de archivos del sistema operativo.", + "setting.sec.reset.name": "Mayor: Permitir reinicio del índice", + "setting.sec.reset.desc": "Permitir restablecer el caché de índice y activar una nueva exploración completa.", + "setting.sec.token.name": "Mayor: token API", + "setting.sec.token.desc": "Almacene el token de autorización de escritura. Majoor lo inserta en los encabezados Autorización y X-MJR-Token.", + "setting.sec.token.placeholder": "Déjelo en blanco para desactivarlo.", + "tab.output": "Producción", + "tab.input": "Aporte", + "tab.custom": "Costumbre", + "btn.add": "Agregar...", + "btn.remove": "Eliminar", + "btn.adding": "Añadiendo...", + "btn.removing": "Eliminando...", + "btn.retry": "Rever", + "btn.clear": "Claro", + "btn.refresh": "Refrescar", + "btn.scan": "Escanear", + "btn.scanning": "Exploración...", + "btn.resetIndex": "Restablecer índice", + "btn.resetting": "Restableciendo...", + "btn.deleteDb": "Eliminar base de datos", + "btn.deletingDb": "Eliminando base de datos...", + "btn.retryServices": "Reintentar servicios", + "btn.retrying": "Reintentando...", + "btn.loadWorkflow": "Cargar flujo de trabajo", + "btn.copyPrompt": "Copiar mensaje", + "btn.close": "Cerca", + "label.folder": "Carpeta", + "label.type": "Tipo", + "label.workflow": "Flujo de trabajo", + "label.rating": "Clasificación", + "label.dateRange": "Rango de fechas", + "label.agenda": "Orden del día", + "label.sort": "Clasificar", + "label.collections": "Colecciones", + "label.filters": "Filtros", + "label.selectFolder": "Seleccionar carpeta...", + "label.thisFolder": "esta carpeta", + "filter.all": "Todo", + "filter.images": "Imágenes", + "filter.videos": "Vídeos", + "filter.onlyWithWorkflow": "Sólo con flujo de trabajo", + "filter.anyRating": "Cualquier calificación", + "filter.minStars": "{n}+ estrellas", + "filter.anytime": "En cualquier momento", + "filter.today": "Hoy", + "filter.yesterday": "Ayer", + "filter.thisWeek": "Esta semana", + "filter.thisMonth": "este mes", + "filter.last7days": "últimos 7 días", + "filter.last30days": "últimos 30 días", + "sort.newest": "Lo nuevo primero", + "sort.oldest": "El más viejo primero", + "sort.nameAZ": "Nombre A-Z", + "sort.nameZA": "Nombre Z-A", + "sort.ratingHigh": "Calificación (alta)", + "sort.ratingLow": "Calificación (baja)", + "sort.sizeDesc": "Tamaño (grande)", + "sort.sizeAsc": "Tamaño (pequeño)", + "status.checking": "De cheques...", + "status.ready": "Listo", + "status.scanning": "Exploración...", + "status.error": "Error", + "status.capabilities": "Capacidades", + "status.toolStatus": "Estado de la herramienta", + "status.selectCustomFolder": "Seleccione una carpeta personalizada primero", + "status.errorGetConfig": "Error: no se pudo obtener la configuración", + "status.discoveringTools": "Capacidades: descubrir herramientas...", + "status.indexStatus": "Estado del índice", + "status.toolStatusChecking": "Estado de la herramienta: comprobando...", + "status.resetIndexHint": "Restablecer caché de índice (requiere enableResetIndex en la configuración).", + "status.scanningHint": "Esto puede tomar un tiempo", + "status.toolAvailable": "{tool} disponible", + "status.toolUnavailable": "{tool} no disponible", + "status.unknown": "desconocido", + "status.available": "disponible", + "status.missing": "desaparecido", + "status.path": "Camino", + "status.pathAuto": "automático/no configurado", + "status.noAssets": "Aún no hay activos indexados ({scope})", + "status.clickToScan": "Haga clic en el punto para iniciar un escaneo", + "status.assetsIndexed": "{count} activos indexados ({scope})", + "status.imagesVideos": "Imágenes: {images} - Vídeos: {videos}", + "status.withWorkflows": "Con flujos de trabajo: {workflows} - Datos de generación: {gendata}", + "status.dbSize": "Tamaño de la base de datos: {size}", + "status.lastScan": "Último escaneo: {date}", + "status.scanStats": "Agregado: {added} - Actualizado: {updated} - Omitido: {skipped}", + "status.watcher.enabled": "Vigilante: habilitado", + "status.watcher.enabledScoped": "Vigilante: habilitado ({scope})", + "status.watcher.disabled": "Vigilante: deshabilitado", + "status.watcher.disabledScoped": "Vigilante: deshabilitado ({scope})", + "status.apiNotFound": "No se encontraron puntos finales de API principales (404)", + "status.apiNotFoundHint": "Las rutas de backend no están cargadas. Reinicie ComfyUI y verifique si hay errores de importación importantes en el terminal.", + "status.errorChecking": "Error al comprobar el estado", + "status.dbCorrupted": "La base de datos está corrupta", + "status.dbCorruptedHint": "Utilice el botón \"Eliminar base de datos\" a continuación para forzar la eliminación y reconstruir el índice.", + "status.retryFailed": "Reintentar falló", + "scope.all": "Entradas + Salidas", + "scope.allFull": "Todo (Entradas + Salidas)", + "scope.input": "Entradas", + "scope.output": "Salidas", + "scope.custom": "Costumbre", + "tool.exiftool": "Metadatos de ExifTool", + "tool.exiftool.hint": "Datos de flujo de trabajo PNG/WEBP (usa ExifTool)", + "tool.ffprobe": "Estadísticas de vídeo de FFprobe", + "tool.ffprobe.hint": "Duración del video, FPS y resolución (usa FFprobe)", + "msg.noCollections": "Aún no hay colecciones.", + "msg.addCustomFolder": "Agregue una carpeta personalizada para explorar.", + "msg.noResults": "No se encontraron resultados.", + "msg.loading": "Cargando...", + "msg.errorLoading": "Error al cargar", + "msg.errorLoadingFolders": "Error al cargar carpetas", + "msg.noGenerationData": "No se encontraron datos de generación para este archivo.", + "msg.rawMetadata": "Metadatos sin procesar", + "viewer.genInfo": "Información de generación", + "viewer.workflow": "Flujo de trabajo", + "viewer.metadata": "Metadatos", + "viewer.noWorkflow": "Sin datos de flujo de trabajo", + "viewer.noMetadata": "No hay metadatos disponibles", + "viewer.copySuccess": "¡Copiado al portapapeles!", + "viewer.copyFailed": "No se pudo copiar", + "sidebar.details": "Detalles", + "sidebar.preview": "Avance", + "sidebar.rating": "Clasificación", + "sidebar.tags": "Etiquetas", + "sidebar.addTag": "Añadir etiqueta...", + "sidebar.noTags": "Sin etiquetas", + "sidebar.filename": "Nombre del archivo", + "sidebar.dimensions": "Dimensiones", + "sidebar.date": "Fecha", + "sidebar.size": "Tamaño", + "sidebar.genTime": "tiempo de generacion", + "ctx.openViewer": "Abrir en el visor", + "ctx.loadWorkflow": "Cargar flujo de trabajo", + "ctx.copyPath": "Copiar ruta", + "ctx.openInFolder": "Abrir en carpeta", + "ctx.rename": "Rebautizar", + "ctx.delete": "Borrar", + "ctx.addToCollection": "Añadir a la colección", + "ctx.removeFromCollection": "Quitar de la colección", + "ctx.newCollection": "Nueva colección...", + "ctx.rescanMetadata": "Volver a escanear metadatos", + "ctx.createCollection": "Crear colección...", + "ctx.exitCollection": "Salir de la vista de colección", + "dialog.confirm": "Confirmar", + "dialog.cancel": "Cancelar", + "dialog.delete.title": "¿Eliminar archivo?", + "dialog.delete.msg": "¿Está seguro de que desea eliminar este archivo? Esto no se puede deshacer.", + "dialog.rename.title": "Cambiar nombre de archivo", + "dialog.rename.placeholder": "Nuevo nombre de archivo", + "dialog.newCollection.title": "Nueva colección", + "dialog.newCollection.placeholder": "Nombre de la colección", + "dialog.resetIndex.title": "¿Restablecer índice?", + "dialog.resetIndex.msg": "Esto eliminará la base de datos y volverá a escanear todos los archivos. ¿Continuar?", + "dialog.securityWarning": "Esto parece un sistema o un directorio muy amplio.\n\nAgregarlo puede exponer archivos confidenciales a través de la función de visor/raíces personalizadas.\n\n¿Continuar?", + "dialog.securityWarningTitle": "Mayor: Advertencia de seguridad", + "dialog.enterFolderPath": "Ingrese una ruta de carpeta para agregar como raíz personalizada:", + "dialog.customFoldersTitle": "Mayor: Carpetas personalizadas", + "dialog.removeFolder": "¿Eliminar la carpeta personalizada \"{name}\"?", + "dialog.deleteCollection": "¿Eliminar la colección \"{name}\"?", + "dialog.createCollection": "Crear colección", + "dialog.collectionPlaceholder": "mi coleccion", + "toast.scanStarted": "Escaneo iniciado", + "toast.scanComplete": "Escaneo completo", + "toast.scanFailed": "Error de escaneo", + "toast.resetTriggered": "Reinicio activado: reindexando todos los archivos...", + "toast.resetStarted": "Se inició el reinicio del índice. Los archivos se reindexarán en segundo plano.", + "toast.resetFailed": "No se pudo restablecer el índice", + "toast.resetFailedCorrupt": "Error al restablecer: la base de datos está dañada. Utilice el botón \"Eliminar base de datos\" para forzar la eliminación y reconstrucción.", + "toast.dbDeleteTriggered": "Eliminando base de datos y reconstruyendo...", + "toast.dbDeleteSuccess": "Base de datos eliminada y reconstruida. Los archivos se están reindexando.", + "toast.dbDeleteFailed": "No se pudo eliminar la base de datos", + "dialog.dbDelete.confirm": "Esto eliminará permanentemente la base de datos del índice y la reconstruirá desde cero. Se perderán todas las calificaciones, etiquetas y metadatos almacenados en caché.\n\n¿Continuar?", + "toast.deleted": "Archivo eliminado", + "toast.renamed": "Archivo renombrado", + "toast.addedToCollection": "Añadido a la colección", + "toast.removedFromCollection": "Eliminado de la colección", + "toast.collectionCreated": "Colección creada", + "toast.permissionDenied": "Permiso denegado", + "toast.tagAdded": "Etiqueta agregada", + "toast.tagRemoved": "Etiqueta eliminada", + "toast.ratingSaved": "Calificación guardada", + "toast.failedAddFolder": "No se pudo agregar la carpeta personalizada", + "toast.failedRemoveFolder": "No se pudo eliminar la carpeta personalizada", + "toast.folderLinked": "Carpeta vinculada correctamente", + "toast.folderRemoved": "Carpeta eliminada", + "toast.nativeBrowserUnavailable": "Explorador de carpetas nativo no disponible. Por favor ingrese la ruta manualmente.", + "toast.errorAddingFolder": "Se produjo un error al agregar la carpeta personalizada", + "toast.errorRemovingFolder": "Se produjo un error al eliminar la carpeta personalizada", + "toast.failedCreateCollection": "No se pudo crear la colección", + "toast.failedDeleteCollection": "No se pudo eliminar la colección", + "toast.languageChanged": "El idioma cambió. Vuelva a cargar la página para obtener el máximo efecto.", + "toast.ratingUpdateFailed": "No se pudo actualizar la calificación", + "toast.ratingUpdateError": "Error al actualizar la calificación", + "toast.tagsUpdateFailed": "No se pudieron actualizar las etiquetas", + "toast.watcherToggleFailed": "No se pudo alternar el observador", + "toast.noValidAssetsSelected": "No se han seleccionado activos válidos.", + "toast.failedCreateCollectionDot": "No se pudo crear la colección.", + "toast.failedAddAssetsToCollection": "No se pudieron agregar recursos a la colección.", + "toast.removeFromCollectionFailed": "No se pudo eliminar de la colección.", + "toast.copyClipboardFailed": "No se pudo copiar al portapapeles", + "toast.metadataRefreshFailed": "No se pudieron actualizar los metadatos.", + "toast.ratingCleared": "Calificación borrada", + "toast.ratingSetN": "Calificación establecida en {n} estrellas", + "toast.tagsUpdated": "Etiquetas actualizadas", + "toast.fileRenamedSuccess": "¡Archivo renombrado exitosamente!", + "toast.fileRenameFailed": "No se pudo cambiar el nombre del archivo.", + "toast.fileDeletedSuccess": "¡Archivo eliminado exitosamente!", + "toast.fileDeleteFailed": "No se pudo eliminar el archivo.", + "toast.openedInFolder": "Abierto en carpeta", + "toast.openFolderFailed": "No se pudo abrir la carpeta.", + "toast.pathCopied": "Ruta del archivo copiada al portapapeles", + "toast.pathCopyFailed": "No se pudo copiar la ruta", + "toast.noFilePath": "No hay ruta de archivo disponible para este activo.", + "toast.downloadingFile": "Descargando {filename}...", + "toast.playbackRate": "Reproducción {rate}x", + "toast.metadataRefreshed": "Metadatos actualizados{suffix}", + "toast.errorRenaming": "Error al cambiar el nombre del archivo: {error}", + "toast.errorDeleting": "Error al eliminar el archivo: {error}", + "toast.tagMergeFailed": "Error al fusionar etiquetas: {error}", + "toast.deleteFailed": "Error al eliminar: {error}", + "toast.analysisNotStarted": "Análisis no iniciado: {error}", + "toast.dupAnalysisStarted": "Se inició el análisis duplicado.", + "toast.tagsMerged": "Etiquetas fusionadas", + "toast.duplicatesDeleted": "Duplicados eliminados", + "toast.playbackVideoOnly": "La velocidad de reproducción está disponible solo para medios de video", + "toast.filesDeletedSuccessN": "{n} archivos eliminados exitosamente!", + "toast.filesDeletedPartial": "{success} archivos eliminados, {failed} falló.", + "toast.filesDeletedShort": "{n} archivos eliminados", + "toast.filesDeletedShortPartial": "{success} eliminado, {failed} falló", + "toast.copiedToClipboardNamed": "{name} ¡copiado al portapapeles!", + "toast.rescanningFile": "Volver a escanear el archivo...", + "hotkey.scan": "Escanear (T)", + "hotkey.search": "Buscar (Ctrl+F)", + "hotkey.details": "Alternar detalles (D)", + "hotkey.delete": "Eliminar (Eliminar)", + "hotkey.viewer": "Abrir visor (Entrar)", + "hotkey.escape": "Cerrar (Esc)" + }, + "ru-RU": { + "cat.grid": "Сетка", + "cat.cards": "Карты", + "cat.badges": "Значки", + "cat.viewer": "Зритель", + "cat.scanning": "Сканирование", + "cat.advanced": "Передовой", + "cat.security": "Безопасность", + "cat.remote": "Удаленный доступ", + "setting.grid.minsize.name": "Основное: Размер миниатюры (пикселей)", + "setting.grid.minsize.desc": "Минимальный размер миниатюр в сетке. Может потребоваться повторное открытие панели.", + "setting.grid.cardSize.group": "Размер карты", + "setting.grid.cardSize.name": "Основной: Размер карты", + "setting.grid.cardSize.desc": "Выберите предустановленный размер карты: маленький, средний или большой.", + "setting.grid.cardSize.small": "Маленький", + "setting.grid.cardSize.medium": "Середина", + "setting.grid.cardSize.large": "Большой", + "setting.grid.gap.name": "Основной: разрыв (пикселей)", + "setting.grid.gap.desc": "Пространство между миниатюрами.", + "setting.sidebar.pos.name": "Основное: положение боковой панели", + "setting.sidebar.pos.desc": "Показывать боковую панель с подробными сведениями слева или справа. Требуется перезагрузка.", + "setting.siblings.hide.name": "Основное: Скрыть одноуровневые элементы PNG", + "setting.siblings.hide.desc": "Если у видео есть соответствующий предварительный просмотр в формате .png, скройте этот .png из сетки.", + "setting.nav.infinite.name": "Мажор: Бесконечная прокрутка", + "setting.nav.infinite.desc": "Автоматически загружать больше файлов при прокрутке.", + "setting.grid.pagesize.name": "Основное: Размер страницы сетки", + "setting.grid.pagesize.desc": "Количество ресурсов, загруженных на страницу/запрос в сетке.", + "setting.viewer.pan.name": "Мажор: Панорамирование без масштабирования", + "setting.viewer.pan.desc": "Разрешить панорамирование изображения даже при уровне масштабирования 1.", + "setting.minimap.enabled.name": "Майор: включить миникарту", + "setting.minimap.enabled.desc": "Глобальная активация миникарты рабочего процесса.", + "setting.scan.startup.name": "Основное: автоматическое сканирование при запуске.", + "setting.scan.startup.desc": "Запустите фоновое сканирование, как только загрузится ComfyUI.", + "setting.watcher.name": "Специальность: Наблюдатель за файлами", + "setting.watcher.desc": "Следите за выходными и пользовательскими папками на предмет файлов, добавленных вручную, и автоматически индексируйте их в режиме реального времени.", + "setting.watcher.debounce.name": "Major: Задержка устранения дребезга Watcher", + "setting.watcher.debounce.desc": "Задержка (мс) для пакетной обработки событий наблюдателя перед индексацией.", + "setting.watcher.debounce.error": "Не удалось обновить задержку устранения дребезга наблюдателя.", + "setting.watcher.dedupe.name": "Основное: окно дедупликации Watcher", + "setting.watcher.dedupe.desc": "Длительность (мс), в течение которой файл считается уже обработанным после события.", + "setting.watcher.dedupe.error": "Не удалось обновить окно дедупликации Watcher.", + "setting.sync.rating.name": "Основное: синхронизация рейтинга/тегов с файлами", + "setting.sync.rating.desc": "Записывайте рейтинги и теги в метаданные файла (ExifTool).", + "cat.badgeColors": "Цвета значков", + "setting.starColor": "Цвет звезды", + "setting.starColor.tooltip": "Цвет рейтинговых звездочек на миниатюрах (шестнадцатеричный, например #FFD45A)", + "setting.badgeImageColor": "Цвет значка изображения", + "setting.badgeImageColor.tooltip": "Цвет для значков изображений: PNG, JPG, WEBP, GIF, BMP, TIF (шестнадцатеричный).", + "setting.badgeVideoColor": "Цвет значка видео", + "setting.badgeVideoColor.tooltip": "Цвет бейджей видео: MP4, WEBM, MOV, AVI, MKV (шестнадцатеричный)", + "setting.badgeAudioColor": "Цвет значка аудио", + "setting.badgeAudioColor.tooltip": "Цвет аудиозначков: MP3, WAV, OGG, FLAC (шестнадцатеричный)", + "setting.badgeModel3dColor": "Цвет значка 3D-модели", + "setting.badgeModel3dColor.tooltip": "Цвет значков 3D-моделей: OBJ, FBX, GLB, GLTF (шестнадцатеричный)", + "setting.obs.enabled.name": "Основное: включить подробные журналы", + "setting.obs.enabled.desc": "Включите подробные журналы серверной части для отладки.", + "setting.probe.mode.name": "Специальность: Серверная часть метаданных", + "setting.probe.mode.desc": "Выберите инструмент, используемый непосредственно для извлечения метаданных.", + "setting.language.name": "Специальность: Язык", + "setting.language.desc": "Выберите язык интерфейса Assets Manager. Для полного применения требуется перезагрузка.", + "setting.sec.safe.name": "Специальность: Безопасный режим", + "setting.sec.safe.desc": "Если этот параметр включен, запись рейтингов/тегов блокируется, если это явно не разрешено.", + "setting.sec.remote.name": "Основное: разрешить удаленный полный доступ", + "setting.sec.remote.desc": "Разрешить нелокальным клиентам выполнять операции записи. Отключение блокировки записи, если токен не настроен.", + "setting.sec.write.name": "Основной: Разрешить запись", + "setting.sec.write.desc": "Разрешить написание оценок и тегов.", + "setting.sec.del.name": "Основной: Разрешить удаление", + "setting.sec.del.desc": "Разрешить удаление файлов.", + "setting.sec.ren.name": "Основное: разрешить переименование", + "setting.sec.ren.desc": "Разрешить переименование файлов.", + "setting.sec.open.name": "Основное: Разрешить открытие в папке", + "setting.sec.open.desc": "Разрешить открытие местоположения файла в файловом менеджере ОС.", + "setting.sec.reset.name": "Основное: разрешить сброс индекса", + "setting.sec.reset.desc": "Разрешить сброс кэша индекса и запуск полного повторного сканирования.", + "setting.sec.token.name": "Специальность: Токен API", + "setting.sec.token.desc": "Сохраните токен авторизации записи. Majoor вставляет его в заголовки Authorization и X-MJR-Token.", + "setting.sec.token.placeholder": "Оставьте пустым, чтобы отключить.", + "tab.output": "Выход", + "tab.input": "Вход", + "tab.custom": "Обычай", + "btn.add": "Добавить…", + "btn.remove": "Удалять", + "btn.adding": "Добавление...", + "btn.removing": "Удаление...", + "btn.retry": "Повторить попытку", + "btn.clear": "Прозрачный", + "btn.refresh": "Обновить", + "btn.scan": "Сканировать", + "btn.scanning": "Сканирование...", + "btn.resetIndex": "Сбросить индекс", + "btn.resetting": "Сброс...", + "btn.deleteDb": "Удалить БД", + "btn.deletingDb": "Удаление БД...", + "btn.retryServices": "Повторить услуги", + "btn.retrying": "Повторная попытка...", + "btn.loadWorkflow": "Загрузить рабочий процесс", + "btn.copyPrompt": "Копировать подсказку", + "btn.close": "Закрывать", + "label.folder": "Папка", + "label.type": "Тип", + "label.workflow": "Рабочий процесс", + "label.rating": "Рейтинг", + "label.dateRange": "Диапазон дат", + "label.agenda": "Повестка дня", + "label.sort": "Сортировать", + "label.collections": "Коллекции", + "label.filters": "Фильтры", + "label.selectFolder": "Выберите папку…", + "label.thisFolder": "эта папка", + "filter.all": "Все", + "filter.images": "Изображения", + "filter.videos": "Видео", + "filter.onlyWithWorkflow": "Только с рабочим процессом", + "filter.anyRating": "Любой рейтинг", + "filter.minStars": "{n}+ звезд", + "filter.anytime": "В любое время", + "filter.today": "Сегодня", + "filter.yesterday": "Вчера", + "filter.thisWeek": "На этой неделе", + "filter.thisMonth": "В этом месяце", + "filter.last7days": "Последние 7 дней", + "filter.last30days": "Последние 30 дней", + "sort.newest": "Сначала самые новые", + "sort.oldest": "Сначала самый старый", + "sort.nameAZ": "Имя от А до Я", + "sort.nameZA": "Имя Z-A", + "sort.ratingHigh": "Рейтинг (высокий)", + "sort.ratingLow": "Рейтинг (низкий)", + "sort.sizeDesc": "Размер (большой)", + "sort.sizeAsc": "Размер (маленький)", + "status.checking": "Проверка...", + "status.ready": "Готовый", + "status.scanning": "Сканирование...", + "status.error": "Ошибка", + "status.capabilities": "Возможности", + "status.toolStatus": "Статус инструмента", + "status.selectCustomFolder": "Сначала выберите пользовательскую папку", + "status.errorGetConfig": "Ошибка: не удалось получить конфигурацию", + "status.discoveringTools": "Возможности: открытие инструментов...", + "status.indexStatus": "Статус индекса", + "status.toolStatusChecking": "Статус инструмента: проверка...", + "status.resetIndexHint": "Сбросить кеш индекса (требуется разрешить ResetIndex в настройках).", + "status.scanningHint": "Это может занять некоторое время", + "status.toolAvailable": "{tool} доступен", + "status.toolUnavailable": "{tool} недоступен", + "status.unknown": "неизвестный", + "status.available": "доступный", + "status.missing": "отсутствующий", + "status.path": "Путь", + "status.pathAuto": "авто/не настроено", + "status.noAssets": "Активы еще не проиндексированы ({scope})", + "status.clickToScan": "Нажмите на точку, чтобы начать сканирование", + "status.assetsIndexed": "{count} активов проиндексировано ({scope})", + "status.imagesVideos": "Изображения: {images} - Видео: {videos}", + "status.withWorkflows": "С рабочими процессами: {workflows} - Данные генерации: {gendata}", + "status.dbSize": "Размер базы данных: {size}", + "status.lastScan": "Последнее сканирование: {date}", + "status.scanStats": "Добавлено: {added} – Обновлено: {updated} – Пропущено: {skipped}", + "status.watcher.enabled": "Наблюдатель: включен", + "status.watcher.enabledScoped": "Наблюдатель: включен ({scope})", + "status.watcher.disabled": "Наблюдатель: отключен", + "status.watcher.disabledScoped": "Наблюдатель: отключен ({scope})", + "status.apiNotFound": "Основные конечные точки API не найдены (404)", + "status.apiNotFoundHint": "Внутренние маршруты не загружены. Перезапустите ComfyUI и проверьте терминал на наличие серьезных ошибок импорта.", + "status.errorChecking": "Ошибка проверки статуса", + "status.dbCorrupted": "База данных повреждена", + "status.dbCorruptedHint": "Используйте кнопку «Удалить БД» ниже, чтобы принудительно удалить и перестроить индекс.", + "status.retryFailed": "Повторная попытка не удалась", + "scope.all": "Входы + Выходы", + "scope.allFull": "Все (входы + выходы)", + "scope.input": "Входы", + "scope.output": "Выходы", + "scope.custom": "Обычай", + "tool.exiftool": "Метаданные ExifTool", + "tool.exiftool.hint": "Данные рабочего процесса PNG/WEBP (использует ExifTool)", + "tool.ffprobe": "Статистика видео FFprobe", + "tool.ffprobe.hint": "Продолжительность видео, частота кадров в секунду и разрешение (используется FFprobe)", + "msg.noCollections": "Коллекций пока нет.", + "msg.addCustomFolder": "Добавьте пользовательскую папку для просмотра.", + "msg.noResults": "Результаты не найдены.", + "msg.loading": "Загрузка...", + "msg.errorLoading": "Ошибка загрузки", + "msg.errorLoadingFolders": "Ошибка загрузки папок.", + "msg.noGenerationData": "Для этого файла не найдены данные о создании.", + "msg.rawMetadata": "Необработанные метаданные", + "viewer.genInfo": "Информация о поколении", + "viewer.workflow": "Рабочий процесс", + "viewer.metadata": "Метаданные", + "viewer.noWorkflow": "Нет данных о рабочем процессе", + "viewer.noMetadata": "Нет доступных метаданных", + "viewer.copySuccess": "Скопировано в буфер обмена!", + "viewer.copyFailed": "Не удалось скопировать", + "sidebar.details": "Подробности", + "sidebar.preview": "Предварительный просмотр", + "sidebar.rating": "Рейтинг", + "sidebar.tags": "Теги", + "sidebar.addTag": "Добавить тег...", + "sidebar.noTags": "Нет тегов", + "sidebar.filename": "Имя файла", + "sidebar.dimensions": "Размеры", + "sidebar.date": "Дата", + "sidebar.size": "Размер", + "sidebar.genTime": "Время генерации", + "ctx.openViewer": "Открыть в программе просмотра", + "ctx.loadWorkflow": "Загрузить рабочий процесс", + "ctx.copyPath": "Копировать путь", + "ctx.openInFolder": "Открыть в папке", + "ctx.rename": "Переименовать", + "ctx.delete": "Удалить", + "ctx.addToCollection": "Добавить в коллекцию", + "ctx.removeFromCollection": "Удалить из коллекции", + "ctx.newCollection": "Новая коллекция...", + "ctx.rescanMetadata": "Повторное сканирование метаданных", + "ctx.createCollection": "Создать коллекцию...", + "ctx.exitCollection": "Выйти из режима коллекции", + "dialog.confirm": "Подтверждать", + "dialog.cancel": "Отмена", + "dialog.delete.title": "Удалить файл?", + "dialog.delete.msg": "Вы уверены, что хотите удалить этот файл? Это невозможно отменить.", + "dialog.rename.title": "Переименовать файл", + "dialog.rename.placeholder": "Новое имя файла", + "dialog.newCollection.title": "Новая коллекция", + "dialog.newCollection.placeholder": "Название коллекции", + "dialog.resetIndex.title": "Сбросить индекс?", + "dialog.resetIndex.msg": "Это приведет к удалению базы данных и повторному сканированию всех файлов. Продолжать?", + "dialog.securityWarning": "Это похоже на систему или очень широкий каталог.\n\nЕго добавление может предоставить доступ к конфиденциальным файлам с помощью функции просмотра/пользовательских корней.\n\nПродолжать?", + "dialog.securityWarningTitle": "Специальность: Предупреждение безопасности", + "dialog.enterFolderPath": "Введите путь к папке, которую нужно добавить в качестве пользовательского корня:", + "dialog.customFoldersTitle": "Специальность: Пользовательские папки", + "dialog.removeFolder": "Удалить пользовательскую папку «{name}»?", + "dialog.deleteCollection": "Удалить коллекцию \"{name}\"?", + "dialog.createCollection": "Создать коллекцию", + "dialog.collectionPlaceholder": "Моя коллекция", + "toast.scanStarted": "Сканирование началось", + "toast.scanComplete": "Сканирование завершено", + "toast.scanFailed": "Сканирование не удалось", + "toast.resetTriggered": "Выполнен сброс: переиндексация всех файлов...", + "toast.resetStarted": "Начался сброс индекса. Файлы будут переиндексированы в фоновом режиме.", + "toast.resetFailed": "Не удалось сбросить индекс.", + "toast.resetFailedCorrupt": "Сброс не выполнен — база данных повреждена. Используйте кнопку «Удалить БД», чтобы принудительно удалить и перестроить.", + "toast.dbDeleteTriggered": "Удаление базы данных и восстановление...", + "toast.dbDeleteSuccess": "База данных удалена и восстановлена. Файлы переиндексируются.", + "toast.dbDeleteFailed": "Не удалось удалить базу данных", + "dialog.dbDelete.confirm": "Это приведет к окончательному удалению базы данных индекса и ее восстановлению с нуля. Все рейтинги, теги и кэшированные метаданные будут потеряны.\n\nПродолжать?", + "toast.deleted": "Файл удален", + "toast.renamed": "Файл переименован", + "toast.addedToCollection": "Добавлено в коллекцию", + "toast.removedFromCollection": "Удален из коллекции", + "toast.collectionCreated": "Коллекция создана", + "toast.permissionDenied": "Доступ запрещен", + "toast.tagAdded": "Тег добавлен", + "toast.tagRemoved": "Тег удален", + "toast.ratingSaved": "Рейтинг сохранен.", + "toast.failedAddFolder": "Не удалось добавить пользовательскую папку.", + "toast.failedRemoveFolder": "Не удалось удалить пользовательскую папку.", + "toast.folderLinked": "Папка успешно связана", + "toast.folderRemoved": "Папка удалена", + "toast.nativeBrowserUnavailable": "Собственный браузер папок недоступен. Пожалуйста, введите путь вручную.", + "toast.errorAddingFolder": "Произошла ошибка при добавлении пользовательской папки.", + "toast.errorRemovingFolder": "Произошла ошибка при удалении пользовательской папки.", + "toast.failedCreateCollection": "Не удалось создать коллекцию.", + "toast.failedDeleteCollection": "Не удалось удалить коллекцию.", + "toast.languageChanged": "Язык изменился. Перезагрузите страницу для полного эффекта.", + "toast.ratingUpdateFailed": "Не удалось обновить рейтинг", + "toast.ratingUpdateError": "Ошибка обновления рейтинга.", + "toast.tagsUpdateFailed": "Не удалось обновить теги.", + "toast.watcherToggleFailed": "Не удалось переключить наблюдателя.", + "toast.noValidAssetsSelected": "Действительные активы не выбраны.", + "toast.failedCreateCollectionDot": "Не удалось создать коллекцию.", + "toast.failedAddAssetsToCollection": "Не удалось добавить ресурсы в коллекцию.", + "toast.removeFromCollectionFailed": "Не удалось удалить из коллекции.", + "toast.copyClipboardFailed": "Не удалось скопировать в буфер обмена", + "toast.metadataRefreshFailed": "Не удалось обновить метаданные.", + "toast.ratingCleared": "Рейтинг удален", + "toast.ratingSetN": "Рейтинг установлен на {n} звезд.", + "toast.tagsUpdated": "Теги обновлены", + "toast.fileRenamedSuccess": "Файл успешно переименован!", + "toast.fileRenameFailed": "Не удалось переименовать файл.", + "toast.fileDeletedSuccess": "Файл успешно удален!", + "toast.fileDeleteFailed": "Не удалось удалить файл.", + "toast.openedInFolder": "Открыто в папке", + "toast.openFolderFailed": "Не удалось открыть папку.", + "toast.pathCopied": "Путь к файлу скопирован в буфер обмена", + "toast.pathCopyFailed": "Не удалось скопировать путь", + "toast.noFilePath": "Для этого ресурса нет пути к файлу.", + "toast.downloadingFile": "Загрузка {filename}...", + "toast.playbackRate": "Воспроизведение {rate}x", + "toast.metadataRefreshed": "Метаданные обновлены{suffix}", + "toast.errorRenaming": "Ошибка переименования файла: {error}.", + "toast.errorDeleting": "Ошибка удаления файла: {error}.", + "toast.tagMergeFailed": "Не удалось объединить теги: {error}", + "toast.deleteFailed": "Не удалось удалить: {error}", + "toast.analysisNotStarted": "Анализ не запущен: {error}", + "toast.dupAnalysisStarted": "Повторный анализ начат", + "toast.tagsMerged": "Теги объединены", + "toast.duplicatesDeleted": "Дубликаты удалены.", + "toast.playbackVideoOnly": "Скорость воспроизведения доступна только для видеоносителей.", + "toast.filesDeletedSuccessN": "{n} файлов успешно удалено!", + "toast.filesDeletedPartial": "Удалены {success} файлов, {failed}_ не удалось.", + "toast.filesDeletedShort": "{n} файлов удалено", + "toast.filesDeletedShortPartial": "{success} удален, {failed} не выполнено", + "toast.copiedToClipboardNamed": "{name} скопировано в буфер обмена!", + "toast.rescanningFile": "Повторное сканирование файла…", + "hotkey.scan": "Сканировать (S)", + "hotkey.search": "Поиск (Ctrl+F)", + "hotkey.details": "Переключить детали (D)", + "hotkey.delete": "Удалить (Дел)", + "hotkey.viewer": "Открыть просмотрщик (Ввод)", + "hotkey.escape": "Закрыть (Esc)" + } +}; diff --git a/js/app/i18n.js b/js/app/i18n.js index a8559e30..e8a72c5c 100644 --- a/js/app/i18n.js +++ b/js/app/i18n.js @@ -2,6 +2,7 @@ * Internationalization support for Majoor Assets Manager. * Detects ComfyUI language and provides translations for the entire UI. */ +import { GENERATED_TRANSLATIONS } from "./i18n.generated.js"; const DEFAULT_LANG = "en-US"; let currentLang = DEFAULT_LANG; @@ -720,6 +721,470 @@ const DICTIONARY = { } }; +const LANGUAGE_NAMES = Object.freeze({ + "en-US": "English", + "fr-FR": "Français", + "zh-CN": "Chinese (Simplified)", + "ja-JP": "Japanese", + "ko-KR": "Korean", + "hi-IN": "Hindi", + "pt-PT": "Portuguese", + "es-ES": "Spanish", + "ru-RU": "Russian", +}); + +// Register additional locales with safe fallback to English until translated keys are added. +[ + "zh-CN", + "ja-JP", + "ko-KR", + "hi-IN", + "pt-PT", + "es-ES", + "ru-RU", +].forEach((code) => { + if (!DICTIONARY[code]) DICTIONARY[code] = {}; +}); + +const CORE_TRANSLATIONS = { + "zh-CN": { + "cat.grid": "网格", + "cat.cards": "卡片", + "cat.badges": "徽章", + "cat.viewer": "查看器", + "cat.scanning": "扫描", + "cat.advanced": "高级", + "cat.security": "安全", + "cat.remote": "远程访问", + "setting.language.name": "Majoor: 语言", + "setting.language.desc": "选择 Assets Manager 界面语言。完整应用需重新加载。", + "tab.output": "输出", + "tab.input": "输入", + "tab.custom": "自定义", + "btn.add": "添加…", + "btn.remove": "移除", + "btn.retry": "重试", + "btn.clear": "清除", + "btn.refresh": "刷新", + "btn.scan": "扫描", + "btn.close": "关闭", + "label.folder": "文件夹", + "label.type": "类型", + "label.workflow": "工作流", + "label.rating": "评分", + "label.dateRange": "日期范围", + "label.sort": "排序", + "label.filters": "筛选", + "filter.all": "全部", + "filter.images": "图片", + "filter.videos": "视频", + "filter.onlyWithWorkflow": "仅有工作流", + "filter.anyRating": "任意评分", + "filter.anytime": "任何时间", + "filter.today": "今天", + "filter.yesterday": "昨天", + "sort.newest": "最新优先", + "sort.oldest": "最旧优先", + "sort.nameAZ": "名称 A-Z", + "sort.nameZA": "名称 Z-A", + "status.ready": "就绪", + "status.scanning": "扫描中...", + "status.error": "错误", + "msg.noResults": "未找到结果。", + "msg.loading": "加载中...", + "msg.addCustomFolder": "添加一个自定义文件夹以浏览。", + "msg.errorLoading": "加载错误", + "sidebar.details": "详情", + "sidebar.preview": "预览", + "sidebar.rating": "评分", + "sidebar.tags": "标签", + "ctx.openViewer": "在查看器中打开", + "ctx.copyPath": "复制路径", + "ctx.openInFolder": "在文件夹中打开", + "ctx.rename": "重命名", + "ctx.delete": "删除", + "toast.ratingUpdateFailed": "更新评分失败", + "toast.tagsUpdated": "标签已更新", + "toast.pathCopied": "路径已复制到剪贴板", + }, + "ja-JP": { + "cat.grid": "グリッド", + "cat.cards": "カード", + "cat.badges": "バッジ", + "cat.viewer": "ビューア", + "cat.scanning": "スキャン", + "cat.advanced": "詳細設定", + "cat.security": "セキュリティ", + "cat.remote": "リモートアクセス", + "setting.language.name": "Majoor: 言語", + "setting.language.desc": "Assets Manager の表示言語を選択します。完全適用には再読み込みが必要です。", + "tab.output": "出力", + "tab.input": "入力", + "tab.custom": "カスタム", + "btn.add": "追加…", + "btn.remove": "削除", + "btn.retry": "再試行", + "btn.clear": "クリア", + "btn.refresh": "更新", + "btn.scan": "スキャン", + "btn.close": "閉じる", + "label.folder": "フォルダ", + "label.type": "種類", + "label.workflow": "ワークフロー", + "label.rating": "評価", + "label.dateRange": "日付範囲", + "label.sort": "並び替え", + "label.filters": "フィルター", + "filter.all": "すべて", + "filter.images": "画像", + "filter.videos": "動画", + "filter.onlyWithWorkflow": "ワークフローありのみ", + "filter.anyRating": "すべての評価", + "filter.anytime": "期間指定なし", + "filter.today": "今日", + "filter.yesterday": "昨日", + "sort.newest": "新しい順", + "sort.oldest": "古い順", + "sort.nameAZ": "名前 A-Z", + "sort.nameZA": "名前 Z-A", + "status.ready": "準備完了", + "status.scanning": "スキャン中...", + "status.error": "エラー", + "msg.noResults": "結果が見つかりません。", + "msg.loading": "読み込み中...", + "msg.addCustomFolder": "参照するカスタムフォルダを追加してください。", + "msg.errorLoading": "読み込みエラー", + "sidebar.details": "詳細", + "sidebar.preview": "プレビュー", + "sidebar.rating": "評価", + "sidebar.tags": "タグ", + "ctx.openViewer": "ビューアで開く", + "ctx.copyPath": "パスをコピー", + "ctx.openInFolder": "フォルダで開く", + "ctx.rename": "名前変更", + "ctx.delete": "削除", + "toast.ratingUpdateFailed": "評価の更新に失敗しました", + "toast.tagsUpdated": "タグを更新しました", + "toast.pathCopied": "パスをクリップボードにコピーしました", + }, + "ko-KR": { + "cat.grid": "그리드", + "cat.cards": "카드", + "cat.badges": "배지", + "cat.viewer": "뷰어", + "cat.scanning": "스캔", + "cat.advanced": "고급", + "cat.security": "보안", + "cat.remote": "원격 액세스", + "setting.language.name": "Majoor: 언어", + "setting.language.desc": "Assets Manager 인터페이스 언어를 선택합니다. 완전 적용하려면 새로고침이 필요합니다.", + "tab.output": "출력", + "tab.input": "입력", + "tab.custom": "사용자 지정", + "btn.add": "추가…", + "btn.remove": "제거", + "btn.retry": "다시 시도", + "btn.clear": "지우기", + "btn.refresh": "새로고침", + "btn.scan": "스캔", + "btn.close": "닫기", + "label.folder": "폴더", + "label.type": "유형", + "label.workflow": "워크플로우", + "label.rating": "평점", + "label.dateRange": "날짜 범위", + "label.sort": "정렬", + "label.filters": "필터", + "filter.all": "전체", + "filter.images": "이미지", + "filter.videos": "동영상", + "filter.onlyWithWorkflow": "워크플로우만", + "filter.anyRating": "모든 평점", + "filter.anytime": "전체 기간", + "filter.today": "오늘", + "filter.yesterday": "어제", + "sort.newest": "최신순", + "sort.oldest": "오래된순", + "sort.nameAZ": "이름 A-Z", + "sort.nameZA": "이름 Z-A", + "status.ready": "준비됨", + "status.scanning": "스캔 중...", + "status.error": "오류", + "msg.noResults": "결과가 없습니다.", + "msg.loading": "로딩 중...", + "msg.addCustomFolder": "찾아볼 사용자 지정 폴더를 추가하세요.", + "msg.errorLoading": "로드 오류", + "sidebar.details": "세부정보", + "sidebar.preview": "미리보기", + "sidebar.rating": "평점", + "sidebar.tags": "태그", + "ctx.openViewer": "뷰어에서 열기", + "ctx.copyPath": "경로 복사", + "ctx.openInFolder": "폴더에서 열기", + "ctx.rename": "이름 바꾸기", + "ctx.delete": "삭제", + "toast.ratingUpdateFailed": "평점 업데이트 실패", + "toast.tagsUpdated": "태그가 업데이트되었습니다", + "toast.pathCopied": "경로가 클립보드에 복사되었습니다", + }, + "hi-IN": { + "cat.grid": "ग्रिड", + "cat.cards": "कार्ड", + "cat.badges": "बैज", + "cat.viewer": "व्यूअर", + "cat.scanning": "स्कैनिंग", + "cat.advanced": "उन्नत", + "cat.security": "सुरक्षा", + "cat.remote": "रिमोट एक्सेस", + "setting.language.name": "Majoor: भाषा", + "setting.language.desc": "Assets Manager इंटरफ़ेस की भाषा चुनें। पूरी तरह लागू करने हेतु रीलोड आवश्यक है।", + "tab.output": "आउटपुट", + "tab.input": "इनपुट", + "tab.custom": "कस्टम", + "btn.add": "जोड़ें…", + "btn.remove": "हटाएं", + "btn.retry": "पुनः प्रयास", + "btn.clear": "साफ़ करें", + "btn.refresh": "रिफ्रेश", + "btn.scan": "स्कैन", + "btn.close": "बंद करें", + "label.folder": "फ़ोल्डर", + "label.type": "प्रकार", + "label.workflow": "वर्कफ़्लो", + "label.rating": "रेटिंग", + "label.dateRange": "तिथि सीमा", + "label.sort": "क्रमबद्ध करें", + "label.filters": "फ़िल्टर", + "filter.all": "सभी", + "filter.images": "छवियां", + "filter.videos": "वीडियो", + "filter.onlyWithWorkflow": "सिर्फ़ वर्कफ़्लो वाले", + "filter.anyRating": "कोई भी रेटिंग", + "filter.anytime": "कभी भी", + "filter.today": "आज", + "filter.yesterday": "कल", + "sort.newest": "नवीनतम पहले", + "sort.oldest": "पुराना पहले", + "sort.nameAZ": "नाम A-Z", + "sort.nameZA": "नाम Z-A", + "status.ready": "तैयार", + "status.scanning": "स्कैन हो रहा है...", + "status.error": "त्रुटि", + "msg.noResults": "कोई परिणाम नहीं मिला।", + "msg.loading": "लोड हो रहा है...", + "msg.addCustomFolder": "ब्राउज़ करने के लिए एक कस्टम फ़ोल्डर जोड़ें।", + "msg.errorLoading": "लोड करने में त्रुटि", + "sidebar.details": "विवरण", + "sidebar.preview": "पूर्वावलोकन", + "sidebar.rating": "रेटिंग", + "sidebar.tags": "टैग", + "ctx.openViewer": "व्यूअर में खोलें", + "ctx.copyPath": "पाथ कॉपी करें", + "ctx.openInFolder": "फ़ोल्डर में खोलें", + "ctx.rename": "नाम बदलें", + "ctx.delete": "हटाएं", + "toast.ratingUpdateFailed": "रेटिंग अपडेट विफल", + "toast.tagsUpdated": "टैग अपडेट हो गए", + "toast.pathCopied": "पाथ क्लिपबोर्ड में कॉपी हो गया", + }, + "pt-PT": { + "cat.grid": "Grelha", + "cat.cards": "Cartões", + "cat.badges": "Emblemas", + "cat.viewer": "Visualizador", + "cat.scanning": "Análise", + "cat.advanced": "Avançado", + "cat.security": "Segurança", + "cat.remote": "Acesso remoto", + "setting.language.name": "Majoor: Idioma", + "setting.language.desc": "Escolha o idioma da interface do Assets Manager. Recarregar para aplicar totalmente.", + "tab.output": "Saída", + "tab.input": "Entrada", + "tab.custom": "Personalizado", + "btn.add": "Adicionar…", + "btn.remove": "Remover", + "btn.retry": "Tentar novamente", + "btn.clear": "Limpar", + "btn.refresh": "Atualizar", + "btn.scan": "Analisar", + "btn.close": "Fechar", + "label.folder": "Pasta", + "label.type": "Tipo", + "label.workflow": "Fluxo", + "label.rating": "Classificação", + "label.dateRange": "Intervalo de datas", + "label.sort": "Ordenar", + "label.filters": "Filtros", + "filter.all": "Todos", + "filter.images": "Imagens", + "filter.videos": "Vídeos", + "filter.onlyWithWorkflow": "Apenas com workflow", + "filter.anyRating": "Qualquer classificação", + "filter.anytime": "Qualquer altura", + "filter.today": "Hoje", + "filter.yesterday": "Ontem", + "sort.newest": "Mais recentes primeiro", + "sort.oldest": "Mais antigos primeiro", + "sort.nameAZ": "Nome A-Z", + "sort.nameZA": "Nome Z-A", + "status.ready": "Pronto", + "status.scanning": "A analisar...", + "status.error": "Erro", + "msg.noResults": "Sem resultados.", + "msg.loading": "A carregar...", + "msg.addCustomFolder": "Adicione uma pasta personalizada para navegar.", + "msg.errorLoading": "Erro ao carregar", + "sidebar.details": "Detalhes", + "sidebar.preview": "Pré-visualização", + "sidebar.rating": "Classificação", + "sidebar.tags": "Etiquetas", + "ctx.openViewer": "Abrir no visualizador", + "ctx.copyPath": "Copiar caminho", + "ctx.openInFolder": "Abrir na pasta", + "ctx.rename": "Renomear", + "ctx.delete": "Eliminar", + "toast.ratingUpdateFailed": "Falha ao atualizar classificação", + "toast.tagsUpdated": "Etiquetas atualizadas", + "toast.pathCopied": "Caminho copiado para a área de transferência", + }, + "es-ES": { + "cat.grid": "Cuadrícula", + "cat.cards": "Tarjetas", + "cat.badges": "Insignias", + "cat.viewer": "Visor", + "cat.scanning": "Escaneo", + "cat.advanced": "Avanzado", + "cat.security": "Seguridad", + "cat.remote": "Acceso remoto", + "setting.language.name": "Majoor: Idioma", + "setting.language.desc": "Elige el idioma de la interfaz de Assets Manager. Recarga para aplicar completamente.", + "tab.output": "Salida", + "tab.input": "Entrada", + "tab.custom": "Personalizado", + "btn.add": "Agregar…", + "btn.remove": "Eliminar", + "btn.retry": "Reintentar", + "btn.clear": "Limpiar", + "btn.refresh": "Actualizar", + "btn.scan": "Escanear", + "btn.close": "Cerrar", + "label.folder": "Carpeta", + "label.type": "Tipo", + "label.workflow": "Workflow", + "label.rating": "Valoración", + "label.dateRange": "Rango de fechas", + "label.sort": "Ordenar", + "label.filters": "Filtros", + "filter.all": "Todo", + "filter.images": "Imágenes", + "filter.videos": "Vídeos", + "filter.onlyWithWorkflow": "Solo con workflow", + "filter.anyRating": "Cualquier valoración", + "filter.anytime": "En cualquier momento", + "filter.today": "Hoy", + "filter.yesterday": "Ayer", + "sort.newest": "Más nuevos primero", + "sort.oldest": "Más antiguos primero", + "sort.nameAZ": "Nombre A-Z", + "sort.nameZA": "Nombre Z-A", + "status.ready": "Listo", + "status.scanning": "Escaneando...", + "status.error": "Error", + "msg.noResults": "No se encontraron resultados.", + "msg.loading": "Cargando...", + "msg.addCustomFolder": "Agrega una carpeta personalizada para explorar.", + "msg.errorLoading": "Error al cargar", + "sidebar.details": "Detalles", + "sidebar.preview": "Vista previa", + "sidebar.rating": "Valoración", + "sidebar.tags": "Etiquetas", + "ctx.openViewer": "Abrir en visor", + "ctx.copyPath": "Copiar ruta", + "ctx.openInFolder": "Abrir en carpeta", + "ctx.rename": "Renombrar", + "ctx.delete": "Eliminar", + "toast.ratingUpdateFailed": "Error al actualizar la valoración", + "toast.tagsUpdated": "Etiquetas actualizadas", + "toast.pathCopied": "Ruta copiada al portapapeles", + }, + "ru-RU": { + "cat.grid": "Сетка", + "cat.cards": "Карточки", + "cat.badges": "Значки", + "cat.viewer": "Просмотрщик", + "cat.scanning": "Сканирование", + "cat.advanced": "Расширенные", + "cat.security": "Безопасность", + "cat.remote": "Удаленный доступ", + "setting.language.name": "Majoor: Язык", + "setting.language.desc": "Выберите язык интерфейса Assets Manager. Для полного применения требуется перезагрузка.", + "tab.output": "Выход", + "tab.input": "Вход", + "tab.custom": "Пользовательский", + "btn.add": "Добавить…", + "btn.remove": "Удалить", + "btn.retry": "Повторить", + "btn.clear": "Очистить", + "btn.refresh": "Обновить", + "btn.scan": "Сканировать", + "btn.close": "Закрыть", + "label.folder": "Папка", + "label.type": "Тип", + "label.workflow": "Воркфлоу", + "label.rating": "Рейтинг", + "label.dateRange": "Диапазон дат", + "label.sort": "Сортировка", + "label.filters": "Фильтры", + "filter.all": "Все", + "filter.images": "Изображения", + "filter.videos": "Видео", + "filter.onlyWithWorkflow": "Только с workflow", + "filter.anyRating": "Любой рейтинг", + "filter.anytime": "За всё время", + "filter.today": "Сегодня", + "filter.yesterday": "Вчера", + "sort.newest": "Сначала новые", + "sort.oldest": "Сначала старые", + "sort.nameAZ": "Имя A-Z", + "sort.nameZA": "Имя Z-A", + "status.ready": "Готово", + "status.scanning": "Сканирование...", + "status.error": "Ошибка", + "msg.noResults": "Результаты не найдены.", + "msg.loading": "Загрузка...", + "msg.addCustomFolder": "Добавьте пользовательскую папку для просмотра.", + "msg.errorLoading": "Ошибка загрузки", + "sidebar.details": "Подробности", + "sidebar.preview": "Предпросмотр", + "sidebar.rating": "Рейтинг", + "sidebar.tags": "Теги", + "ctx.openViewer": "Открыть в просмотрщике", + "ctx.copyPath": "Копировать путь", + "ctx.openInFolder": "Открыть в папке", + "ctx.rename": "Переименовать", + "ctx.delete": "Удалить", + "toast.ratingUpdateFailed": "Не удалось обновить рейтинг", + "toast.tagsUpdated": "Теги обновлены", + "toast.pathCopied": "Путь скопирован в буфер обмена", + }, +}; + +Object.entries(GENERATED_TRANSLATIONS || {}).forEach(([code, entries]) => { + DICTIONARY[code] = { ...(DICTIONARY[code] || {}), ...(entries || {}) }; +}); + +Object.entries(CORE_TRANSLATIONS).forEach(([code, entries]) => { + DICTIONARY[code] = { ...(DICTIONARY[code] || {}), ...entries }; +}); + +// Ensure complete key coverage for all registered locales: +// any missing key falls back to the English string inside each locale pack. +const EN_US_DICT = DICTIONARY["en-US"] || {}; +Object.keys(DICTIONARY).forEach((code) => { + if (code === "en-US") return; + DICTIONARY[code] = { ...EN_US_DICT, ...(DICTIONARY[code] || {}) }; +}); + // ───────────────────────────────────────────────────────────────────────────── // API // ───────────────────────────────────────────────────────────────────────────── @@ -736,6 +1201,27 @@ function mapLocale(locale) { // English variants if (lower.startsWith("en")) return "en-US"; + + // Chinese variants + if (lower.startsWith("zh")) return "zh-CN"; + + // Japanese variants + if (lower.startsWith("ja")) return "ja-JP"; + + // Korean variants + if (lower.startsWith("ko")) return "ko-KR"; + + // Hindi variants + if (lower.startsWith("hi")) return "hi-IN"; + + // Portuguese variants + if (lower.startsWith("pt")) return "pt-PT"; + + // Spanish variants + if (lower.startsWith("es")) return "es-ES"; + + // Russian variants + if (lower.startsWith("ru")) return "ru-RU"; // Direct match if (DICTIONARY[locale]) return locale; @@ -814,7 +1300,7 @@ export const getCurrentLang = () => currentLang; */ export const getSupportedLanguages = () => Object.keys(DICTIONARY).map(code => ({ code, - name: code === "en-US" ? "English" : code === "fr-FR" ? "Français" : code + name: LANGUAGE_NAMES[code] || code })); /** diff --git a/js/app/settings.js b/js/app/settings.js index 0bcdb75e..6869b33f 100644 --- a/js/app/settings.js +++ b/js/app/settings.js @@ -96,6 +96,9 @@ const DEFAULT_SETTINGS = { cache: { tagsTTLms: 30_000, }, + search: { + maxResults: APP_DEFAULTS.SEARCH_DEFAULT_LIMIT, + }, workflowMinimap: { enabled: false, nodeColors: true, @@ -301,6 +304,11 @@ const applySettingsToConfig = (settings) => { APP_CONFIG.DELETE_CONFIRMATION = !!settings.safety?.confirmDeletion; APP_CONFIG.DEBUG_VERBOSE_ERRORS = !!settings.observability?.verboseErrors; + // Search request limit (client-side); backend still enforces MAJOOR_SEARCH_MAX_LIMIT + APP_CONFIG.SEARCH_REQUEST_LIMIT = Math.max( + 10, + Math.min(APP_DEFAULTS.MAX_PAGE_SIZE || 2000, Math.round(_safeNum(settings.search?.maxResults, APP_DEFAULTS.SEARCH_DEFAULT_LIMIT))) + ); }; export async function syncBackendSecuritySettings() { @@ -1214,6 +1222,29 @@ export const registerMajoorSettings = (app, onApplied) => { }, }); + // ------------------------ + // Search UI setting + // ------------------------ + safeAddSetting({ + id: `${SETTINGS_PREFIX}.Search.MaxResults`, + category: cat(t("cat.search", "Search")), + name: t("setting.search.maxResults.name", "Max search results (client)"), + tooltip: t( + "setting.search.maxResults.desc", + "Maximum number of results requested per search. The backend still enforces MAJOOR_SEARCH_MAX_LIMIT; increase that env var if you need a higher hard cap." + ), + type: "number", + defaultValue: Number(settings.search?.maxResults || APP_DEFAULTS.SEARCH_DEFAULT_LIMIT), + attrs: { min: 10, max: APP_DEFAULTS.MAX_PAGE_SIZE || 2000, step: 1 }, + onChange: (value) => { + settings.search = settings.search || {}; + settings.search.maxResults = Math.max(10, Math.min(APP_DEFAULTS.MAX_PAGE_SIZE || 2000, Number(value) || APP_DEFAULTS.SEARCH_DEFAULT_LIMIT)); + saveMajoorSettings(settings); + applySettingsToConfig(settings); + notifyApplied("search.maxResults"); + }, + }); + safeAddSetting({ id: `${SETTINGS_PREFIX}.EnvVars.Reference`, category: cat(t("cat.advanced"), "Environment variables"), diff --git a/js/features/grid/GridView.js b/js/features/grid/GridView.js index f1b3c43c..fd9e453b 100644 --- a/js/features/grid/GridView.js +++ b/js/features/grid/GridView.js @@ -542,8 +542,9 @@ function clearGridMessage(gridContainer) { } catch {} } -function setGridMessage(gridContainer, text, { error = false } = {}) { +function setGridMessage(gridContainer, text, { error = false, clear = false } = {}) { if (!gridContainer) return; + // Remove previous message elements clearGridMessage(gridContainer); try { const msg = document.createElement("div"); @@ -564,7 +565,15 @@ function setGridMessage(gridContainer, text, { error = false } = {}) { background: rgba(0, 0, 0, 0.35); z-index: 2; `; - gridContainer.appendChild(msg); + if (clear) { + try { + // Clear existing DOM content to avoid overlap with previous grid elements + gridContainer.replaceChildren(); + } catch {} + gridContainer.appendChild(msg); + } else { + gridContainer.appendChild(msg); + } } catch {} } @@ -1620,10 +1629,10 @@ export async function loadAssets(gridContainer, query = "*", options = {}) { hideLoadingOverlay(gridContainer); // Check if items present in state, not just DOM (logic shift) const hasItems = state.assets && state.assets.length > 0; - if (!hasItems && state.offset === 0) { + if (!hasItems && state.offset === 0) { // Manually clear to show message if (state.virtualGrid) state.virtualGrid.setItems([]); - setGridMessage(gridContainer, "No assets found"); + setGridMessage(gridContainer, "No assets found", { clear: true }); } else { startInfiniteScroll(gridContainer, state); } @@ -1763,7 +1772,7 @@ export async function loadAssetsFromList(gridContainer, assets, options = {}) { const hasItems = state.assets && state.assets.length > 0; if (!hasItems) { if (state.virtualGrid) state.virtualGrid.setItems([]); - setGridMessage(gridContainer, "No assets found"); + setGridMessage(gridContainer, "No assets found", { clear: true }); } } diff --git a/pyproject.toml b/pyproject.toml index b2bc7fdd..d540ca40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,8 +48,4 @@ Icon = "https://raw.githubusercontent.com/MajoorWaldi/ComfyUI-Majoor-AssetsManag includes = [] # "requires-comfyui" = ">=1.0.0" # ComfyUI version compatibility -[tool.pytest.ini_options] -# Keep pytest temp artifacts (tmp_path, tmp_path_factory) inside the repo under `tests/`. -# This ensures *.db / *.db-wal / *.db-shm created by tests are easy to find and never pollute the repo root. -addopts = ["--basetemp=tests/__pytest_tmp__"] diff --git a/pytest.ini b/pytest.ini index c88f8eca..58df9dfa 100644 --- a/pytest.ini +++ b/pytest.ini @@ -3,4 +3,6 @@ testpaths = tests # Évite que l'archive d'ancienne version (et autres dossiers) perturbe sys.path / conftest. norecursedirs = __old_version_zip .* build dist __pycache__ node_modules -addopts = -ra +# Keep pytest temp artifacts (tmp_path, tmp_path_factory) inside the repo under `tests/`. +# This ensures *.db / *.db-wal / *.db-shm created by tests are easy to find and never pollute the repo root. +addopts = -ra --basetemp=tests/__pytest_tmp__